#!/usr/bin/env python3
"""Document target result display branches for per-hit result flags.

This report joins two already grounded facts:

* actor +0x62 bit 0x20/0x08 is produced by the hit/avoid branch.
* target result object script 0x00454578 tests those same bits and chooses
  WLK/helper/display-frame branches.

The goal is a runner-facing contract, with the helper label/effect proof linked
from `battle_result_helper_label_review`.
"""
from __future__ import annotations

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


ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "out"
RESULT_LAYER_JSON = OUT / "battle_result_layer_trace_review.json"
PER_HIT_JSON = OUT / "battle_per_hit_result_flag_review.json"


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


def read_json(path: Path) -> dict[str, Any]:
    return json.loads(path.read_text(encoding="utf-8"))


BRANCH_ROWS = [
    {
        "case": "normal hit",
        "flag": "neither 0x20 nor 0x08",
        "scriptVa": "0x00454588",
        "scriptBytes": "bd 02 00 00; 21 01 08 00 00 00 04 00",
        "effect": "helper 0x02(dec 2), target frame 4 gate 8",
        "wlk": "normal result WLK selected earlier by 0xc2 operand",
        "runnerAction": "show normal hit frame/shake path and damage number from +0x6e",
        "confidence": "확정",
    },
    {
        "case": "0x20 full miss / HP apply skip",
        "flag": "target +0x62 & 0x20",
        "scriptVa": "0x00454578 -> 0x0045459c",
        "scriptBytes": "13 c6 62 20 9c 45 45 00; 24 01 00 0c; bd 05 00 00; 21 01 08 00 00 00 00 00",
        "effect": "WLK id 12, helper 0x05(dec 5), helper body direct btl_etc MISS F81, target frame 0 gate 8",
        "wlk": "WLK id 12 from target result script, while normal 0xc2 sound is suppressed by mask 0x28",
        "runnerAction": "do not apply/show damage; show MISS badge from btl_etc frame 81 and route to miss-style target result branch",
        "confidence": "확정",
    },
    {
        "case": "0x08 guard/glancing chip damage",
        "flag": "target +0x62 & 0x08",
        "scriptVa": "0x00454580 -> 0x004545b4",
        "scriptBytes": "13 c6 62 08 b4 45 45 00; 24 01 00 0b; bd 16 00 00; 21 01 08 00 00 00 00 00",
        "effect": "WLK id 11, helper 0x16(dec 22), btl_efc F56..59, target frame 0 gate 8",
        "wlk": "WLK id 11 from target result script, while normal 0xc2 sound is suppressed by mask 0x28",
        "runnerAction": "show 1..3 guard/glancing chip result from +0x6e with branch-specific WLK/helper effect; do not show MISS label and keep target on normal frame",
        "confidence": "확정",
    },
    {
        "case": "hit-frame4 shake loop",
        "flag": "normal result continuation / repeated hit display",
        "scriptVa": "0x004545d0",
        "scriptBytes": "21 frame 4 gates, helper 0x07(dec 7), alternating arithmetic writes, AD mask 0x20",
        "effect": "target frame 4 held with shake-style arithmetic writes",
        "wlk": "no separate WLK in this block",
        "runnerAction": "keep target in hit frame and shake during repeated-hit windows",
        "confidence": "정적 분기 + 오프닝 자동 재생 보조 확인",
    },
]


RUNNER_RULES = [
    {
        "priority": 1,
        "rule": "If target has 0x20, skip HP apply and suppress the skill normal result WLK; execute target branch WLK id 12/helper 0x05(dec 5)/frame0.",
        "reason": "0x4340ae returns before HP apply, and target result script tests 0x20 first.",
    },
    {
        "priority": 2,
        "rule": "If target has 0x08, use +0x6e=1..3, suppress the skill normal result WLK, then execute WLK id 11/helper 0x16(dec 22)/frame0 without MISS text.",
        "reason": "0x43414d sets 1..3 and clears +0x6c, while 0x40ef1b suppresses normal sound through mask 0x28.",
    },
    {
        "priority": 3,
        "rule": "If attacker has 0x10 and was not downgraded, alternate sound/effect is consumed before normal branch handling.",
        "reason": "0x40ef1b checks 0x10 first, spawns 0x402321, and clears 0x10.",
    },
    {
        "priority": 4,
        "rule": "Otherwise use skill 0xc2 normal result WLK and normal target frame4/shake path.",
        "reason": "Target result script falls through to helper 0x02(dec 2)/frame4, and static continuation block 0x004545d0 handles the repeated-hit shake path.",
    },
]


