#!/usr/bin/env python3
"""Build an external proof packet for the selector-merge blocker."""
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"
SOURCE = "map1_01a"
TARGET = "map2_02d"
SELECTOR_MERGE_EXTERNAL_EVIDENCE_REFS = [
    {
        "path": "out/save_selector_predecessor_persistence_gap.json",
        "fields": [
            "predecessorPersistenceProofFound",
            "selectorMergeGapOpen",
            "routeOrderProven",
            "missingEvidence",
        ],
    },
    {
        "path": "out/save_selector_predecessor_route_order.json",
        "fields": [
            "predecessorRouteOrderProofFound",
            "selectorMergeGapOpen",
            "routeOrderProven",
            "missingEvidence",
        ],
    },
    {
        "path": "out/save_selector_merge_runtime_context.json",
        "fields": [
            "selectorMergeRuntimeProofFound",
            "selectorMergeExecutionProofFound",
            "failedSelectorMergeRuntimeGateIds",
            "missingEvidence",
        ],
    },
    {
        "path": "out/save_selector_merge_closure_context.json",
        "fields": [
            "selectorMergeClosureProofFound",
            "failedSelectorMergeGateIds",
            "missingEvidence",
            "selectorMergeGapOpen",
        ],
    },
    {
        "path": "out/save_selector_merge_execution_gap.json",
        "fields": [
            "selectorMergeExecutionProofFound",
            "failedSelectorMergeExecutionGateIds",
            "missingEvidence",
            "sourceToCurrentBridgeHitCount",
            "predecessorToCurrentHitCount",
            "forwardMergeBridgeHitCount",
        ],
    },
    {
        "path": "out/save_selector_set_decomposition.json",
        "fields": [
            "currentEqualsPredecessorPlusSource",
            "sourcePredecessorUnionCoversCurrent",
            "executionOrderProven",
            "failedSelectorSetDecompositionGateIds",
        ],
    },
    {
        "path": "out/save_selector_target_alias_bridges.json",
        "fields": [
            "targetAliasBridgeProofFound",
            "failedTargetAliasBridgeGateIds",
            "missingEvidence",
        ],
    },
    {
        "path": "out/save_selector_route_root_ref_context.json",
        "fields": [
            "routeRootRefProofFound",
            "routeOrderProven",
            "failedRouteRootRefGateIds",
            "missingEvidence",
        ],
    },
    {
        "path": "out/save_selector_reverse_reuse_context.json",
        "fields": [
            "reverseReuseProofFound",
            "failedReverseReuseGateIds",
            "forwardMergeBridgeHitCount",
            "directMergeExecutionBridgeFound",
        ],
    },
    {
        "path": "out/save_selector_merge_bridge_matrix.json",
        "fields": [
            "selectorMergeProofStatus",
            "failedMergeBridgeGateIds",
            "predecessorToCurrentHitCount",
            "forwardMergeBridgeHitCount",
            "currentPredecessorHitsBeforeFillOnly",
        ],
    },
]


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


def first_present(*values: Any) -> Any:
    for value in values:
        if value is not None:
            return value
    return None


def dedupe(*lists: list[str]) -> list[str]:
    output: list[str] = []
    seen: set[str] = set()
    for values in lists:
        for value in values or []:
            text = str(value)
            if not text or text in seen:
                continue
            seen.add(text)
            output.append(text)
    return output


