#!/usr/bin/env python3
"""Summarize player battle records that used to be payload-only fallback."""
from __future__ import annotations

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


ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "out"
MAPPING_JSON = OUT / "battle_action_mapping.json"
DISPLAY_JSON = OUT / "battle_display_vm_static_decode.json"
TIMELINE_JSON = OUT / "battle_action_event_timeline_review.json"


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


def wlk_list(values: list[int | None]) -> str:
    clean = [value for value in values if value is not None]
    return ", ".join(f"WLK id {int(value):02d}" for value in clean) if clean else "-"


def compact_list(values: list[Any]) -> str:
    return ", ".join(str(value) for value in values) if values else "-"


def mapping_key(row: dict[str, Any]) -> tuple[str, str]:
    return str(row.get("ownerKey")), str(row.get("skillIdHex")).lower()


def classify(row: dict[str, Any], mapped: dict[str, Any]) -> str:
    name = str(mapped.get("name") or "")
    scopes = set(mapped.get("targetScopes") or [])
    families = set(mapped.get("families") or [])
    effect_count = int(mapped.get("effectCount") or 0)
    mp_cost = int(mapped.get("mpCost") or 0)
    if name in {"도주", "방어"}:
        return "mode-command"
    if name == "비기·맹호유성각":
        return "full-screen-child-effect-action"
    if scopes == {1} and name in {"마시기", "도발", "눈요기", "인법·몸감추기", "신격방어", "기합일발", "천조족", "주 백약지장"}:
        return "self/special-command"
    if scopes == {10} and mp_cost == 0:
        return "basic-or-weapon-basic"
    if scopes == {10}:
        return "single-target-weapon/fixed"
    if scopes == {6}:
        return "all-target-weapon/fixed"
    if effect_count > 1 or len(scopes) > 1 or len(families) > 1:
        return "composite-payload-action"
    return "fixed-display-action"


