#!/usr/bin/env python3
"""Summarize the current static boundary for scene/event VM routing.

The prompt renderer, selected text root consumer, choice handlers, and command
stream candidates are now separate evidence surfaces.  This report keeps those
grounded pieces apart from the still-missing map/scene root producer so future
passes do not treat route-like candidates as confirmed story flow.
"""
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"


def read_json(name: str) -> dict[str, Any]:
    try:
        data = json.loads((OUT / name).read_text(encoding="utf-8"))
    except FileNotFoundError:
        return {}
    return data if isinstance(data, dict) else {}


def summary(data: dict[str, Any]) -> dict[str, Any]:
    value = data.get("summary")
    return value if isinstance(value, dict) else {}


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


def h(value: Any) -> str:
    return html.escape("" if value is None else str(value), quote=True)


def build_report() -> dict[str, Any]:
    text_consumer = summary(read_json("scene_event_text_consumer_trace_review.json"))
    selected_consumer = summary(read_json("selected_scene_text_root_consumer_review.json"))
    command_stream = summary(read_json("scene_event_vm_command_stream_candidates.json"))
    choice_target = summary(read_json("scene_event_vm_choice_target_review.json"))
    seq_resource = summary(read_json("scene_seq_resource_record_link_review.json"))
    execution_route = summary(read_json("scene_event_vm_execution_route_review.json"))
    prompt_sequence = summary(read_json("scene_event_vm_prompt_sequence_review.json"))
    branch_flag = summary(read_json("scene_event_vm_branch_flag_review.json"))
    conditional_route = summary(read_json("scene_event_conditional_map_route_review.json"))
    coordinate_semantics = summary(read_json("scene_event_coordinate_semantics_review.json"))
    opcode08_windows = read_json("save_selector_opcode08_activation_windows.json")
    merge_bridge = read_json("save_selector_merge_bridge_matrix.json")
    preentry_refs = read_json("selector_root_preentry_ref_review.json")
    strict_coverage = read_json("strict_event_source_coverage.json")
    runtime_feasibility = read_json("runtime_trace_feasibility.json")

    surfaces = [
        {
            "surface": "prompt render/source consumer",
            "promotion": "confirmed-consumer / route-unbound",
            "artifact": "scene_event_text_consumer_trace_review.json",
            "finding": (
                f"textConsumerProofFound={text_consumer.get('textConsumerProofFound')}; "
                f"opcode0bRenderCommandCount={text_consumer.get('opcode0bRenderCommandCount', 0)}; "
                f"rootRefCount={text_consumer.get('rootRefCount', 0)}."
            ),
            "gap": (
                f"routeTextBindingProofFound={text_consumer.get('routeTextBindingProofFound')}; "
                "특정 scene/map root가 이 prompt list를 실행한다는 producer 증거가 없다."
            ),
        },
        {
            "surface": "selected text root global consumer",
            "promotion": "confirmed-consumer / producer-missing",
            "artifact": "selected_scene_text_root_consumer_review.json",
            "finding": (
                f"selectedPointerGlobal={selected_consumer.get('selectedPointerGlobalHex')}; "
                f"consumerHandler={selected_consumer.get('consumerHandlerVaHex')}; "
                f"consumerVerified={selected_consumer.get('consumerVerified')}."
            ),
            "gap": (
                f"directExecutionRootFound={selected_consumer.get('directExecutionRootFound')}; "
                f"routeBridgeProofFound={selected_consumer.get('routeBridgeProofFound')}."
            ),
        },
        {
            "surface": "opcode 0x08 activation producer windows",
            "promotion": "negative-static-proof / route-unpromoted",
            "artifact": "save_selector_opcode08_activation_windows.json",
            "finding": (
                f"activators={opcode08_windows.get('sourceOrPredecessorOpcode08ActivatorCount', 0)}; "
                f"current-root producers={opcode08_windows.get('sourceOrPredecessorCurrentRootProducerCount', 0)}; "
                f"current-range producers={opcode08_windows.get('sourceOrPredecessorCurrentRangeProducerCount', 0)}; "
                f"own-range={opcode08_windows.get('sourceOrPredecessorOwnRangeProducerCount', 0)}; "
                f"script-scalar={opcode08_windows.get('sourceOrPredecessorScriptScalarProducerCount', 0)}; "
                f"unreadable={opcode08_windows.get('sourceOrPredecessorUnreadableProducerCount', 0)}."
            ),
            "gap": (
                f"opcode08ActivationPromotesRoute={opcode08_windows.get('opcode08ActivationPromotesRoute')}; "
                "source/predecessor 0x08 활성화 직전 창에서 selector 2:0 root/range producer가 없다."
            ),
        },
        {
            "surface": "selector merge bridge matrix",
            "promotion": "negative-static-proof / merge-unproven",
            "artifact": "save_selector_merge_bridge_matrix.json",
            "finding": (
                f"source->predecessor={merge_bridge.get('sourceToPredecessorHitCount', 0)}; "
                f"source->current={merge_bridge.get('sourceToCurrentHitCount', 0)}; "
                f"predecessor->current={merge_bridge.get('predecessorToCurrentHitCount', 0)}; "
                f"current->predecessor={merge_bridge.get('currentToPredecessorHitCount', 0)}; "
                f"forward merge={merge_bridge.get('forwardMergeBridgeHitCount', 0)}."
            ),
            "gap": (
                f"selectorMergeProofStatus={merge_bridge.get('selectorMergeProofStatus')}; "
                f"proofFound={merge_bridge.get('proofFound')}. "
                "역방향 reuse는 있지만 source/predecessor에서 current 2:0으로 가는 실행 bridge가 없다."
            ),
        },
        {
            "surface": "selector root pre-entry references",
            "promotion": "negative-static-proof / preentry-producer-missing",
            "artifact": "selector_root_preentry_ref_review.json",
            "finding": (
                f"selectorTableOnlyRootRefCount={preentry_refs.get('selectorTableOnlyRootRefCount', 0)}; "
                f"exactSelfWriterRefCount={preentry_refs.get('exactSelfWriterRefCount', 0)}; "
                f"source/predecessor current-range refs={preentry_refs.get('sourceOrPredecessorToCurrentRangeRefCount', 0)}; "
                f"address-predecessor refs={preentry_refs.get('addressPredecessorToCurrentRangeRefCount', 0)}."
            ),
            "gap": (
                f"preEntryProducerProofFound={preentry_refs.get('preEntryProducerProofFound')}; "
                f"addressPredecessorBridgeRouteExecutionProofFound={preentry_refs.get('addressPredecessorBridgeRouteExecutionProofFound')}. "
                "selector-table membership과 주소 인접성은 route 실행 증거가 아니다."
            ),
        },
        {
            "surface": "scene/event command stream candidates",
            "promotion": "candidate-streams / route-unlinked",
            "artifact": "scene_event_vm_command_stream_candidates.json",
            "finding": (
                f"commandBlockCount={command_stream.get('commandBlockCount', 0)}; "
                f"dialogueLikeBlockCount={command_stream.get('dialogueLikeBlockCount', 0)}; "
                f"textSourceCommandCount={command_stream.get('textSourceCommandCount', 0)}; "
                f"mapLinkedCommandBlockCount={command_stream.get('mapLinkedCommandBlockCount', 0)}."
            ),
            "gap": (
                f"routeLinkedCommandBlockCount={command_stream.get('routeLinkedCommandBlockCount', 0)}; "
                f"fullSceneCommandListProofFound={command_stream.get('fullSceneCommandListProofFound')}."
            ),
        },
        {
            "surface": "choice target / branch handlers",
            "promotion": "confirmed-handlers / branch-instance-unlinked",
            "artifact": "scene_event_vm_choice_target_review.json",
            "finding": (
                f"commonStream={choice_target.get('commonStreamVaHex')}; "
                f"opcode16Handler={choice_target.get('opcode16HandlerVaHex')}; "
                f"opcode18Handler={choice_target.get('opcode18HandlerVaHex')}; "
                f"choiceTargetGlobal={choice_target.get('choiceTargetGlobalHex')}."
            ),
            "gap": (
                f"choiceBranchProofFound={choice_target.get('choiceBranchProofFound')}; "
                f"selectionToNextPromptProofFound={choice_target.get('selectionToNextPromptProofFound')}."
            ),
        },
        {
            "surface": "scene sequence / resource manifest relation",
            "promotion": "candidate-link / same-root-unproven",
            "artifact": "scene_seq_resource_record_link_review.json",
            "finding": (
                f"groupCount={seq_resource.get('groupCount', 0)}; "
                f"sequenceCount={seq_resource.get('sequenceCount', 0)}; "
                f"selectorRootWithManifestRecordCount={seq_resource.get('selectorRootWithManifestRecordCount', 0)}; "
                f"manifestRecordLinkedGroupCount={seq_resource.get('manifestRecordLinkedGroupCount', 0)}."
            ),
            "gap": (
                f"directSameEventRootProofFound={seq_resource.get('directSameEventRootProofFound')}; "
                f"directEventRootProofFound={seq_resource.get('directEventRootProofFound')}."
            ),
        },
        {
            "surface": "map transition payload / coordinate semantics",
            "promotion": "payload-candidate / coordinate-condition-unverified",
            "artifact": "scene_event_vm_command_stream_candidates.json + scene_event_coordinate_semantics_review.json",
            "finding": (
                f"transitionPayloadRecordCount={command_stream.get('transitionPayloadRecordCount', 0)}; "
                f"coordinateSummary={coordinate_semantics.get('status', 'unknown')}; "
                f"conditionalRouteStatus={conditional_route.get('status', 'unknown')}."
            ),
            "gap": (
                f"mapTransitionCommandOpcodeProofFound={command_stream.get('mapTransitionCommandOpcodeProofFound')}; "
                "좌표가 trigger/spawn/object/camera 중 무엇인지 record 단위로 아직 분리 중이다."
            ),
        },
        {
            "surface": "map1 execution route probe",
            "promotion": "partial-route / direct-root-missing",
            "artifact": "scene_event_vm_execution_route_review.json",
            "finding": (
                f"map1PromptCommandRows={execution_route.get('map1PromptCommandRows', 0)}; "
                f"map1CommandGroundedRows={execution_route.get('map1CommandGroundedRows', 0)}; "
                f"selectorAdjacencyOccurrenceCount={execution_route.get('selectorAdjacencyOccurrenceCount', 0)}."
            ),
            "gap": (
                f"directExecutionRootFound={execution_route.get('directExecutionRootFound')}; "
                f"routeRootRefProofFound={execution_route.get('routeRootRefProofFound')}; "
                f"routeSelectorRootTextRefsFound={execution_route.get('routeSelectorRootTextRefsFound')}."
            ),
        },
        {
            "surface": "prompt grouping / sequence UI",
            "promotion": "review-usable / route-order-unproven",
            "artifact": "scene_event_vm_prompt_sequence_review.json",
            "finding": (
                f"promptSequenceStatus={prompt_sequence.get('status', 'unknown')}; "
                f"branchFlagStatus={branch_flag.get('status', 'unknown')}."
            ),
            "gap": "인게임처럼 출력 가능한 단위는 만들 수 있지만, 모든 순서/분기/호출 source가 확정된 것은 아니다.",
        },
        {
            "surface": "strict event source coverage",
            "promotion": "negative-coverage / source-hotspot-missing",
            "artifact": "strict_event_source_coverage.json",
            "finding": (
                f"fieldMapCount={strict_coverage.get('fieldMapCount', 0)}; "
                f"strictEventRecordCount={strict_coverage.get('strictEventRecordCount', 0)}; "
                f"strictEventSourceMapCount={strict_coverage.get('strictEventSourceMapCount', 0)}; "
                f"strictEventSourceCoveragePercent={strict_coverage.get('strictEventSourceCoveragePercent', 0)}."
            ),
            "gap": (
                f"promotionAllowed={strict_coverage.get('promotionAllowed')}; "
                "map1_01a는 extracted strict event source가 아니므로 수동 출구/scene adjacency를 transition proof로 승격하지 않는다."
            ),
        },
        {
            "surface": "runtime feasibility / external proof gate",
            "promotion": "blocked-runtime-gate / no-static-substitute",
            "artifact": "runtime_trace_feasibility.json",
            "finding": (
                f"proofFound={runtime_feasibility.get('proofFound')}; "
                f"canRunRuntimeTraceNow={runtime_feasibility.get('canRunRuntimeTraceNow')}; "
                f"evidenceRefCount={runtime_feasibility.get('evidenceRefCount', 0)}; "
                f"promotionStatus={runtime_feasibility.get('promotionStatus')}."
            ),
            "gap": (
                "정적 bridge가 없고 WineDbg/watchpoint 경로도 현재 VM에서 막혀 있다. "
                "real selector 2:0 save, route watchpoint, strict source hotspot 중 하나가 필요하다."
            ),
        },
    ]

    grounded_consumer_count = sum(
        1
        for row in surfaces
        if row["promotion"].startswith("confirmed")
        or row["promotion"].startswith("review-usable")
    )
    blocked_count = sum(
        1
        for row in surfaces
        if "unbound" in row["promotion"]
        or "missing" in row["promotion"]
        or "unlinked" in row["promotion"]
        or "unproven" in row["promotion"]
        or "unverified" in row["promotion"]
    )

    return {
        "kind": "hwanse-scene-event-vm-route-boundary-review",
        "status": "consumers-grounded-route-root-unproven",
        "source": [
            "out/scene_event_text_consumer_trace_review.json",
            "out/selected_scene_text_root_consumer_review.json",
            "out/scene_event_vm_command_stream_candidates.json",
            "out/scene_event_vm_choice_target_review.json",
            "out/scene_seq_resource_record_link_review.json",
            "out/scene_event_vm_execution_route_review.json",
            "out/scene_event_vm_prompt_sequence_review.json",
            "out/scene_event_vm_branch_flag_review.json",
            "out/scene_event_conditional_map_route_review.json",
            "out/scene_event_coordinate_semantics_review.json",
            "out/save_selector_opcode08_activation_windows.json",
            "out/save_selector_merge_bridge_matrix.json",
            "out/selector_root_preentry_ref_review.json",
            "out/strict_event_source_coverage.json",
            "out/runtime_trace_feasibility.json",
            "tools/build_scene_event_vm_route_boundary_review.py",
        ],
        "summary": {
            "surfaceCount": len(surfaces),
            "groundedConsumerOrReviewCount": grounded_consumer_count,
            "blockedRouteSurfaceCount": blocked_count,
            "directSceneCommandRootProven": False,
            "textConsumerProofFound": bool(text_consumer.get("textConsumerProofFound")),
            "selectedRootConsumerVerified": bool(selected_consumer.get("consumerVerified")),
            "choiceHandlersGrounded": bool(choice_target.get("opcode16HandlerVaHex"))
            and bool(choice_target.get("opcode18HandlerVaHex")),
            "commandStreamCandidatesFound": command_stream.get("commandBlockCount", 0) > 0,
            "opcode08ActivationPromotesRoute": bool(opcode08_windows.get("opcode08ActivationPromotesRoute")),
            "selectorMergeProofFound": bool(merge_bridge.get("proofFound")),
            "preEntryProducerProofFound": bool(preentry_refs.get("preEntryProducerProofFound")),
            "strictEventPromotionAllowed": bool(strict_coverage.get("promotionAllowed")),
            "runtimeRouteProofFound": bool(runtime_feasibility.get("proofFound")),
            "decision": (
                "프롬프트/선택지/selected-root consumer와 command-like storage는 충분히 분리됐지만, "
                "selector 2:0 실행 bridge, strict source hotspot, runtime watchpoint가 모두 막혀 있어 "
                "특정 맵/씬 root가 특정 command stream을 실행한다는 직접 producer 증거는 아직 없다."
            ),
        },
        "surfaces": surfaces,
        "nextFrontier": [
            "source/predecessor selector에서 current selector 2:0으로 이어지는 새 bridge 가설이 생길 때만 save_selector 계열 정적 스캔을 재개한다.",
            "map1_01a strict source hotspot 또는 tile-confirmed trigger proof가 생기면 route promotion gate를 다시 평가한다.",
            "정적 증거가 막히면 Wine runtime에서 prompt 출력 직전 selected pointer/global write(0x0040adfe)만 좁게 trace한다.",
            "real gameplay savedata가 selector 2:0을 담는 경우 save/load 복원 경로는 즉시 route proof 후보가 된다.",
        ],
    }


