#!/usr/bin/env python3
"""Scan direct dword bridges between map1_01a confirmed and frontier selector contexts."""
from __future__ import annotations

import argparse
import html
import json
import struct
from pathlib import Path

from probe_exe_scene_tables import read_sections, va_to_offset


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

CONFIRMED_CONTEXT = {
    "label": "confirmed entry selector",
    "selector": "0:0",
    "rootVa": 0x00501808,
    "scanStartVa": 0x00501808,
    "scanEndVa": 0x00503570,
    "recordRangeStartVa": 0x005032D8,
    "recordRangeEndVa": 0x00503570,
}
FRONTIER_CONTEXT = {
    "label": "selector-only frontier",
    "selector": "2:0",
    "rootVa": 0x00540714,
    "scanStartVa": 0x00540714,
    "scanEndVa": 0x00542EF4,
    "recordRangeStartVa": 0x00542B44,
    "recordRangeEndVa": 0x00542E78,
}

FRONTIER_EXACT_TARGETS = {
    0x00540714: "frontier root 2:0",
    0x005429DC: "frontier pointer node",
    0x00542A84: "frontier map1_01a leaf",
    0x00542A98: "frontier map1_01a leaf",
    0x00542AAC: "frontier map1_01a leaf",
    0x00542AC0: "frontier shared leaf",
    0x00542AD4: "frontier shared leaf",
    0x00542AE8: "frontier shared leaf",
    0x00542B0C: "frontier reader/resource gate",
    0x00542B44: "frontier source scene record map1_01a",
    0x00542BAC: "frontier target scene record map2_02d",
}
CONFIRMED_EXACT_TARGETS = {
    0x00501808: "confirmed root 0:0",
    0x00503234: "confirmed pointer node",
    0x00503244: "confirmed map1_01a leaf",
    0x00503260: "confirmed shared leaf",
    0x0050327C: "confirmed shared leaf",
    0x005032E4: "confirmed map1_02b leaf",
    0x005032D8: "confirmed entry scene record map1_01a",
    0x00503350: "confirmed event scene record map1_02b",
    0x0050335C: "confirmed event dispatch neighborhood",
}


def va_hex(value: int) -> str:
    return f"0x{value:08x}"


def range_hex(start_va: int, end_va: int) -> str:
    return f"{va_hex(start_va)}..{va_hex(end_va)}"


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


def context_summary(context: dict) -> dict:
    return {
        "label": context["label"],
        "selector": context["selector"],
        "rootVa": context["rootVa"],
        "rootVaHex": va_hex(context["rootVa"]),
        "scanStartVa": context["scanStartVa"],
        "scanEndVa": context["scanEndVa"],
        "scanRangeHex": range_hex(context["scanStartVa"], context["scanEndVa"]),
        "recordRangeStartVa": context["recordRangeStartVa"],
        "recordRangeEndVa": context["recordRangeEndVa"],
        "recordRangeHex": range_hex(context["recordRangeStartVa"], context["recordRangeEndVa"]),
    }


def exact_target_rows(targets: dict[int, str]) -> list[dict]:
    return [
        {"va": va, "vaHex": va_hex(va), "label": label}
        for va, label in sorted(targets.items())
    ]


def target_range_rows(ranges: list[dict]) -> list[dict]:
    return [
        {
            "label": row["label"],
            "startVa": row["startVa"],
            "startVaHex": va_hex(row["startVa"]),
            "endVa": row["endVa"],
            "endVaHex": va_hex(row["endVa"]),
            "rangeHex": range_hex(row["startVa"], row["endVa"]),
        }
        for row in ranges
    ]