def rows_by_va(layer: dict[str, Any]) -> dict[str, dict[str, Any]]:
    return {row.get("vaHex", ""): row for row in layer.get("targetScriptRows") or []}


def target_evidence_rows(layer: dict[str, Any]) -> list[dict[str, Any]]:
    wanted = {
        "0x00454578",
        "0x00454580",
        "0x00454588",
        "0x0045458c",
        "0x0045459c",
        "0x004545a0",
        "0x004545a4",
        "0x004545b4",
        "0x004545b8",
        "0x004545bc",
        "0x004545c4",
        "0x004545d8",
        "0x004545e0",
        "0x0045462c",
    }
    return [row for row in layer.get("targetScriptRows") or [] if row.get("vaHex") in wanted]


def build_data() -> dict[str, Any]:
    layer = read_json(RESULT_LAYER_JSON)
    per_hit = read_json(PER_HIT_JSON)
    return {
        "version": 1,
        "kind": "battle-result-display-branch-review",
        "source": [
            "out/battle_result_layer_trace_review.json",
            "out/battle_per_hit_result_flag_review.json",
        ],
        "status": "target-result-display-branches-grounded",
        "summary": [
            "Target result object script 0x00454578 tests actor sideFlags(+0x62) bit 0x20 first, then bit 0x08.",
            "The 0x20 branch selects WLK id 12, helper operand 0x05(dec 5), and target frame 0. This is the display/sound branch paired with the full miss / HP-apply skip bit.",
            "The 0x08 branch selects WLK id 11, helper operand 0x16(dec 22), and target frame 0. This is the display/sound branch paired with the guard/glancing chip-damage bit.",
            "The normal branch falls through to helper operand 0x02(dec 2) and target frame 4; the 0x004545d0 frame-4 shake loop is a static continuation block.",
            "The helper label/effect binding is now grounded in battle_result_helper_label_review: 0x05 directly renders btl_etc MISS F81 and 0x16 runs btl_efc F56..59.",
        ],
        "branchRows": BRANCH_ROWS,
        "runnerRules": RUNNER_RULES,
        "targetEvidenceRows": target_evidence_rows(layer),
        "perHitFlagSummary": per_hit.get("summary") or [],
    }


def write_json(data: dict[str, Any]) -> None:
    (OUT / "battle_result_display_branch_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 Result Display Branch Review",
        "",
        "`target result object script 0x00454578`의 `+0x62` 결과 플래그 분기를 정리한다.",
        "",
        "## 결론",
        "",
    ]
    lines.extend(f"- {item}" for item in data["summary"])
    lines.extend(["", "## Branches", ""])
    lines.append("| Case | Flag | Script VA | Effect | WLK | Runner action | Confidence |")
    lines.append("|---|---|---|---|---|---|---|")
    for row in data["branchRows"]:
        lines.append(
            f"| {row['case']} | `{row['flag']}` | `{row['scriptVa']}` | {row['effect']} | {row['wlk']} | {row['runnerAction']} | {row['confidence']} |"
        )
    lines.extend(["", "## Runner Rules", ""])
    lines.append("| Priority | Rule | Reason |")
    lines.append("|---:|---|---|")
    for row in data["runnerRules"]:
        lines.append(f"| {row['priority']} | {row['rule']} | {row['reason']} |")
    lines.extend(["", "## Target Script Evidence", ""])
    lines.append("| VA | Opcode | Bytes | Summary |")
    lines.append("|---|---|---|---|")
    for row in data["targetEvidenceRows"]:
        lines.append(
            f"| `{row.get('vaHex')}` | `{row.get('opcode')}` | `{row.get('bytes')}` | {row.get('summary')} |"
        )
    (OUT / "battle_result_display_branch_review.md").write_text("\n".join(lines).rstrip() + "\n", encoding="utf-8")


