#!/usr/bin/env python3
"""Build an external review packet for the blocked strict source hotspot gate."""
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"
EXPECTED_SIDES = ["top", "bottom", "left", "right"]


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


def by_side(rows: list[dict] | None) -> dict[str, dict]:
    return {row.get("side"): row for row in rows or [] if row.get("side")}


def owner_pair_summary(rows: list[dict] | None) -> str:
    parts = []
    for row in rows or []:
        owner = row.get("owner")
        count = row.get("count")
        if owner is not None and count is not None:
            parts.append(f"{owner}:{count}")
    return "; ".join(parts) or "-"


def block_reason_counts(rows: list[dict]) -> dict[str, int]:
    counts: dict[str, int] = {}
    for row in rows:
        for reason in row.get("blockReasons") or []:
            counts[reason] = counts.get(reason, 0) + 1
    return counts


def truthy_count(rows: list[dict], section: str, key: str) -> int:
    count = 0
    for row in rows:
        if (row.get(section) or {}).get(key) is True:
            count += 1
    return count


def accepted_signal_present(row: dict) -> bool:
    review = row.get("reviewEvidence") or {}
    variant = row.get("variantEvidence") or {}
    tile = row.get("tileSignatureEvidence") or {}
    return (
        (review.get("eventTransitionCount") or 0) > 0
        or variant.get("strictCoordinateEvidenceFound") is True
        or tile.get("tileSignaturePromotes") is True
    )


def manual_review_row(row: dict) -> dict:
    source = row.get("sourceTile") or {}
    target = row.get("targetSpawn") or {}
    tile = row.get("tileSignatureEvidence") or {}
    return {
        "side": row.get("side"),
        "sourceTile": {"x": source.get("x"), "y": source.get("y")},
        "sourceOriginalStandable": source.get("sourceOriginalStandable"),
        "sourceGeometryStandable": source.get("geometryStandable"),
        "sourceLayer0Hex": source.get("layer0Hex"),
        "sourceLayer1Hex": source.get("layer1Hex"),
        "sourceCenterPairKey": source.get("centerPairKey"),
        "sourceLow3x3Key": source.get("low3x3Key"),
        "sourcePair3x3Key": source.get("pair3x3Key"),
        "targetSpawn": {
            "side": target.get("side"),
            "x": target.get("x"),
            "y": target.get("y"),
        },
        "targetSpawnStandable": target.get("standable"),
        "targetSpawnOriginalStandable": target.get("targetSpawnOriginalStandable"),
        "targetSpawnInwardMoveAllowed": target.get("inwardMoveAllowed"),
        "targetSpawnAutoTrigger": target.get("autoTrigger"),
        "targetSpawnProjectionDelta": target.get("projectionDelta"),
        "targetSpawnCenterPairKey": target.get("centerPairKey"),
        "targetSpawnLow3x3Key": target.get("low3x3Key"),
        "targetSpawnPair3x3Key": target.get("pair3x3Key"),
        "centerPairOwnerSummary": owner_pair_summary(tile.get("centerPairOwnerPairs")),
        "targetSpawnLow3x3OwnerSummary": owner_pair_summary(tile.get("targetSpawnLow3x3OwnerPairs")),
        "acceptedSignalPresent": accepted_signal_present(row),
        "blockingDecision": "blocked" if row.get("promotionStatus") == "blocked" else "external-review-only",
        "reviewUrls": row.get("reviewUrls") or {},
    }