def proof_gate_rows(
    closure: dict,
    execution: dict,
    runtime: dict,
    predecessor_route: dict,
    bridge_matrix: dict,
    target_alias: dict,
    route_root: dict,
) -> list[dict]:
    shape_only = (
        closure.get("currentEqualsPredecessorPlusSource") is True
        and closure.get("sourcePredecessorUnionCoversCurrent") is True
        and closure.get("selectorMergeExecutionProofFound") is not True
    )
    source_to_current = execution.get("sourceToCurrentBridgeHitCount") or 0
    predecessor_to_current = execution.get("predecessorToCurrentHitCount") or 0
    forward_merge = execution.get("forwardMergeBridgeHitCount") or 0
    execution_like_found = any(
        [
            execution.get("directMergeExecutionBridgeFound") is True,
            execution.get("encodedMergeExecutionBridgeFound") is True,
            target_alias.get("targetAliasBridgeProofFound") is True,
            route_root.get("routeRootRefProofFound") is True,
        ]
    )
    return [
        {
            "id": "selectorMergeShapeEvidence",
            "pass": False,
            "status": "shape-only-not-proof" if shape_only else "missing-shape-context",
            "detail": (
                "current selector 2:0 matches predecessor 1:0 plus source 0:0 set shape, "
                "but no execution/order bridge promotes that shape"
            ),
        },
        {
            "id": "sourceCurrentBridge",
            "pass": source_to_current > 0,
            "status": "missing" if source_to_current == 0 else "present",
            "detail": f"source-to-current VM/control-flow bridge hits: {source_to_current}",
        },
        {
            "id": "predecessorCurrentForwardBridge",
            "pass": predecessor_to_current > 0 or forward_merge > 0,
            "status": "missing" if predecessor_to_current == 0 and forward_merge == 0 else "present",
            "detail": (
                f"predecessor-to-current hits: {predecessor_to_current}; "
                f"forward merge bridge hits: {forward_merge}"
            ),
        },
        {
            "id": "executionLikeBridgeOrRootRef",
            "pass": execution_like_found,
            "status": "missing" if not execution_like_found else "present",
            "detail": (
                "direct/encoded merge bridge, target-alias bridge, and route-root ref are all "
                f"non-promoting; before-fill reverse refs only: "
                f"{bridge_matrix.get('currentPredecessorHitsBeforeFillOnly')}"
            ),
        },
        {
            "id": "selectedRootRuntimeExecution",
            "pass": runtime.get("selectorMergeRuntimeProofFound") is True,
            "status": "missing" if runtime.get("selectorMergeRuntimeProofFound") is not True else "present",
            "detail": "selected root 0x00540714 is not observed on a non-diagnostic runtime route path",
        },
        {
            "id": "predecessorFillRouteOrderProof",
            "pass": predecessor_route.get("predecessorRouteOrderProofFound") is True,
            "status": "missing" if predecessor_route.get("predecessorRouteOrderProofFound") is not True else "present",
            "detail": "predecessor fill/order is not proven before the current selector reader",
        },
        {
            "id": "strictSourceHotspot",
            "pass": False,
            "status": "missing",
            "detail": "strict map1_01a source coordinate or tile hotspot evidence remains absent",
        },
    ]


