#!/usr/bin/env python3
"""Cross-check map1_01a route candidates against strict hotspot review evidence."""
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"
CONFIRMED_SOURCE = "map1_02b"
CONFIRMED_TARGET = "map1_01a"
SIDE_ORDER = {"top": 0, "bottom": 1, "left": 2, "right": 3}
FAILED_STRICT_HOTSPOT_REVIEW_GATE_IDS = [
    "strict-source-coordinate",
    "tile-hotspot-confirmation",
    "selected-root-execution-ref",
]
STRICT_HOTSPOT_REVIEW_MISSING_EVIDENCE = [
    "strict source coordinate",
    "tile hotspot confirmation",
    "selected-root execution ref",
]
STRICT_HOTSPOT_REVIEW_EVIDENCE_REFS = [
    {"path": "out/map_exit_candidates.json", "fields": ["exitCandidates", "blockedTargetCandidates"]},
    {"path": "out/map_exit_coordinate_refs.json", "fields": ["rows", "promotable", "classification"]},
    {
        "path": "out/map1_01a_exit_coordinate_variant_scan.json",
        "fields": ["strictCoordinateEvidenceFound", "targetSpawnStrictCoordinateEvidenceFound", "promotionStatus"],
    },
    {
        "path": "out/map1_01a_tile_hotspot_pattern_contrast.json",
        "fields": ["candidates", "matchesConfirmedLowNibble"],
    },
    {"path": "data/transition_reviews.json", "fields": ["source", "target", "state", "recordVaHex"]},
    {"path": "out/event_transitions.json", "fields": ["map", "targets", "events"]},
]


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


def state_counts(rows: list[dict]) -> dict[str, int]:
    counts: dict[str, int] = {}
    for row in rows:
        state = str(row.get("state") or "unknown")
        counts[state] = counts.get(state, 0) + 1
    return dict(sorted(counts.items()))


def review_rows(reviews: dict, source: str | None = None, target: str | None = None) -> list[dict]:
    rows = []
    for row in reviews.values():
        if source is not None and row.get("source") != source:
            continue
        if target is not None and row.get("target") != target:
            continue
        rows.append(row)
    return sorted(rows, key=lambda row: (row.get("recordVaHex") or "", row.get("x") or -1, row.get("y") or -1))


def event_transition_rows(events: list[dict], source: str, target: str) -> list[dict]:
    return [
        row for row in events
        if row.get("map") == source and target in (row.get("targets") or [])
    ]


def exit_candidates(map_exit_candidates: list[dict]) -> list[dict]:
    rows = []
    for map_row in map_exit_candidates:
        if map_row.get("map") != SOURCE:
            continue
        for candidate in map_row.get("exitCandidates") or []:
            sample = candidate.get("sample") or {}
            targets = [
                *(candidate.get("blockedTargetCandidates") or []),
                *(candidate.get("selectorTargetCandidates") or []),
            ]
            if TARGET not in targets:
                continue
            if not isinstance(sample.get("x"), int) or not isinstance(sample.get("y"), int):
                continue
            rows.append(candidate)
    return sorted(
        rows,
        key=lambda row: (
            SIDE_ORDER.get(row.get("side"), 99),
            (row.get("sample") or {}).get("y") or -1,
            (row.get("sample") or {}).get("x") or -1,
        ),
    )


def first_target_hint(candidate: dict) -> dict:
    for hint in candidate.get("targetHints") or []:
        if hint.get("target") == TARGET:
            return {
                "side": hint.get("side"),
                "x": hint.get("x"),
                "y": hint.get("y"),
                "standable": hint.get("standable"),
                "autoTrigger": hint.get("autoTrigger"),
                "sideMatch": hint.get("sideMatch"),
                "projectionDelta": hint.get("projectionDelta"),
            }
    return {}


def coordinate_ref_key(row: dict) -> tuple[str | None, int | None, int | None]:
    return (row.get("side"), row.get("x"), row.get("y"))


