#!/usr/bin/env python3
"""Review opcode 0x13 choice-branch evidence in active object scripts."""
from __future__ import annotations

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

from probe_exe_scene_tables import read_sections
from summarize_active_object_script_inventory import scan_initializers
from summarize_object_payload_442c75_callers import decode_stream, hex32, is_va


ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "out"
EXE = ROOT / "Hwanse2.exe"
ROUTE_WORDS = (
    "문을 통과",
    "출발",
    "올라탄다",
    "올라 타본다",
    "돌아갈 수",
    "고향으로",
)


def text_preview(stream: dict[str, Any], limit: int = 260) -> str:
    parts = []
    for ref in stream.get("textPayloadRefs") or []:
        preview = (ref.get("textPreview") or "").replace("\n", " / ").strip()
        if preview:
            parts.append(preview)
    return " --- ".join(parts)[:limit]


def script_vas(exe: bytes, sections: list[dict[str, Any]]) -> list[int]:
    return sorted({
        int(write["value"])
        for init in scan_initializers(exe, sections)
        for write in init["ecWrites"]
        if is_va(sections, int(write["value"]))
    })


def build_summary(exe_path: Path) -> dict[str, Any]:
    exe = exe_path.read_bytes()
    sections = read_sections(exe)
    rows: list[dict[str, Any]] = []
    mode_test_counts: Counter[str] = Counter()
    test_counts: Counter[str] = Counter()
    prev_next_counts: Counter[str] = Counter()
    dominant_samples: list[dict[str, Any]] = []
    for script_va in script_vas(exe, sections):
        stream = decode_stream(exe, sections, script_va, max_commands=120, max_bytes=0x700)
        commands = stream.get("commands") or []
        preview = text_preview(stream)
        route_like = any(word in preview for word in ROUTE_WORDS)
        for index, command in enumerate(commands):
            if command.get("opcodeHex") != "0x13":
                continue
            mode = command.get("mode")
            test_value = command.get("testValue")
            mode_test = f"mode=0x{int(mode):02x}/test=0x{int(test_value):04x}" if mode is not None and test_value is not None else "unknown"
            test_key = f"0x{int(test_value):04x}" if test_value is not None else "unknown"
            prev_opcode = commands[index - 1].get("opcodeHex") if index else "start"
            next_opcode = commands[index + 1].get("opcodeHex") if index + 1 < len(commands) else "end"
            prev_next = f"{prev_opcode}->{next_opcode}"
            mode_test_counts[mode_test] += 1
            test_counts[test_key] += 1
            prev_next_counts[prev_next] += 1
            row = {
                "scriptVaHex": hex32(script_va),
                "commandVaHex": command.get("vaHex"),
                "modeTest": mode_test,
                "prevNext": prev_next,
                "targetVaHex": command.get("targetVaHex"),
                "routeLikeText": route_like,
                "textPreview": preview,
                "summary": command.get("summary", ""),
                "rawHex": command.get("rawHex", ""),
            }
            rows.append(row)
            if mode_test == "mode=0xc1/test=0x013a" and len(dominant_samples) < 40:
                dominant_samples.append(row)

    route_like_dominant = [
        row
        for row in rows
        if row["modeTest"] == "mode=0xc1/test=0x013a" and row["routeLikeText"]
    ]
    return {
        "title": "Opcode 0x13 Choice Branch Review",
        "summary": {
            "manualMovementAssumption": True,
            "sceneAutoTransitionClaim": False,
            "activeScriptCount": len(script_vas(exe, sections)),
            "opcode13RowCount": len(rows),
            "dominantChoiceBranchCount": mode_test_counts.get("mode=0xc1/test=0x013a", 0),
            "routeLikeDominantChoiceBranchCount": len(route_like_dominant),
            "routeProofCount": 0,
            "modeTestCounts": dict(mode_test_counts.most_common(12)),
            "testCounts": dict(test_counts.most_common(12)),
            "prevNextCounts": dict(prev_next_counts.most_common(16)),
            "interpretation": [
                "0x13 is a generic conditional branch, not a map transition opcode by itself.",
                "mode=0xc1/test=0x013a is the dominant post-prompt two-choice branch shape.",
                "The same 0x013a branch appears in chests, switches, tests, inns, and route-like prompts, so it should be treated as choice-result control.",
                "Route-like scripts still require a later map-loader/root/target proof; opcode 0x13 only selects the next local script branch.",
            ],
        },
        "dominantSamples": dominant_samples,
        "routeLikeDominantSamples": route_like_dominant,
        "rows": rows,
    }


