#!/usr/bin/env python3
"""Build a handoff page for the remaining scene/event producer evidence gap.

The static audits have grounded the generic selected-root consumer/writer and
prompt pipeline, but they still do not prove the route-specific scheduler that
chooses one concrete selector/root.  This artifact records the exact runtime
evidence needed next so future work does not repeat saturated static scans.
"""
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"
WEB = ROOT / "web"

JSON_OUT = OUT / "scene_event_runtime_evidence_handoff.json"
WEB_HTML_OUT = WEB / "scene_event_runtime_evidence_handoff.html"


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


def write_json(path: Path, payload: Any) -> None:
    path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")


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


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


def build_payload() -> dict[str, Any]:
    completion_summary = {
        "checklistItemCount": 9,
        "coveredItemCount": 6,
        "partialOrBlockedItemCount": 3,
    }
    frontier_summary = {
        "staticFrontierSaturated": True,
        "routeSpecificProducerPromoted": False,
        "completionDecision": "do-not-call-update-goal",
        "nextEvidenceNeeded": [
            "runtime trace of selector byte writes before selected-root dispatch",
            "runtime trace of descriptor-run order/count values and caller path at 0x004576e8/0x004576e9",
            "new instruction-bound scheduler/list consumer not covered by current static xref and pattern scans",
        ],
    }
    route34 = load_json(OUT / "scene_034b_route34_upper_scheduler_gap_audit.json", {})
    route34_summary = route34.get("summary") or {}

    watchpoints = [
        {
            "name": "selector group byte",
            "vaHex": hx(0x004576DA),
            "width": 1,
            "reason": "Generic selected-root lookup reads this as the selector directory group.",
            "capture": [
                "write caller/call stack",
                "current script/object pointer if available",
                "new value and adjacent selector slot byte",
            ],
        },
        {
            "name": "selector slot byte",
            "vaHex": hx(0x004576DB),
            "width": 1,
            "reason": "Generic selected-root lookup reads this as the selector row slot.",
            "capture": [
                "write caller/call stack",
                "new value",
                "paired selector group byte",
            ],
        },
        {
            "name": "selected root global",
            "vaHex": hx(0x0059DE30),
            "width": 4,
            "reason": "This is the selected-root pointer consumed by the prompt pipeline.",
            "capture": [
                "write caller/call stack",
                "written root pointer",
                "selector bytes at 0x004576da/db at the same moment",
                "whether the writer is generic opcode handling or an outer scheduler",
            ],
        },
        {
            "name": "runtime descriptor count",
            "vaHex": hx(0x004576E8),
            "width": 1,
            "reason": "Generic active-object descriptor add/remove writes this count.",
            "capture": [
                "writer opcode/caller",
                "count before/after",
                "whether the write happens before a route-like prompt root is selected",
            ],
        },
        {
            "name": "runtime descriptor order bytes",
            "vaHex": hx(0x004576E9),
            "width": "array",
            "reason": "Descriptor run order is generic-grounded, but route-specific producer is not.",
            "capture": [
                "index/order byte written",
                "owning command stream if available",
                "active selector key if available",
            ],
        },
    ]

    route_focus = [
        {
            "selectorKey": "34:0",
            "map": "map8_32q",
            "ownerRootHex": "0x0052db58",
            "sceneRecordHex": "0x0052da58",
            "routeScriptHex": "0x0052f95c",
            "promptPayloadHex": "0x004eeaa8",
            "expectedSelectorCommandBytes": "4f 01 22 00",
            "currentStaticStatus": "blocked: exact opcode4f mode1 hit count is 0; route boundary values have no direct .text refs",
        },
        {
            "selectorKey": "13:1",
            "currentStaticStatus": "grounded local prompt pipeline; outer selector58 scheduler remains data-only blocked",
        },
        {
            "selectorKey": "14:0",
            "currentStaticStatus": "route-like positive selector command exists; terminal scheduler remains data-only blocked",
        },
        {
            "selectorKey": "18:0",
            "currentStaticStatus": "route-like positive selector command exists; terminal scheduler remains data-only blocked",
        },
    ]

    success_criteria = [
        "A write to 0x004576da/db or 0x0059de30 is captured with an instruction-bound caller that is outside the already-grounded generic save/load or opcode handler path.",
        "The captured caller can be tied to a concrete scene/event record, selector root, descriptor run, or command stream address.",
        "The captured selector/root corresponds to a route-like prompt root and is followed by the known prompt consumer path.",
        "If only generic opcode handlers are captured, the upstream owner stream/object pointer must be recorded so static analysis can continue from that concrete owner instead of re-scanning all data.",
    ]

    blocked_static_paths = [
        "direct dword .text refs to route-specific roots and terminal values",
        "selector-pair exact command scan for 34:0",
        "group-list code-pattern consumer scan",
        "group-list tag-literal consumer scan",
        "runtime index 0x62/0x63 generic writer analysis",
        "terminal data-cell one-hop closure",
        "descriptor index writer boundary/invocation scans",
    ]

    payload = {
        "kind": "scene-event-runtime-evidence-handoff",
        "purpose": "Record the next evidence needed after static selected-root/prompt-producer frontier saturation.",
        "completionDecision": "do-not-call-update-goal",
        "completionSummary": {
            "checklistItemCount": completion_summary.get("checklistItemCount"),
            "coveredItemCount": completion_summary.get("coveredItemCount"),
            "partialOrBlockedItemCount": completion_summary.get("partialOrBlockedItemCount"),
        },
        "frontierSummary": {
            "staticFrontierSaturated": frontier_summary.get("staticFrontierSaturated"),
            "routeSpecificProducerPromoted": frontier_summary.get("routeSpecificProducerPromoted"),
            "completionDecision": frontier_summary.get("completionDecision"),
            "nextEvidenceNeeded": frontier_summary.get("nextEvidenceNeeded"),
        },
        "route34Summary": {
            "routeSpecificUpperSchedulerPromoted": route34_summary.get("routeSpecificUpperSchedulerPromoted"),
            "blockedReason": route34_summary.get("blockedReason"),
            "nextEvidenceNeeded": route34_summary.get("nextEvidenceNeeded"),
        },
        "watchpoints": watchpoints,
        "routeFocus": route_focus,
        "successCriteria": success_criteria,
        "blockedStaticPaths": blocked_static_paths,
        "nonGoals": [
            "Do not repeat broad direct-xref scans unless a new concrete owner/caller is captured.",
            "Do not promote a route producer from proximity-only data.",
            "Do not treat passing web verification as proof of producer discovery.",
        ],
        "inputsForNextWorker": [
            "runtime trace/watchpoint log with caller addresses and written values",
            "or one new instruction-bound scheduler/list consumer not covered by the blocked static paths",
        ],
    }
    return payload


