#!/usr/bin/env python3
"""Scan selected-pointer opcode paths across every save-selector root."""
from __future__ import annotations

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

sys.path.insert(0, str(Path(__file__).resolve().parent))

from probe_exe_scene_tables import read_sections, va_to_offset
from summarize_script_handler_table import handler_for_opcode, section_name_for_va


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

SOURCE = "map1_01a"
TARGET = "map2_02d"
CURRENT_SELECTOR = "2:0"
CURRENT_ROOT = 0x00540714
CURRENT_RANGE_START = 0x00540714
CURRENT_RANGE_END = 0x00543578

OPCODE_HANDLERS = {
    0x07: 0x0040AD9B,
    0x08: 0x0040ADC9,
    0x09: 0x0040AE0E,
}


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


def range_hex(start: int, end: int | None) -> str:
    return f"{hex32(start)}..{hex32(end) if end is not None else '?'}"


def parse_hex(value: str | None) -> int | None:
    if not value:
        return None
    return int(value, 16)


def load_json(path: Path, fallback: Any) -> Any:
    if not path.exists():
        return fallback
    return json.loads(path.read_text(encoding="utf-8"))


def dword_at(exe: bytes, sections: list[dict], va: int | None) -> int | None:
    if va is None:
        return None
    offset = va_to_offset(sections, va)
    if offset is None or offset + 4 > len(exe):
        return None
    return struct.unpack_from("<I", exe, offset)[0]


def selector_label(row: dict) -> str:
    return f"{row.get('group')}:{row.get('slot')}"


def unique_roots(selectors: list[dict]) -> list[int]:
    roots = {
        root
        for row in selectors
        if row.get("fieldMaps")
        for root in [parse_hex(row.get("selectedPointerHex"))]
        if root is not None
    }
    return sorted(roots)


def section_end_for_va(sections: list[dict], va: int) -> int | None:
    for section in sections:
        start = int(section["va"])
        end = start + int(section["raw_size"])
        if start <= va < end:
            return end
    return None


def root_end(sections: list[dict], roots: list[int], index: int) -> int | None:
    root = roots[index]
    next_root = roots[index + 1] if index + 1 < len(roots) else None
    section_end = section_end_for_va(sections, root)
    if next_root is not None and section_end is not None:
        return min(next_root, section_end)
    return next_root if next_root is not None else section_end


def selector_rows_for_root(selectors: list[dict], root: int) -> list[dict]:
    return [
        row for row in selectors
        if parse_hex(row.get("selectedPointerHex")) == root
    ]


def field_maps_for_rows(rows: list[dict]) -> list[str]:
    maps = []
    seen = set()
    for row in rows:
        for name in row.get("fieldMaps") or []:
            if name not in seen:
                maps.append(name)
                seen.add(name)
    return maps


def root_metadata(sections: list[dict], selectors: list[dict]) -> list[dict]:
    roots = unique_roots(selectors)
    metas = []
    for index, root in enumerate(roots):
        end = root_end(sections, roots, index)
        rows = selector_rows_for_root(selectors, root)
        labels = [selector_label(row) for row in rows]
        metas.append({
            "root": root,
            "end": end,
            "rootHex": hex32(root),
            "rangeHex": range_hex(root, end),
            "selectorLabels": labels,
            "primarySelector": labels[0] if labels else None,
            "fieldMaps": field_maps_for_rows(rows),
            "isCurrentRoot": root == CURRENT_ROOT or CURRENT_SELECTOR in labels,
            "section": section_name_for_va(sections, root),
        })
    return metas


def current_range_contains(value: int | None) -> bool:
    return value is not None and CURRENT_RANGE_START <= value < CURRENT_RANGE_END


def classify_value(sections: list[dict], metas: list[dict], value: int | None) -> dict:
    if value is None:
        return {"kind": "unreadable"}
    for meta in metas:
        if value == meta["root"]:
            return {
                "kind": "selector-root",
                "selectorLabels": meta["selectorLabels"],
                "primarySelector": meta["primarySelector"],
                "rootHex": meta["rootHex"],
            }
    for meta in metas:
        end = meta.get("end")
        if end is not None and meta["root"] <= value < end:
            return {
                "kind": "selector-range",
                "selectorLabels": meta["selectorLabels"],
                "primarySelector": meta["primarySelector"],
                "rootHex": meta["rootHex"],
            }
    section = section_name_for_va(sections, value)
    if section:
        return {"kind": "pointer", "section": section}
    if (value >> 16) <= 0x400 and (value & 0xFFFF) <= 0x10000:
        return {"kind": "script-scalar-or-pair"}
    return {"kind": "scalar"}