def scan_range_for_targets(
    data: bytes,
    sections: list[dict],
    source_range: dict,
    exact_targets: dict[int, str],
    target_ranges: list[dict],
) -> dict:
    hits = []
    readable_dwords = 0
    unreadable_dwords = 0
    for va in range(source_range["startVa"], source_range["endVa"], 4):
        value = dword_at_va(data, sections, va)
        if value is None:
            unreadable_dwords += 1
            continue
        readable_dwords += 1
        exact_label = exact_targets.get(value)
        range_labels = [
            row["label"]
            for row in target_ranges
            if row["startVa"] <= value < row["endVa"]
        ]
        if exact_label is None and not range_labels:
            continue
        hits.append({
            "sourceVa": va,
            "sourceVaHex": va_hex(va),
            "value": value,
            "valueHex": va_hex(value),
            "exactLabel": exact_label,
            "rangeLabels": range_labels,
        })
    return {
        "sourceRange": range_hex(source_range["startVa"], source_range["endVa"]),
        "readableDwordCount": readable_dwords,
        "unreadableDwordCount": unreadable_dwords,
        "exactTargets": exact_target_rows(exact_targets),
        "targetRanges": target_range_rows(target_ranges),
        "dwordHitCount": len(hits),
        "hits": hits,
    }


def build_summary(exe: bytes) -> dict:
    sections = read_sections(exe)
    confirmed_target_ranges = [
        {
            "label": "confirmed entry record cluster",
            "startVa": CONFIRMED_CONTEXT["recordRangeStartVa"],
            "endVa": CONFIRMED_CONTEXT["recordRangeEndVa"],
        }
    ]
    frontier_target_ranges = [
        {
            "label": "frontier selector-only record cluster",
            "startVa": FRONTIER_CONTEXT["recordRangeStartVa"],
            "endVa": FRONTIER_CONTEXT["recordRangeEndVa"],
        }
    ]
    confirmed_to_frontier = scan_range_for_targets(
        exe,
        sections,
        {
            "startVa": CONFIRMED_CONTEXT["scanStartVa"],
            "endVa": CONFIRMED_CONTEXT["scanEndVa"],
        },
        FRONTIER_EXACT_TARGETS,
        frontier_target_ranges,
    )
    frontier_to_confirmed = scan_range_for_targets(
        exe,
        sections,
        {
            "startVa": FRONTIER_CONTEXT["scanStartVa"],
            "endVa": FRONTIER_CONTEXT["scanEndVa"],
        },
        CONFIRMED_EXACT_TARGETS,
        confirmed_target_ranges,
    )
    bridge_found = (
        confirmed_to_frontier["dwordHitCount"] > 0
        or frontier_to_confirmed["dwordHitCount"] > 0
    )
    conclusion = (
        "No direct dword pointer/data bridge was found between the confirmed 0:0 map1_01a entry context "
        "and the selector-only 2:0 map1_01a -> map2_02d frontier context. This is not a full VM execution "
        "proof, but it keeps the contexts separated until a strict hotspot/source coordinate or a runtime "
        "state trace proves selector 2:0 is reached from the confirmed entry path."
    )
    return {
        "map": MAP,
        "frontierTarget": FRONTIER_TARGET,
        "confirmedContext": context_summary(CONFIRMED_CONTEXT),
        "frontierContext": context_summary(FRONTIER_CONTEXT),
        "confirmedToFrontier": confirmed_to_frontier,
        "frontierToConfirmed": frontier_to_confirmed,
        "bridgeFound": bridge_found,
        "promotionStatus": "blocked",
        "limitations": [
            "This scan only checks direct dword values in the known selector/root context windows.",
            "It does not prove dynamic VM execution order or runtime branch-state values.",
            "Promotion still requires a strict map1_01a hotspot/source coordinate or a confirmed runtime trace into selector 2:0.",
        ],
        "conclusion": conclusion,
    }


