#!/usr/bin/env python3
"""Consolidate the selector address-predecessor bridge decision.

The 10:0 -> 2:0 address-adjacent block was tempting because it contains many
current-range pointers.  This report keeps that evidence visible while making
the promotion boundary explicit: those references are data/resource adjacency,
not live route execution proof.
"""
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 load_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 h(value: Any) -> str:
    return html.escape("" if value is None else str(value), quote=True)


def link(path: str) -> str:
    if path.startswith("out/"):
        return "../" + path
    if path.startswith("web/"):
        return path.removeprefix("web/")
    return path


def first_ref_role(refs: list[Any]) -> str | None:
    if refs and isinstance(refs[0], dict):
        return refs[0].get("role")
    return None


def build() -> dict[str, Any]:
    preentry = load_json("selector_root_preentry_ref_review.json")
    ctx = load_json("save_selector_address_predecessor_context.json")
    bridge = load_json("save_selector_predecessor_bridge_refs.json")
    route_order = load_json("save_selector_predecessor_route_order.json")
    fill_gap = load_json("save_selector_predecessor_fill_execution_order_gap.json")
    descriptor_gap = load_json("save_selector_predecessor_descriptor_bridge_gap.json")
    structure = load_json("selector_root_structure_review.json")

    fill_entry_scan = fill_gap.get("fillEntryCandidateScan")
    fill_entry_scan = fill_entry_scan if isinstance(fill_entry_scan, dict) else {}
    direct_fill_ref_counts = fill_gap.get("directFillSiteRefCounts")
    direct_fill_ref_counts = direct_fill_ref_counts if isinstance(direct_fill_ref_counts, dict) else {}
    failed_gates = route_order.get("failedGates")
    failed_gates = failed_gates if isinstance(failed_gates, list) else []
    remaining_proofs = route_order.get("remainingProofs")
    remaining_proofs = remaining_proofs if isinstance(remaining_proofs, list) else []

    selector_map_sets = [
        {
            "selector": ctx.get("sourceSelector"),
            "role": "source selector",
            "rootHex": ctx.get("sourceRootHex"),
            "maps": ctx.get("sourceMaps", []),
        },
        {
            "selector": ctx.get("logicalPredecessorSelector"),
            "role": "logical predecessor",
            "rootHex": ctx.get("logicalPredecessorRootHex"),
            "maps": ctx.get("logicalPredecessorMaps", []),
        },
        {
            "selector": ctx.get("addressPredecessorSelector"),
            "role": "address predecessor",
            "rootHex": ctx.get("addressPredecessorRootHex"),
            "rangeHex": ctx.get("addressPredecessorRangeHex"),
            "maps": ctx.get("addressPredecessorMaps", []),
        },
        {
            "selector": ctx.get("currentSelector"),
            "role": "current selector",
            "rootHex": ctx.get("currentRootHex"),
            "maps": ctx.get("currentMaps", []),
        },
    ]

    exact_root_refs = ctx.get("addressPredecessorToCurrentRootRefs")
    exact_root_refs = exact_root_refs if isinstance(exact_root_refs, list) else []
    following_strings = ctx.get("addressPredecessorCurrentRootRefFollowingStrings")
    following_strings = following_strings if isinstance(following_strings, list) else []

    gates = [
        {
            "name": "source/predecessor aligned current-range refs",
            "passed": preentry.get("sourceOrPredecessorToCurrentRangeRefCount", 0) > 0,
            "evidence": preentry.get("sourceOrPredecessorToCurrentRangeRefCount", 0),
        },
        {
            "name": "address predecessor exact current-root ref is execution edge",
            "passed": first_ref_role(exact_root_refs) != "current-selector-row-selected-root-entry",
            "evidence": first_ref_role(exact_root_refs) or "none",
        },
        {
            "name": "logical predecessor -> current direct dword bridge",
            "passed": bool(bridge.get("forwardExecutionBridgeFound")),
            "evidence": (bridge.get("predecessorToCurrent") or {}).get("hitCount", 0),
        },
        {
            "name": "predecessor fill sites reachable from root entry",
            "passed": bool(fill_gap.get("rootEntryFixedTraversalFillSitesReachable")),
            "evidence": fill_gap.get("rootEntryFixedTraversalFillSitesReachable"),
        },
        {
            "name": "predecessor fill entry candidate found",
            "passed": bool(fill_entry_scan.get("entryCandidateFound")),
            "evidence": fill_entry_scan.get("entryCandidateFound"),
        },
        {
            "name": "descriptor closure contains route execution edge",
            "passed": descriptor_gap.get("descriptorEdgeRouteExecutionTargetEdgeCount", 0) > 0,
            "evidence": descriptor_gap.get("descriptorEdgeRouteExecutionTargetEdgeCount", 0),
        },
        {
            "name": "route order proven",
            "passed": bool(route_order.get("routeOrderProven")),
            "evidence": route_order.get("routeOrderProven"),
        },
    ]

    promoted = all(gate["passed"] for gate in gates)
    decision = (
        "address-predecessor selector 10:0 is target-side resource/data adjacency only; "
        "do not promote it as a route producer."
    )

    summary = {
        "addressPredecessorSelector": ctx.get("addressPredecessorSelector"),
        "addressPredecessorRootHex": ctx.get("addressPredecessorRootHex"),
        "currentSelector": ctx.get("currentSelector"),
        "currentRootHex": ctx.get("currentRootHex"),
        "addressPredecessorEqualsLogicalPredecessorMapSet": ctx.get(
            "addressPredecessorEqualsLogicalPredecessorMapSet"
        ),
        "currentEqualsAddressPredecessorPlusSource": ctx.get("currentEqualsAddressPredecessorPlusSource"),
        "sourceOrPredecessorToCurrentRangeRefCount": preentry.get(
            "sourceOrPredecessorToCurrentRangeRefCount", 0
        ),
        "addressPredecessorToCurrentRangeRefCount": preentry.get(
            "addressPredecessorToCurrentRangeRefCount", 0
        ),
        "addressPredecessorToCurrentRootRefCount": len(exact_root_refs),
        "predecessorToCurrentHitCount": (bridge.get("predecessorToCurrent") or {}).get("hitCount", 0),
        "routeOrderProven": route_order.get("routeOrderProven"),
        "fillExecutionProven": bool(fill_gap.get("rootEntryFixedTraversalFillSitesReachable"))
        and any(int(value or 0) > 0 for value in direct_fill_ref_counts.values())
        and bool(fill_entry_scan.get("entryCandidateFound")),
        "descriptorRouteExecutionEdgeCount": descriptor_gap.get("descriptorEdgeRouteExecutionTargetEdgeCount", 0),
        "addressPredecessorBridgeRouteExecutionProofFound": preentry.get(
            "addressPredecessorBridgeRouteExecutionProofFound"
        ),
        "promotedRouteProof": promoted,
        "bridgeContextClassification": "context-only-target-side-resource-adjacency",
        "decision": decision,
    }

    return {
        "version": 1,
        "kind": "hwanse-selector-address-predecessor-bridge-review",
        "sourceArtifacts": [
            "out/selector_root_preentry_ref_review.json",
            "out/selector_root_structure_review.json",
            "out/save_selector_address_predecessor_context.json",
            "out/save_selector_predecessor_bridge_refs.json",
            "out/save_selector_predecessor_route_order.json",
            "out/save_selector_predecessor_fill_execution_order_gap.json",
            "out/save_selector_predecessor_descriptor_bridge_gap.json",
        ],
        "summary": summary,
        "selectorMapSets": selector_map_sets,
        "classificationCounts": preentry.get("addressPredecessorBridgeClassificationCounts", {}),
        "exactCurrentRootRefs": exact_root_refs,
        "followingResourceStrings": following_strings[:40],
        "decisionGates": gates,
        "routeOrderFailedGates": failed_gates,
        "routeOrderRemainingProofs": remaining_proofs,
        "fillGap": {
            "localFillTraceReachesCurrentReader": fill_gap.get("localFillTraceReachesCurrentReader"),
            "rootEntryFixedTraversalFillSitesReachable": fill_gap.get(
                "rootEntryFixedTraversalFillSitesReachable"
            ),
            "directFillSiteRefCounts": direct_fill_ref_counts,
            "entryCandidateFound": fill_entry_scan.get("entryCandidateFound"),
            "localFillTraceStopReason": fill_gap.get("localFillTraceStopReason"),
            "localFillTraceStopHex": fill_gap.get("localFillTraceStopHex"),
        },
        "descriptorGap": {
            "classification": descriptor_gap.get("descriptorEdgeRejectionClassification"),
            "descriptorTargetEdgeCount": descriptor_gap.get("descriptorEdgeDescriptorTargetEdgeCount"),
            "routeExecutionTargetEdgeCount": descriptor_gap.get("descriptorEdgeRouteExecutionTargetEdgeCount"),
            "allTargetSectionsData": descriptor_gap.get("descriptorEdgeAllTargetSectionsData"),
        },
        "structureContext": {
            "rootReviewCount": len(structure.get("rootReviews", []) if isinstance(structure.get("rootReviews"), list) else []),
        },
    }