def decode_opcode07(exe: bytes, sections: list[dict], metas: list[dict], va: int, value: int) -> dict:
    index = (value >> 8) & 0xFF
    table = dword_at(exe, sections, va + 4)
    selected_slot = table + index * 4 if table is not None else None
    selected_value = dword_at(exe, sections, selected_slot)
    return {
        "index": index,
        "operandTableHex": hex32(table),
        "operandTableClass": classify_value(sections, metas, table),
        "selectedSlotHex": hex32(selected_slot),
        "selectedValueHex": hex32(selected_value),
        "selectedValueClass": classify_value(sections, metas, selected_value),
        "selectedCurrentRoot": selected_value == CURRENT_ROOT,
        "selectedCurrentRange": current_range_contains(selected_value),
    }


def decode_opcode09(exe: bytes, sections: list[dict], metas: list[dict], va: int, value: int) -> dict:
    mode = (value >> 8) & 0xFF
    if mode == 0:
        stored = va + 4
        source = "next-stream"
    elif mode == 1:
        stored = dword_at(exe, sections, va + 4)
        source = "stream+4-pointer"
    else:
        stored = None
        source = "unsupported-mode"
    return {
        "mode": mode,
        "modeHex": f"0x{mode:02x}",
        "storedPointerSource": source,
        "storedPointerHex": hex32(stored),
        "storedPointerClass": classify_value(sections, metas, stored),
        "storesCurrentRoot": stored == CURRENT_ROOT,
        "storesCurrentRange": current_range_contains(stored),
    }


def producer_from_row(row: dict) -> dict:
    opcode = row.get("opcodeHex")
    if opcode == "0x07":
        pointer_class = row.get("selectedValueClass") or {}
        return {
            "producerVaHex": row.get("vaHex"),
            "producerOpcodeHex": opcode,
            "producerValueHex": row.get("valueHex"),
            "producerPointerHex": row.get("selectedValueHex"),
            "producerPointerClass": pointer_class,
            "producerPointerKind": pointer_class.get("kind"),
            "producerPointerSelector": pointer_class.get("primarySelector"),
            "producesCurrentRoot": row.get("selectedCurrentRoot") is True,
            "producesCurrentRange": row.get("selectedCurrentRange") is True,
        }
    if opcode == "0x09":
        pointer_class = row.get("storedPointerClass") or {}
        return {
            "producerVaHex": row.get("vaHex"),
            "producerOpcodeHex": opcode,
            "producerValueHex": row.get("valueHex"),
            "producerPointerHex": row.get("storedPointerHex"),
            "producerPointerClass": pointer_class,
            "producerPointerKind": pointer_class.get("kind"),
            "producerPointerSelector": pointer_class.get("primarySelector"),
            "producesCurrentRoot": row.get("storesCurrentRoot") is True,
            "producesCurrentRange": row.get("storesCurrentRange") is True,
        }
    return {}


def producer_bucket(meta: dict, producer: dict | None) -> str:
    if not producer:
        return "no-local-producer"
    if producer.get("producesCurrentRoot"):
        return "current-root"
    if producer.get("producesCurrentRange"):
        return "current-range"
    if producer.get("producerPointerSelector") in set(meta.get("selectorLabels") or []):
        return "own-range"
    kind = producer.get("producerPointerKind")
    return str(kind) if kind else "unknown"


def candidate_row(meta: dict, row: dict, kind: str, producer: dict | None = None) -> dict:
    out = {
        "selectorLabels": meta.get("selectorLabels") or [],
        "primarySelector": meta.get("primarySelector"),
        "rootHex": meta.get("rootHex"),
        "rangeHex": meta.get("rangeHex"),
        "vaHex": row.get("vaHex"),
        "valueHex": row.get("valueHex"),
        "opcodeHex": row.get("opcodeHex"),
        "candidateKind": kind,
    }
    if row.get("selectedValueHex"):
        out["selectedValueHex"] = row.get("selectedValueHex")
        out["selectedValueClass"] = row.get("selectedValueClass")
    if row.get("storedPointerHex"):
        out["storedPointerHex"] = row.get("storedPointerHex")
        out["storedPointerClass"] = row.get("storedPointerClass")
    if producer:
        out["nearestProducerVaHex"] = producer.get("producerVaHex")
        out["nearestProducerOpcodeHex"] = producer.get("producerOpcodeHex")
        out["nearestProducerPointerHex"] = producer.get("producerPointerHex")
        out["nearestProducerPointerClass"] = producer.get("producerPointerClass")
    return out


