#!/usr/bin/env python3
"""Summarize event/object VM handlers that reference battle action text tables."""
from __future__ import annotations

import argparse
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"

BATTLE_ACTION_OPCODE = "0x36"
BATTLE_ACTION_HANDLER = "0x0041ff44"
BATTLE_ACTION_TABLE = "0x004d24ac"


def load_json(path: Path, fallback: Any) -> Any:
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except FileNotFoundError:
        return fallback


def table_by_key(text_tables: dict) -> dict[str, dict]:
    return {
        str(row.get("key")): row
        for row in text_tables.get("tables") or []
        if row.get("key")
    }


def opcode_storage_counts(event_dialogue_blocks: dict) -> dict[str, int]:
    counts: Counter[str] = Counter()
    for block in event_dialogue_blocks.get("blocks") or []:
        trace = block.get("vmTrace") or {}
        for event_key in [
            "groundedControlEvents",
            "literalTextEvents",
            "renderEvents",
            "sourceEvents",
            "lineAdvanceEvents",
        ]:
            for event in trace.get(event_key) or []:
                opcode = event.get("opcodeHex")
                if opcode:
                    counts[str(opcode)] += 1
        for opcode, count in (trace.get("opcodeCounts") or {}).items():
            counts[str(opcode)] += int(count or 0)
    return dict(sorted(counts.items()))


def compact_table_ref(row: dict) -> dict:
    return {
        "refVaHex": row.get("refVaHex") or "",
        "valueHex": row.get("valueHex") or "",
        "clusterStartVaHex": row.get("clusterStartVaHex") or "",
        "clusterEndVaHex": row.get("clusterEndVaHex") or "",
        "entryCount": row.get("entryCount", 0),
        "sampleTexts": row.get("sampleTexts") or [],
    }


def matched_handler_rows(event_handler_text_refs: dict) -> list[dict]:
    rows = []
    for row in event_handler_text_refs.get("rows") or []:
        if row.get("classification") not in {"battle-action", "item-menu", "text-table"}:
            continue
        rows.append({
            "opcodes": row.get("opcodes") or [],
            "handlerVaHex": row.get("handlerVaHex") or "",
            "endVaHex": row.get("endVaHex") or "",
            "classification": row.get("classification") or "",
            "textTableRefCount": row.get("textTableRefCount", 0),
            "textTableRefs": [compact_table_ref(ref) for ref in (row.get("textTableRefs") or [])],
            "directCallTargetCounts": row.get("directCallTargetCounts") or {},
            "samples": row.get("samples") or "",
            "codeBytesPrefix": row.get("codeBytes") or "",
        })
    return rows


def build_summary(
    event_handler_text_refs: dict,
    text_tables: dict,
    event_dialogue_blocks: dict,
) -> dict:
    tables = table_by_key(text_tables)
    ataho_actions = tables.get("atahoActions") or {}
    action_entries = ataho_actions.get("entries") or []
    action_texts = [str(row.get("text") or "") for row in action_entries]
    storage_counts = opcode_storage_counts(event_dialogue_blocks)
    rows = matched_handler_rows(event_handler_text_refs)
    battle_row = next(
        (
            row for row in rows
            if BATTLE_ACTION_OPCODE in row.get("opcodes", [])
            and row.get("handlerVaHex") == BATTLE_ACTION_HANDLER
        ),
        {},
    )
    battle_refs = battle_row.get("textTableRefs") or []
    table_ref = next(
        (ref for ref in battle_refs if ref.get("clusterStartVaHex") == BATTLE_ACTION_TABLE),
        {},
    )
    battle_action_handler_grounded = bool(
        battle_row
        and table_ref
        and action_texts[:3] == ["정권", "돌려차기", "던지기"]
    )
    storage_opcode_count = int(storage_counts.get(BATTLE_ACTION_OPCODE) or 0)
    battle_entry_dispatch_proof_found = False
    original_combat_formula_identified = False
    return {
        "scope": "battle action text handler context inside the event/object VM dispatch table",
        "source": [
            "out/event_handler_text_refs.json",
            "out/text_tables.json",
            "out/event_dialogue_blocks.json",
        ],
        "promotionStatus": "battle-action-handler-grounded-entry-dispatch-missing",
        "battleActionHandlerGrounded": battle_action_handler_grounded,
        "battleActionOpcodeHex": BATTLE_ACTION_OPCODE,
        "battleActionHandlerVaHex": battle_row.get("handlerVaHex") or BATTLE_ACTION_HANDLER,
        "battleActionHandlerEndVaHex": battle_row.get("endVaHex") or "",
        "battleActionTableVaHex": BATTLE_ACTION_TABLE,
        "battleActionTableKey": ataho_actions.get("key") or "atahoActions",
        "battleActionTableEntryCount": len(action_entries),
        "battleActionTableSamples": action_texts[:8],
        "battleActionHandlerTextTableRefCount": len(battle_refs),
        "battleActionHandlerTableRef": table_ref,
        "battleActionHandlerDirectCallTargetCounts": battle_row.get("directCallTargetCounts") or {},
        "battleActionHandlerStorageOpcodeCount": storage_opcode_count,
        "battleActionOpcodeInDialogueStorage": storage_opcode_count > 0,
        "battleEntryDispatchProofFound": battle_entry_dispatch_proof_found,
        "enemyRowProofFound": False,
        "formationTableProofFound": False,
        "rewardTableProofFound": False,
        "originalCombatFormulaIdentified": original_combat_formula_identified,
        "matchedHandlerRows": rows,
        "checks": {
            "battleActionHandlerGrounded": battle_action_handler_grounded,
            "battleActionTextTableGrounded": bool(table_ref),
            "battleActionOpcodeAbsentFromDialogueStorage": storage_opcode_count == 0,
            "battleEntryDispatchProofFound": battle_entry_dispatch_proof_found,
            "originalCombatFormulaIdentified": original_combat_formula_identified,
        },
        "missingEvidence": [
            "event/object VM command storage that executes opcode 0x36 from a battle-entry path",
            "handler/call edge from event VM battle action handler to battle scene entry",
            "enemy row or formation table consumed by this handler",
            "original combat formula or reward table writes reached from this handler",
        ],
        "conclusion": (
            "Event/object VM opcode 0x36 is grounded as a battle-action text handler because handler "
            "0x0041ff44 references the 0x004d24ac action text table (정권/돌려차기/던지기/마시기). "
            "That is useful original battle UI/action evidence, but opcode 0x36 does not appear in the current "
            "dialogue storage scan and there is no proven event path from a btl_* dialogue candidate into a battle "
            "scene, enemy row, formation, reward table, or combat formula."
        ),
    }