def manual_review_summary(candidates: list[dict], context: dict) -> dict:
    return {
        "candidateCount": len(candidates),
        "sourceOriginalStandableCount": truthy_count(candidates, "sourceTile", "sourceOriginalStandable"),
        "sourceGeometryStandableCount": truthy_count(candidates, "sourceTile", "geometryStandable"),
        "targetSpawnStandableCount": truthy_count(candidates, "targetSpawn", "standable"),
        "targetSpawnOriginalStandableCount": truthy_count(
            candidates, "targetSpawn", "targetSpawnOriginalStandable"
        ),
        "targetSpawnInwardMoveAllowedCount": truthy_count(candidates, "targetSpawn", "inwardMoveAllowed"),
        "targetSpawnAutoTriggerCount": truthy_count(candidates, "targetSpawn", "autoTrigger"),
        "strictSourceHotspotProofFound": context.get("strictSourceHotspotProofFound"),
        "tileHotspotConfirmed": context.get("tileHotspotConfirmed"),
        "acceptedSignalPresentCount": sum(1 for row in candidates if accepted_signal_present(row)),
        "nonPromotingReviewScope": (
            "geometry/tile context only; no strict source coordinate, tile hotspot confirmation, "
            "or runtime trigger proof"
        ),
    }


def candidate_row(review_row: dict, signature_row: dict, contrast_row: dict, context_row: dict) -> dict:
    coordinate = review_row.get("coordinateRef") or {}
    variant = review_row.get("variantScan") or {}
    tile = review_row.get("tile") or {}
    target_hint = review_row.get("targetHint") or {}
    target_spawn = review_row.get("targetSpawn") or {}
    contrast_source_tile = contrast_row.get("sourceTile") or {}
    contrast_target_spawn = contrast_row.get("targetSpawn") or {}
    target_spawn_signature = signature_row.get("targetSpawnSignature") or {}
    signature = signature_row.get("signature") or (contrast_row.get("sourceTileSignature") or {})
    return {
        "side": review_row.get("side"),
        "sourceTile": {
            "x": tile.get("x"),
            "y": tile.get("y"),
            "span": review_row.get("span") or {},
            "geometryStandable": review_row.get("geometryStandable"),
            "sourceOriginalStandable": (
                review_row.get("sourceOriginalStandable")
                if review_row.get("sourceOriginalStandable") is not None
                else contrast_source_tile.get("originalStandable")
            ),
            "layer0Hex": signature.get("layer0Hex"),
            "layer1Hex": signature.get("layer1Hex"),
            "centerPairKey": signature.get("centerPairKey"),
            "low3x3Key": signature.get("low3x3Key"),
            "pair3x3Key": signature.get("pair3x3Key"),
        },
        "targetSpawn": {
            "side": target_hint.get("side") or target_spawn.get("side"),
            "x": target_hint.get("x") if target_hint.get("x") is not None else target_spawn.get("x"),
            "y": target_hint.get("y") if target_hint.get("y") is not None else target_spawn.get("y"),
            "standable": target_hint.get("standable"),
            "autoTrigger": target_hint.get("autoTrigger"),
            "projectionDelta": target_hint.get("projectionDelta"),
            "targetSpawnOriginalStandable": (
                review_row.get("targetSpawnOriginalStandable")
                if review_row.get("targetSpawnOriginalStandable") is not None
                else contrast_target_spawn.get("originalStandable")
            ),
            "inwardMoveAllowed": (
                target_spawn.get("inwardMoveAllowed")
                if target_spawn.get("inwardMoveAllowed") is not None
                else contrast_target_spawn.get("inwardMoveAllowed")
            ),
            "centerPairKey": target_spawn_signature.get("centerPairKey"),
            "low3x3Key": target_spawn_signature.get("low3x3Key"),
            "pair3x3Key": target_spawn_signature.get("pair3x3Key"),
        },
        "reviewUrls": {
            "routeAssistUrl": review_row.get("routeAssistUrl") or context_row.get("routeAssistUrl"),
            "sourceReviewUrl": review_row.get("sourceReviewUrl") or context_row.get("routeAssistUrl"),
            "targetReviewUrl": review_row.get("targetReviewUrl") or context_row.get("targetReviewUrl"),
            "targetSpawnReviewUrl": (
                review_row.get("targetSpawnReviewUrl") or context_row.get("targetSpawnReviewUrl")
            ),
        },
        "coordinateEvidence": {
            "status": coordinate.get("status"),
            "promotable": coordinate.get("promotable"),
            "xyHitCount": coordinate.get("xyHitCount"),
            "xyAlignedHitCount": coordinate.get("xyAlignedHitCount"),
            "yxHitCount": coordinate.get("yxHitCount"),
            "yxAlignedHitCount": coordinate.get("yxAlignedHitCount"),
        },
        "variantEvidence": {
            "interestingHitCount": variant.get("interestingHitCount"),
            "currentRootHitCount": variant.get("currentRootHitCount"),
            "characterDescriptorHitCount": variant.get("characterDescriptorHitCount"),
            "spanBoundHitCount": variant.get("spanBoundHitCount"),
            "spanBoundCurrentRootHitCount": variant.get("spanBoundCurrentRootHitCount"),
            "spanSequenceHitCount": variant.get("spanSequenceHitCount"),
            "xyRowSequenceHitCount": variant.get("xyRowSequenceHitCount"),
            "yxOpcodeSequenceHitCount": variant.get("yxOpcodeSequenceHitCount"),
            "strictCoordinateEvidenceFound": variant.get("strictCoordinateEvidenceFound"),
        },
        "reviewEvidence": {
            "routeReviewRowCount": review_row.get("routeReviewRowCount"),
            "eventTransitionCount": review_row.get("eventTransitionCount"),
            "strictTransitionReviewRowsForRoute": review_row.get("strictTransitionReviewRowsForRoute"),
        },
        "tileSignatureEvidence": {
            "lowNibbleMatch": review_row.get("lowNibbleMatch") if review_row.get("lowNibbleMatch") is not None else context_row.get("lowNibbleMatch"),
            "centerPairStrictEventMatchCount": signature_row.get("centerPairStrictEventMatchCount"),
            "centerPairRejectedReviewMatchCount": signature_row.get("centerPairRejectedReviewMatchCount"),
            "centerPairAllMatchesRejectedReview": signature_row.get("centerPairAllMatchesRejectedReview"),
            "centerPairOwnerPairs": signature_row.get("centerPairOwnerPairs") or [],
            "low3x3StrictEventMatchCount": signature_row.get("low3x3StrictEventMatchCount"),
            "pair3x3StrictEventMatchCount": signature_row.get("pair3x3StrictEventMatchCount"),
            "targetSpawnCenterPairStrictEventMatchCount": signature_row.get(
                "targetSpawnCenterPairStrictEventMatchCount"
            ),
            "targetSpawnLow3x3StrictEventMatchCount": signature_row.get(
                "targetSpawnLow3x3StrictEventMatchCount"
            ),
            "targetSpawnPair3x3StrictEventMatchCount": signature_row.get(
                "targetSpawnPair3x3StrictEventMatchCount"
            ),
            "targetSpawnLow3x3TargetLinkedMatchCount": signature_row.get(
                "targetSpawnLow3x3TargetLinkedMatchCount"
            ),
            "targetSpawnLow3x3ConfirmedReviewMatchCount": signature_row.get(
                "targetSpawnLow3x3ConfirmedReviewMatchCount"
            ),
            "targetSpawnLow3x3RejectedReviewMatchCount": signature_row.get(
                "targetSpawnLow3x3RejectedReviewMatchCount"
            ),
            "targetSpawnLow3x3OwnerPairs": signature_row.get("targetSpawnLow3x3OwnerPairs") or [],
            "targetLinkedStrictEventMatchCount": signature_row.get("targetLinkedStrictEventMatchCount"),
            "directSourceTargetStrictEventMatchCount": signature_row.get(
                "directSourceTargetStrictEventMatchCount"
            ),
            "tileSignaturePromotes": signature_row.get("tileSignaturePromotes"),
        },
        "promotionStatus": review_row.get("promotionStatus") or "blocked",
        "blockReasons": review_row.get("blockReasons") or [],
    }