def handler_context(exe: bytes, sections: list[dict], opcode: int) -> dict:
    handler = handler_for_opcode(exe, sections, opcode)
    return {
        "handlerVaHex": handler.get("handlerVaHex"),
        "validHandler": handler.get("handlerVa") == OPCODE_HANDLERS[opcode],
    }


def scan_root(exe: bytes, sections: list[dict], metas: list[dict], meta: dict) -> dict:
    start = meta["root"]
    end = meta.get("end")
    counts = {
        "opcode07Count": 0,
        "opcode08Count": 0,
        "opcode09Count": 0,
        "opcode09Mode0Count": 0,
        "opcode09Mode1Count": 0,
        "opcode07SelectsCurrentRootCount": 0,
        "opcode07SelectsCurrentRangeCount": 0,
        "opcode09StoresCurrentRootCount": 0,
        "opcode09StoresCurrentRangeCount": 0,
        "opcode09StoresOwnRangeCount": 0,
        "opcode08NearestCurrentRootProducerCount": 0,
        "opcode08NearestCurrentRangeProducerCount": 0,
        "opcode08NearestOwnRangeProducerCount": 0,
        "opcode08NearestScriptScalarProducerCount": 0,
        "opcode08NearestUnreadableProducerCount": 0,
        "opcode08NearestNoLocalProducerCount": 0,
        "promotingCandidateCount": 0,
    }
    candidate_rows = []
    sample_rows = []
    last_producer: dict | None = None
    if end is None:
        return {**meta, **counts, "scanned": False, "candidateRows": [], "sampleRows": []}
    for va in range(start, end, 4):
        value = dword_at(exe, sections, va)
        if value is None:
            continue
        opcode = value & 0xFF
        if opcode not in OPCODE_HANDLERS:
            continue
        row = {
            "vaHex": hex32(va),
            "valueHex": hex32(value),
            "opcodeHex": f"0x{opcode:02x}",
        }
        if opcode == 0x07:
            counts["opcode07Count"] += 1
            row.update(decode_opcode07(exe, sections, metas, va, value))
            if row["selectedCurrentRoot"]:
                counts["opcode07SelectsCurrentRootCount"] += 1
            if row["selectedCurrentRange"]:
                counts["opcode07SelectsCurrentRangeCount"] += 1
            last_producer = producer_from_row(row)
            if not meta["isCurrentRoot"] and row["selectedCurrentRange"]:
                row.update(handler_context(exe, sections, opcode))
                candidate_rows.append(candidate_row(meta, row, "opcode07-selects-current-range"))
        elif opcode == 0x08:
            counts["opcode08Count"] += 1
            bucket = producer_bucket(meta, last_producer)
            if last_producer and last_producer.get("producesCurrentRoot"):
                counts["opcode08NearestCurrentRootProducerCount"] += 1
            if last_producer and last_producer.get("producesCurrentRange"):
                counts["opcode08NearestCurrentRangeProducerCount"] += 1
            if bucket == "own-range":
                counts["opcode08NearestOwnRangeProducerCount"] += 1
            elif bucket == "script-scalar-or-pair":
                counts["opcode08NearestScriptScalarProducerCount"] += 1
            elif bucket == "unreadable":
                counts["opcode08NearestUnreadableProducerCount"] += 1
            elif bucket == "no-local-producer":
                counts["opcode08NearestNoLocalProducerCount"] += 1
            if not meta["isCurrentRoot"] and last_producer and last_producer.get("producesCurrentRange"):
                row.update(handler_context(exe, sections, opcode))
                candidate_rows.append(candidate_row(meta, row, "opcode08-activates-current-range", last_producer))
            if meta["isCurrentRoot"] and last_producer and last_producer.get("producesCurrentRange") and len(sample_rows) < 12:
                sample_rows.append(candidate_row(meta, row, "current-internal-opcode08-current-range", last_producer))
        elif opcode == 0x09:
            counts["opcode09Count"] += 1
            row.update(decode_opcode09(exe, sections, metas, va, value))
            if row["mode"] == 0:
                counts["opcode09Mode0Count"] += 1
            elif row["mode"] == 1:
                counts["opcode09Mode1Count"] += 1
            if row["storesCurrentRoot"]:
                counts["opcode09StoresCurrentRootCount"] += 1
            if row["storesCurrentRange"]:
                counts["opcode09StoresCurrentRangeCount"] += 1
            pointer_class = row.get("storedPointerClass") or {}
            if pointer_class.get("primarySelector") in set(meta.get("selectorLabels") or []):
                counts["opcode09StoresOwnRangeCount"] += 1
            last_producer = producer_from_row(row)
            if not meta["isCurrentRoot"] and row["storesCurrentRange"]:
                row.update(handler_context(exe, sections, opcode))
                candidate_rows.append(candidate_row(meta, row, "opcode09-stores-current-range"))
            if meta["isCurrentRoot"] and row["storesCurrentRange"] and len(sample_rows) < 12:
                sample_rows.append(candidate_row(meta, row, "current-internal-opcode09-current-range"))
    counts["promotingCandidateCount"] = len(candidate_rows)
    return {
        **meta,
        **counts,
        "scanned": True,
        "candidateRows": candidate_rows,
        "sampleRows": sample_rows,
    }