def html_page(summary: dict) -> str:
    check_rows = "".join(
        f"<tr><td>{html.escape(key)}</td><td><code>{html.escape(str(value))}</code></td></tr>"
        for key, value in summary["checks"].items()
    )
    handler_rows = "".join(
        "<tr>"
        f"<td>{html.escape(', '.join(row.get('opcodes') or []))}</td>"
        f"<td><code>{html.escape(row.get('handlerVaHex') or '')}..{html.escape(row.get('endVaHex') or '')}</code></td>"
        f"<td>{html.escape(row.get('classification') or '')}</td>"
        f"<td>{html.escape(str(row.get('textTableRefCount') or 0))}</td>"
        f"<td>{html.escape(row.get('samples') or '-')}</td>"
        "</tr>"
        for row in summary.get("matchedHandlerRows") or []
    )
    missing = "".join(f"<li>{html.escape(item)}</li>" for item in summary["missingEvidence"])
    return "\n".join([
        "<!doctype html>",
        '<html lang="en">',
        "<head>",
        '  <meta charset="utf-8">',
        '  <meta name="viewport" content="width=device-width, initial-scale=1">',
        "  <title>Battle Action Handler Context</title>",
        "  <style>body{font-family:system-ui,sans-serif;background:#101010;color:#eee;margin:24px}table{border-collapse:collapse;width:100%;margin:16px 0}td,th{border:1px solid #333;padding:6px 8px;vertical-align:top}th{background:#1d1d1d}code{color:#f5d76e}</style>",
        "</head>",
        "<body>",
        "  <h1>Battle Action Handler Context</h1>",
        f"  <p>{html.escape(summary['conclusion'])}</p>",
        f"  <p>Promotion status <code>{html.escape(summary['promotionStatus'])}</code>.</p>",
        f"  <p>Opcode <code>{html.escape(summary['battleActionOpcodeHex'])}</code>, handler "
        f"<code>{html.escape(summary['battleActionHandlerVaHex'])}</code>, table "
        f"<code>{html.escape(summary['battleActionTableVaHex'])}</code> / "
        f"<code>{html.escape(summary['battleActionTableKey'])}</code>.</p>",
        "  <h2>Checks</h2>",
        "  <table><thead><tr><th>check</th><th>value</th></tr></thead>",
        f"  <tbody>{check_rows}</tbody></table>",
        "  <h2>Matched Handler Rows</h2>",
        "  <table><thead><tr><th>opcodes</th><th>handler</th><th>class</th><th>table refs</th><th>samples</th></tr></thead>",
        f"  <tbody>{handler_rows}</tbody></table>",
        "  <h2>Missing Evidence</h2>",
        f"  <ul>{missing}</ul>",
        "</body>",
        "</html>",
        "",
    ])


def write_outputs(summary: dict, out_dir: Path = OUT, html_out: Path | None = None) -> Path:
    out_dir.mkdir(parents=True, exist_ok=True)
    json_out = out_dir / "battle_action_handler_context.json"
    json_out.write_text(
        json.dumps(summary, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )
    if html_out is not None:
        html_out.parent.mkdir(parents=True, exist_ok=True)
        html_out.write_text(html_page(summary), encoding="utf-8")
    return json_out


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--out-dir", type=Path, default=OUT)
    parser.add_argument("--html-out", type=Path)
    args = parser.parse_args()
    summary = build_summary(
        load_json(args.out_dir / "event_handler_text_refs.json", {}),
        load_json(args.out_dir / "text_tables.json", {}),
        load_json(args.out_dir / "event_dialogue_blocks.json", {}),
    )
    json_out = write_outputs(summary, args.out_dir, args.html_out)
    print(f"wrote battle action handler context -> {json_out}")


if __name__ == "__main__":
    main()