def build_summary(out_dir: Path = OUT) -> dict:
    predecessor_persistence = load_json(out_dir / "save_selector_predecessor_persistence_gap.json", {})
    predecessor_route = load_json(out_dir / "save_selector_predecessor_route_order.json", {})
    runtime = load_json(out_dir / "save_selector_merge_runtime_context.json", {})
    closure = load_json(out_dir / "save_selector_merge_closure_context.json", {})
    execution = load_json(out_dir / "save_selector_merge_execution_gap.json", {})
    decomposition = load_json(out_dir / "save_selector_set_decomposition.json", {})
    target_alias = load_json(out_dir / "save_selector_target_alias_bridges.json", {})
    route_root = load_json(out_dir / "save_selector_route_root_ref_context.json", {})
    reverse_reuse = load_json(out_dir / "save_selector_reverse_reuse_context.json", {})
    bridge_matrix = load_json(out_dir / "save_selector_merge_bridge_matrix.json", {})

    gate_rows = proof_gate_rows(
        closure,
        execution,
        runtime,
        predecessor_route,
        bridge_matrix,
        target_alias,
        route_root,
    )
    bridge = {
        "sourceToCurrentBridgeHitCount": first_present(
            closure.get("sourceToCurrentBridgeHitCount"),
            execution.get("sourceToCurrentBridgeHitCount"),
            runtime.get("sourceToCurrentBridgeHitCount"),
        ),
        "currentToSourceBridgeHitCount": first_present(
            closure.get("currentToSourceBridgeHitCount"),
            execution.get("currentToSourceBridgeHitCount"),
        ),
        "predecessorToCurrentHitCount": first_present(
            closure.get("predecessorToCurrentHitCount"),
            execution.get("predecessorToCurrentHitCount"),
            bridge_matrix.get("predecessorToCurrentHitCount"),
        ),
        "forwardMergeBridgeHitCount": first_present(
            closure.get("forwardMergeBridgeHitCount"),
            execution.get("forwardMergeBridgeHitCount"),
            bridge_matrix.get("forwardMergeBridgeHitCount"),
        ),
        "directMergeExecutionBridgeFound": first_present(
            execution.get("directMergeExecutionBridgeFound"),
            reverse_reuse.get("directMergeExecutionBridgeFound"),
            bridge_matrix.get("directMergeExecutionBridgeFound"),
        ),
        "encodedMergeExecutionBridgeFound": first_present(
            closure.get("encodedMergeExecutionBridgeFound"),
            execution.get("encodedMergeExecutionBridgeFound"),
            bridge_matrix.get("encodedMergeExecutionBridgeFound"),
        ),
        "forwardEncodedAnchorRawScalarCandidateCount": first_present(
            closure.get("forwardEncodedAnchorRawScalarCandidateCount"),
            execution.get("forwardEncodedAnchorRawScalarCandidateCount"),
            bridge_matrix.get("forwardEncodedAnchorRawScalarCandidateCount"),
        ),
        "forwardEncodedAnchorPromotingCandidateCount": first_present(
            closure.get("forwardEncodedAnchorPromotingCandidateCount"),
            execution.get("forwardEncodedAnchorPromotingCandidateCount"),
            bridge_matrix.get("forwardEncodedAnchorPromotingCandidateCount"),
        ),
        "currentToPredecessorHitCount": first_present(
            closure.get("currentToPredecessorHitCount"),
            execution.get("currentToPredecessorHitCount"),
            bridge_matrix.get("currentToPredecessorHitCount"),
        ),
        "currentToPredecessorBeforeFillHitCount": first_present(
            closure.get("currentToPredecessorBeforeFillHitCount"),
            execution.get("currentToPredecessorBeforeFillHitCount"),
            bridge_matrix.get("currentToPredecessorBeforeFillHitCount"),
        ),
        "currentToPredecessorFillSiteHitCount": first_present(
            closure.get("currentToPredecessorFillSiteHitCount"),
            execution.get("currentToPredecessorFillSiteHitCount"),
            bridge_matrix.get("currentToPredecessorFillSiteHitCount"),
        ),
        "currentPredecessorHitsBeforeFillOnly": bridge_matrix.get("currentPredecessorHitsBeforeFillOnly"),
    }
    merge_shape_only = (
        closure.get("currentEqualsPredecessorPlusSource") is True
        and closure.get("sourcePredecessorUnionCoversCurrent") is True
        and not any(
            [
                bridge.get("sourceToCurrentBridgeHitCount"),
                bridge.get("predecessorToCurrentHitCount"),
                bridge.get("forwardMergeBridgeHitCount"),
                bridge.get("directMergeExecutionBridgeFound"),
                bridge.get("encodedMergeExecutionBridgeFound"),
            ]
        )
    )
    return {
        "source": SOURCE,
        "target": TARGET,
        "promotionStatus": "blocked",
        "sourceSelector": closure.get("sourceSelector") or execution.get("sourceSelector") or runtime.get("sourceSelector"),
        "predecessorSelector": closure.get("predecessorSelector") or execution.get("predecessorSelector"),
        "currentSelector": closure.get("currentSelector") or execution.get("currentSelector"),
        "currentRootHex": closure.get("currentRootHex") or execution.get("currentRootHex"),
        "proofFound": closure.get("proofFound"),
        "selectorMergeExternalProofFound": closure.get("proofFound"),
        "failedSelectorMergeExternalGateIds": closure.get("failedSelectorMergeGateIds") or [],
        "missingEvidence": closure.get("missingEvidence") or [],
        "mergedMissingEvidence": dedupe(
            closure.get("missingEvidence") or [],
            execution.get("missingEvidence") or [],
            runtime.get("missingEvidence") or [],
            decomposition.get("missingEvidence") or [],
            predecessor_persistence.get("missingEvidence") or [],
            predecessor_route.get("missingEvidence") or [],
            target_alias.get("missingEvidence") or [],
            route_root.get("missingEvidence") or [],
            reverse_reuse.get("missingEvidence") or [],
            bridge_matrix.get("missingEvidence") or [],
        ),
        "currentEqualsPredecessorPlusSource": closure.get("currentEqualsPredecessorPlusSource"),
        "sourcePredecessorUnionCoversCurrent": closure.get("sourcePredecessorUnionCoversCurrent"),
        "sourcePredecessorUnionExtraMaps": closure.get("sourcePredecessorUnionExtraMaps") or [],
        "mergeShapeOnly": merge_shape_only,
        "selectorMergeGapOpen": closure.get("selectorMergeGapOpen"),
        "routeOrderProven": closure.get("routeOrderProven"),
        "selectorMergeExecutionProofFound": closure.get("selectorMergeExecutionProofFound"),
        "selectorMergeRuntimeProofFound": runtime.get("selectorMergeRuntimeProofFound"),
        "selectorMergeClosureProofFound": closure.get("selectorMergeClosureProofFound"),
        "predecessorPersistenceProofFound": predecessor_persistence.get("predecessorPersistenceProofFound"),
        "predecessorRouteOrderProofFound": predecessor_route.get("predecessorRouteOrderProofFound"),
        "targetAliasBridgeProofFound": target_alias.get("targetAliasBridgeProofFound"),
        "routeRootRefProofFound": route_root.get("routeRootRefProofFound"),
        "reverseReuseProofFound": reverse_reuse.get("reverseReuseProofFound"),
        "shapeEvidence": {
            "currentEqualsPredecessorPlusSource": closure.get("currentEqualsPredecessorPlusSource"),
            "sourcePredecessorUnionCoversCurrent": closure.get("sourcePredecessorUnionCoversCurrent"),
            "sourcePredecessorUnionExtraMaps": closure.get("sourcePredecessorUnionExtraMaps") or [],
            "sourceSelector": closure.get("sourceSelector") or execution.get("sourceSelector"),
            "predecessorSelector": closure.get("predecessorSelector") or execution.get("predecessorSelector"),
            "currentSelector": closure.get("currentSelector") or execution.get("currentSelector"),
            "mergeShapeOnly": merge_shape_only,
            "nonPromotingReason": (
                "set-shape decomposition is true, but no source/predecessor execution bridge or "
                "runtime selected-root proof reaches selector 2:0"
            ),
        },
        "executionBridgeMatrix": bridge,
        "proofGateCount": len(gate_rows),
        "proofGatePassCount": sum(1 for row in gate_rows if row.get("pass") is True),
        "proofGateBlockedCount": sum(1 for row in gate_rows if row.get("pass") is not True),
        "proofGatesAllBlocked": all(row.get("pass") is not True for row in gate_rows),
        "proofGateRows": gate_rows,
        "sourceReports": {
            "runtimeFailedGateIds": runtime.get("failedSelectorMergeRuntimeGateIds") or [],
            "closureFailedGateIds": closure.get("failedSelectorMergeGateIds") or [],
            "executionFailedGateIds": execution.get("failedSelectorMergeExecutionGateIds") or [],
            "decompositionFailedGateIds": decomposition.get("failedSelectorSetDecompositionGateIds") or [],
            "targetAliasFailedGateIds": target_alias.get("failedTargetAliasBridgeGateIds") or [],
            "routeRootFailedGateIds": route_root.get("failedRouteRootRefGateIds") or [],
            "reverseReuseFailedGateIds": reverse_reuse.get("failedReverseReuseGateIds") or [],
            "bridgeMatrixFailedGateIds": bridge_matrix.get("failedMergeBridgeGateIds") or [],
        },
        "acceptedEvidenceChecklist": [
            {
                "requirement": "VM control-flow edge from source selector 0:0 into current selector 2:0",
                "currentStatus": "missing",
                "acceptedSignal": "sourceToCurrentBridgeHitCount > 0",
            },
            {
                "requirement": "predecessor/target-side forward bridge into current selector 2:0",
                "currentStatus": "missing",
                "acceptedSignal": "predecessorToCurrentHitCount > 0 or forwardMergeBridgeHitCount > 0",
            },
            {
                "requirement": "execution-like target-alias bridge, encoded merge bridge, or route-root ref",
                "currentStatus": "missing",
                "acceptedSignal": "directMergeExecutionBridgeFound == true or encodedMergeExecutionBridgeFound == true",
            },
            {
                "requirement": "real selected-root runtime execution for 0x00540714",
                "currentStatus": "missing",
                "acceptedSignal": "selectorMergeRuntimeProofFound == true",
            },
            {
                "requirement": "observed predecessor fill context and route-order proof before the current reader",
                "currentStatus": "missing",
                "acceptedSignal": "predecessorRouteOrderProofFound == true",
            },
            {
                "requirement": "strict map1_01a source coordinate or tile hotspot evidence",
                "currentStatus": "missing",
                "acceptedSignal": "strictSourceHotspotProofFound == true",
            },
        ],
        "notAcceptedEvidence": [
            "current selector 2:0 equals predecessor 1:0 plus source 0:0 set shape by itself",
            "current-to-predecessor reverse refs that occur only before the predecessor fill sites",
            "zero forward bridge counts across source/current and predecessor/current scans",
            "table/root refs without text/control-flow/runtime execution refs",
            "constructed or patched diagnostics that are not captured gameplay savedata",
        ],
        "evidenceRefs": SELECTOR_MERGE_EXTERNAL_EVIDENCE_REFS,
        "evidenceRefCount": len(SELECTOR_MERGE_EXTERNAL_EVIDENCE_REFS),
        "relatedReports": [
            "out/save_selector_predecessor_persistence_gap.html",
            "out/save_selector_predecessor_route_order.html",
            "out/save_selector_merge_runtime_context.json",
            "out/save_selector_merge_closure_context.json",
            "out/save_selector_merge_execution_gap.html",
            "out/save_selector_set_decomposition.html",
            "out/save_selector_target_alias_bridges.html",
            "out/save_selector_route_root_ref_context.json",
            "out/save_selector_reverse_reuse_context.json",
            "out/save_selector_merge_bridge_matrix.json",
        ],
        "regenerateAndVerifyCommands": [
            "python3 tools/summarize_selector_merge_external_proof_packet.py",
            "python3 tools/verify_web_assets.py",
        ],
        "remainingProofs": [
            "source/predecessor forward bridge or execution-like selector merge bridge into selector 2:0",
            "real selected-root runtime execution for 0x00540714",
            "observed predecessor opcode 0x10 fill context and route-order proof",
            "strict map1_01a source coordinate or tile hotspot evidence",
        ],
        "conclusion": (
            "Selector merge remains blocked: the selector-set shape matches, but no normal "
            "execution/order proof carries source or predecessor state into selector 2:0."
        ),
    }