def build_summary(out_dir: Path = OUT) -> dict:
    review = load_json(out_dir / "map1_01a_strict_hotspot_review_matrix.json", {})
    signature = load_json(out_dir / "map1_01a_strict_event_tile_signature_scan.json", {})
    contrast = load_json(out_dir / "map1_01a_tile_hotspot_pattern_contrast.json", {})
    context = load_json(out_dir / "map1_01a_strict_source_hotspot_context.json", {})
    review_by_side = by_side(review.get("candidateRows") or [])
    signature_by_side = by_side(signature.get("candidateRows") or [])
    contrast_by_side = by_side(contrast.get("currentRows") or [])
    context_by_side = by_side(context.get("candidateSummaries") or [])
    candidates = [
        candidate_row(
            review_by_side.get(side, {}),
            signature_by_side.get(side, {}),
            contrast_by_side.get(side, {}),
            context_by_side.get(side, {}),
        )
        for side in EXPECTED_SIDES
    ]
    evidence_refs = context.get("evidenceRefs") or []
    manual_rows = [manual_review_row(row) for row in candidates]
    accepted_evidence = [
        {
            "requirement": "strict source event/coordinate row for map1_01a -> map2_02d",
            "currentStatus": "missing",
            "acceptedSignal": "candidate row has eventTransitionCount > 0 or strictSourceCoordinateFound == true",
        },
        {
            "requirement": "tile hotspot confirmation tied to the same source-target pair",
            "currentStatus": "missing",
            "acceptedSignal": "tileHotspotConfirmed == true and route review row is present",
        },
        {
            "requirement": "equivalent runtime trigger proof",
            "currentStatus": "missing",
            "acceptedSignal": "normal route runtime reaches the target from one candidate without routeAssist/debug state",
        },
    ]
    return {
        "source": SOURCE,
        "target": TARGET,
        "promotionStatus": "blocked",
        "proofFound": context.get("proofFound"),
        "strictSourceHotspotExternalReviewProofFound": context.get("proofFound"),
        "failedStrictSourceHotspotReviewGateIds": context.get("failedStrictHotspotGateIds") or [],
        "missingEvidence": context.get("missingEvidence") or [],
        "strictSourceHotspotProofFound": context.get("strictSourceHotspotProofFound"),
        "tileHotspotConfirmed": context.get("tileHotspotConfirmed"),
        "strictHotspotRejectionClassification": context.get("strictHotspotRejectionClassification"),
        "candidateCount": len(candidates),
        "candidateAllBlocked": all(row.get("promotionStatus") == "blocked" for row in candidates),
        "candidateBlockReasonCounts": block_reason_counts(candidates),
        "candidateRows": candidates,
        "manualReviewSummary": manual_review_summary(candidates, context),
        "manualReviewRows": manual_rows,
        "acceptedEvidenceChecklist": accepted_evidence,
        "notAcceptedEvidence": [
            "geometry/routeAssist candidates without a strict event or runtime trigger",
            "low-nibble-only tile matches",
            "center-pair matches that belong only to rejected map1_02b -> map1_01a reviews",
            "target-spawn low3x3 matches that are generic or owned by unrelated routes",
        ],
        "relatedReports": [
            "out/map1_01a_strict_hotspot_review_matrix.html",
            "out/map1_01a_strict_event_tile_signature_scan.html",
            "out/map1_01a_tile_hotspot_pattern_contrast.html",
            "out/map1_01a_strict_source_hotspot_context.json",
        ],
        "evidenceRefs": evidence_refs,
        "evidenceRefCount": len(evidence_refs),
        "regenerateAndVerifyCommands": [
            "python3 tools/summarize_strict_source_hotspot_external_review_packet.py",
            "python3 tools/verify_web_assets.py",
        ],
        "conclusion": (
            "All four map1_01a candidate exits remain external-review candidates only. "
            "They provide geometry/tile context, but no strict source hotspot or tile-hotspot proof."
        ),
    }


