#!/usr/bin/env python3
"""Build a branch/flag review for the Scene/Event VM goal.

This does not introduce a new route proof.  It consolidates the existing
selected-root branch-state artifacts and separates:

- values that are grounded from EXE/static analysis,
- equations that are narrowed but still route-dependent,
- event/object writer candidates that are useful for VM decoding but blocked
  as current route proof.
"""
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"
WEB = ROOT / "web"


def load_json(path: Path, default: Any) -> Any:
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except FileNotFoundError:
        return default


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


def write_text(path: Path, text: str) -> None:
    path.write_text(text, encoding="utf-8")


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


def short(value: Any, limit: int = 180) -> str:
    if isinstance(value, (dict, list)):
        value = json.dumps(value, ensure_ascii=False, sort_keys=True)
    text = " ".join((str(value) if value is not None else "").split())
    return text if len(text) <= limit else text[: limit - 1] + "..."


def as_int(value: Any, default: int = 0) -> int:
    try:
        return int(value)
    except (TypeError, ValueError):
        return default


def rel(path: Path) -> str:
    try:
        return path.resolve().relative_to(ROOT).as_posix()
    except ValueError:
        return path.as_posix()


def build(args: argparse.Namespace) -> dict[str, Any]:
    writers = load_json(args.branch_writers, {})
    dispatch = load_json(args.branch_dispatch, {})
    gate = load_json(args.branch_gate, {})
    reader = load_json(args.frontier_reader, {})
    equation = load_json(args.branch_equation, {})
    active_sources = load_json(args.active_flag_sources, {})
    active_effect = load_json(args.active_flag_effect, {})
    opcode24 = load_json(args.opcode24_runtime_flag, {})
    event_candidates = load_json(args.event_object_candidates, {})
    event_links = load_json(args.event_object_links, {})
    event_context = load_json(args.event_object_context, {})

    source = gate.get("source") or reader.get("source") or "map1_01a"
    target = gate.get("target") or reader.get("target") or "map2_02d"

    primary_writer_ref_count = sum(as_int(row.get("writerCount")) for row in writers.get("clusters") or [])

    summary = {
        "source": source,
        "target": target,
        "promotionStatus": "branch-flag-grounded-route-proof-blocked",
        "branchFlagProofFound": False,
        "activeFlagVaHex": active_sources.get("activeFlagVaHex") or equation.get("activeFlagVaHex"),
        "activeFlagDefaultGrounded": bool(active_sources.get("resolvedStaticDefault")),
        "activeFlagStaticInitialByteHex": active_sources.get("activeFlagStaticInitialByteHex"),
        "activeFlagSaveOffsetHex": active_effect.get("activeFlagSaveOffsetHex"),
        "activeFlagStartupInitVaHex": active_sources.get("startupInitVaHex"),
        "sameTableAndOffset": bool(gate.get("sameTableAndOffset")),
        "sameSelectionBufferOffsetHex": gate.get("sameSelectionBufferOffsetHex") or equation.get("selectionOffsetHex"),
        "statePreservedByKnownOpcodes": bool(gate.get("statePreservedByKnownOpcodes")),
        "branchStateValueStillRuntimeDependent": bool(gate.get("branchStateValueStillRuntimeDependent")),
        "controlPathStillUnproven": bool(gate.get("controlPathStillUnproven")),
        "strictHotspotStillMissing": bool(gate.get("strictHotspotStillMissing")),
        "frontierReaderVaHex": reader.get("readerVaHex"),
        "frontierReaderCondition": reader.get("condition"),
        "frontierReaderTraceReachesCount": as_int(reader.get("routePairCorrectedTraceReachesReaderCount")),
        "predecessorHypothesisSelector": equation.get("predecessorFillHypothesis", {}).get("predecessorSelector")
        or reader.get("predecessorHypothesisSelector"),
        "predecessorHypothesisFillHex": equation.get("predecessorFillHypothesis", {}).get("fillValueHex")
        or reader.get("predecessorHypothesisFillHex"),
        "equationNarrowedByPredecessorHypothesis": bool(equation.get("equationNarrowedByPredecessorHypothesis")),
        "allPredecessorStartsPass": bool(active_effect.get("allPredecessorStartsPass")),
        "primaryBranchStateWriterClusterCount": len(writers.get("clusters") or []),
        "primaryBranchStateWriterDirectRefCount": primary_writer_ref_count,
        "branchDispatchTableEntryCount": len(dispatch.get("tableEntries") or []),
        "eventObjectRawCandidateCount": as_int(event_candidates.get("candidateCount")),
        "eventObjectMediumCandidateCount": as_int(event_links.get("mediumCandidateCount")),
        "eventObjectCurrentRouteLinkedCount": as_int(event_links.get("currentRouteLinkedCount")),
        "eventObjectCurrentRouteRangeHitCount": as_int(event_links.get("currentRouteRangeHitCount")),
        "eventObjectNearbyCnsCandidateCount": as_int(event_context.get("nearbyCnsCandidateCount")),
        "runtimeOpcode24FlagProofFound": bool(opcode24.get("runtimeOpcode24FlagProofFound")),
        "runtimeOpcode24RealRouteHitObserved": bool(opcode24.get("realRouteHitObserved")),
        "runtimeOpcode24ConstructedRouteHitObserved": bool(opcode24.get("constructedRouteHitObserved")),
    }

    decisions = [
        {
            "item": "opcode 0x12 active-selection flag",
            "promotion": "grounded",
            "evidence": (
                f"{summary['activeFlagVaHex']} default={summary['activeFlagStaticInitialByteHex']}, "
                f"startup init={summary['activeFlagStartupInitVaHex']}, save offset={summary['activeFlagSaveOffsetHex']}"
            ),
            "remainingGap": "loaded save can override the default; route runtime value is not proven",
        },
        {
            "item": "opcode 0x12 writer -> opcode 0x11 reader slot",
            "promotion": "partial",
            "evidence": (
                f"writer/reader use {gate.get('sameTableName')} offset {summary['sameSelectionBufferOffsetHex']}; "
                f"known opcodes preserve the same slot={summary['statePreservedByKnownOpcodes']}"
            ),
            "remainingGap": "selected slot value and control path to the frontier reader remain unproven",
        },
        {
            "item": "predecessor-fill narrowing",
            "promotion": "partial",
            "evidence": (
                f"predecessor {summary['predecessorHypothesisSelector']} fill "
                f"{summary['predecessorHypothesisFillHex']} makes all starts pass={summary['allPredecessorStartsPass']}"
            ),
            "remainingGap": "normal route has not proven that predecessor executes before selector 2:0 and persists",
        },
        {
            "item": "primaryBranchState writer handlers",
            "promotion": "grounded-blocked",
            "evidence": f"{summary['primaryBranchStateWriterClusterCount']} writer clusters are known direct writers",
            "remainingGap": "writer clusters are dispatched by event/object VM and are not linked to current selector 2:0 route",
        },
        {
            "item": "event/object branch-state byte candidates",
            "promotion": "blocked",
            "evidence": (
                f"medium={summary['eventObjectMediumCandidateCount']}, "
                f"current-route links={summary['eventObjectCurrentRouteLinkedCount']}, "
                f"range hits={summary['eventObjectCurrentRouteRangeHitCount']}"
            ),
            "remainingGap": "raw byte-pair candidates do not prove command starts or current route execution",
        },
        {
            "item": "opcode24 runtime flag route proof",
            "promotion": "blocked",
            "evidence": (
                f"real route hit={summary['runtimeOpcode24RealRouteHitObserved']}, "
                f"constructed route hit={summary['runtimeOpcode24ConstructedRouteHitObserved']}"
            ),
            "remainingGap": "runtime flag samples do not produce selected-root execution proof for the route",
        },
    ]

    writer_clusters = []
    for cluster in writers.get("clusters") or []:
        counts = cluster.get("writeValueCounts") or {}
        writer_clusters.append(
            {
                "name": cluster.get("label") or cluster.get("name"),
                "rangeHex": cluster.get("rangeHex"),
                "refCount": as_int(cluster.get("writerCount")) or len(cluster.get("writers") or []),
                "valueCounts": counts,
                "sourceGlobals": cluster.get("sourceGlobals") or [],
                "note": cluster.get("note"),
            }
        )

    branch_gate_rows = []
    for row in gate.get("selectionOpcodeRowsBetween") or []:
        branch_gate_rows.append(
            {
                "vaHex": row.get("vaHex"),
                "valueHex": row.get("valueHex"),
                "opcodeHex": row.get("opcodeHex"),
                "operation": row.get("operation"),
                "tableName": row.get("stateTable") or row.get("tableName"),
                "selectionBufferOffsetHex": row.get("selectionBufferOffsetHex"),
                "helperValid": row.get("helperValid"),
            }
        )

    event_candidate_rows = []
    for row in event_links.get("candidates") or []:
        event_candidate_rows.append(
            {
                "candidateVaHex": row.get("candidateVaHex") or row.get("vaHex"),
                "indexHex": row.get("indexHex"),
                "group": row.get("handlerLabel") or row.get("handlerGroup") or row.get("indexName"),
                "selectorContainer": (row.get("routeRangeHits") or {})
                .get("candidateSelectorContainer", {})
                .get("rangeHex"),
                "routeRelevance": row.get("routeRelevance"),
                "nextStep": row.get("nextStep"),
            }
        )

    return {
        "scope": "scene/event VM branch and flag review",
        "promotionStatus": summary["promotionStatus"],
        "summary": summary,
        "decisions": decisions,
        "writerClusters": writer_clusters,
        "branchGateRows": branch_gate_rows,
        "eventObjectCandidates": event_candidate_rows,
        "remainingProofs": [
            "prove predecessor 1:0 executes before current selector 2:0 in normal route",
            "prove secondaryBranchState persists to opcode 0x12 writer and opcode 0x11 frontier reader",
            "prove control-flow reaches 0x00542b0c on the route",
            "find a strict source hotspot/event row for map1_01a",
            "capture real selector 2:0 runtime/save state or equivalent trace",
        ],
        "sourceArtifacts": {
            "branchWriters": rel(args.branch_writers),
            "branchDispatch": rel(args.branch_dispatch),
            "branchGate": rel(args.branch_gate),
            "frontierReader": rel(args.frontier_reader),
            "branchEquation": rel(args.branch_equation),
            "activeFlagSources": rel(args.active_flag_sources),
            "activeFlagEffect": rel(args.active_flag_effect),
            "opcode24RuntimeFlag": rel(args.opcode24_runtime_flag),
            "eventObjectCandidates": rel(args.event_object_candidates),
            "eventObjectLinks": rel(args.event_object_links),
            "eventObjectContext": rel(args.event_object_context),
        },
    }