def markdown(summary: dict) -> str:
    lines = [
        "# Selector Merge External Proof Packet",
        "",
        f"- route: `{summary['source']} -> {summary['target']}`",
        f"- promotion status: `{summary['promotionStatus']}`",
        f"- selector path: `{summary.get('sourceSelector')}` + `{summary.get('predecessorSelector')}` -> `{summary.get('currentSelector')}`",
        f"- current root: `{summary.get('currentRootHex')}`",
        f"- proof found: {summary.get('proofFound')}",
        f"- selectorMergeExternalProofFound: {summary.get('selectorMergeExternalProofFound')}",
        f"- failed selector-merge external gates: `{', '.join(summary.get('failedSelectorMergeExternalGateIds') or [])}`",
        f"- missing evidence count: {len(summary.get('missingEvidence') or [])}",
        f"- evidence refs: {summary.get('evidenceRefCount')}",
        "",
        "## Shape Evidence",
        "",
    ]
    shape = summary.get("shapeEvidence") or {}
    lines.extend([
        f"- current equals predecessor plus source: {shape.get('currentEqualsPredecessorPlusSource')}",
        f"- source/predecessor union covers current: {shape.get('sourcePredecessorUnionCoversCurrent')}",
        f"- extra maps in union: `{shape.get('sourcePredecessorUnionExtraMaps')}`",
        f"- mergeShapeOnly: {shape.get('mergeShapeOnly')}",
        f"- non-promoting reason: {shape.get('nonPromotingReason')}",
        "",
        "## Execution Bridge Matrix",
        "",
    ])
    bridge = summary.get("executionBridgeMatrix") or {}
    for key in [
        "sourceToCurrentBridgeHitCount",
        "currentToSourceBridgeHitCount",
        "predecessorToCurrentHitCount",
        "forwardMergeBridgeHitCount",
        "directMergeExecutionBridgeFound",
        "encodedMergeExecutionBridgeFound",
        "forwardEncodedAnchorRawScalarCandidateCount",
        "forwardEncodedAnchorPromotingCandidateCount",
        "currentToPredecessorHitCount",
        "currentToPredecessorBeforeFillHitCount",
        "currentToPredecessorFillSiteHitCount",
        "currentPredecessorHitsBeforeFillOnly",
    ]:
        lines.append(f"- {key}: `{bridge.get(key)}`")
    lines.extend([
        "",
        "## Closure Gates",
        "",
        f"- gate pass/block: {summary.get('proofGatePassCount')}/{summary.get('proofGateBlockedCount')}",
        f"- all blocked: {summary.get('proofGatesAllBlocked')}",
        "",
        "| gate | pass | status | detail |",
        "| --- | --- | --- | --- |",
    ])
    for row in summary.get("proofGateRows") or []:
        lines.append(
            f"| `{row.get('id')}` | {row.get('pass')} | `{row.get('status')}` | {row.get('detail')} |"
        )
    lines.extend([
        "",
        "## Accepted Evidence Checklist",
        "",
        "| requirement | current status | accepted signal |",
        "| --- | --- | --- |",
    ])
    for row in summary.get("acceptedEvidenceChecklist") or []:
        lines.append(f"| {row.get('requirement')} | {row.get('currentStatus')} | {row.get('acceptedSignal')} |")
    lines.extend(["", "## Missing Evidence", ""])
    lines.extend(f"- {item}" for item in summary.get("missingEvidence") or [])
    lines.extend(["", "## Not Accepted Evidence", ""])
    lines.extend(f"- {item}" for item in summary.get("notAcceptedEvidence") or [])
    lines.extend(["", "## Evidence Refs", ""])
    lines.extend(
        f"- `{ref.get('path')}`: {', '.join(ref.get('fields') or [])}"
        for ref in summary.get("evidenceRefs") or []
    )
    lines.extend(["", "## Related Reports", ""])
    lines.extend(f"- `{item}`" for item in summary.get("relatedReports") or [])
    lines.extend(["", "## Regenerate And Verify", ""])
    lines.extend(f"- `{item}`" for item in summary.get("regenerateAndVerifyCommands") or [])
    lines.extend(["", summary.get("conclusion") or "", ""])
    return "\n".join(lines)