def markdown(summary: dict) -> str:
    manual = summary.get("manualReviewSummary") or {}
    manual_total = manual.get("candidateCount")
    lines = [
        "# Strict Source Hotspot External Review Packet",
        "",
        f"- route: `{summary['source']} -> {summary['target']}`",
        f"- promotion status: `{summary['promotionStatus']}`",
        f"- proof found: {summary.get('proofFound')}",
        f"- strictSourceHotspotExternalReviewProofFound: {summary.get('strictSourceHotspotExternalReviewProofFound')}",
        f"- failed strict-source hotspot review gates: `{', '.join(summary.get('failedStrictSourceHotspotReviewGateIds') or [])}`",
        f"- missing evidence count: {len(summary.get('missingEvidence') or [])}",
        f"- evidence refs: {summary.get('evidenceRefCount')}",
        f"- strict source hotspot proof found: {summary.get('strictSourceHotspotProofFound')}",
        f"- tile hotspot confirmed: {summary.get('tileHotspotConfirmed')}",
        f"- rejection: `{summary.get('strictHotspotRejectionClassification')}`",
        f"- candidate count: {summary.get('candidateCount')}",
        (
            "- manual review summary: "
            f"source original standable: {manual.get('sourceOriginalStandableCount')}/{manual_total}; "
            f"target spawn original standable: {manual.get('targetSpawnOriginalStandableCount')}/{manual_total}; "
            f"inward move allowed: {manual.get('targetSpawnInwardMoveAllowedCount')}/{manual_total}; "
            f"auto trigger: {manual.get('targetSpawnAutoTriggerCount')}/{manual_total}"
        ),
        f"- manual review scope: {manual.get('nonPromotingReviewScope')}",
        "",
        "## Manual Review Matrix",
        "",
        "| side | source flags | source tile context | target flags | target spawn context | owner summary | review links | decision |",
        "| --- | --- | --- | --- | --- | --- | --- | --- |",
    ]
    for row in summary.get("manualReviewRows") or []:
        source = row.get("sourceTile") or {}
        target = row.get("targetSpawn") or {}
        urls = row.get("reviewUrls") or {}
        source_flags = (
            f"sourceOriginalStandable={row.get('sourceOriginalStandable')}; "
            f"geometryStandable={row.get('sourceGeometryStandable')}"
        )
        source_context = (
            f"`{source.get('x')},{source.get('y')}`; "
            f"layer0/layer1=`{row.get('sourceLayer0Hex')}/{row.get('sourceLayer1Hex')}`; "
            f"center=`{row.get('sourceCenterPairKey')}`; "
            f"low3x3=`{row.get('sourceLow3x3Key')}`; "
            f"pair3x3=`{row.get('sourcePair3x3Key')}`"
        )
        target_flags = (
            f"targetSpawnOriginalStandable={row.get('targetSpawnOriginalStandable')}; "
            f"standable={row.get('targetSpawnStandable')}; "
            f"inwardMoveAllowed={row.get('targetSpawnInwardMoveAllowed')}; "
            f"autoTrigger={row.get('targetSpawnAutoTrigger')}; "
            f"projectionDelta={row.get('targetSpawnProjectionDelta')}"
        )
        target_context = (
            f"`{target.get('side')}:{target.get('x')},{target.get('y')}`; "
            f"center=`{row.get('targetSpawnCenterPairKey')}`; "
            f"low3x3=`{row.get('targetSpawnLow3x3Key')}`; "
            f"pair3x3=`{row.get('targetSpawnPair3x3Key')}`"
        )
        owners = (
            f"center={row.get('centerPairOwnerSummary')}; "
            f"targetLow3x3={row.get('targetSpawnLow3x3OwnerSummary')}"
        )
        links = (
            f"[routeAssist]({urls.get('routeAssistUrl')}) / "
            f"[source]({urls.get('sourceReviewUrl')}) / "
            f"[target]({urls.get('targetReviewUrl')}) / "
            f"[targetSpawn]({urls.get('targetSpawnReviewUrl')})"
        )
        decision = (
            f"{row.get('blockingDecision')}; "
            f"acceptedSignalPresent={row.get('acceptedSignalPresent')}"
        )
        lines.append(
            f"| {row.get('side')} | {source_flags} | {source_context} | {target_flags} | "
            f"{target_context} | {owners} | {links} | {decision} |"
        )
    lines.extend(
        [
        "",
        "## Candidates",
        "",
        "| side | source tile | target spawn | coordinate | variant strict | tile signature | block reasons | review |",
        "| --- | --- | --- | --- | --- | --- | --- | --- |",
        ]
    )
    for row in summary.get("candidateRows") or []:
        source = row.get("sourceTile") or {}
        target = row.get("targetSpawn") or {}
        coord = row.get("coordinateEvidence") or {}
        variant = row.get("variantEvidence") or {}
        tile = row.get("tileSignatureEvidence") or {}
        urls = row.get("reviewUrls") or {}
        tile_text = (
            f"centerPairStrictEventMatch={tile.get('centerPairStrictEventMatchCount')}; "
            f"centerRejected={tile.get('centerPairRejectedReviewMatchCount')}; "
            f"targetSpawnLow3x3StrictEventMatch={tile.get('targetSpawnLow3x3StrictEventMatchCount')}; "
            f"promotes={tile.get('tileSignaturePromotes')}"
        )
        lines.append(
            f"| {row.get('side')} | `{source.get('x')},{source.get('y')}` | "
            f"`{target.get('side')}:{target.get('x')},{target.get('y')}` | "
            f"{coord.get('status')} xy/yx={coord.get('xyHitCount')}/{coord.get('yxHitCount')} "
            f"promotable={coord.get('promotable')} | "
            f"{variant.get('strictCoordinateEvidenceFound')} "
            f"hits={variant.get('interestingHitCount')}/{variant.get('currentRootHitCount')}/"
            f"{variant.get('characterDescriptorHitCount')} | "
            f"{tile_text} | {'; '.join(row.get('blockReasons') or [])} | "
            f"[routeAssist]({urls.get('routeAssistUrl')}) / [target]({urls.get('targetReviewUrl')}) |"
        )
    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(["", "## Evidence Refs", ""])
    lines.extend(
        f"- `{ref.get('path')}`: {', '.join(ref.get('fields') or [])}"
        for ref in summary.get("evidenceRefs") or []
    )
    lines.extend(["", "## Not Accepted Evidence", ""])
    lines.extend(f"- {item}" for item in summary.get("notAcceptedEvidence") 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))

    def out_href(path: Any) -> str:
        text = str(path or "")
        return text[4:] if text.startswith("out/") else text

    def linked_code(path: Any) -> str:
        text = str(path or "")
        return f'<a href="{esc(out_href(text))}"><code>{esc(text)}</code></a>'

    manual = summary.get("manualReviewSummary") or {}
    manual_total = manual.get("candidateCount")
    manual_rows = []
    for row in summary.get("manualReviewRows") or []:
        source = row.get("sourceTile") or {}
        target = row.get("targetSpawn") or {}
        urls = row.get("reviewUrls") or {}
        source_flags = (
            f"sourceOriginalStandable={row.get('sourceOriginalStandable')}; "
            f"geometryStandable={row.get('sourceGeometryStandable')}"
        )
        source_context = (
            f"{source.get('x')},{source.get('y')}; "
            f"layer0/layer1={row.get('sourceLayer0Hex')}/{row.get('sourceLayer1Hex')}; "
            f"center={row.get('sourceCenterPairKey')}; "
            f"low3x3={row.get('sourceLow3x3Key')}; "
            f"pair3x3={row.get('sourcePair3x3Key')}"
        )
        target_flags = (
            f"targetSpawnOriginalStandable={row.get('targetSpawnOriginalStandable')}; "
            f"standable={row.get('targetSpawnStandable')}; "
            f"inwardMoveAllowed={row.get('targetSpawnInwardMoveAllowed')}; "
            f"autoTrigger={row.get('targetSpawnAutoTrigger')}; "
            f"projectionDelta={row.get('targetSpawnProjectionDelta')}"
        )
        target_context = (
            f"{target.get('side')}:{target.get('x')},{target.get('y')}; "
            f"center={row.get('targetSpawnCenterPairKey')}; "
            f"low3x3={row.get('targetSpawnLow3x3Key')}; "
            f"pair3x3={row.get('targetSpawnPair3x3Key')}"
        )
        owners = (
            f"center={row.get('centerPairOwnerSummary')}; "
            f"targetLow3x3={row.get('targetSpawnLow3x3OwnerSummary')}"
        )
        manual_rows.append(
            "<tr>"
            f"<td>{esc(row.get('side'))}</td>"
            f"<td>{esc(source_flags)}</td>"
            f"<td><code>{esc(source_context)}</code></td>"
            f"<td>{esc(target_flags)}</td>"
            f"<td><code>{esc(target_context)}</code></td>"
            f"<td>{esc(owners)}</td>"
            f"<td><a href=\"{esc(urls.get('routeAssistUrl'))}\">routeAssist</a><br>"
            f"<a href=\"{esc(urls.get('sourceReviewUrl'))}\">source</a><br>"
            f"<a href=\"{esc(urls.get('targetReviewUrl'))}\">target</a><br>"
            f"<a href=\"{esc(urls.get('targetSpawnReviewUrl'))}\">targetSpawn</a></td>"
            f"<td>{esc(row.get('blockingDecision'))}<br>"
            f"acceptedSignalPresent={esc(row.get('acceptedSignalPresent'))}</td>"
            "</tr>"
        )

    rows = []
    for row in summary.get("candidateRows") or []:
        source = row.get("sourceTile") or {}
        target = row.get("targetSpawn") or {}
        coord = row.get("coordinateEvidence") or {}
        variant = row.get("variantEvidence") or {}
        tile = row.get("tileSignatureEvidence") or {}
        urls = row.get("reviewUrls") or {}
        tile_text = (
            f"centerPairStrictEventMatch={tile.get('centerPairStrictEventMatchCount')}; "
            f"centerRejected={tile.get('centerPairRejectedReviewMatchCount')}; "
            f"targetSpawnLow3x3StrictEventMatch={tile.get('targetSpawnLow3x3StrictEventMatchCount')}; "
            f"targetSpawnOwners={owner_pair_summary(tile.get('targetSpawnLow3x3OwnerPairs'))}; "
            f"promotes={tile.get('tileSignaturePromotes')}"
        )
        rows.append(
            "<tr>"
            f"<td>{esc(row.get('side'))}</td>"
            f"<td><code>{esc(source.get('x'))},{esc(source.get('y'))}</code></td>"
            f"<td><code>{esc(target.get('side'))}:{esc(target.get('x'))},{esc(target.get('y'))}</code></td>"
            f"<td>{esc(coord.get('status'))}<br>xy/yx {esc(coord.get('xyHitCount'))}/{esc(coord.get('yxHitCount'))}<br>promotable {esc(coord.get('promotable'))}</td>"
            f"<td>strict {esc(variant.get('strictCoordinateEvidenceFound'))}<br>hits {esc(variant.get('interestingHitCount'))}/{esc(variant.get('currentRootHitCount'))}/{esc(variant.get('characterDescriptorHitCount'))}</td>"
            f"<td>{esc(tile_text)}</td>"
            f"<td>{esc('; '.join(row.get('blockReasons') or []))}</td>"
            f"<td><a href=\"{esc(urls.get('routeAssistUrl'))}\">routeAssist</a><br><a href=\"{esc(urls.get('targetReviewUrl'))}\">target</a></td>"
            "</tr>"
        )
    checklist = "".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 []
    )
    not_accepted = "".join(f"<li>{esc(item)}</li>" for item in summary.get("notAcceptedEvidence") or [])
    missing = "".join(f"<li>{esc(item)}</li>" for item in summary.get("missingEvidence") or [])
    evidence_refs = "".join(
        f"<li>{linked_code(ref.get('path'))}: {esc(', '.join(ref.get('fields') or []))}</li>"
        for ref in summary.get("evidenceRefs") or []
    )
    related = "".join(f"<li>{linked_code(item)}</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>Strict Source Hotspot External Review 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}a{color:#8fd3ff}</style>",
        "</head>",
        "<body>",
        "  <h1>Strict Source Hotspot External Review Packet</h1>",
        f"  <p>route <code>{esc(summary['source'])}</code> -&gt; <code>{esc(summary['target'])}</code>; "
        f"promotion status <code>{esc(summary.get('promotionStatus'))}</code>; "
        f"strict source hotspot proof {esc(summary.get('strictSourceHotspotProofFound'))}; "
        f"tile hotspot confirmed {esc(summary.get('tileHotspotConfirmed'))}.</p>",
        f"  <p>proof found {esc(summary.get('proofFound'))}; "
        f"strictSourceHotspotExternalReviewProofFound {esc(summary.get('strictSourceHotspotExternalReviewProofFound'))}; "
        "failed strict-source hotspot review gates "
        f"<code>{esc(', '.join(summary.get('failedStrictSourceHotspotReviewGateIds') or []))}</code>; "
        f"missing evidence count <code>{esc(len(summary.get('missingEvidence') or []))}</code>; "
        f"evidence refs <code>{esc(summary.get('evidenceRefCount'))}</code>.</p>",
        f"  <p>rejection <code>{esc(summary.get('strictHotspotRejectionClassification'))}</code>; candidate count {esc(summary.get('candidateCount'))}.</p>",
        "  <p>manual review summary: "
        f"source original standable: {esc(manual.get('sourceOriginalStandableCount'))}/{esc(manual_total)}; "
        f"target spawn original standable: {esc(manual.get('targetSpawnOriginalStandableCount'))}/{esc(manual_total)}; "
        f"inward move allowed: {esc(manual.get('targetSpawnInwardMoveAllowedCount'))}/{esc(manual_total)}; "
        f"auto trigger: {esc(manual.get('targetSpawnAutoTriggerCount'))}/{esc(manual_total)}.</p>",
        f"  <p>manual review scope: {esc(manual.get('nonPromotingReviewScope'))}</p>",
        "  <h2>Manual Review Matrix</h2>",
        "  <table><thead><tr><th>side</th><th>source flags</th><th>source tile context</th><th>target flags</th><th>target spawn context</th><th>owner summary</th><th>review links</th><th>decision</th></tr></thead>",
        f"  <tbody>{''.join(manual_rows)}</tbody></table>",
        "  <h2>Candidates</h2>",
        "  <table><thead><tr><th>side</th><th>source tile</th><th>target spawn</th><th>coordinate</th><th>variant</th><th>tile signature</th><th>block reasons</th><th>review</th></tr></thead>",
        f"  <tbody>{''.join(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}</tbody></table>",
        "  <h2>Missing Evidence</h2>",
        f"  <ul>{missing}</ul>",
        "  <h2>Evidence Refs</h2>",
        f"  <ul>{evidence_refs}</ul>",
        "  <h2>Not Accepted Evidence</h2>",
        f"  <ul>{not_accepted}</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 / "strict_source_hotspot_external_review_packet.json").write_text(
        json.dumps(summary, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )
    (out_dir / "strict_source_hotspot_external_review_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(
        "wrote strict source hotspot external review packet -> "
        f"{args.out_dir / 'strict_source_hotspot_external_review_packet.html'}"
    )


if __name__ == "__main__":
    main()
