#!/usr/bin/env python3
"""Document the critical/alternate result presentation path.

The sound-role report promotes attacker +0x62 bit 0x10 as the critical-like
alternate result branch.  This pass follows the spawned child script that the
0x10 consumer attaches through [0x442da1 + 0x150].  The resulting script is a
palette operation, not a sprite label.
"""
from __future__ import annotations

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


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

if str(TOOLS) not in sys.path:
    sys.path.insert(0, str(TOOLS))

from build_battle_display_vm_static_decode import EXE, read_sections  # noqa: E402
from build_battle_helper_body_review import (  # noqa: E402
    CHILD_SCRIPT_TABLE_PTR_VA,
    disassemble,
    pe_sections,
    read_u32,
)
from build_battle_helper_child_script_review import walk_child_script  # noqa: E402


CHILD_SCRIPT_TABLE_OFFSET = 0x150
CRITICAL_CONSUMER_VA = 0x0040EF16
CRITICAL_CONSUMER_END_VA = 0x0040EFAA
GENERIC_RUNNER_VA = 0x00402321
OPCODE_3B_HANDLER_VA = 0x00406313
OPCODE_38_HANDLER_VA = 0x004060DB
PALETTE_BACKUP_HELPER_VA = 0x004012C0
PALETTE_DIRTY_FLAG = 0x00559D98
PALETTE_PTR = 0x004676E8
PALETTE_BACKUP_BUF = 0x00559DA1


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


def hx(value: int | None) -> str:
    return "" if value is None else f"0x{value:08x}"


def extract_key_rows(decoded: dict[str, Any]) -> list[dict[str, Any]]:
    out = []
    for row in decoded.get("rows") or []:
        out.append(
            {
                "vaHex": row.get("vaHex"),
                "opcode": row.get("opcode"),
                "category": row.get("category"),
                "length": row.get("length"),
                "bytes": row.get("bytes"),
                "summary": row.get("summary"),
                "paletteStartHex": row.get("paletteStartHex"),
                "paletteCountHex": row.get("paletteCountHex"),
                "argHex": row.get("argHex"),
                "repeatCount": row.get("repeatCount"),
                "targetVaHex": row.get("targetVaHex"),
                "waitFrames": row.get("waitFrames"),
            }
        )
    return out


def build() -> dict[str, Any]:
    blob = EXE.read_bytes()
    _image_base, body_sections = pe_sections(blob)
    child_sections = read_sections(blob)
    child_table_base = read_u32(blob, body_sections, CHILD_SCRIPT_TABLE_PTR_VA)
    child_script_va = read_u32(blob, body_sections, child_table_base + CHILD_SCRIPT_TABLE_OFFSET)
    decoded = walk_child_script(blob, child_sections, child_script_va, max_steps=80)
    rows = extract_key_rows(decoded)
    palette_rows = [row for row in rows if str(row.get("category") or "").startswith("palette")]
    repeat_rows = [row for row in rows if row.get("opcode") == "0x06"]
    yield_count = sum(1 for row in rows if row.get("opcode") == "0x01")
    return {
        "version": 1,
        "kind": "hwanse-battle-critical-presentation-review",
        "source": [
            "Hwanse2.exe",
            "out/battle_sound_role_review.json",
            "tools/build_battle_helper_child_script_review.py",
        ],
        "status": "critical-palette-flash-script-grounded",
        "consumer": {
            "consumerVaHex": hx(CRITICAL_CONSUMER_VA),
            "consumerEndVaHex": hx(CRITICAL_CONSUMER_END_VA),
            "flag": "attacker +0x62 bit 0x10",
            "alternateWlkOperand": "stream +3",
            "runnerVaHex": hx(GENERIC_RUNNER_VA),
            "childScriptTablePointerVaHex": hx(CHILD_SCRIPT_TABLE_PTR_VA),
            "childScriptTableOffsetHex": f"0x{CHILD_SCRIPT_TABLE_OFFSET:03x}",
            "childScriptIndex": CHILD_SCRIPT_TABLE_OFFSET // 4,
            "childScriptVaHex": hx(child_script_va),
        },
        "paletteBinding": {
            "opcode3bHandlerVaHex": hx(OPCODE_3B_HANDLER_VA),
            "opcode38HandlerVaHex": hx(OPCODE_38_HANDLER_VA),
            "paletteBackupHelperVaHex": hx(PALETTE_BACKUP_HELPER_VA),
            "palettePointerVaHex": hx(PALETTE_PTR),
            "paletteBackupBufferVaHex": hx(PALETTE_BACKUP_BUF),
            "paletteDirtyFlagVaHex": hx(PALETTE_DIRTY_FLAG),
            "meaning": "0x3b backs up palette RGB triplets, 0x38 mode 1 applies the bright flash range, and 0x38 mode 2 performs a fade/step sequence before the object exits.",
        },
        "scriptSummary": {
            "childScriptVaHex": hx(child_script_va),
            "stopReason": decoded.get("stopReason"),
            "instructionCount": decoded.get("instructionCount"),
            "opcodeCounts": decoded.get("opcodeCounts") or {},
            "paletteRows": len(palette_rows),
            "yieldCount": yield_count,
            "repeatLoopCount": len(repeat_rows),
            "durationInterpretation": "The script has one immediate flash write, then a mode2 palette step at 0x004b85e8, a yield, and a repeat-loop count 8 back to that mode2 step. Exact wall-clock ms still depends on the display VM tick cadence.",
        },
        "scriptRows": rows,
        "consumerDisassembly": disassemble(CRITICAL_CONSUMER_VA, CRITICAL_CONSUMER_END_VA),
        "opcode3bDisassembly": disassemble(OPCODE_3B_HANDLER_VA, 0x00406341),
        "opcode38Disassembly": disassemble(OPCODE_38_HANDLER_VA, 0x00406206),
        "conclusions": [
            "Critical/alternate result presentation is not a MISS/HIT text sprite path.",
            "The 0x10 consumer plays alternate WLK id 14 and spawns generic runner 0x00402321.",
            "The spawned child script [0x442da1 + 0x150] resolves to 0x004b85cc.",
            "That child script backs up palette range 0x30 count 0xa0, applies palette transform mode 1 with arg 0xffffffff, then loops palette transform mode 2 with 32-step parameter eight times.",
            "Therefore the critical white flash should be implemented as a palette/full-screen flash presentation; only exact wall-clock duration remains approximate in the browser runner.",
        ],
    }