def render_html(payload: dict[str, Any]) -> str:
    rows = []
    for wp in payload["watchpoints"]:
        rows.append(
            "<tr>"
            f"<td><code>{esc(wp['vaHex'])}</code></td>"
            f"<td>{esc(wp['name'])}</td>"
            f"<td>{esc(wp['width'])}</td>"
            f"<td>{esc(wp['reason'])}</td>"
            f"<td>{'<br>'.join(esc(x) for x in wp['capture'])}</td>"
            "</tr>"
        )
    route_rows = []
    for item in payload["routeFocus"]:
        route_rows.append(
            "<tr>"
            f"<td><code>{esc(item['selectorKey'])}</code></td>"
            f"<td>{esc(item.get('map','-'))}</td>"
            f"<td><code>{esc(item.get('ownerRootHex','-'))}</code></td>"
            f"<td>{esc(item['currentStaticStatus'])}</td>"
            "</tr>"
        )
    blocked = "".join(f"<li>{esc(x)}</li>" for x in payload["blockedStaticPaths"])
    criteria = "".join(f"<li>{esc(x)}</li>" for x in payload["successCriteria"])
    next_inputs = "".join(f"<li>{esc(x)}</li>" for x in payload["inputsForNextWorker"])
    summary = payload["completionSummary"]
    frontier = payload["frontierSummary"]
    route34 = payload["route34Summary"]
    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>Scene/Event Runtime Evidence Handoff</title>
  <style>
    body {{ font-family: system-ui, sans-serif; margin: 24px; color: #172033; background: #f7f8fb; }}
    h1, h2 {{ margin: 0 0 12px; }}
    section {{ background: #fff; border: 1px solid #d9dfeb; border-radius: 8px; padding: 16px; margin: 16px 0; }}
    table {{ width: 100%; border-collapse: collapse; font-size: 14px; }}
    th, td {{ border-bottom: 1px solid #e6eaf2; padding: 8px; text-align: left; vertical-align: top; }}
    th {{ background: #eef2f8; }}
    code {{ background: #eef2f8; padding: 1px 4px; border-radius: 4px; }}
    .tag {{ display: inline-block; padding: 2px 8px; border-radius: 999px; background: #fff0cc; color: #7a4b00; font-weight: 650; }}
    .grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 10px; }}
    .card {{ border: 1px solid #e0e5ef; border-radius: 8px; padding: 10px; background: #fbfcff; }}
    .num {{ font-size: 22px; font-weight: 750; }}
  </style>
</head>
<body>
  <h1>Scene/Event Runtime Evidence Handoff</h1>
  <p><span class="tag">{esc(payload['completionDecision'])}</span> 정적 frontier 이후 필요한 증거만 정리한다.</p>

  <section>
    <h2>Completion Audit Snapshot</h2>
    <div class="grid">
      <div class="card"><div class="num">{esc(summary.get('checklistItemCount'))}</div><div>checklist items</div></div>
      <div class="card"><div class="num">{esc(summary.get('coveredItemCount'))}</div><div>covered</div></div>
      <div class="card"><div class="num">{esc(summary.get('partialOrBlockedItemCount'))}</div><div>partial / blocked</div></div>
      <div class="card"><div class="num">{esc(frontier.get('staticFrontierSaturated'))}</div><div>static frontier saturated</div></div>
      <div class="card"><div class="num">{esc(frontier.get('routeSpecificProducerPromoted'))}</div><div>route producer promoted</div></div>
    </div>
  </section>

  <section>
    <h2>Watchpoint Targets</h2>
    <table><thead><tr><th>VA</th><th>Target</th><th>Width</th><th>Why</th><th>Capture</th></tr></thead><tbody>{''.join(rows)}</tbody></table>
  </section>

  <section>
    <h2>Route Focus</h2>
    <table><thead><tr><th>selector</th><th>map</th><th>owner root</th><th>current static status</th></tr></thead><tbody>{''.join(route_rows)}</tbody></table>
  </section>

  <section>
    <h2>Success Criteria</h2>
    <ol>{criteria}</ol>
  </section>

  <section>
    <h2>Already Exhausted Static Paths</h2>
    <ul>{blocked}</ul>
  </section>

  <section>
    <h2>Route34 Blocker</h2>
    <p>{esc(route34.get('blockedReason'))}</p>
    <p><strong>Next evidence:</strong> {esc(route34.get('nextEvidenceNeeded'))}</p>
  </section>

  <section>
    <h2>Inputs For Next Worker</h2>
    <ul>{next_inputs}</ul>
    <p><a href="../out/scene_event_runtime_evidence_handoff.json">JSON artifact</a></p>
  </section>
  <script>
    window.HWANSE_SCENE_EVENT_RUNTIME_EVIDENCE_HANDOFF_READY = true;
    window.HWANSE_SCENE_EVENT_RUNTIME_EVIDENCE_HANDOFF_SUMMARY = {json.dumps({
        "completionDecision": payload["completionDecision"],
        "watchpointCount": len(payload["watchpoints"]),
        "routeFocusCount": len(payload["routeFocus"]),
        "blockedStaticPathCount": len(payload["blockedStaticPaths"]),
    }, ensure_ascii=False, sort_keys=True)};
  </script>
</body>
</html>
"""


def main() -> None:
    payload = build_payload()
    write_json(JSON_OUT, payload)
    html_text = render_html(payload)
    WEB_HTML_OUT.write_text(html_text, encoding="utf-8")
    print(json.dumps({
        "json": str(JSON_OUT.relative_to(ROOT)),
        "web": str(WEB_HTML_OUT.relative_to(ROOT)),
        "completionDecision": payload["completionDecision"],
    }, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    main()