def scan_brief(row: dict) -> str:
    return (
        f"op7={row.get('opcode07Count')} op8={row.get('opcode08Count')} op9={row.get('opcode09Count')} "
        f"selectCurrent={row.get('opcode07SelectsCurrentRangeCount')} "
        f"storeCurrent={row.get('opcode09StoresCurrentRangeCount')} "
        f"op8Current={row.get('opcode08NearestCurrentRangeProducerCount')} "
        f"candidates={row.get('promotingCandidateCount')}"
    )


def build_summary(exe: bytes, selectors: list[dict] | None = None) -> dict:
    sections = read_sections(exe)
    selectors = selectors if selectors is not None else load_json(OUT / "save_scene_selectors.json", [])
    metas = root_metadata(sections, selectors)
    scans = [scan_root(exe, sections, metas, meta) for meta in metas]
    non_current_scans = [row for row in scans if not row.get("isCurrentRoot")]
    current_scan = next((row for row in scans if row.get("isCurrentRoot")), {})
    candidate_rows = [
        candidate
        for scan in non_current_scans
        for candidate in scan.get("candidateRows") or []
    ]
    total = lambda key: sum(int(row.get(key) or 0) for row in scans)
    non_current_total = lambda key: sum(int(row.get(key) or 0) for row in non_current_scans)
    selected_root_execution_ref_found = len(candidate_rows) > 0
    conclusion = (
        "The all-selector selected-pointer scan found no non-current save-selector root that selects, stores, "
        "or locally activates the current 2:0 bounded root/range. Current 2:0 still has internal continuation "
        "stores/activations, but those do not prove a predecessor/source root enters the current root. Selected-root "
        "execution remains blocked until a runtime trace, captured selector 2:0 save, or strict map1_01a hotspot is found."
        if not selected_root_execution_ref_found
        else (
            "The all-selector scan found non-current selected-pointer references into the current 2:0 bounded range. "
            "Those rows require manual/runtime review before any route promotion."
        )
    )
    top_activators = sorted(
        (
            {
                "primarySelector": row.get("primarySelector"),
                "selectorLabels": row.get("selectorLabels"),
                "rootHex": row.get("rootHex"),
                "fieldMaps": row.get("fieldMaps")[:12],
                "opcode08Count": row.get("opcode08Count"),
                "opcode08NearestCurrentRangeProducerCount": row.get("opcode08NearestCurrentRangeProducerCount"),
                "promotingCandidateCount": row.get("promotingCandidateCount"),
            }
            for row in scans
            if row.get("opcode08Count")
        ),
        key=lambda row: (row.get("opcode08Count") or 0, row.get("promotingCandidateCount") or 0),
        reverse=True,
    )[:12]
    compact_scans = [
        {
            "primarySelector": row.get("primarySelector"),
            "selectorLabels": row.get("selectorLabels"),
            "rootHex": row.get("rootHex"),
            "rangeHex": row.get("rangeHex"),
            "section": row.get("section"),
            "isCurrentRoot": row.get("isCurrentRoot"),
            "fieldMaps": row.get("fieldMaps")[:16],
            "fieldMapCount": len(row.get("fieldMaps") or []),
            "scanned": row.get("scanned"),
            "opcode07Count": row.get("opcode07Count"),
            "opcode08Count": row.get("opcode08Count"),
            "opcode09Count": row.get("opcode09Count"),
            "opcode09Mode0Count": row.get("opcode09Mode0Count"),
            "opcode09Mode1Count": row.get("opcode09Mode1Count"),
            "opcode07SelectsCurrentRootCount": row.get("opcode07SelectsCurrentRootCount"),
            "opcode07SelectsCurrentRangeCount": row.get("opcode07SelectsCurrentRangeCount"),
            "opcode09StoresCurrentRootCount": row.get("opcode09StoresCurrentRootCount"),
            "opcode09StoresCurrentRangeCount": row.get("opcode09StoresCurrentRangeCount"),
            "opcode08NearestCurrentRootProducerCount": row.get("opcode08NearestCurrentRootProducerCount"),
            "opcode08NearestCurrentRangeProducerCount": row.get("opcode08NearestCurrentRangeProducerCount"),
            "opcode08NearestOwnRangeProducerCount": row.get("opcode08NearestOwnRangeProducerCount"),
            "opcode08NearestScriptScalarProducerCount": row.get("opcode08NearestScriptScalarProducerCount"),
            "opcode08NearestUnreadableProducerCount": row.get("opcode08NearestUnreadableProducerCount"),
            "opcode08NearestNoLocalProducerCount": row.get("opcode08NearestNoLocalProducerCount"),
            "promotingCandidateCount": row.get("promotingCandidateCount"),
        }
        for row in scans
    ]
    return {
        "source": SOURCE,
        "target": TARGET,
        "currentSelector": CURRENT_SELECTOR,
        "currentRootHex": hex32(CURRENT_ROOT),
        "currentRangeHex": range_hex(CURRENT_RANGE_START, CURRENT_RANGE_END),
        "selectorRowCount": len(selectors),
        "selectorRootCount": len(metas),
        "scannedRootCount": sum(1 for row in scans if row.get("scanned")),
        "opcode07RowCount": total("opcode07Count"),
        "opcode08RowCount": total("opcode08Count"),
        "opcode09RowCount": total("opcode09Count"),
        "nonCurrentOpcode07CurrentRootSelectCount": non_current_total("opcode07SelectsCurrentRootCount"),
        "nonCurrentOpcode07CurrentRangeSelectCount": non_current_total("opcode07SelectsCurrentRangeCount"),
        "nonCurrentOpcode09CurrentRootStoreCount": non_current_total("opcode09StoresCurrentRootCount"),
        "nonCurrentOpcode09CurrentRangeStoreCount": non_current_total("opcode09StoresCurrentRangeCount"),
        "nonCurrentOpcode08ActivatorCount": non_current_total("opcode08Count"),
        "nonCurrentOpcode08NearestCurrentRootProducerCount": non_current_total("opcode08NearestCurrentRootProducerCount"),
        "nonCurrentOpcode08NearestCurrentRangeProducerCount": non_current_total("opcode08NearestCurrentRangeProducerCount"),
        "currentInternalOpcode09CurrentRangeStoreCount": current_scan.get("opcode09StoresCurrentRangeCount", 0),
        "currentInternalOpcode08NearestCurrentRangeProducerCount": current_scan.get("opcode08NearestCurrentRangeProducerCount", 0),
        "promotingSelectedPointerPathCount": len(candidate_rows),
        "selectedRootExecutionRefFound": selected_root_execution_ref_found,
        "promotionStatus": "needs-review" if selected_root_execution_ref_found else "blocked",
        "candidateRows": candidate_rows[:128],
        "currentInternalSampleRows": (current_scan.get("sampleRows") or [])[:24],
        "topSelectorsByOpcode08ActivatorCount": top_activators,
        "selectorRootScans": compact_scans,
        "remainingProofs": [
            "capture a runtime selected-pointer trace proving 0x0059de30 reaches 0x00540714",
            "replace the synthetic selector 2:0 savedat vector with a captured gameplay save",
            "find a strict map1_01a source coordinate or hotspot",
        ],
        "conclusion": conclusion,
    }