def coordinate_refs_by_candidate(coordinate_refs: dict) -> dict[tuple[str | None, int | None, int | None], dict]:
    rows = {}
    for row in coordinate_refs.get("rows") or []:
        if row.get("source") == SOURCE and row.get("target") == TARGET:
            rows[coordinate_ref_key(row)] = row
    return rows


def variant_summaries_by_candidate(variant_scan: dict) -> dict[tuple[str | None, int | None, int | None], dict]:
    rows = {}
    for row in variant_scan.get("candidateSummaries") or []:
        tile = row.get("tile") or {}
        rows[(row.get("side"), tile.get("x"), tile.get("y"))] = row
    return rows


def tile_rows_by_candidate(tile_pattern: dict) -> dict[tuple[str | None, int | None, int | None], dict]:
    rows = {}
    for row in tile_pattern.get("currentRows") or []:
        rows[(row.get("side"), row.get("x"), row.get("y"))] = row
    return rows


def scan_count(scan: dict, key: str) -> int:
    value = scan.get(key)
    return value if isinstance(value, int) else 0


def compact_coordinate_ref(row: dict | None) -> dict:
    if not row:
        return {
            "status": "missing",
            "promotable": False,
            "xyHitCount": 0,
            "xyAlignedHitCount": 0,
            "yxHitCount": 0,
            "yxAlignedHitCount": 0,
            "sceneEvidence": {},
        }
    scans = row.get("packedScans") or {}
    xy = scans.get("xy") or {}
    yx = scans.get("yx") or {}
    return {
        "status": row.get("status"),
        "promotable": row.get("promotable") is True,
        "xyHitCount": scan_count(xy, "total"),
        "xyAlignedHitCount": scan_count(xy, "alignedTotal"),
        "yxHitCount": scan_count(yx, "total"),
        "yxAlignedHitCount": scan_count(yx, "alignedTotal"),
        "sceneEvidence": row.get("sceneEvidence") or {},
    }


def compact_variant(row: dict | None) -> dict:
    if not row:
        return {
            "interestingHitCount": 0,
            "currentRootHitCount": 0,
            "characterDescriptorHitCount": 0,
            "spanBoundHitCount": 0,
            "spanBoundCurrentRootHitCount": 0,
            "spanSequenceHitCount": 0,
            "xyRowSequenceHitCount": 0,
            "yxAxisSequenceHitCount": 0,
            "yxOpcodeSequenceHitCount": 0,
            "strictCoordinateEvidenceFound": False,
        }
    return {
        "interestingHitCount": row.get("interestingHitCount"),
        "currentRootHitCount": row.get("currentRootHitCount"),
        "characterDescriptorHitCount": row.get("characterDescriptorHitCount"),
        "spanBoundHitCount": row.get("spanBoundHitCount"),
        "spanBoundCurrentRootHitCount": row.get("spanBoundCurrentRootHitCount"),
        "spanSequenceHitCount": row.get("spanSequenceHitCount"),
        "xyRowSequenceHitCount": row.get("xyRowSequenceHitCount"),
        "yxAxisSequenceHitCount": row.get("yxAxisSequenceHitCount"),
        "yxOpcodeSequenceHitCount": row.get("yxOpcodeSequenceHitCount"),
        "strictCoordinateEvidenceFound": row.get("strictCoordinateEvidenceFound") is True,
    }


def compact_tile(row: dict | None, confirmed_low_nibbles: set[str]) -> dict:
    if not row:
        return {
            "sourceOriginalStandable": None,
            "sourceLayer1LowNibbleHex": None,
            "matchesConfirmedLowNibble": False,
            "targetSpawnOriginalStandable": None,
            "strictTransitionReviewRowsForRoute": 0,
        }
    source_tile = row.get("sourceTile") or {}
    target_spawn = row.get("targetSpawn") or {}
    low_nibble = source_tile.get("lowNibbleHex")
    return {
        "sourceOriginalStandable": source_tile.get("originalStandable"),
        "sourceLayer1LowNibbleHex": low_nibble,
        "matchesConfirmedLowNibble": low_nibble in confirmed_low_nibbles,
        "targetSpawnOriginalStandable": target_spawn.get("originalStandable"),
        "targetSpawn": {
            "side": target_spawn.get("side"),
            "x": target_spawn.get("x"),
            "y": target_spawn.get("y"),
            "lowNibbleHex": target_spawn.get("lowNibbleHex"),
            "inwardMoveAllowed": target_spawn.get("inwardMoveAllowed"),
        },
        "strictTransitionReviewRowsForRoute": row.get("strictTransitionReviewRowsForRoute", 0),
    }