def html_doc(payload: dict[str, Any]) -> str:
    s = payload["summary"]
    def tag(status: Any) -> str:
        text = str(status)
        cls = "good" if text in {"grounded", "confirmed"} else "warn" if "partial" in text or "grounded" in text else "bad"
        return f'<span class="tag {cls}">{h(text)}</span>'

    metric_rows = [
        ("active flag", f"{s['activeFlagVaHex']} default {s['activeFlagStaticInitialByteHex']}"),
        ("same slot", f"{s['sameTableAndOffset']} / {s['sameSelectionBufferOffsetHex']}"),
        ("state preserved", s["statePreservedByKnownOpcodes"]),
        ("runtime dependent", s["branchStateValueStillRuntimeDependent"]),
        ("event route links", s["eventObjectCurrentRouteLinkedCount"]),
        ("proof", s["branchFlagProofFound"]),
    ]
    decision_rows = "\n".join(
        f"<tr><td>{h(row['item'])}</td><td>{tag(row['promotion'])}</td><td>{h(row['evidence'])}</td><td>{h(row['remainingGap'])}</td></tr>"
        for row in payload["decisions"]
    )
    cluster_rows = "\n".join(
        f"<tr><td>{h(row['name'])}</td><td><code>{h(row['rangeHex'])}</code></td><td>{row['refCount']}</td><td>{h(json.dumps(row['valueCounts'], ensure_ascii=False))}</td><td>{h(row['note'])}</td></tr>"
        for row in payload["writerClusters"]
    )
    gate_rows = "\n".join(
        f"<tr><td><code>{h(row['vaHex'])}</code></td><td><code>{h(row['valueHex'])}</code></td><td><code>{h(row['opcodeHex'])}</code></td><td>{h(row['operation'])}</td><td>{h(row['tableName'])}</td><td><code>{h(row['selectionBufferOffsetHex'])}</code></td><td>{h(row['helperValid'])}</td></tr>"
        for row in payload["branchGateRows"]
    )
    candidate_rows = "\n".join(
        f"<tr><td><code>{h(row['candidateVaHex'])}</code></td><td><code>{h(row['indexHex'])}</code></td><td>{h(row['group'])}</td><td>{h(short(row['selectorContainer'], 100))}</td><td>{h(short(row['routeRelevance'], 140))}</td><td>{h(short(row['nextStep'], 140))}</td></tr>"
        for row in payload["eventObjectCandidates"]
    )
    metrics = "\n".join(f"<div class='metric'><span>{h(k)}</span><strong>{h(v)}</strong></div>" for k, v in metric_rows)
    artifacts = "\n".join(f"<li><code>{h(k)}</code>: <a href='../{h(v)}'>{h(v)}</a></li>" for k, v in payload["sourceArtifacts"].items())
    proofs = "\n".join(f"<li>{h(row)}</li>" for row in payload["remainingProofs"])
    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 VM branch/flag 검토</title>
  <style>
    :root {{ color-scheme: light; --border:#d8dee6; --ink:#17202a; --muted:#607080; --panel:#fff; --head:#eef2f6; --bg:#f6f7f9; }}
    * {{ box-sizing:border-box; }}
    body {{ margin:0; background:var(--bg); color:var(--ink); font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; line-height:1.45; }}
    main {{ max-width:1500px; margin:0 auto; padding:18px; }}
    header {{ display:flex; justify-content:space-between; align-items:flex-start; gap:16px; margin-bottom:14px; }}
    h1 {{ margin:0; font-size:24px; }}
    h2 {{ margin:0; font-size:17px; }}
    a {{ color:#185abc; font-weight:700; text-decoration:none; }}
    a:hover {{ text-decoration:underline; }}
    nav {{ display:flex; flex-wrap:wrap; gap:10px; justify-content:flex-end; }}
    section, details {{ background:var(--panel); border:1px solid var(--border); border-radius:8px; margin:14px 0; overflow:hidden; }}
    summary {{ cursor:pointer; padding:12px 14px; background:var(--head); font-weight:800; }}
    .head {{ display:flex; justify-content:space-between; gap:12px; padding:12px 14px; background:var(--head); border-bottom:1px solid var(--border); }}
    .body {{ padding:14px; }}
    .metrics {{ display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:10px; }}
    .metric {{ border:1px solid var(--border); border-radius:6px; background:#f8fafc; padding:10px; }}
    .metric span {{ display:block; color:var(--muted); font-size:12px; }}
    .metric strong {{ display:block; font-size:18px; word-break:break-word; }}
    table {{ width:100%; border-collapse:collapse; }}
    th,td {{ padding:8px 10px; border-bottom:1px solid var(--border); vertical-align:top; text-align:left; font-size:13px; }}
    th {{ background:#f8fafc; color:#344050; }}
    code {{ font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }}
    .tag {{ display:inline-block; padding:2px 7px; border-radius:999px; background:#edf2f7; color:#334155; font-size:12px; white-space:nowrap; }}
    .tag.good {{ color:#0f766e; background:#e6f4f1; }}
    .tag.warn {{ color:#a15c00; background:#fff4df; }}
    .tag.bad {{ color:#b42318; background:#fdebea; }}
    .muted {{ color:var(--muted); }}
  </style>
</head>
<body>
<main data-page="scene-event-vm-branch-flag-review">
  <header>
    <div>
      <h1>Scene/Event VM branch/flag 검토</h1>
      <p class="muted">selected text root 경로의 branch-state, active flag, event/object writer 후보를 확정/blocked로 분리한다.</p>
    </div>
    <nav aria-label="review links">
      <a href="index.html">관리 홈</a>
      <a href="scene_event_vm_review.html">VM 검토</a>
      <a href="scene_event_vm_execution_route_review.html">실행 루트</a>
      <a href="scene_event_vm_prompt_sequence_review.html">prompt sequence</a>
      <a href="scene_event_vm_choice_target_review.html">choice target</a>
      <a href="scene_event_vm_random_gate_review.html">random gate</a>
      <a href="selected_scene_text_root_consumer_review.html">consumer</a>
      <a href="scene_event_runtime_evidence_handoff.html">runtime handoff</a>
      <a href="../out/scene_event_vm_branch_flag_review.json">branch/flag JSON</a>
    </nav>
  </header>
  <section>
    <div class="head"><h2>요약</h2><span>{tag(payload['promotionStatus'])}</span></div>
    <div class="body metrics">{metrics}</div>
  </section>
  <section>
    <div class="head"><h2>Decisions</h2><span>확정과 blocked 분리</span></div>
    <table><thead><tr><th>item</th><th>promotion</th><th>evidence</th><th>remaining gap</th></tr></thead><tbody>{decision_rows}</tbody></table>
  </section>
  <section>
    <div class="head"><h2>Primary Branch-State Writer Clusters</h2><span>{s['primaryBranchStateWriterClusterCount']} clusters</span></div>
    <table><thead><tr><th>cluster</th><th>range</th><th>refs</th><th>values</th><th>note</th></tr></thead><tbody>{cluster_rows}</tbody></table>
  </section>
  <section>
    <div class="head"><h2>Writer → Reader Gate Rows</h2><span>{s['frontierReaderVaHex']}</span></div>
    <table><thead><tr><th>VA</th><th>value</th><th>opcode</th><th>operation</th><th>table</th><th>offset</th><th>helper valid</th></tr></thead><tbody>{gate_rows}</tbody></table>
  </section>
  <details>
    <summary>Event/Object writer candidates - current route proof blocked</summary>
    <table><thead><tr><th>candidate</th><th>index</th><th>group</th><th>selector container</th><th>route relevance</th><th>next step</th></tr></thead><tbody>{candidate_rows}</tbody></table>
  </details>
  <section>
    <div class="head"><h2>Remaining Proofs</h2><span>goal continuation</span></div>
    <div class="body"><ul>{proofs}</ul></div>
  </section>
  <details>
    <summary>Source Artifacts</summary>
    <div class="body"><ul>{artifacts}</ul></div>
  </details>
</main>
<script>
window.HWANSE_SCENE_EVENT_VM_BRANCH_FLAG_REVIEW_READY = true;
window.HWANSE_SCENE_EVENT_VM_BRANCH_FLAG_REVIEW = {{
  branchFlagProofFound: false,
  activeFlagDefaultGrounded: {str(s['activeFlagDefaultGrounded']).lower()},
  sameTableAndOffset: {str(s['sameTableAndOffset']).lower()},
  statePreservedByKnownOpcodes: {str(s['statePreservedByKnownOpcodes']).lower()},
  eventObjectCurrentRouteLinkedCount: {s['eventObjectCurrentRouteLinkedCount']},
  runtimeOpcode24FlagProofFound: {str(s['runtimeOpcode24FlagProofFound']).lower()},
  promotionStatus: "{h(payload['promotionStatus'])}"
}};
</script>
</body>
</html>
"""


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser()
    parser.add_argument("--branch-writers", type=Path, default=OUT / "save_selector_branch_state_writers.json")
    parser.add_argument("--branch-dispatch", type=Path, default=OUT / "save_selector_branch_state_dispatch.json")
    parser.add_argument("--branch-gate", type=Path, default=OUT / "save_selector_branch_gate_consistency.json")
    parser.add_argument("--frontier-reader", type=Path, default=OUT / "save_selector_frontier_reader_branch_context.json")
    parser.add_argument("--branch-equation", type=Path, default=OUT / "save_selector_branch_selector_equation.json")
    parser.add_argument("--active-flag-sources", type=Path, default=OUT / "save_selector_active_flag_sources.json")
    parser.add_argument("--active-flag-effect", type=Path, default=OUT / "save_selector_active_flag_effect.json")
    parser.add_argument("--opcode24-runtime-flag", type=Path, default=OUT / "runtime_opcode24_flag_context.json")
    parser.add_argument("--event-object-candidates", type=Path, default=OUT / "event_object_branch_state_stream_candidates.json")
    parser.add_argument("--event-object-links", type=Path, default=OUT / "event_object_branch_state_candidate_links.json")
    parser.add_argument("--event-object-context", type=Path, default=OUT / "event_object_branch_state_block_context.json")
    parser.add_argument("--json-out", type=Path, default=OUT / "scene_event_vm_branch_flag_review.json")
    parser.add_argument("--html-out", type=Path, default=WEB / "scene_event_vm_branch_flag_review.html")
    parser.add_argument("--web-out", type=Path, default=WEB / "scene_event_vm_branch_flag_review.html")
    return parser.parse_args()


def main() -> None:
    args = parse_args()
    payload = build(args)
    write_json(args.json_out, payload)
    html = html_doc(payload)
    write_text(args.html_out, html)
    write_text(args.web_out, html)
    print(f"wrote scene/event VM branch/flag review -> {args.html_out}")


if __name__ == "__main__":
    main()
