#!/usr/bin/env python3
"""Check whether status-success has a dedicated WLK/helper sound path.

The status payload byte is already mapped to target +0x6c and the success gate
at 0x4344ab.  This narrow report answers only one question: after that gate
succeeds, does the status path itself play a separate sound?
"""
from __future__ import annotations

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

from build_battle_status_transition_review import disassemble


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

ROUTINES = [
    {
        "name": "status success gate",
        "range": "0x004344ab..0x0043461f",
        "start": 0x004344AB,
        "end": 0x0043461F,
        "role": "target +0x6c success/failure decision",
    },
    {
        "name": "status applier",
        "range": "0x00434e84..0x0043512e",
        "start": 0x00434E84,
        "end": 0x0043512E,
        "role": "convert target +0x6c to actor +0x2a status/timer/flags",
    },
    {
        "name": "temporary status recovery",
        "range": "0x0043512e..0x00435295",
        "start": 0x0043512E,
        "end": 0x00435295,
        "role": "recover temporary status and drunk hazard checks",
    },
]

KNOWN_CALLS = {
    0x00427730: "RNG/random helper",
    0x0042AF73: "WLK/audio playback helper used by display/action sound paths",
    0x00435B5B: "display/helper object spawn",
}

CALL_RE = re.compile(r"\bcall\s+0x([0-9a-fA-F]+)")


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


def call_rows(asm: str) -> list[dict[str, Any]]:
    rows: list[dict[str, Any]] = []
    for line in asm.splitlines():
        match = CALL_RE.search(line)
        if not match:
            continue
        target = int(match.group(1), 16)
        rows.append(
            {
                "line": line.strip(),
                "targetVaHex": f"0x{target:08x}",
                "meaning": KNOWN_CALLS.get(target, "other/internal call"),
                "isAudio": target == 0x0042AF73,
                "isDisplaySpawn": target == 0x00435B5B,
            }
        )
    return rows


def build() -> dict[str, Any]:
    routine_rows = []
    all_calls = []
    for routine in ROUTINES:
        asm = disassemble(routine["start"], routine["end"])
        calls = call_rows(asm)
        all_calls.extend(calls)
        routine_rows.append(
            {
                **{key: routine[key] for key in ("name", "range", "role")},
                "callCount": len(calls),
                "audioCallCount": sum(1 for row in calls if row["isAudio"]),
                "displaySpawnCallCount": sum(1 for row in calls if row["isDisplaySpawn"]),
                "calls": calls,
                "disassembly": asm,
            }
        )
    audio_calls = [row for row in all_calls if row["isAudio"]]
    display_calls = [row for row in all_calls if row["isDisplaySpawn"]]
    return {
        "version": 1,
        "kind": "hwanse-battle-status-success-sound-review",
        "source": ["Hwanse2.exe", "out/battle_status_transition_review.json"],
        "status": "no-dedicated-status-success-sound-in-status-routines",
        "summary": {
            "routineCount": len(routine_rows),
            "callCount": len(all_calls),
            "audioCallCount": len(audio_calls),
            "displaySpawnCallCount": len(display_calls),
        },
        "conclusions": [
            "The status success gate and status applier contain RNG/internal calls but no direct WLK/audio playback helper call.",
            "They also do not spawn a separate display/helper object for a status-success presentation.",
            "Therefore status success should not be treated as a fourth independent result sound layer. It rides on the existing hit/result presentation unless later runtime evidence proves an outer caller adds a cue.",
        ],
        "routineRows": routine_rows,
    }


def write_json(data: dict[str, Any]) -> None:
    (OUT / "battle_status_success_sound_review.json").write_text(
        json.dumps(data, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )


def write_md(data: dict[str, Any]) -> None:
    lines = [
        "# Battle Status Success Sound Review",
        "",
        f"- status: `{data['status']}`",
        f"- audio calls in checked routines: `{data['summary']['audioCallCount']}`",
        f"- display spawns in checked routines: `{data['summary']['displaySpawnCallCount']}`",
        "",
        "## Conclusions",
        "",
    ]
    lines.extend(f"- {item}" for item in data["conclusions"])
    lines += ["", "## Routines", "", "| routine | range | calls | audio | display spawn | call targets |", "|---|---|---:|---:|---:|---|"]
    for row in data["routineRows"]:
        targets = "<br>".join(f"`{call['targetVaHex']}` {call['meaning']}" for call in row["calls"]) or "-"
        lines.append(
            f"| {row['name']} | `{row['range']}` | {row['callCount']} | {row['audioCallCount']} | {row['displaySpawnCallCount']} | {targets} |"
        )
    (OUT / "battle_status_success_sound_review.md").write_text("\n".join(lines) + "\n", encoding="utf-8")


def write_html(data: dict[str, Any]) -> None:
    routine_rows = ""
    for row in data["routineRows"]:
        calls = "<br>".join(
            f"<code>{esc(call['targetVaHex'])}</code> {esc(call['meaning'])}"
            for call in row["calls"]
        ) or "-"
        routine_rows += (
            "<tr>"
            f"<td>{esc(row['name'])}</td>"
            f"<td><code>{esc(row['range'])}</code></td>"
            f"<td>{esc(row['role'])}</td>"
            f"<td>{esc(row['callCount'])}</td>"
            f"<td>{esc(row['audioCallCount'])}</td>"
            f"<td>{esc(row['displaySpawnCallCount'])}</td>"
            f"<td>{calls}</td>"
            "</tr>"
        )
    conclusions = "".join(f"<li>{esc(item)}</li>" for item in data["conclusions"])
    html_text = 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 Status Success Sound Review</title>
  <style>
    body {{ margin: 20px; font-family: system-ui, sans-serif; background: #101114; color: #eef1f5; }}
    a {{ color: #9ecbff; }} code {{ color: #ffd37a; }}
    table {{ width: 100%; border-collapse: collapse; margin: 12px 0 24px; font-size: 13px; }}
    th, td {{ border: 1px solid #30343d; padding: 7px 8px; vertical-align: top; text-align: left; }}
    th {{ background: #1b1f27; color: #c7d0dc; position: sticky; top: 0; z-index: 2; }}
    tr:nth-child(even) td {{ background: #151922; }}
    .panel {{ border: 1px solid #30343d; background: #151821; border-radius: 8px; padding: 12px; }}
    .wide {{ overflow: auto; max-height: 78vh; border: 1px solid #30343d; }}
  </style>
</head>
<body>
  <h1>Battle Status Success Sound Review</h1>
  <p><a href="../web/index.html">홈</a> · <a href="battle_sound_role_review.html">사운드 역할</a> · <a href="battle_status_transition_review.html">상태 전환</a> · <a href="battle_status_success_sound_review.json">JSON</a> · <a href="battle_status_success_sound_review.md">MD</a></p>
  <section class="panel"><h2>Conclusions</h2><ul>{conclusions}</ul></section>
  <h2>Checked Routines</h2>
  <div class="wide"><table><thead><tr><th>routine</th><th>range</th><th>role</th><th>calls</th><th>audio</th><th>display spawn</th><th>targets</th></tr></thead><tbody>{routine_rows}</tbody></table></div>
</body>
</html>
"""
    (OUT / "battle_status_success_sound_review.html").write_text(html_text, encoding="utf-8")


def main() -> None:
    data = build()
    write_json(data)
    write_md(data)
    write_html(data)
    print("wrote battle_status_success_sound_review")


if __name__ == "__main__":
    main()