def render_html(report: dict[str, Any]) -> str:
    s = report["summary"]
    cards = [
        ("status", report["status"]),
        ("surfaces", s["surfaceCount"]),
        ("grounded", s["groundedConsumerOrReviewCount"]),
        ("blocked", s["blockedRouteSurfaceCount"]),
        ("direct root", "proven" if s["directSceneCommandRootProven"] else "unproven"),
    ]
    card_html = "".join(f"<div class='card'><b>{h(k)}</b><span>{h(v)}</span></div>" for k, v in cards)
    rows = []
    for row in report["surfaces"]:
        rows.append(
            "<tr>"
            f"<td><b>{h(row['surface'])}</b><br><code>{h(row['artifact'])}</code></td>"
            f"<td>{h(row['promotion'])}</td>"
            f"<td>{h(row['finding'])}</td>"
            f"<td>{h(row['gap'])}</td>"
            "</tr>"
        )
    frontier = "".join(f"<li>{h(item)}</li>" for item in report["nextFrontier"])
    payload = json.dumps(report, ensure_ascii=False)
    return f"""<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Scene/Event VM Route Boundary Review</title>
  <style>
    body {{ margin:0; background:#101318; color:#edf1f7; font-family:system-ui,sans-serif; }}
    main {{ max-width:1240px; margin:0 auto; padding:24px; }}
    a {{ color:#8ecbff; }}
    .nav {{ display:flex; flex-wrap:wrap; gap:8px; margin-bottom:16px; }}
    .chip {{ border:1px solid #334155; border-radius:999px; padding:6px 10px; text-decoration:none; background:#161b22; }}
    .summary {{ display:grid; grid-template-columns:repeat(auto-fit,minmax(170px,1fr)); gap:12px; margin:16px 0; }}
    .card {{ border:1px solid #2b3544; border-radius:8px; padding:12px; background:#161b22; }}
    .card b {{ display:block; color:#9fb1c9; font-size:12px; text-transform:uppercase; }}
    .card span {{ display:block; margin-top:8px; font-size:18px; overflow-wrap:anywhere; }}
    section {{ border:1px solid #273244; border-radius:10px; padding:16px; margin:16px 0; background:#141922; overflow:auto; }}
    table {{ border-collapse:collapse; width:100%; min-width:1080px; font-size:13px; }}
    th,td {{ border-bottom:1px solid #283342; padding:8px; text-align:left; vertical-align:top; }}
    th {{ color:#b9c7dc; background:#111722; }}
    code {{ color:#dbeafe; overflow-wrap:anywhere; }}
    pre {{ white-space:pre-wrap; background:#0b0f14; border:1px solid #253044; border-radius:8px; padding:12px; max-height:360px; overflow:auto; }}
  </style>
</head>
<body>
<main>
  <div class="nav">
    <a class="chip" href="index.html">index</a>
    <a class="chip" href="scene_event_text_consumer_trace_review.html">text consumer</a>
    <a class="chip" href="selected_scene_text_root_consumer_review.html">selected root</a>
    <a class="chip" href="scene_event_vm_command_stream_candidates.html">command streams</a>
    <a class="chip" href="scene_event_vm_choice_target_review.html">choice target</a>
    <a class="chip" href="scene_event_vm_execution_route_review.html">execution route</a>
    <a class="chip" href="../out/save_selector_opcode08_activation_windows.json">opcode08 windows JSON</a>
    <a class="chip" href="../out/save_selector_merge_bridge_matrix.json">merge bridge JSON</a>
    <a class="chip" href="../out/strict_event_source_coverage.html">strict coverage</a>
  </div>
  <h1>Scene/Event VM Route Boundary Review</h1>
  <p>{h(s["decision"])}</p>
  <div class="summary">{card_html}</div>
  <section>
    <h2>Route Boundary</h2>
    <table>
      <thead><tr><th>surface</th><th>promotion</th><th>finding</th><th>gap</th></tr></thead>
      <tbody>{''.join(rows)}</tbody>
    </table>
  </section>
  <section>
    <h2>Next Frontier</h2>
    <ul>{frontier}</ul>
  </section>
  <section>
    <h2>Raw JSON</h2>
    <pre id="json"></pre>
  </section>
</main>
<script>
window.HWANSE_SCENE_EVENT_VM_ROUTE_BOUNDARY_REVIEW = {payload};
document.getElementById('json').textContent = JSON.stringify(window.HWANSE_SCENE_EVENT_VM_ROUTE_BOUNDARY_REVIEW, null, 2);
</script>
</body>
</html>
"""


def main() -> int:
    report = build_report()
    OUT.mkdir(exist_ok=True)
    WEB.mkdir(exist_ok=True)
    write_json(OUT / "scene_event_vm_route_boundary_review.json", report)
    html_text = render_html(report)
    print("scene_event_vm_route_boundary_review ok")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