def block_reasons(candidate: dict) -> list[str]:
    reasons = []
    if candidate["routeReviewRowCount"] == 0:
        reasons.append("no map1_01a->map2_02d transition-review row")
    if candidate["eventTransitionCount"] == 0:
        reasons.append("no strict map1_01a event transition")
    if candidate["coordinateRef"]["promotable"] is not True:
        reasons.append("coordinate refs are non-promotable")
    if candidate["variantScan"]["strictCoordinateEvidenceFound"] is not True:
        reasons.append("variant coordinate scan found no strict source coordinate")
    if candidate["tileEvidence"]["matchesConfirmedLowNibble"] is True:
        reasons.append("tile signature matches only as geometry/tile evidence")
    return reasons


def build_summary(
    map_exit_candidates: list[dict] | None = None,
    map_exit_coordinate_refs: dict | None = None,
    coordinate_variant_scan: dict | None = None,
    tile_hotspot_pattern: dict | None = None,
    transition_reviews: dict | None = None,
    event_transitions: list[dict] | None = None,
) -> dict:
    map_exit_candidates = map_exit_candidates if map_exit_candidates is not None else load_json(
        OUT / "map_exit_candidates.json",
        [],
    )
    map_exit_coordinate_refs = map_exit_coordinate_refs if map_exit_coordinate_refs is not None else load_json(
        OUT / "map_exit_coordinate_refs.json",
        {},
    )
    coordinate_variant_scan = coordinate_variant_scan if coordinate_variant_scan is not None else load_json(
        OUT / "map1_01a_exit_coordinate_variant_scan.json",
        {},
    )
    tile_hotspot_pattern = tile_hotspot_pattern if tile_hotspot_pattern is not None else load_json(
        OUT / "map1_01a_tile_hotspot_pattern_contrast.json",
        {},
    )
    transition_reviews = transition_reviews if transition_reviews is not None else load_json(
        ROOT / "data" / "transition_reviews.json",
        {},
    )
    event_transitions = event_transitions if event_transitions is not None else load_json(
        OUT / "event_transitions.json",
        [],
    )

    route_reviews = review_rows(transition_reviews, SOURCE, TARGET)
    source_reviews = review_rows(transition_reviews, SOURCE, None)
    incoming_reviews = review_rows(transition_reviews, CONFIRMED_SOURCE, CONFIRMED_TARGET)
    confirmed_incoming = [row for row in incoming_reviews if row.get("state") == "confirmed"]
    confirmed_low_nibbles = {
        ((row.get("tile") or {}).get("lowNibbleHex"))
        for row in tile_hotspot_pattern.get("confirmedRows") or []
        if row.get("state") == "confirmed"
    }
    confirmed_low_nibbles.discard(None)

    coord_by_candidate = coordinate_refs_by_candidate(map_exit_coordinate_refs)
    variant_by_candidate = variant_summaries_by_candidate(coordinate_variant_scan)
    tile_by_candidate = tile_rows_by_candidate(tile_hotspot_pattern)
    route_events = event_transition_rows(event_transitions, SOURCE, TARGET)
    incoming_events = event_transition_rows(event_transitions, CONFIRMED_SOURCE, CONFIRMED_TARGET)

    candidate_rows = []
    for candidate in exit_candidates(map_exit_candidates):
        sample = candidate.get("sample") or {}
        key = (candidate.get("side"), sample.get("x"), sample.get("y"))
        row = {
            "side": candidate.get("side"),
            "tile": {"x": sample.get("x"), "y": sample.get("y")},
            "span": candidate.get("span") or {},
            "geometryStandable": sample.get("standable"),
            "tileCount": candidate.get("tileCount"),
            "targetHint": first_target_hint(candidate),
            "routeReviewRowCount": len(route_reviews),
            "eventTransitionCount": len(route_events),
            "coordinateRef": compact_coordinate_ref(coord_by_candidate.get(key)),
            "variantScan": compact_variant(variant_by_candidate.get(key)),
            "tileEvidence": compact_tile(tile_by_candidate.get(key), confirmed_low_nibbles),
            "promotionStatus": "blocked",
        }
        row["blockReasons"] = block_reasons(row)
        candidate_rows.append(row)

    all_no_reviews = all(row["routeReviewRowCount"] == 0 for row in candidate_rows)
    all_no_events = all(row["eventTransitionCount"] == 0 for row in candidate_rows)
    all_coord_refs_blocked = all(row["coordinateRef"]["promotable"] is not True for row in candidate_rows)
    all_variants_blocked = all(
        row["variantScan"]["strictCoordinateEvidenceFound"] is not True for row in candidate_rows
    )
    low_nibble_matches = sum(
        1 for row in candidate_rows
        if row["tileEvidence"]["matchesConfirmedLowNibble"] is True
    )
    conclusion = (
        "The review matrix keeps map1_01a->map2_02d blocked: all four geometry candidates have zero "
        "route review rows and zero strict event-transition rows. Their coordinate refs and broader variant scans "
        "remain non-promotable, while the confirmed review ledger only covers the incoming map1_02b->map1_01a "
        "record at 0x00503350. Matching passable tile signatures are therefore diagnostic only, not a strict "
        "source hotspot."
    )
    return {
        "source": SOURCE,
        "target": TARGET,
        "confirmedReferenceRoute": f"{CONFIRMED_SOURCE} -> {CONFIRMED_TARGET}",
        "promotionStatus": "blocked",
        "proofFound": False,
        "strictHotspotReviewProofFound": False,
        "failedStrictHotspotReviewGateIds": FAILED_STRICT_HOTSPOT_REVIEW_GATE_IDS,
        "missingEvidence": STRICT_HOTSPOT_REVIEW_MISSING_EVIDENCE,
        "evidenceRefs": STRICT_HOTSPOT_REVIEW_EVIDENCE_REFS,
        "evidenceRefCount": len(STRICT_HOTSPOT_REVIEW_EVIDENCE_REFS),
        "strictSourceCoordinateFound": False,
        "tileHotspotConfirmed": False,
        "eventTransitionCount": len(route_events),
        "transitionReviewRowCount": len(route_reviews),
        "sourceReviewRowCount": len(source_reviews),
        "confirmedIncomingEventRecordCount": len(incoming_events),
        "confirmedIncomingReviewCount": len(confirmed_incoming),
        "incomingReviewCount": len(incoming_reviews),
        "incomingReviewStateCounts": state_counts(incoming_reviews),
        "confirmedIncomingRecordHexes": sorted({
            row.get("recordVaHex")
            for row in incoming_reviews
            if row.get("recordVaHex")
        }),
        "confirmedIncomingTiles": [
            {"x": row.get("x"), "y": row.get("y"), "spawnX": row.get("spawnX"), "spawnY": row.get("spawnY")}
            for row in confirmed_incoming
        ],
        "candidateCount": len(candidate_rows),
        "candidateRows": candidate_rows,
        "candidateGateSummary": {
            "allCandidatesHaveNoRouteReviewRows": all_no_reviews,
            "allCandidatesHaveNoStrictEventRows": all_no_events,
            "allCoordinateRefsNonPromotable": all_coord_refs_blocked,
            "allVariantScansNonPromotable": all_variants_blocked,
            "lowNibbleMatchCount": low_nibble_matches,
            "tileSignatureOnly": low_nibble_matches == len(candidate_rows) and all_no_reviews and all_no_events,
        },
        "remainingProofs": STRICT_HOTSPOT_REVIEW_MISSING_EVIDENCE,
        "conclusion": conclusion,
    }