def markdown(summary: dict) -> str:
    lines = [
        "# Save Selector Global Selected-Pointer Paths",
        "",
        f"- route: {summary['source']} -> {summary['target']}",
        f"- current selector: `{summary['currentSelector']}` root `{summary['currentRootHex']}` range `{summary['currentRangeHex']}`",
        f"- selector rows / roots scanned: {summary['selectorRowCount']} / {summary['scannedRootCount']}",
        f"- opcode rows: 0x07={summary['opcode07RowCount']}, 0x08={summary['opcode08RowCount']}, 0x09={summary['opcode09RowCount']}",
        f"- non-current current-root selects/stores/producers: {summary['nonCurrentOpcode07CurrentRootSelectCount']} / {summary['nonCurrentOpcode09CurrentRootStoreCount']} / {summary['nonCurrentOpcode08NearestCurrentRootProducerCount']}",
        f"- non-current current-range selects/stores/producers: {summary['nonCurrentOpcode07CurrentRangeSelectCount']} / {summary['nonCurrentOpcode09CurrentRangeStoreCount']} / {summary['nonCurrentOpcode08NearestCurrentRangeProducerCount']}",
        f"- current internal current-range stores/producers: {summary['currentInternalOpcode09CurrentRangeStoreCount']} / {summary['currentInternalOpcode08NearestCurrentRangeProducerCount']}",
        f"- promoting selected-pointer path count: {summary['promotingSelectedPointerPathCount']}",
        f"- selected-root execution ref found: {summary['selectedRootExecutionRefFound']}",
        f"- promotion status: `{summary['promotionStatus']}`",
        "",
        summary["conclusion"],
        "",
        "## Selector Root Counts",
        "",
        "| selector | root | range | maps | counts |",
        "| --- | --- | --- | ---: | --- |",
    ]
    for row in summary["selectorRootScans"]:
        labels = ",".join(row.get("selectorLabels") or [])
        current = " current" if row.get("isCurrentRoot") else ""
        lines.append(
            f"| `{labels}`{current} | `{row.get('rootHex')}` | `{row.get('rangeHex')}` | "
            f"{row.get('fieldMapCount')} | {scan_brief(row)} |"
        )
    lines.extend([
        "",
        "## Candidate Rows",
        "",
        "| selector | root | va | opcode | kind | pointer |",
        "| --- | --- | --- | --- | --- | --- |",
    ])
    if not summary["candidateRows"]:
        lines.append("| - | - | - | - | none | - |")
    for row in summary["candidateRows"]:
        pointer = row.get("selectedValueHex") or row.get("storedPointerHex") or row.get("nearestProducerPointerHex") or "-"
        lines.append(
            f"| `{','.join(row.get('selectorLabels') or [])}` | `{row.get('rootHex')}` | `{row.get('vaHex')}` | "
            f"`{row.get('opcodeHex')}` | {row.get('candidateKind')} | `{pointer}` |"
        )
    lines.extend(["", "## Remaining Proofs", ""])
    lines.extend(f"- {item}" for item in summary["remainingProofs"])
    lines.append("")
    return "\n".join(lines)