def markdown(summary: dict) -> str:
    confirmed = summary["confirmedContext"]
    frontier = summary["frontierContext"]
    c2f = summary["confirmedToFrontier"]
    f2c = summary["frontierToConfirmed"]
    lines = [
        "# map1_01a Selector Bridge Refs",
        "",
        f"- map: `{summary['map']}`",
        f"- frontier target: `{summary['frontierTarget']}`",
        f"- confirmed selector/root: `{confirmed['selector']}` / `{confirmed['rootVaHex']}`",
        f"- frontier selector/root: `{frontier['selector']}` / `{frontier['rootVaHex']}`",
        f"- bridge found: {summary['bridgeFound']}",
        f"- promotion status: `{summary['promotionStatus']}`",
        "",
        summary["conclusion"],
        "",
        "## Scan Summary",
        "",
        "| direction | source range | target context | dword hits |",
        "| --- | --- | --- | ---: |",
        (
            f"| confirmed -> frontier | `{c2f['sourceRange']}` | "
            f"`{frontier['selector']}` `{frontier['rootVaHex']}` / `{frontier['recordRangeHex']}` | "
            f"{c2f['dwordHitCount']} hits |"
        ),
        (
            f"| frontier -> confirmed | `{f2c['sourceRange']}` | "
            f"`{confirmed['selector']}` `{confirmed['rootVaHex']}` / `{confirmed['recordRangeHex']}` | "
            f"{f2c['dwordHitCount']} hits |"
        ),
        "",
        "## Target Exact Values",
        "",
        "### Frontier Targets",
        "",
        "| VA | label |",
        "| --- | --- |",
    ]
    for row in c2f["exactTargets"]:
        lines.append(f"| `{row['vaHex']}` | {row['label']} |")
    lines.extend(["", "### Confirmed Targets", "", "| VA | label |", "| --- | --- |"])
    for row in f2c["exactTargets"]:
        lines.append(f"| `{row['vaHex']}` | {row['label']} |")
    lines.extend(["", "## Hits", ""])
    if not c2f["hits"] and not f2c["hits"]:
        lines.append("- 0 hits in both directions; known contexts remain different selector/root contexts.")
    else:
        for direction, scan in (("confirmed -> frontier", c2f), ("frontier -> confirmed", f2c)):
            for hit in scan["hits"]:
                labels = [hit.get("exactLabel") or "-"] + (hit.get("rangeLabels") or [])
                lines.append(
                    f"- {direction}: `{hit['sourceVaHex']}` = `{hit['valueHex']}` ({', '.join(labels)})"
                )
    lines.extend(["", "## Limitations", ""])
    lines.extend(f"- {item}" for item in summary["limitations"])
    lines.append("")
    return "\n".join(lines)