def markdown(summary: dict) -> str:
    gates = summary["candidateGateSummary"]
    lines = [
        "# map1_01a Strict Hotspot Review Matrix",
        "",
        f"- route: `{summary['source']} -> {summary['target']}`",
        f"- confirmed reference: `{summary['confirmedReferenceRoute']}`",
        f"- promotion status: `{summary['promotionStatus']}`",
        f"- proof found: `{summary['proofFound']}`",
        f"- failed strict hotspot review gates: `{', '.join(summary['failedStrictHotspotReviewGateIds'])}`",
        f"- missing evidence count: `{len(summary['missingEvidence'])}`",
        f"- evidence refs: `{summary.get('evidenceRefCount')}`",
        f"- route transition reviews: {summary['transitionReviewRowCount']}",
        f"- route strict event transitions: {summary['eventTransitionCount']}",
        f"- confirmed incoming reviews: {summary['confirmedIncomingReviewCount']}/{summary['incomingReviewCount']}",
        f"- candidate count: {summary['candidateCount']}",
        f"- all candidates blocked: reviews={gates['allCandidatesHaveNoRouteReviewRows']} "
        f"events={gates['allCandidatesHaveNoStrictEventRows']} "
        f"coordRefs={gates['allCoordinateRefsNonPromotable']} variants={gates['allVariantScansNonPromotable']}",
        "",
        summary["conclusion"],
        "",
        "## Candidate Rows",
        "",
        "| side | tile | target spawn | reviews | events | coord ref | xy/yx hits | variant hits | tile evidence | status |",
        "| --- | --- | --- | ---: | ---: | --- | --- | --- | --- | --- |",
    ]
    for row in summary["candidateRows"]:
        tile = row["tile"]
        target = row["targetHint"]
        coord = row["coordinateRef"]
        variant = row["variantScan"]
        tile_ev = row["tileEvidence"]
        lines.append(
            f"| {row['side']} | `{tile['x']},{tile['y']}` | "
            f"`{target.get('side') or '-'} {target.get('x')},{target.get('y')}` "
            f"standable={target.get('standable')} auto={target.get('autoTrigger')} | "
            f"{row['routeReviewRowCount']} | {row['eventTransitionCount']} | {coord['status']} | "
            f"{coord['xyHitCount']}/{coord['yxHitCount']} | "
            f"interesting={variant['interestingHitCount']} currentRoot={variant['currentRootHitCount']} "
            f"spanSeq={variant['spanSequenceHitCount']} strict={variant['strictCoordinateEvidenceFound']} | "
            f"sourceStandable={tile_ev['sourceOriginalStandable']} "
            f"targetStandable={tile_ev['targetSpawnOriginalStandable']} "
            f"lowNibbleMatch={tile_ev['matchesConfirmedLowNibble']} | {row['promotionStatus']} |"
        )
    lines.extend([
        "",
        "## Review Ledger",
        "",
        f"- incoming review states: {summary['incomingReviewStateCounts']}",
        f"- incoming record hexes: {', '.join(summary['confirmedIncomingRecordHexes']) or 'none'}",
        f"- confirmed incoming tiles: "
        + ", ".join(f"{row['x']},{row['y']}->{row['spawnX']},{row['spawnY']}" for row in summary["confirmedIncomingTiles"]),
        "",
        "## Missing Evidence",
        "",
    ])
    lines.extend(f"- {item}" for item in summary["missingEvidence"])
    lines.append("")
    return "\n".join(lines)