def html_page(summary: dict) -> str:
    root_rows = "\n".join(
        "<tr>"
        f"<td><code>{html.escape(','.join(row.get('selectorLabels') or []))}</code>{' current' if row.get('isCurrentRoot') else ''}</td>"
        f"<td><code>{html.escape(str(row.get('rootHex')))}</code></td>"
        f"<td><code>{html.escape(str(row.get('rangeHex')))}</code></td>"
        f"<td>{row.get('fieldMapCount')}</td>"
        f"<td>{html.escape(scan_brief(row))}</td>"
        "</tr>"
        for row in summary["selectorRootScans"]
    )
    candidate_rows = "\n".join(
        "<tr>"
        f"<td><code>{html.escape(','.join(row.get('selectorLabels') or []))}</code></td>"
        f"<td><code>{html.escape(str(row.get('rootHex')))}</code></td>"
        f"<td><code>{html.escape(str(row.get('vaHex')))}</code></td>"
        f"<td><code>{html.escape(str(row.get('opcodeHex')))}</code></td>"
        f"<td>{html.escape(str(row.get('candidateKind')))}</td>"
        f"<td><code>{html.escape(str(row.get('selectedValueHex') or row.get('storedPointerHex') or row.get('nearestProducerPointerHex') or '-'))}</code></td>"
        "</tr>"
        for row in summary["candidateRows"]
    ) or "<tr><td colspan=\"6\">none</td></tr>"
    proofs = "".join(f"<li>{html.escape(item)}</li>" for item in summary["remainingProofs"])
    return "\n".join([
        "<!doctype html>",
        '<html lang="en">',
        "<head>",
        '  <meta charset="utf-8">',
        '  <meta name="viewport" content="width=device-width, initial-scale=1">',
        "  <title>Save Selector Global Selected-Pointer Paths</title>",
        "  <style>",
        "    :root { color-scheme: dark; font-family: system-ui, sans-serif; background: #101010; color: #eee; }",
        "    body { margin: 0; padding: 24px; }",
        "    table { width: 100%; border-collapse: collapse; margin: 12px 0 20px; font-size: 13px; }",
        "    th, td { border-bottom: 1px solid #303030; padding: 7px 8px; text-align: left; vertical-align: top; }",
        "    th { background: #181818; color: #ddd; }",
        "    code { color: #d7f0ff; }",
        "  </style>",
        "</head>",
        "<body>",
        "  <h1>Save Selector Global Selected-Pointer Paths</h1>",
        f"  <p>route {html.escape(summary['source'])} -&gt; {html.escape(summary['target'])}; roots scanned {summary['scannedRootCount']}; promoting selected-pointer path count: {summary['promotingSelectedPointerPathCount']}; selected-root execution ref found: {summary['selectedRootExecutionRefFound']}; promotion status <code>{html.escape(summary['promotionStatus'])}</code>.</p>",
        f"  <p>opcode rows: 0x07={summary['opcode07RowCount']}, 0x08={summary['opcode08RowCount']}, 0x09={summary['opcode09RowCount']}.</p>",
        f"  <p>non-current current-range selects/stores/producers: {summary['nonCurrentOpcode07CurrentRangeSelectCount']} / {summary['nonCurrentOpcode09CurrentRangeStoreCount']} / {summary['nonCurrentOpcode08NearestCurrentRangeProducerCount']}; current internal current-range stores/producers: {summary['currentInternalOpcode09CurrentRangeStoreCount']} / {summary['currentInternalOpcode08NearestCurrentRangeProducerCount']}.</p>",
        f"  <p>{html.escape(summary['conclusion'])}</p>",
        "  <h2>Selector Root Counts</h2>",
        "  <table><thead><tr><th>selector</th><th>root</th><th>range</th><th>maps</th><th>counts</th></tr></thead><tbody>",
        root_rows,
        "  </tbody></table>",
        "  <h2>Candidate Rows</h2>",
        "  <table><thead><tr><th>selector</th><th>root</th><th>va</th><th>opcode</th><th>kind</th><th>pointer</th></tr></thead><tbody>",
        candidate_rows,
        "  </tbody></table>",
        f"  <h2>Remaining Proofs</h2><ul>{proofs}</ul>",
        "</body></html>",
    ])


def write_outputs(summary: dict, out_dir: Path, html_out: Path | None = None) -> None:
    out_dir.mkdir(parents=True, exist_ok=True)
    (out_dir / "save_selector_global_selected_pointer_paths.json").write_text(
        json.dumps(summary, ensure_ascii=False, separators=(",", ":")),
        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")


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--exe", type=Path, default=ROOT / "Hwanse2.exe")
    parser.add_argument("--selectors", type=Path, default=OUT / "save_scene_selectors.json")
    parser.add_argument("--out-dir", type=Path, default=OUT)
    parser.add_argument("--html-out", type=Path, default=None, help="Optional HTML report output path.")
    args = parser.parse_args()
    summary = build_summary(
        args.exe.read_bytes(),
        load_json(args.selectors, []),
    )
    write_outputs(summary, args.out_dir, args.html_out)
    print(
        "wrote save selector global selected-pointer paths -> "
        f"{args.out_dir / 'save_selector_global_selected_pointer_paths.json'}"
    )


if __name__ == "__main__":
    main()