def html_doc(report: dict[str, Any]) -> str:
    s = report["summary"]
    rows = []
    for row in report["routeLikeDominantSamples"][:80]:
        rows.append(
            "<tr>"
            f"<td><code>{html.escape(row['scriptVaHex'])}</code></td>"
            f"<td><code>{html.escape(row['commandVaHex'])}</code></td>"
            f"<td>{html.escape(row['prevNext'])}</td>"
            f"<td><code>{html.escape(row['targetVaHex'] or '')}</code></td>"
            f"<td>{html.escape(row['textPreview'])}</td>"
            "</tr>"
        )
    return f"""<!doctype html>
<html lang=\"ko\">
<head>
  <meta charset=\"utf-8\">
  <title>{html.escape(report['title'])}</title>
  <style>
    body {{ font-family: system-ui, sans-serif; margin: 24px; color: #17202a; }}
    table {{ width: 100%; border-collapse: collapse; }}
    th, td {{ border: 1px solid #d7dde6; padding: 8px; vertical-align: top; }}
    th {{ background: #eef2f6; }}
    code {{ background: #f3f5f7; padding: 1px 3px; border-radius: 3px; }}
    .metrics {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 8px; }}
    .metric {{ background: #f6f8fb; border: 1px solid #d7dde6; padding: 10px; border-radius: 6px; }}
  </style>
</head>
<body>
  <h1>{html.escape(report['title'])}</h1>
  <div class=\"metrics\">
    <div class=\"metric\">manual movement assumption: <strong>{s['manualMovementAssumption']}</strong></div>
    <div class=\"metric\">scene auto transition claim: <strong>{s['sceneAutoTransitionClaim']}</strong></div>
    <div class=\"metric\">opcode13 rows: <strong>{s['opcode13RowCount']}</strong></div>
    <div class=\"metric\">dominant choice branch rows: <strong>{s['dominantChoiceBranchCount']}</strong></div>
    <div class=\"metric\">route proof count: <strong>{s['routeProofCount']}</strong></div>
  </div>
  <h2>Interpretation</h2>
  <ul>{''.join(f'<li>{html.escape(item)}</li>' for item in s['interpretation'])}</ul>
  <h2>Mode/Test Counts</h2>
  <pre>{html.escape(json.dumps(s['modeTestCounts'], ensure_ascii=False, indent=2))}</pre>
  <h2>Route-like Dominant Samples</h2>
  <table>
    <thead><tr><th>script</th><th>0x13 VA</th><th>prev/next</th><th>target</th><th>preview</th></tr></thead>
    <tbody>{''.join(rows)}</tbody>
  </table>
  <script>
    window.opcode13ChoiceBranchReview = {{
      manualMovementAssumption: true,
      sceneAutoTransitionClaim: false,
      opcode13RowCount: {s['opcode13RowCount']},
      dominantChoiceBranchCount: {s['dominantChoiceBranchCount']},
      routeProofCount: {s['routeProofCount']}
    }};
  </script>
</body>
</html>
"""


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--exe", type=Path, default=EXE)
    parser.add_argument("--out", type=Path, default=OUT)
    args = parser.parse_args()
    report = build_summary(args.exe)
    args.out.mkdir(parents=True, exist_ok=True)
    (args.out / "opcode13_choice_branch_review.json").write_text(
        json.dumps(report, ensure_ascii=False, indent=2),
        encoding="utf-8",
    )
    print(json.dumps(report["summary"], ensure_ascii=False, indent=2))


if __name__ == "__main__":
    main()
