#!/usr/bin/env python3
"""Summarize whether opcode 0x24 context+0x58 state promotes the current route."""
from __future__ import annotations

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


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


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


def build_summary(
    object_field_refs: dict,
    opcode24_handler: dict,
    opcode20_nested_base_modes: dict,
) -> dict:
    offset_summary = (object_field_refs.get("offsetSummary") or {}).get("0x58") or {}
    context58_refs = (object_field_refs.get("fieldRefsByOffset") or {}).get("0x58") or []
    route_relevant = [
        row for row in object_field_refs.get("routeRelevantRefs") or []
        if row.get("offsetHex") == "0x58"
    ]
    current_boundary = opcode24_handler.get("currentBoundary") or {}
    current_mode = opcode24_handler.get("currentMode") or {}
    context58_handlers = [
        row for row in opcode20_nested_base_modes.get("generalHandlerRows") or []
        if "context+0x58" in (row.get("meaning") or "")
    ]
    current_mode_text = str(current_boundary.get("streamPlus1Hex") or "")
    current_mode_touches_context58 = any(
        "mode1" in (row.get("routeNote") or "").lower()
        for row in route_relevant
    )
    conclusion = (
        "The current map1_01a->map2_02d opcode 0x24 boundary is mode 1, which writes object+0x61 "
        "from 0x0059e348 and advances by +4. The static object-field scan finds context/object+0x58 "
        "references, but the route-relevant opcode 0x24 context+0x58 reference is mode 0 only. "
        "The known nested opcode 0x20 handler that stores context+0x58 is a generic saved-pointer-table "
        "helper, not a direct leaf-table, frontier-reader, or map transition selector. This closes "
        "context+0x58 as standalone promotion evidence for the current route."
    )
    return {
        "source": ROUTE_SOURCE,
        "target": ROUTE_TARGET,
        "objective": "classify opcode 0x24 context+0x58 consumers for the current route blocker",
        "context58RefCount": offset_summary.get("total"),
        "context58ReadCount": offset_summary.get("reads"),
        "context58WriteCount": offset_summary.get("writes"),
        "currentOpcode24VaHex": current_boundary.get("opcodeVaHex"),
        "currentOpcode24ValueHex": current_boundary.get("opcodeValueHex"),
        "currentOpcode24ModeHex": current_boundary.get("streamPlus1Hex"),
        "currentOpcode24ModeMeaning": current_mode.get("meaning"),
        "currentOpcode24ModeTouchesContext58": current_mode_touches_context58,
        "opcode24Mode0Context58RefCount": len(route_relevant),
        "opcode24Mode0Context58Refs": route_relevant,
        "context58Refs": context58_refs,
        "opcode20Context58HandlerCount": len(context58_handlers),
        "opcode20Context58Handlers": context58_handlers,
        "context58PromotionStatus": "blocked-not-current-opcode24-path"
        if current_mode_text == "0x01" and not current_mode_touches_context58
        else "inspect",
        "promotionStatus": "blocked",
        "nextEvidenceNeeded": [
            "runtime producer proof for 0x0059e348 or selected pointer 0x0059de30",
            "strict map1_01a source coordinate or hotspot",
            "selector merge/control-flow proof that reaches selector 2:0",
        ],
        "conclusion": conclusion,
    }


def markdown(summary: dict) -> str:
    lines = [
        "# Save Selector Context+0x58 Consumers",
        "",
        f"- route: `{summary['source']} -> {summary['target']}`",
        f"- context+0x58 refs: {summary.get('context58RefCount')} "
        f"(reads={summary.get('context58ReadCount')}, writes={summary.get('context58WriteCount')})",
        f"- current opcode 0x24: `{summary.get('currentOpcode24VaHex')}` value `{summary.get('currentOpcode24ValueHex')}`",
        f"- current opcode 0x24 mode: `{summary.get('currentOpcode24ModeHex')}`",
        f"- current mode touches context+0x58: {summary.get('currentOpcode24ModeTouchesContext58')}",
        f"- opcode20 context+0x58 handler count: {summary.get('opcode20Context58HandlerCount')}",
        f"- promotion status: `{summary.get('promotionStatus')}`",
        "",
        summary.get("conclusion") or "",
        "",
        "## Route-Relevant Context+0x58 Refs",
        "",
        "| va | access | raw | note |",
        "| --- | --- | --- | --- |",
    ]
    for row in summary.get("opcode24Mode0Context58Refs") or []:
        lines.append(
            f"| `{row.get('vaHex')}` | {row.get('access')} | `{row.get('rawHex')}` | {row.get('routeNote') or '-'} |"
        )
    if not summary.get("opcode24Mode0Context58Refs"):
        lines.append("| - | - | - | - |")
    lines.extend([
        "",
        "## Opcode20 Context+0x58 Handlers",
        "",
        "| opcode | handler | meaning |",
        "| --- | --- | --- |",
    ])
    for row in summary.get("opcode20Context58Handlers") or []:
        lines.append(
            f"| `{row.get('opcodeHex')}` | `{row.get('handlerVaHex')}` | {row.get('meaning') or '-'} |"
        )
    if not summary.get("opcode20Context58Handlers"):
        lines.append("| - | - | - |")
    lines.append("")
    return "\n".join(lines)