def html_page(summary: dict) -> str:
    rows = "\n".join(
        "<tr>"
        f"<td>{html.escape(str(row['side']))}</td>"
        f"<td><code>{row['tile']['x']},{row['tile']['y']}</code></td>"
        f"<td><code>{html.escape(str((row['targetHint'] or {}).get('side') or '-'))} "
        f"{(row['targetHint'] or {}).get('x')},{(row['targetHint'] or {}).get('y')}</code></td>"
        f"<td>{row['routeReviewRowCount']}</td>"
        f"<td>{row['eventTransitionCount']}</td>"
        f"<td>{html.escape(str((row['coordinateRef'] or {}).get('status')))}</td>"
        f"<td>{(row['coordinateRef'] or {}).get('xyHitCount')}/{(row['coordinateRef'] or {}).get('yxHitCount')}</td>"
        f"<td>interesting={(row['variantScan'] or {}).get('interestingHitCount')}; "
        f"currentRoot={(row['variantScan'] or {}).get('currentRootHitCount')}; "
        f"strict={(row['variantScan'] or {}).get('strictCoordinateEvidenceFound')}</td>"
        f"<td>lowNibbleMatch={(row['tileEvidence'] or {}).get('matchesConfirmedLowNibble')}</td>"
        f"<td>{html.escape(str(row['promotionStatus']))}</td>"
        "</tr>"
        for row in summary["candidateRows"]
    )
    proofs = "".join(f"<li>{html.escape(item)}</li>" for item in summary["missingEvidence"])
    return "\n".join([
        "<!doctype html><meta charset=\"utf-8\"><title>map1_01a Strict Hotspot Review Matrix</title>",
        "<style>body{font-family:system-ui,sans-serif;background:#111;color:#eee;max-width:1200px;margin:24px auto}table{border-collapse:collapse}td,th{border:1px solid #444;padding:6px 8px;vertical-align:top}code{color:#9bd4ff}</style>",
        "<h1>map1_01a Strict Hotspot Review Matrix</h1>",
        f"<p>Route <code>{summary['source']} -&gt; {summary['target']}</code>; promotion status <code>{summary['promotionStatus']}</code>.</p>",
        f"<p>proof found <code>{summary['proofFound']}</code>; failed strict hotspot review gates <code>{html.escape(', '.join(summary['failedStrictHotspotReviewGateIds']))}</code>; missing evidence count <code>{len(summary['missingEvidence'])}</code>; evidence refs <code>{summary.get('evidenceRefCount')}</code>.</p>",
        f"<p>route transition reviews: {summary['transitionReviewRowCount']}; "
        f"route strict event transitions: {summary['eventTransitionCount']}; "
        f"all candidates blocked: reviews={summary['candidateGateSummary']['allCandidatesHaveNoRouteReviewRows']} "
        f"events={summary['candidateGateSummary']['allCandidatesHaveNoStrictEventRows']} "
        f"coordRefs={summary['candidateGateSummary']['allCoordinateRefsNonPromotable']} "
        f"variants={summary['candidateGateSummary']['allVariantScansNonPromotable']}.</p>",
        f"<p>{html.escape(summary['conclusion'])}</p>",
        "<table><thead><tr><th>side</th><th>tile</th><th>target spawn</th><th>reviews</th><th>events</th><th>coord ref</th><th>xy/yx hits</th><th>variant</th><th>tile</th><th>status</th></tr></thead><tbody>",
        rows,
        "</tbody></table>",
        "<h2>Review Ledger</h2>",
        f"<p>incoming review states: {html.escape(str(summary['incomingReviewStateCounts']))}; "
        f"incoming records: {html.escape(', '.join(summary['confirmedIncomingRecordHexes']) or 'none')}</p>",
        f"<h2>Missing Evidence</h2><ul>{proofs}</ul>",
    ])


def write_outputs(summary: dict, out_dir: Path) -> None:
    out_dir.mkdir(parents=True, exist_ok=True)
    (out_dir / "map1_01a_strict_hotspot_review_matrix.json").write_text(
        json.dumps(summary, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )
    (out_dir / "map1_01a_strict_hotspot_review_matrix.html").write_text(html_page(summary), encoding="utf-8")


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--out-dir", type=Path, default=OUT)
    args = parser.parse_args()
    summary = build_summary()
    write_outputs(summary, args.out_dir)
    print(f"wrote strict hotspot review matrix -> {args.out_dir / 'map1_01a_strict_hotspot_review_matrix.html'}")


if __name__ == "__main__":
    main()