def build() -> dict[str, Any]:
    mapping = json.loads(MAPPING_JSON.read_text(encoding="utf-8"))
    display = json.loads(DISPLAY_JSON.read_text(encoding="utf-8"))
    timeline = json.loads(TIMELINE_JSON.read_text(encoding="utf-8"))
    mapped_by_key = {mapping_key(row): row for row in mapping.get("playerRows") or []}
    timeline_by_key = {mapping_key(row): row for row in timeline.get("rows") or []}
    rows: list[dict[str, Any]] = []
    for decoded in display.get("decodedRows") or []:
        if decoded.get("confidence") != "display-table-phase-pointer":
            continue
        key = mapping_key(decoded)
        mapped = mapped_by_key.get(key, {})
        tl = timeline_by_key.get(key, {})
        sound_wlks = [sound.get("wlkNo") for sound in decoded.get("sounds") or []]
        effect_wlks = [sound.get("wlkNo") for sound in decoded.get("effectSounds") or []]
        movements = [
            {
                "mode": movement.get("movementMode"),
                "selector": movement.get("selector"),
                "divisor": movement.get("divisor"),
                "vaHex": movement.get("vaHex"),
            }
            for movement in decoded.get("movements") or []
        ]
        row = {
            "ownerKey": decoded.get("ownerKey"),
            "ownerName": decoded.get("ownerName"),
            "skillIdHex": decoded.get("skillIdHex"),
            "skillName": decoded.get("skillName"),
            "phaseHex": decoded.get("phaseHex"),
            "payloadVaHex": decoded.get("payloadVaHex"),
            "displayTableBaseVaHex": decoded.get("displayTableBaseVaHex"),
            "displayTableEntryVaHex": decoded.get("displayTableEntryVaHex"),
            "displayStartVaHex": decoded.get("entryStartVaHex") or decoded.get("startVaHex"),
            "category": classify(decoded, mapped),
            "mpCost": mapped.get("mpCost"),
            "effectCount": mapped.get("effectCount"),
            "targetScopes": mapped.get("targetScopes") or [],
            "families": mapped.get("families") or [],
            "statuses": mapped.get("statuses") or [],
            "unitsHex": mapped.get("unitsHex") or [],
            "frameSequence": decoded.get("frameSequence") or [],
            "resultWlkNos": sound_wlks,
            "effectWlkNos": effect_wlks,
            "movements": movements,
            "hitEventCount": tl.get("hitEventCount", 0),
            "helperIds": tl.get("helperIds") or [],
            "stopReason": decoded.get("stopReason"),
            "instructionCount": decoded.get("instructionCount"),
            "confidence": decoded.get("confidence"),
            "note": decoded.get("note"),
        }
        rows.append(row)

    category_counts = Counter(row["category"] for row in rows)
    owner_counts = Counter(row["ownerName"] for row in rows)
    stop_counts = Counter(str(row["stopReason"]).split(" at ")[0] for row in rows)
    return {
        "version": 1,
        "kind": "hwanse-battle-fallback-record-review",
        "source": [
            "out/battle_action_mapping.json",
            "out/battle_display_vm_static_decode.json",
            "out/battle_action_event_timeline_review.json",
        ],
        "status": "payload-only-fallback-rows-promoted-through-phase-display-table",
        "summary": {
            "rows": len(rows),
            "allHaveDisplayTablePointer": all(row.get("displayStartVaHex") for row in rows),
            "idleEndRows": sum(1 for row in rows if str(row.get("stopReason", "")).startswith("idle/end marker")),
            "unknownStopRows": sum(1 for row in rows if str(row.get("stopReason", "")).startswith("unknown opcode")),
            "categoryCounts": dict(category_counts),
            "ownerCounts": dict(owner_counts),
            "stopReasonCounts": dict(stop_counts),
        },
        "interpretationNotes": [
            "These 55 rows were the battle_skill_runner payload-only fallback set before phase display table decoding.",
            "The display VM pointer is exact: per actor table base + phase * 4, where battle_action_mapping already proves phase = skillId + 0x0a.",
            "All 55 rows currently walk to an idle/end marker after narrow decoding of 0x1f/0x25/0x42.",
            "아타호 비기·맹호유성각 is classified separately because its actor VM starts effect sounds and spawns child VM 0x004d7490. This matches the observed full-screen flying/meteor-style effect rather than a normal direct-hit actor motion.",
            "Payload fields still describe damage/effect semantics. This report only promotes display/action-script linkage and does not solve damage formulas.",
        ],
        "rows": rows,
    }


def markdown(report: dict[str, Any]) -> str:
    lines = [
        "# Battle Fallback Record Review",
        "",
        f"- status: `{report['status']}`",
        f"- rows: {report['summary']['rows']}",
        f"- idle/end rows: {report['summary']['idleEndRows']}",
        f"- unknown stop rows: {report['summary']['unknownStopRows']}",
        "",
        "## Category Counts",
        "",
    ]
    for key, value in sorted(report["summary"]["categoryCounts"].items()):
        lines.append(f"- `{key}`: {value}")
    lines += [
        "",
        "## Rows",
        "",
        "| actor | skill | id | phase | category | table entry | display start | frames | result WLK | effect WLK | stop | payload |",
        "| --- | --- | ---: | ---: | --- | --- | --- | --- | --- | --- | --- | --- |",
    ]
    for row in report["rows"]:
        payload = f"scope={row['targetScopes']} family={row['families']} status={row['statuses']}"
        lines.append(
            f"| {row['ownerName']} | {row['skillName']} | `{row['skillIdHex']}` | `{row['phaseHex']}` | `{row['category']}` | "
            f"`{row['displayTableEntryVaHex']}` | `{row['displayStartVaHex']}` | {compact_list(row['frameSequence'])} | "
            f"{wlk_list(row['resultWlkNos'])} | {wlk_list(row['effectWlkNos'])} | {row['stopReason']} | {payload} |"
        )
    lines += ["", "## Notes", ""]
    lines.extend(f"- {note}" for note in report["interpretationNotes"])
    lines.append("")
    return "\n".join(lines)