def html_page(summary: dict) -> str:
    route_rows = "\n".join(
        "<tr>"
        f"<td><code>{html.escape(str(row.get('vaHex')))}</code></td>"
        f"<td>{html.escape(str(row.get('access')))}</td>"
        f"<td><code>{html.escape(str(row.get('rawHex')))}</code></td>"
        f"<td>{html.escape(str(row.get('routeNote') or '-'))}</td>"
        "</tr>"
        for row in summary.get("opcode24Mode0Context58Refs") or []
    )
    handler_rows = "\n".join(
        "<tr>"
        f"<td><code>{html.escape(str(row.get('opcodeHex')))}</code></td>"
        f"<td><code>{html.escape(str(row.get('handlerVaHex')))}</code></td>"
        f"<td>{html.escape(str(row.get('meaning') or '-'))}</td>"
        "</tr>"
        for row in summary.get("opcode20Context58Handlers") or []
    )
    return "\n".join([
        "<!doctype html><meta charset=\"utf-8\"><title>Save Selector Context+0x58 Consumers</title>",
        "<style>body{font-family:system-ui,sans-serif;background:#111;color:#eee;max-width:1100px;margin:24px auto}table{border-collapse:collapse}td,th{border:1px solid #444;padding:6px 8px;vertical-align:top}code{color:#9bd4ff}</style>",
        "<h1>Save Selector Context+0x58 Consumers</h1>",
        f"<p>route: <code>{html.escape(summary['source'])} -&gt; {html.escape(summary['target'])}</code></p>",
        "<ul>",
        f"<li>context+0x58 refs: {summary.get('context58RefCount')} (reads={summary.get('context58ReadCount')}, writes={summary.get('context58WriteCount')})</li>",
        f"<li>current opcode 0x24: <code>{html.escape(str(summary.get('currentOpcode24VaHex')))}</code> value <code>{html.escape(str(summary.get('currentOpcode24ValueHex')))}</code></li>",
        f"<li>current opcode 0x24 mode: <code>{html.escape(str(summary.get('currentOpcode24ModeHex')))}</code></li>",
        f"<li>current mode touches context+0x58: {summary.get('currentOpcode24ModeTouchesContext58')}</li>",
        f"<li>opcode20 context+0x58 handler count: {summary.get('opcode20Context58HandlerCount')}</li>",
        f"<li>promotion status: <code>{html.escape(str(summary.get('promotionStatus')))}</code></li>",
        "</ul>",
        f"<p>{html.escape(str(summary.get('conclusion') or ''))}</p>",
        "<h2>Route-Relevant Context+0x58 Refs</h2>",
        "<table><thead><tr><th>va</th><th>access</th><th>raw</th><th>note</th></tr></thead><tbody>",
        route_rows or "<tr><td>-</td><td>-</td><td>-</td><td>-</td></tr>",
        "</tbody></table>",
        "<h2>Opcode20 Context+0x58 Handlers</h2>",
        "<table><thead><tr><th>opcode</th><th>handler</th><th>meaning</th></tr></thead><tbody>",
        handler_rows or "<tr><td>-</td><td>-</td><td>-</td></tr>",
        "</tbody></table>",
    ])


def write_outputs(summary: dict, out_dir: Path = OUT) -> None:
    out_dir.mkdir(parents=True, exist_ok=True)
    (out_dir / "save_selector_context58_consumers.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("--out-dir", type=Path, default=OUT)
    args = parser.parse_args()
    out_dir = args.out_dir
    summary = build_summary(
        load_json(out_dir / "save_selector_object_field_refs.json", {}),
        load_json(out_dir / "save_selector_opcode24_handler.json", {}),
        load_json(out_dir / "save_selector_opcode20_nested_base_modes.json", {}),
    )
    write_outputs(summary, out_dir)
    print(f"wrote context+0x58 consumers -> {out_dir / 'save_selector_context58_consumers.json'}")


if __name__ == "__main__":
    main()