def html_page(summary: dict) -> str:
    confirmed = summary["confirmedContext"]
    frontier = summary["frontierContext"]
    c2f = summary["confirmedToFrontier"]
    f2c = summary["frontierToConfirmed"]
    scan_rows = [
        (
            "<tr>"
            "<td>confirmed -&gt; frontier</td>"
            f"<td><code>{html.escape(c2f['sourceRange'])}</code></td>"
            f"<td><code>{html.escape(frontier['selector'])}</code> "
            f"<code>{html.escape(frontier['rootVaHex'])}</code> / "
            f"<code>{html.escape(frontier['recordRangeHex'])}</code></td>"
            f"<td>{c2f['dwordHitCount']} hits</td>"
            "</tr>"
        ),
        (
            "<tr>"
            "<td>frontier -&gt; confirmed</td>"
            f"<td><code>{html.escape(f2c['sourceRange'])}</code></td>"
            f"<td><code>{html.escape(confirmed['selector'])}</code> "
            f"<code>{html.escape(confirmed['rootVaHex'])}</code> / "
            f"<code>{html.escape(confirmed['recordRangeHex'])}</code></td>"
            f"<td>{f2c['dwordHitCount']} hits</td>"
            "</tr>"
        ),
    ]
    target_rows = []
    for row in c2f["exactTargets"]:
        target_rows.append(
            "<tr>"
            "<td>frontier</td>"
            f"<td><code>{html.escape(row['vaHex'])}</code></td>"
            f"<td>{html.escape(row['label'])}</td>"
            "</tr>"
        )
    for row in f2c["exactTargets"]:
        target_rows.append(
            "<tr>"
            "<td>confirmed</td>"
            f"<td><code>{html.escape(row['vaHex'])}</code></td>"
            f"<td>{html.escape(row['label'])}</td>"
            "</tr>"
        )
    if c2f["hits"] or f2c["hits"]:
        hit_items = []
        for direction, scan in (("confirmed -> frontier", c2f), ("frontier -> confirmed", f2c)):
            for hit in scan["hits"]:
                labels = [hit.get("exactLabel") or "-"] + (hit.get("rangeLabels") or [])
                hit_items.append(
                    "<li>"
                    f"{html.escape(direction)}: <code>{html.escape(hit['sourceVaHex'])}</code> = "
                    f"<code>{html.escape(hit['valueHex'])}</code> "
                    f"({html.escape(', '.join(labels))})"
                    "</li>"
                )
        hit_html = "<ul>" + "".join(hit_items) + "</ul>"
    else:
        hit_html = "<p>0 hits in both directions; known contexts remain different selector/root contexts.</p>"
    limitations = "".join(f"<li>{html.escape(item)}</li>" for item in summary["limitations"])
    return "\n".join([
        "<!doctype html>",
        '<html lang="en">',
        "<head>",
        '  <meta charset="utf-8">',
        '  <meta name="viewport" content="width=device-width, initial-scale=1">',
        "  <title>map1_01a Selector Bridge Refs</title>",
        "  <style>",
        "    :root { color-scheme: dark; font-family: system-ui, sans-serif; background: #101010; color: #eee; }",
        "    body { margin: 0; padding: 24px; }",
        "    h1 { margin: 0 0 8px; font-size: 24px; }",
        "    h2 { margin: 24px 0 8px; font-size: 18px; }",
        "    p { max-width: 1100px; color: #bbb; line-height: 1.45; }",
        "    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 { position: sticky; top: 0; background: #181818; color: #ddd; }",
        "    code { color: #d7f0ff; }",
        "  </style>",
        "</head>",
        "<body>",
        "  <h1>map1_01a Selector Bridge Refs</h1>",
        f"  <p>confirmed selector/root <code>{html.escape(confirmed['selector'])}</code> "
        f"<code>{html.escape(confirmed['rootVaHex'])}</code>; frontier selector/root "
        f"<code>{html.escape(frontier['selector'])}</code> <code>{html.escape(frontier['rootVaHex'])}</code>; "
        f"bridge found: {summary['bridgeFound']}; promotion status "
        f"<code>{html.escape(summary['promotionStatus'])}</code></p>",
        f"  <p>{html.escape(summary['conclusion'])}</p>",
        "  <h2>Scan Summary</h2>",
        "  <table><thead><tr><th>direction</th><th>source range</th><th>target context</th><th>dword hits</th></tr></thead><tbody>",
        *scan_rows,
        "  </tbody></table>",
        "  <h2>Target Exact Values</h2>",
        "  <table><thead><tr><th>context</th><th>VA</th><th>label</th></tr></thead><tbody>",
        *target_rows,
        "  </tbody></table>",
        "  <h2>Hits</h2>",
        f"  {hit_html}",
        "  <h2>Limitations</h2>",
        f"  <ul>{limitations}</ul>",
        "</body>",
        "</html>",
        "",
    ])


def write_outputs(summary: dict, out_dir: Path = OUT) -> None:
    out_dir.mkdir(parents=True, exist_ok=True)
    (out_dir / "map1_01a_selector_bridge_refs.json").write_text(
        json.dumps(summary, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--exe", type=Path, default=ROOT / "Hwanse2.exe")
    parser.add_argument("--out-dir", type=Path, default=OUT)
    args = parser.parse_args()
    summary = build_summary(args.exe.read_bytes())
    write_outputs(summary, args.out_dir)
    print(f"wrote map1_01a selector bridge refs -> {args.out_dir / 'map1_01a_selector_bridge_refs.json'}")


if __name__ == "__main__":
    main()