def html_page(report: dict[str, Any]) -> str:
    category_rows = "".join(
        f"<tr><td><code>{esc(key)}</code></td><td>{esc(value)}</td></tr>"
        for key, value in sorted(report["summary"]["categoryCounts"].items())
    )
    rows = "".join(
        "<tr>"
        f"<td>{esc(row['ownerName'])}</td>"
        f"<td>{esc(row['skillName'])}</td>"
        f"<td><code>{esc(row['skillIdHex'])}</code></td>"
        f"<td><code>{esc(row['phaseHex'])}</code></td>"
        f"<td><code>{esc(row['category'])}</code></td>"
        f"<td><code>{esc(row['displayTableEntryVaHex'])}</code></td>"
        f"<td><code>{esc(row['displayStartVaHex'])}</code></td>"
        f"<td>{esc(compact_list(row['frameSequence']))}</td>"
        f"<td>{esc(wlk_list(row['resultWlkNos']))}</td>"
        f"<td>{esc(wlk_list(row['effectWlkNos']))}</td>"
        f"<td>{esc(row['stopReason'])}</td>"
        f"<td><code>scope={esc(row['targetScopes'])}</code><br><code>family={esc(row['families'])}</code><br><code>status={esc(row['statuses'])}</code></td>"
        "</tr>"
        for row in report["rows"]
    )
    notes = "".join(f"<li>{esc(note)}</li>" for note in report["interpretationNotes"])
    summary = report["summary"]
    return f"""<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="../favicon.ico">
  <title>Battle Fallback Record Review</title>
  <style>
    body {{ margin: 20px; background: #101114; color: #f1f3f5; font-family: system-ui, sans-serif; }}
    a {{ color: #9ecbff; }} code {{ color: #ffd37a; }}
    table {{ width: 100%; border-collapse: collapse; margin: 14px 0 24px; }}
    th, td {{ border: 1px solid #30343d; padding: 6px 8px; font-size: 12px; vertical-align: top; }}
    th {{ background: #1a1d24; color: #bac2cf; position: sticky; top: 0; }}
    .summary {{ display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0; }}
    .pill {{ border: 1px solid #394150; border-radius: 999px; padding: 5px 10px; background: #171a21; }}
  </style>
</head>
<body>
  <h1>Battle Fallback Record Review</h1>
  <p><a href="../web/index.html">홈</a> · <a href="../web/battle_simulator.html">전투 기술 실행</a> · <a href="battle_display_vm_static_decode.html">전투 VM 정적 디코드</a> · <a href="battle_fallback_record_review.json">JSON</a></p>
  <div class="summary">
    <span class="pill">rows {esc(summary['rows'])}</span>
    <span class="pill">idle/end {esc(summary['idleEndRows'])}</span>
    <span class="pill">unknown stop {esc(summary['unknownStopRows'])}</span>
    <span class="pill">table pointer {esc(summary['allHaveDisplayTablePointer'])}</span>
  </div>
  <h2>Category Counts</h2>
  <table><thead><tr><th>category</th><th>count</th></tr></thead><tbody>{category_rows}</tbody></table>
  <h2>Rows</h2>
  <table><thead><tr><th>actor</th><th>skill</th><th>id</th><th>phase</th><th>category</th><th>table entry</th><th>display start</th><th>frames</th><th>result WLK</th><th>effect WLK</th><th>stop</th><th>payload</th></tr></thead><tbody>{rows}</tbody></table>
  <h2>Notes</h2>
  <ul>{notes}</ul>
</body>
</html>
"""


def main() -> None:
    report = build()
    OUT.mkdir(parents=True, exist_ok=True)
    (OUT / "battle_fallback_record_review.json").write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    (OUT / "battle_fallback_record_review.html").write_text(html_page(report), encoding="utf-8")
    print("wrote out/battle_fallback_record_review.{json,html}")


if __name__ == "__main__":
    main()