def render_html(data: dict[str, Any]) -> str:
    s = data["summary"]
    cards = "".join(
        f"<div class='card'><b>{h(k)}</b><span>{h(v)}</span></div>"
        for k, v in s.items()
        if k
        in {
            "addressPredecessorSelector",
            "currentSelector",
            "addressPredecessorToCurrentRangeRefCount",
            "sourceOrPredecessorToCurrentRangeRefCount",
            "predecessorToCurrentHitCount",
            "promotedRouteProof",
            "bridgeContextClassification",
        }
    )
    map_rows = "".join(
        "<tr>"
        f"<td>{h(row.get('selector'))}</td>"
        f"<td>{h(row.get('role'))}</td>"
        f"<td><code>{h(row.get('rootHex'))}</code></td>"
        f"<td>{h(', '.join(row.get('maps') or []))}</td>"
        "</tr>"
        for row in data["selectorMapSets"]
    )
    class_rows = "".join(
        f"<tr><td>{h(k)}</td><td>{h(v)}</td></tr>"
        for k, v in sorted(data["classificationCounts"].items())
    )
    ref_rows = "".join(
        "<tr>"
        f"<td><code>{h(row.get('refVaHex'))}</code></td>"
        f"<td><code>{h(row.get('targetVaHex'))}</code></td>"
        f"<td>{h(row.get('role'))}</td>"
        f"<td>{h(row.get('isCurrentSelectorRowPointer'))}</td>"
        "</tr>"
        for row in data["exactCurrentRootRefs"]
    )
    gate_rows = "".join(
        "<tr>"
        f"<td>{h(row['name'])}</td>"
        f"<td class='{h('pass' if row['passed'] else 'fail')}'>{h(row['passed'])}</td>"
        f"<td>{h(row.get('evidence'))}</td>"
        "</tr>"
        for row in data["decisionGates"]
    )
    links = "".join(
        f"<a class='chip' href='{h(link(path))}'>{h(path)}</a>"
        for path in data["sourceArtifacts"]
    )
    resources = "".join(f"<code>{h(value)}</code>" for value in data["followingResourceStrings"])
    failed = "".join(f"<li>{h(value)}</li>" for value in data["routeOrderFailedGates"])
    remaining = "".join(f"<li>{h(value)}</li>" for value in data["routeOrderRemainingProofs"])
    fill_gap = data["fillGap"]
    descriptor_gap = data["descriptorGap"]

    return f"""<!doctype html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Selector Address Predecessor Bridge Review</title>
<style>
  :root {{ --bg:#f6f7f9; --panel:#fff; --line:#d7dde8; --text:#20242b; --muted:#677080; --bad:#a33a2a; --good:#1d7f48; }}
  * {{ box-sizing:border-box; }}
  body {{ margin:0; background:var(--bg); color:var(--text); font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; }}
  main {{ width:min(1180px, calc(100vw - 28px)); margin:0 auto; padding:24px 0 40px; }}
  h1 {{ margin:0 0 6px; font-size:28px; }}
  h2 {{ margin:22px 0 8px; font-size:18px; }}
  p {{ margin:0; line-height:1.45; }}
  .lead,.muted {{ color:var(--muted); }}
  .decision {{ margin-top:14px; padding:12px; border:1px solid #efc3bb; background:#fff5f3; border-radius:8px; }}
  .cards {{ display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:10px; margin:16px 0; }}
  .card,section {{ background:var(--panel); border:1px solid var(--line); border-radius:8px; padding:12px; }}
  .card b {{ display:block; color:var(--muted); font-size:12px; }}
  .card span {{ display:block; margin-top:4px; font-weight:700; overflow-wrap:anywhere; }}
  table {{ width:100%; border-collapse:collapse; font-size:13px; }}
  th,td {{ border-top:1px solid var(--line); padding:7px; text-align:left; vertical-align:top; }}
  th {{ color:var(--muted); font-size:12px; }}
  code {{ font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }}
  .pass {{ color:var(--good); font-weight:700; }}
  .fail {{ color:var(--bad); font-weight:700; }}
  .chips {{ display:flex; flex-wrap:wrap; gap:6px; }}
  .chip {{ display:inline-flex; min-height:30px; align-items:center; padding:5px 9px; border:1px solid var(--line); border-radius:7px; background:#eef2f7; color:var(--text); text-decoration:none; font-size:13px; }}
  .resource-list {{ display:flex; flex-wrap:wrap; gap:6px; }}
  .resource-list code {{ padding:4px 6px; border-radius:5px; background:#eef2f7; }}
  .grid2 {{ display:grid; grid-template-columns:1fr 1fr; gap:12px; }}
  @media (max-width:760px) {{ main {{ width:min(100vw - 18px, 1180px); padding-top:16px; }} .grid2 {{ grid-template-columns:1fr; }} table {{ display:block; overflow-x:auto; white-space:nowrap; }} }}
</style>
<main>
  <h1>Selector Address Predecessor Bridge Review</h1>
  <p class="lead">주소상 선행 selector <code>{h(s.get('addressPredecessorSelector'))}</code>가 current selector <code>{h(s.get('currentSelector'))}</code>로 이어지는 실행 증거인지 재분류한 요약입니다.</p>
  <div class="decision"><b>결론:</b> {h(s.get('decision'))}</div>
  <div class="cards">{cards}</div>

  <section>
    <h2>Selector Map Sets</h2>
    <table><thead><tr><th>selector</th><th>role</th><th>root</th><th>maps</th></tr></thead><tbody>{map_rows}</tbody></table>
  </section>

  <section>
    <h2>Decision Gates</h2>
    <table><thead><tr><th>gate</th><th>passed</th><th>evidence</th></tr></thead><tbody>{gate_rows}</tbody></table>
  </section>

  <div class="grid2">
    <section>
      <h2>Bridge Classifications</h2>
      <table><thead><tr><th>classification</th><th>count</th></tr></thead><tbody>{class_rows}</tbody></table>
    </section>
    <section>
      <h2>Exact Current Root Refs</h2>
      <table><thead><tr><th>ref</th><th>target</th><th>role</th><th>row pointer</th></tr></thead><tbody>{ref_rows}</tbody></table>
    </section>
  </div>

  <div class="grid2">
    <section>
      <h2>Fill Gap</h2>
      <p class="muted">fill trace reaches current reader: <b>{h(fill_gap.get('localFillTraceReachesCurrentReader'))}</b></p>
      <p class="muted">root traversal reaches fill sites: <b>{h(fill_gap.get('rootEntryFixedTraversalFillSitesReachable'))}</b></p>
      <p class="muted">entry candidate found: <b>{h(fill_gap.get('entryCandidateFound'))}</b></p>
      <p class="muted">stop: <code>{h(fill_gap.get('localFillTraceStopHex'))}</code> {h(fill_gap.get('localFillTraceStopReason'))}</p>
    </section>
    <section>
      <h2>Descriptor Gap</h2>
      <p class="muted">classification: <b>{h(descriptor_gap.get('classification'))}</b></p>
      <p class="muted">descriptor edges: <b>{h(descriptor_gap.get('descriptorTargetEdgeCount'))}</b></p>
      <p class="muted">route execution edges: <b>{h(descriptor_gap.get('routeExecutionTargetEdgeCount'))}</b></p>
      <p class="muted">all targets data: <b>{h(descriptor_gap.get('allTargetSectionsData'))}</b></p>
    </section>
  </div>

  <section>
    <h2>Following Resource Strings</h2>
    <div class="resource-list">{resources}</div>
  </section>

  <div class="grid2">
    <section>
      <h2>Route Order Failed Gates</h2>
      <ul>{failed}</ul>
    </section>
    <section>
      <h2>Remaining Proofs</h2>
      <ul>{remaining}</ul>
    </section>
  </div>

  <section>
    <h2>Source Artifacts</h2>
    <div class="chips">{links}</div>
  </section>
</main>
"""


def main() -> None:
    OUT.mkdir(exist_ok=True)
    WEB.mkdir(exist_ok=True)
    data = build()
    (OUT / "selector_address_predecessor_bridge_review.json").write_text(
        json.dumps(data, ensure_ascii=False, separators=(",", ":")),
        encoding="utf-8",
    )
    (WEB / "selector_address_predecessor_bridge_review.html").write_text(
        render_html(data),
        encoding="utf-8",
    )
    s = data["summary"]
    print(
        "wrote selector address predecessor bridge review "
        f"({s['addressPredecessorSelector']} -> {s['currentSelector']}, "
        f"promoted={s['promotedRouteProof']})"
    )


if __name__ == "__main__":
    main()