def table(headers: list[str], rows: list[list[Any]]) -> str:
    chunks = ["<table><thead><tr>"]
    chunks.extend(f"<th>{esc(header)}</th>" for header in headers)
    chunks.append("</tr></thead><tbody>")
    for row in rows:
        chunks.append("<tr>")
        chunks.extend(f"<td>{cell}</td>" for cell in row)
        chunks.append("</tr>")
    chunks.append("</tbody></table>")
    return "".join(chunks)


def write_html(data: dict[str, Any]) -> None:
    branch_rows = [
        [
            esc(row["case"]),
            f"<code>{esc(row['flag'])}</code>",
            f"<code>{esc(row['scriptVa'])}</code>",
            esc(row["effect"]),
            esc(row["wlk"]),
            esc(row["runnerAction"]),
            f"<span class=\"tag good\">{esc(row['confidence'])}</span>",
        ]
        for row in data["branchRows"]
    ]
    rule_rows = [
        [str(row["priority"]), esc(row["rule"]), esc(row["reason"])]
        for row in data["runnerRules"]
    ]
    evidence_rows = [
        [
            f"<code>{esc(row.get('vaHex'))}</code>",
            f"<code>{esc(row.get('opcode'))}</code>",
            f"<code>{esc(row.get('bytes'))}</code>",
            esc(row.get("summary")),
        ]
        for row in data["targetEvidenceRows"]
    ]
    html_text = f"""<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Battle Result Display Branch Review</title>
  <style>
    :root {{ color-scheme: light; --bg:#f5f7fb; --panel:#fff; --line:#d8dee9; --ink:#18202b; --muted:#5b6472; --good:#127a43; }}
    * {{ box-sizing:border-box; }}
    body {{ margin:0; font-family:Arial, "Malgun Gothic", sans-serif; background:var(--bg); color:var(--ink); }}
    main {{ max-width:1220px; margin:0 auto; padding:24px; }}
    h1 {{ margin:0 0 8px; font-size:26px; }}
    h2 {{ margin:0 0 12px; font-size:18px; }}
    a {{ color:#1f5fbf; text-decoration:none; }}
    a:hover {{ text-decoration:underline; }}
    .nav {{ display:flex; gap:12px; flex-wrap:wrap; margin:0 0 18px; }}
    .card {{ background:var(--panel); border:1px solid var(--line); border-radius:8px; padding:16px; margin:14px 0; box-shadow:0 1px 2px rgba(20,30,50,.05); }}
    .summary {{ margin:8px 0 0; padding-left:20px; }}
    .summary li {{ margin:6px 0; }}
    table {{ width:100%; border-collapse:collapse; font-size:13px; }}
    th, td {{ border:1px solid var(--line); padding:8px; vertical-align:top; }}
    th {{ background:#eef2f8; text-align:left; }}
    code {{ font-family:Consolas, "Liberation Mono", monospace; background:#f0f3f8; padding:1px 4px; border-radius:4px; }}
    .tag {{ display:inline-block; border:1px solid #bfe3cc; border-radius:999px; padding:2px 7px; background:#eefaf2; color:var(--good); white-space:nowrap; }}
  </style>
</head>
<body>
<main>
  <h1>Battle Result Display Branch Review</h1>
  <div class="nav"><a href="../web/index.html">index</a><a href="battle_per_hit_result_flag_review.html">per-hit result flags</a><a href="battle_result_layer_trace_review.html">result layer trace</a><a href="battle_sound_role_review.html">sound role</a></div>
  <section class="card">
    <h2>결론</h2>
    <ul class="summary">{''.join(f'<li>{esc(item)}</li>' for item in data['summary'])}</ul>
  </section>
  <section class="card"><h2>Branches</h2>{table(["case", "flag", "script VA", "effect", "WLK", "runner action", "confidence"], branch_rows)}</section>
  <section class="card"><h2>Runner Rules</h2>{table(["priority", "rule", "reason"], rule_rows)}</section>
  <section class="card"><h2>Target Script Evidence</h2>{table(["VA", "opcode", "bytes", "summary"], evidence_rows)}</section>
</main>
</body>
</html>
"""
    (OUT / "battle_result_display_branch_review.html").write_text(html_text, encoding="utf-8")


def main() -> None:
    OUT.mkdir(exist_ok=True)
    data = build_data()
    write_json(data)
    write_md(data)
    write_html(data)
    print("wrote battle_result_display_branch_review html/json/md")


if __name__ == "__main__":
    main()