def html_page(summary: dict) -> str:
    def esc(value: Any) -> str:
        return html.escape(str(value))

    bridge = summary.get("executionBridgeMatrix") or {}
    shape = summary.get("shapeEvidence") or {}
    bridge_rows = "".join(
        f"<tr><td><code>{esc(key)}</code></td><td>{esc(bridge.get(key))}</td></tr>"
        for key in [
            "sourceToCurrentBridgeHitCount",
            "currentToSourceBridgeHitCount",
            "predecessorToCurrentHitCount",
            "forwardMergeBridgeHitCount",
            "directMergeExecutionBridgeFound",
            "encodedMergeExecutionBridgeFound",
            "forwardEncodedAnchorRawScalarCandidateCount",
            "forwardEncodedAnchorPromotingCandidateCount",
            "currentToPredecessorHitCount",
            "currentToPredecessorBeforeFillHitCount",
            "currentToPredecessorFillSiteHitCount",
            "currentPredecessorHitsBeforeFillOnly",
        ]
    )
    gate_rows = "".join(
        "<tr>"
        f"<td><code>{esc(row.get('id'))}</code></td>"
        f"<td>{esc(row.get('pass'))}</td>"
        f"<td><code>{esc(row.get('status'))}</code></td>"
        f"<td>{esc(row.get('detail'))}</td>"
        "</tr>"
        for row in summary.get("proofGateRows") or []
    )
    checklist_rows = "".join(
        "<tr>"
        f"<td>{esc(row.get('requirement'))}</td>"
        f"<td>{esc(row.get('currentStatus'))}</td>"
        f"<td>{esc(row.get('acceptedSignal'))}</td>"
        "</tr>"
        for row in summary.get("acceptedEvidenceChecklist") or []
    )
    missing = "".join(f"<li>{esc(item)}</li>" for item in summary.get("missingEvidence") or [])
    not_accepted = "".join(f"<li>{esc(item)}</li>" for item in summary.get("notAcceptedEvidence") or [])
    evidence_refs = "".join(
        f"<li><code>{esc(ref.get('path'))}</code>: {esc(', '.join(ref.get('fields') or []))}</li>"
        for ref in summary.get("evidenceRefs") or []
    )
    related = "".join(f"<li><code>{esc(item)}</code></li>" for item in summary.get("relatedReports") or [])
    commands = "".join(f"<li><code>{esc(item)}</code></li>" for item in summary.get("regenerateAndVerifyCommands") or [])
    return "\n".join([
        "<!doctype html>",
        '<html lang="en">',
        "<head>",
        '  <meta charset="utf-8">',
        '  <meta name="viewport" content="width=device-width, initial-scale=1">',
        "  <title>Selector Merge External Proof Packet</title>",
        "  <style>body{margin:24px;background:#101010;color:#eee;font:14px system-ui,sans-serif}table{border-collapse:collapse;width:100%;margin:16px 0 28px}th,td{border:1px solid #333;padding:6px 8px;vertical-align:top}code{color:#f5d76e}</style>",
        "</head>",
        "<body>",
        "  <h1>Selector Merge External Proof Packet</h1>",
        f"  <p>route <code>{esc(summary['source'])}</code> -&gt; <code>{esc(summary['target'])}</code>; "
        f"selector path <code>{esc(summary.get('sourceSelector'))}</code> + "
        f"<code>{esc(summary.get('predecessorSelector'))}</code> -&gt; "
        f"<code>{esc(summary.get('currentSelector'))}</code>; current root "
        f"<code>{esc(summary.get('currentRootHex'))}</code>.</p>",
        f"  <p>proof found {esc(summary.get('proofFound'))}; selectorMergeExternalProofFound "
        f"{esc(summary.get('selectorMergeExternalProofFound'))}; failed selector-merge external gates "
        f"<code>{esc(', '.join(summary.get('failedSelectorMergeExternalGateIds') or []))}</code>; "
        f"missing evidence count <code>{esc(len(summary.get('missingEvidence') or []))}</code>; "
        f"evidence refs <code>{esc(summary.get('evidenceRefCount'))}</code>.</p>",
        "  <h2>Shape Evidence</h2>",
        f"  <p>current equals predecessor plus source {esc(shape.get('currentEqualsPredecessorPlusSource'))}; "
        f"source/predecessor union covers current {esc(shape.get('sourcePredecessorUnionCoversCurrent'))}; "
        f"extra maps <code>{esc(shape.get('sourcePredecessorUnionExtraMaps'))}</code>; "
        f"mergeShapeOnly {esc(shape.get('mergeShapeOnly'))}; reason {esc(shape.get('nonPromotingReason'))}.</p>",
        "  <h2>Execution Bridge Matrix</h2>",
        f"  <table><thead><tr><th>signal</th><th>value</th></tr></thead><tbody>{bridge_rows}</tbody></table>",
        "  <h2>Closure Gates</h2>",
        f"  <p>gate pass/block {esc(summary.get('proofGatePassCount'))}/{esc(summary.get('proofGateBlockedCount'))}; "
        f"all blocked {esc(summary.get('proofGatesAllBlocked'))}.</p>",
        "  <table><thead><tr><th>gate</th><th>pass</th><th>status</th><th>detail</th></tr></thead>",
        f"  <tbody>{gate_rows}</tbody></table>",
        "  <h2>Accepted Evidence Checklist</h2>",
        f"  <table><thead><tr><th>requirement</th><th>status</th><th>accepted signal</th></tr></thead><tbody>{checklist_rows}</tbody></table>",
        "  <h2>Missing Evidence</h2>",
        f"  <ul>{missing}</ul>",
        "  <h2>Not Accepted Evidence</h2>",
        f"  <ul>{not_accepted}</ul>",
        "  <h2>Evidence Refs</h2>",
        f"  <ul>{evidence_refs}</ul>",
        "  <h2>Related Reports</h2>",
        f"  <ul>{related}</ul>",
        "  <h2>Regenerate And Verify</h2>",
        f"  <ul>{commands}</ul>",
        f"  <p>{esc(summary.get('conclusion'))}</p>",
        "</body>",
        "</html>",
        "",
    ])


def write_outputs(summary: dict, out_dir: Path = OUT) -> None:
    out_dir.mkdir(parents=True, exist_ok=True)
    (out_dir / "selector_merge_external_proof_packet.json").write_text(
        json.dumps(summary, ensure_ascii=False, separators=(",", ":")),
        encoding="utf-8",
    )
    (out_dir / "selector_merge_external_proof_packet.html").write_text(html_page(summary), encoding="utf-8")


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--out-dir", type=Path, default=OUT)
    args = parser.parse_args()
    summary = build_summary(args.out_dir)
    write_outputs(summary, args.out_dir)
    print(f"wrote selector merge external proof packet -> {args.out_dir / 'selector_merge_external_proof_packet.html'}")


if __name__ == "__main__":
    main()