def markdown(data: dict[str, Any]) -> str:
    lines = [
        "# Battle Critical Presentation Review",
        "",
        f"- status: `{data['status']}`",
        f"- child script: `{data['consumer']['childScriptVaHex']}`",
        f"- child table index: `{data['consumer']['childScriptIndex']}`",
        "",
        "## Conclusions",
        "",
    ]
    lines.extend(f"- {item}" for item in data["conclusions"])
    lines += [
        "",
        "## Script Rows",
        "",
        "| VA | opcode | category | bytes | summary |",
        "| --- | --- | --- | --- | --- |",
    ]
    for row in data["scriptRows"]:
        lines.append(
            f"| `{row['vaHex']}` | `{row['opcode']}` | {row['category']} | `{row['bytes']}` | {row['summary']} |"
        )
    lines += [
        "",
        "## Consumer",
        "",
        "```asm",
        data["consumerDisassembly"],
        "```",
        "",
        "## Palette Opcode 0x3b",
        "",
        "```asm",
        data["opcode3bDisassembly"],
        "```",
        "",
        "## Palette Opcode 0x38",
        "",
        "```asm",
        data["opcode38Disassembly"],
        "```",
    ]
    return "\n".join(lines) + "\n"


def html_page(data: dict[str, Any]) -> str:
    consumer_rows = "".join(
        f"<tr><td>{esc(key)}</td><td><code>{esc(value)}</code></td></tr>"
        for key, value in data["consumer"].items()
    )
    summary_rows = "".join(
        f"<tr><td>{esc(key)}</td><td><code>{esc(value)}</code></td></tr>"
        for key, value in data["scriptSummary"].items()
    )
    script_rows = "".join(
        "<tr>"
        f"<td><code>{esc(row['vaHex'])}</code></td>"
        f"<td><code>{esc(row['opcode'])}</code></td>"
        f"<td>{esc(row['category'])}</td>"
        f"<td><code>{esc(row['bytes'])}</code></td>"
        f"<td>{esc(row['summary'])}</td>"
        "</tr>"
        for row in data["scriptRows"]
    )
    conclusions = "".join(f"<li>{esc(item)}</li>" for item in data["conclusions"])
    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 Critical Presentation 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; }}
    .grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 12px; }}
    .panel {{ border: 1px solid #30343d; background: #151821; border-radius: 8px; padding: 12px; }}
    .wide {{ overflow: auto; max-height: 78vh; border: 1px solid #30343d; }}
    pre {{ white-space: pre-wrap; background: #151922; border: 1px solid #30343d; padding: 10px; max-height: 420px; overflow: auto; }}
  </style>
</head>
<body>
  <h1>Battle Critical Presentation Review</h1>
  <p><a href="../web/index.html">홈</a> · <a href="battle_sound_role_review.html">사운드 역할</a> · <a href="battle_result_flag_lifecycle_review.html">결과 플래그</a> · <a href="battle_critical_presentation_review.json">JSON</a> · <a href="battle_critical_presentation_review.md">MD</a></p>
  <div class="grid">
    <section class="panel"><h2>Consumer</h2><table><tbody>{consumer_rows}</tbody></table></section>
    <section class="panel"><h2>Script Summary</h2><table><tbody>{summary_rows}</tbody></table></section>
    <section class="panel"><h2>Conclusions</h2><ul>{conclusions}</ul></section>
  </div>
  <h2>Script Rows</h2>
  <div class="wide"><table><thead><tr><th>VA</th><th>opcode</th><th>category</th><th>bytes</th><th>summary</th></tr></thead><tbody>{script_rows}</tbody></table></div>
  <h2>Consumer Disassembly</h2>
  <pre>{esc(data["consumerDisassembly"])}</pre>
  <h2>Palette Opcode 0x3b</h2>
  <pre>{esc(data["opcode3bDisassembly"])}</pre>
  <h2>Palette Opcode 0x38</h2>
  <pre>{esc(data["opcode38Disassembly"])}</pre>
</body>
</html>
"""


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


if __name__ == "__main__":
    main()
