#!/usr/bin/env python3
"""Build a focused proof report for Rinshan's dragon effect helpers.

The 암각·영상승룡파 family is a good stress case for battle effects because
the visible dragon is not a single overlaid sprite.  The EXE helper scripts
spawn child display objects repeatedly; each child has its own CNS frame,
counter/branch evidence, and target-relative display offset.  This report
keeps those EXE-derived facts separate from browser preview projection.
"""

from __future__ import annotations

import html
import json
from pathlib import Path
from typing import Any


ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "out"


def load_json(name: str) -> dict[str, Any]:
    return json.loads((OUT / name).read_text(encoding="utf-8"))


def esc(value: Any) -> str:
    return html.escape(str(value if value is not None else ""))


def compact(values: list[Any] | tuple[Any, ...], limit: int = 12) -> str:
    items = [str(value) for value in values if value not in (None, "")]
    if not items:
        return "-"
    if len(items) > limit:
        items = items[:limit] + [f"... +{len(items) - limit}"]
    return ", ".join(items)


def btl_efc_rects(rect_scan: dict[str, Any]) -> dict[int, dict[str, Any]]:
    for row in rect_scan.get("rows") or []:
        if row.get("asset") != "btl_efc":
            continue
        rects = (row.get("bestTable") or {}).get("rects") or []
        out: dict[int, dict[str, Any]] = {}
        for frame in range(184, 189):
            if frame < len(rects):
                rect = dict(rects[frame])
                rect["asset"] = "btl_efc"
                rect["frame"] = frame
                out[frame] = rect
        return out
    return {}


def first_skill_row(skill_rows: list[dict[str, Any]], helper_id: int) -> dict[str, Any]:
    for row in skill_rows:
        if helper_id in [int(value) for value in row.get("helperIds") or []]:
            if row.get("ownerKey") == "rinshan" and row.get("skillName") == "암각·영상승룡파":
                return row
    return {}


def child_row(child_rows: list[dict[str, Any]], helper_id: int) -> dict[str, Any]:
    return next((row for row in child_rows if int(row.get("helperId") or -1) == helper_id), {})


def visual_row(visual_rows: list[dict[str, Any]], helper_id: int) -> dict[str, Any]:
    return next((row for row in visual_rows if int(row.get("helperId") or -1) == helper_id), {})


def pattern_row(pattern_rows: list[dict[str, Any]], helper_id: int) -> dict[str, Any]:
    return next((row for row in pattern_rows if int(row.get("helperId") or -1) == helper_id), {})


def event_summary(event: dict[str, Any], rects: dict[int, dict[str, Any]]) -> dict[str, Any]:
    frame = int(event.get("frame") or 0)
    preview = event.get("previewTransform") or {}
    return {
        "tick": event.get("absoluteTick", event.get("tick")),
        "frame": frame,
        "label": event.get("label") or f"btl_efc#{frame}",
        "rect": rects.get(frame),
        "targetVaHex": event.get("targetVaHex"),
        "spawnVaHex": event.get("spawnVaHex"),
        "reason": event.get("reason"),
        "repeatIndex": event.get("repeatIndex"),
        "offsetX": preview.get("offsetX", event.get("offsetX")),
        "offsetY": preview.get("offsetY", event.get("offsetY")),
        "targetAnchorX": preview.get("targetAnchorX", event.get("targetAnchorX")),
        "targetAnchorY": preview.get("targetAnchorY", event.get("targetAnchorY")),
        "basis": preview.get("basis"),
        "coordinateSummary": preview.get("coordinateSummary")
        or ((event.get("transformScope") or {}).get("coordinateSummary")),
        "transformStatus": event.get("transformStatus"),
    }


def branch_summary(decoded: dict[str, Any]) -> list[dict[str, Any]]:
    rows = []
    for branch in decoded.get("branchTargets") or []:
        rows.append(
            {
                "vaHex": branch.get("vaHex"),
                "targetVaHex": branch.get("targetVaHex"),
                "comparison": branch.get("comparison"),
                "summary": branch.get("summary"),
            }
        )
    return rows


def counter_sets(pattern: dict[str, Any]) -> dict[str, Any]:
    return dict(pattern.get("rootCounterSets") or {})


def build() -> dict[str, Any]:
    visual = load_json("battle_helper_visual_behavior_review.json")
    child = load_json("battle_helper_child_script_review.json")
    patterns = load_json("battle_effect_animation_pattern_review.json")
    rect_scan = load_json("cns_frame_rect_exe_scan.json")
    rects = btl_efc_rects(rect_scan)

    skill_rows = patterns.get("skillRows") or []
    pattern_helpers = patterns.get("helperRows") or []
    child_rows = child.get("helperRows") or []
    visual_rows = visual.get("rows") or []

    rows: list[dict[str, Any]] = []
    for helper_id in [95, 96, 97, 98]:
        skill = first_skill_row(skill_rows, helper_id)
        pattern = pattern_row(pattern_helpers, helper_id)
        child_helper = child_row(child_rows, helper_id)
        visual_helper = visual_row(visual_rows, helper_id)
        direct = visual_helper.get("directSpawn") or pattern.get("directSpawn") or {}
        events = [event_summary(event, rects) for event in direct.get("events") or []]
        first_events_by_frame: dict[int, dict[str, Any]] = {}
        for event in events:
            first_events_by_frame.setdefault(int(event.get("frame") or 0), event)

        decoded = child_helper.get("decoded") or {}
        row = {
            "level": skill.get("levelOrFixed"),
            "ownerKey": skill.get("ownerKey") or "rinshan",
            "ownerName": skill.get("ownerName") or "린샹",
            "skillName": skill.get("skillName") or "암각·영상승룡파",
            "skillIdHex": skill.get("skillIdHex"),
            "recordKey": skill.get("recordKey"),
            "helperId": helper_id,
            "childScriptVaHex": child_helper.get("childScriptVaHex") or pattern.get("childScriptVaHex"),
            "behaviorClass": visual_helper.get("behaviorClass") or pattern.get("behaviorClass"),
            "animationClass": pattern.get("animationClass"),
            "directSpawnCount": direct.get("count"),
            "frameTimeline": direct.get("frameTimeline") or [],
            "ticks": direct.get("ticks") or [],
            "uniformTickDelta": direct.get("uniformTickDelta"),
            "targetVaCounts": direct.get("targetVaCounts") or {},
            "targetStopReasons": direct.get("targetStopReasons") or [],
            "coordinateSummaries": direct.get("coordinateSummaries") or [],
            "transformPatternCounts": direct.get("transformPatternCounts") or {},
            "rootCounterSets": counter_sets(pattern),
            "rootBranches": pattern.get("rootBranches") or [],
            "rootCounterSteps": pattern.get("rootCounterSteps") or [],
            "decodedBranches": branch_summary(decoded),
            "randomRanges": pattern.get("randomRanges") or decoded.get("randomRanges") or [],
            "effectFrameLabels": pattern.get("effectFrameLabels") or [],
            "effectRects": [rects[frame] for frame in sorted(first_events_by_frame) if frame in rects],
            "events": events,
            "interpretation": pattern.get("interpretation")
            or "counter-driven child display object spawn stream",
            "exeConfirmed": [
                "helper id and child script VA",
                "direct child spawn frame selector(s)",
                "spawn tick stream and repeat count",
                "target-relative display offsets from child motion scope",
                "root branch/counter structure",
            ],
            "previewOnly": [
                "runtime RNG seed",
                "final screen-space position after actor/target runtime state",
                "browser-side target placeholder projection",
            ],
        }
        rows.append(row)

    return {
        "version": 1,
        "kind": "battle-dragon-effect-motion-review",
        "status": "static-exe-dragon-child-spawn-stream-confirmed",
        "source": [
            "out/battle_effect_animation_pattern_review.json",
            "out/battle_helper_visual_behavior_review.json",
            "out/battle_helper_child_script_review.json",
            "out/cns_frame_rect_exe_scan.json",
            "Hwanse2.exe helper display VM scripts 0x004b9644..0x004b9e5c",
        ],
        "summary": {
            "helperIds": [row["helperId"] for row in rows],
            "levels": [row["level"] for row in rows],
            "spawnCountsByLevel": [
                {
                    "level": row["level"],
                    "skillIdHex": row["skillIdHex"],
                    "helperId": row["helperId"],
                    "directSpawnCount": row["directSpawnCount"],
                    "frameTimeline": row["frameTimeline"],
                }
                for row in rows
            ],
            "btlEfcRects": rects,
        },
        "interpretationNotes": [
            "암각·영상승룡파 계열은 단일 overlay가 아니라 helper 95..98이 child display object를 반복 spawn한다.",
            "level 1은 btl_efc#186을 17회, 2 tick 간격으로 생성한다. 이 helper는 빙조권 0x19와도 공유된다.",
            "level 2는 큰 조각 btl_efc#184 1회 후 btl_efc#186 6회를 1 tick 간격으로 생성한다.",
            "level 3은 btl_efc#184 1회 후 btl_efc#187 8회를 1 tick 간격으로 생성한다.",
            "level 4는 더 큰 조각 btl_efc#185 1회 후 btl_efc#188 12회를 1 tick 간격으로 생성한다.",
            "offsetX/offsetY는 child display.x/y scope에서 target anchor 기준으로 해석한 값이다. 최종 화면 좌표는 런타임 target 위치와 RNG 상태가 필요하므로 preview projection으로만 취급한다.",
        ],
        "rows": rows,
    }


def markdown(report: dict[str, Any]) -> str:
    lines = [
        "# 암각·영상승룡파 Dragon Effect Motion Review",
        "",
        f"- status: `{report['status']}`",
        "",
        "## Spawn Count",
        "",
        "| level | skill id | helper | child script | count | frames | delta | offsets |",
        "|---:|---|---:|---|---:|---|---:|---|",
    ]
    for row in report["rows"]:
        offsets = compact([
            f"{event.get('frame')}:{event.get('offsetX')},{event.get('offsetY')}"
            for event in row.get("events", [])[:3]
        ])
        lines.append(
            "| "
            + " | ".join(
                [
                    str(row.get("level")),
                    str(row.get("skillIdHex")),
                    str(row.get("helperId")),
                    str(row.get("childScriptVaHex")),
                    str(row.get("directSpawnCount")),
                    compact(row.get("frameTimeline") or [], 8),
                    str(row.get("uniformTickDelta")),
                    offsets,
                ]
            )
            + " |"
        )
    lines += [
        "",
        "## Notes",
        "",
        *[f"- {note}" for note in report["interpretationNotes"]],
    ]
    return "\n".join(lines) + "\n"


def html_page(report: dict[str, Any]) -> str:
    rows = "\n".join(
        "<tr>"
        f"<td>{esc(row.get('level'))}</td>"
        f"<td><code>{esc(row.get('skillIdHex'))}</code></td>"
        f"<td>{esc(row.get('helperId'))}</td>"
        f"<td><code>{esc(row.get('childScriptVaHex'))}</code></td>"
        f"<td>{esc(row.get('directSpawnCount'))}</td>"
        f"<td>{esc(compact(row.get('frameTimeline') or [], 12))}</td>"
        f"<td>{esc(row.get('uniformTickDelta'))}</td>"
        f"<td>{esc(compact(row.get('coordinateSummaries') or [], 4))}</td>"
        f"<td>{esc(compact([r.get('randomRangeHex') for r in row.get('randomRanges') or []], 6))}</td>"
        "</tr>"
        for row in report["rows"]
    )
    rect_rows = "\n".join(
        "<tr>"
        f"<td>{esc(frame)}</td>"
        f"<td>{esc(rect.get('x'))}</td>"
        f"<td>{esc(rect.get('y'))}</td>"
        f"<td>{esc(rect.get('w'))}</td>"
        f"<td>{esc(rect.get('h'))}</td>"
        "</tr>"
        for frame, rect in sorted((report["summary"].get("btlEfcRects") or {}).items(), key=lambda item: int(item[0]))
    )
    notes = "".join(f"<li>{esc(note)}</li>" for note in report["interpretationNotes"])
    detail = "\n".join(
        f"<h3>Lv{esc(row.get('level'))} helper {esc(row.get('helperId'))}</h3>"
        "<table><thead><tr><th>tick</th><th>frame</th><th>target</th><th>reason</th><th>offset</th><th>basis</th></tr></thead><tbody>"
        + "\n".join(
            "<tr>"
            f"<td>{esc(event.get('tick'))}</td>"
            f"<td>{esc(event.get('label'))}</td>"
            f"<td><code>{esc(event.get('targetVaHex'))}</code></td>"
            f"<td>{esc(event.get('reason'))}</td>"
            f"<td>{esc(event.get('offsetX'))}, {esc(event.get('offsetY'))}</td>"
            f"<td>{esc(event.get('coordinateSummary'))}</td>"
            "</tr>"
            for event in row.get("events") or []
        )
        + "</tbody></table>"
        for row in report["rows"]
    )
    return f"""<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<title>Dragon Effect Motion Review</title>
<style>
body {{ font-family: system-ui, sans-serif; margin: 24px; background: #f6f2ea; color: #211d19; }}
table {{ border-collapse: collapse; width: 100%; background: #fff; margin: 14px 0 24px; }}
th, td {{ border: 1px solid #ddd0bf; padding: 6px 8px; text-align: left; vertical-align: top; }}
th {{ background: #efe1cf; }}
code {{ background: #f2ece4; padding: 1px 4px; border-radius: 3px; }}
.pill {{ display: inline-block; padding: 3px 8px; border-radius: 999px; background: #225c7d; color: white; }}
</style>
</head>
<body>
<h1>암각·영상승룡파 Dragon Effect Motion Review</h1>
<p><span class="pill">{esc(report['status'])}</span></p>
<h2>Spawn Stream</h2>
<table><thead><tr><th>lv</th><th>skill</th><th>helper</th><th>script</th><th>count</th><th>frames</th><th>delta</th><th>coordinate scope</th><th>rng</th></tr></thead><tbody>{rows}</tbody></table>
<h2>btl_efc Rects</h2>
<table><thead><tr><th>frame</th><th>x</th><th>y</th><th>w</th><th>h</th></tr></thead><tbody>{rect_rows}</tbody></table>
<h2>Events</h2>
{detail}
<h2>Notes</h2>
<ul>{notes}</ul>
</body>
</html>
"""


def main() -> None:
    report = build()
    (OUT / "battle_dragon_effect_motion_review.json").write_text(
        json.dumps(report, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )
    (OUT / "battle_dragon_effect_motion_review.md").write_text(markdown(report), encoding="utf-8")
    (OUT / "battle_dragon_effect_motion_review.html").write_text(html_page(report), encoding="utf-8")


if __name__ == "__main__":
    main()
