#!/usr/bin/env python3
"""Summarize the strict event-link gap for map1_01a -> map2_02d."""
from __future__ import annotations

import argparse
import html
import json
from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "out"
SOURCE = "map1_01a"
TARGET = "map2_02d"
EVIDENCE_REFS = [
        {
            "path": "out/field_map_record_roots.json",
            "fields": [
                "clusters",
                "strictEventLinkedClusterCount",
                "selectorOnlyClusterCount",
                "proofFound",
                "fieldMapRecordRootsProofFound",
                "failedFieldMapRecordRootGateIds",
                "missingEvidence",
                "evidenceRefs",
                "evidenceRefCount",
                "promotionStatus",
            ],
        },
    {
        "path": "out/event_transitions.json",
        "fields": [
            "map",
            "targets",
            "eventDispatchRefs",
            "conditionLinkedStrings",
        ],
    },
]
FAILED_STRICT_TARGET_LINK_GATE_IDS = [
    "direct-strict-event-transition",
    "source-outgoing-strict-cluster",
    "target-strict-cluster",
    "selector-only-frontier-not-event-linked",
]
STRICT_TARGET_LINK_MISSING_EVIDENCE = [
    "direct strict event transition map1_01a -> map2_02d",
    "strict event cluster sourced by map1_01a and linking to map2_02d",
    "strict target-linked cluster for map2_02d",
    "non-selector-only current frontier event link or equivalent trigger proof",
]
STRICT_TARGET_LINK_REMAINING_PROOFS = [
    "find a direct strict event transition from map1_01a to map2_02d",
    "find a source-outgoing strict cluster from map1_01a that links map2_02d",
    "find a strict target-linked cluster for map2_02d",
    "replace selector-only frontier adjacency with a strict event or equivalent runtime trigger",
]


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


def csv(values: list[object] | None) -> str:
    return ",".join(str(value) for value in values or []) or "-"


def cluster_range(cluster: dict | None) -> str:
    if not cluster:
        return "-"
    return f"{cluster.get('clusterStartHex')}..{cluster.get('clusterEndHex')}"


def event_targets(event: dict) -> list[str]:
    targets = list(event.get("targets") or [])
    for ref in event.get("eventDispatchRefs") or []:
        for value in ref.get("conditionLinkedStrings") or []:
            if isinstance(value, str) and value.endswith(".cns"):
                targets.append(value[:-4])
    return sorted(set(targets))


def compact_event(event: dict) -> dict:
    return {
        "map": event.get("map"),
        "recordVaHex": event.get("recordVaHex"),
        "rawPointCount": event.get("rawPointCount"),
        "targets": event_targets(event),
    }


def compact_cluster(cluster: dict, *, source: str = SOURCE, target: str = TARGET) -> dict:
    manifest_maps = cluster.get("manifestMaps") or []
    event_sources = cluster.get("eventSources") or []
    event_links = cluster.get("eventFieldLinks") or []
    route_pairs = cluster.get("routePairs") or []
    event_records = [
        {
            "recordVaHex": record.get("recordVaHex"),
            "map": record.get("map"),
            "pointCount": record.get("pointCount"),
            "fieldLinks": record.get("fieldLinks") or [],
        }
        for record in cluster.get("eventRecords") or []
    ]
    return {
        "clusterRangeHex": cluster_range(cluster),
        "clusterStartHex": cluster.get("clusterStartHex"),
        "clusterEndHex": cluster.get("clusterEndHex"),
        "classification": cluster.get("classification"),
        "manifestMaps": manifest_maps,
        "eventSources": event_sources,
        "eventFieldLinks": event_links,
        "eventRecordCount": cluster.get("eventRecordCount", 0),
        "eventRecords": event_records,
        "saveSelectorRefCount": cluster.get("saveSelectorRefCount", 0),
        "currentFrontierPairCount": cluster.get("currentFrontierPairCount", 0),
        "manifestMapCount": len(manifest_maps),
        "routePairCount": len(route_pairs),
        "sourceOutgoingRoutePairCount": sum(
            1 for row in route_pairs if row.get("source") == source
        ),
        "targetIncomingRoutePairCount": sum(
            1 for row in route_pairs if row.get("target") == target
        ),
        "sourceTargetRoutePairCount": sum(
            1
            for row in route_pairs
            if row.get("source") == source and row.get("target") == target
        ),
        "containsSource": source in manifest_maps,
        "containsTarget": target in manifest_maps,
        "incomingOnlyForSource": source in event_links and source not in event_sources,
        "outgoingFromSource": source in event_sources,
        "linksTarget": target in event_links,
        "hasSourceTargetRoutePair": any(
            row.get("source") == source and row.get("target") == target
            for row in route_pairs
        ),
        "hasCurrentFrontierPair": any(
            row.get("source") == source
            and row.get("target") == target
            and row.get("isCurrentFrontier")
            for row in route_pairs
        ),
    }


def contains_map(cluster: dict, map_name: str) -> bool:
    return map_name in (cluster.get("manifestMaps") or [])


def is_strict(cluster: dict) -> bool:
    return cluster.get("classification") == "strict event-linked cluster" or cluster.get("eventRecordCount", 0) > 0


def is_selector_only(cluster: dict) -> bool:
    return cluster.get("eventRecordCount", 0) == 0 and cluster.get("saveSelectorRefCount", 0) > 0


def find_current_frontier_cluster(clusters: list[dict]) -> dict | None:
    for cluster in clusters:
        for pair in cluster.get("routePairs") or []:
            if (
                pair.get("source") == SOURCE
                and pair.get("target") == TARGET
                and pair.get("isCurrentFrontier")
            ):
                return cluster
    return None


def build_summary(field_map_roots: dict, event_transitions: list[dict]) -> dict:
    clusters = field_map_roots.get("clusters") or []
    direct_strict_events = [
        event
        for event in event_transitions
        if event.get("map") == SOURCE and TARGET in event_targets(event)
    ]
    source_strict_clusters = [
        cluster for cluster in clusters
        if is_strict(cluster) and contains_map(cluster, SOURCE)
    ]
    source_incoming_only_clusters = [
        cluster for cluster in source_strict_clusters
        if SOURCE in (cluster.get("eventFieldLinks") or []) and SOURCE not in (cluster.get("eventSources") or [])
    ]
    source_outgoing_clusters = [
        cluster for cluster in source_strict_clusters
        if SOURCE in (cluster.get("eventSources") or [])
    ]
    target_strict_clusters = [
        cluster for cluster in clusters
        if is_strict(cluster) and contains_map(cluster, TARGET)
    ]
    target_selector_only_clusters = [
        cluster for cluster in clusters
        if is_selector_only(cluster) and contains_map(cluster, TARGET)
    ]
    target_selector_only_source_overlap = [
        cluster for cluster in target_selector_only_clusters if contains_map(cluster, SOURCE)
    ]
    target_selector_only_source_target_pair_clusters = [
        cluster
        for cluster in target_selector_only_clusters
        if any(
            row.get("source") == SOURCE and row.get("target") == TARGET
            for row in cluster.get("routePairs") or []
        )
    ]
    current_frontier_cluster = find_current_frontier_cluster(clusters)
    current_frontier = compact_cluster(current_frontier_cluster) if current_frontier_cluster else None
    current_frontier_route_pair_count = (current_frontier or {}).get("routePairCount", 0)
    current_frontier_source_outgoing_count = (current_frontier or {}).get(
        "sourceOutgoingRoutePairCount", 0
    )
    current_frontier_target_incoming_count = (current_frontier or {}).get(
        "targetIncomingRoutePairCount", 0
    )
    strict_target_link_found = bool(direct_strict_events) or any(
        SOURCE in (cluster.get("eventSources") or [])
        and TARGET in (cluster.get("eventFieldLinks") or [])
        for cluster in source_strict_clusters
    )
    current_frontier_is_selector_only = bool(
        current_frontier_cluster
        and current_frontier_cluster.get("eventRecordCount", 0) == 0
        and "selector-only" in str(current_frontier_cluster.get("classification") or "")
    )
    summary = {
        "source": SOURCE,
        "target": TARGET,
        "directStrictEventTransitionCount": len(direct_strict_events),
        "directStrictEventTransitions": [compact_event(event) for event in direct_strict_events],
        "sourceStrictClusterCount": len(source_strict_clusters),
        "sourceStrictClusters": [compact_cluster(cluster) for cluster in source_strict_clusters],
        "sourceIncomingOnlyStrictClusterCount": len(source_incoming_only_clusters),
        "sourceOutgoingStrictClusterCount": len(source_outgoing_clusters),
        "targetStrictClusterCount": len(target_strict_clusters),
        "targetStrictClusters": [compact_cluster(cluster) for cluster in target_strict_clusters],
        "targetSelectorOnlyClusterCount": len(target_selector_only_clusters),
        "targetSelectorOnlyClusters": [compact_cluster(cluster) for cluster in target_selector_only_clusters],
        "targetSelectorOnlySourceOverlapCount": len(target_selector_only_source_overlap),
        "targetSelectorOnlySourceTargetRoutePairClusterCount": len(
            target_selector_only_source_target_pair_clusters
        ),
        "targetSelectorOnlyCurrentFrontierClusterCount": 1 if current_frontier_cluster else 0,
        "currentFrontierManifestMapCount": (current_frontier or {}).get("manifestMapCount"),
        "currentFrontierRoutePairCount": current_frontier_route_pair_count,
        "currentFrontierSourceOutgoingRoutePairCount": current_frontier_source_outgoing_count,
        "currentFrontierTargetIncomingRoutePairCount": current_frontier_target_incoming_count,
        "currentFrontierSourceTargetRoutePairCount": (current_frontier or {}).get(
            "sourceTargetRoutePairCount"
        ),
        "currentFrontierCluster": current_frontier,
        "currentFrontierClusterIsSelectorOnly": current_frontier_is_selector_only,
        "strictTargetLinkFound": strict_target_link_found,
        "proofFound": False,
        "strictTargetLinkProofFound": False,
        "failedStrictTargetLinkGateIds": FAILED_STRICT_TARGET_LINK_GATE_IDS,
        "missingEvidence": STRICT_TARGET_LINK_MISSING_EVIDENCE,
        "remainingProofs": STRICT_TARGET_LINK_REMAINING_PROOFS,
        "evidenceRefs": EVIDENCE_REFS,
        "evidenceRefCount": len(EVIDENCE_REFS),
        "promotionStatus": "blocked",
        "conclusion": (
            "Strict event evidence around map1_01a is incoming-only: the nearby strict cluster "
            "0x005032d8..0x00503350 is sourced by map1_02b and links into map1_01a. "
            "No strict event transition starts at map1_01a and targets map2_02d, while map2_02d "
            "appears only in selector-only clusters, including the current frontier "
            "0x00542b44..0x00542e74. The only selector-only cluster that overlaps both source "
            "and target is that current frontier, where the source-target edge is one member of "
            "a broad route-pair list rather than an event row. This keeps map1_01a -> map2_02d "
            "blocked until a strict source hotspot, runtime trace, or equivalent trigger proof is found."
        ),
    }
    return summary


def markdown(summary: dict) -> str:
    lines = [
        "# map1_01a Strict Target Link Gap",
        "",
        f"- route under test: `{summary['source']} -> {summary['target']}`",
        f"- direct strict event transitions: {summary['directStrictEventTransitionCount']}",
        f"- source strict clusters: {summary['sourceStrictClusterCount']}",
        f"- source incoming-only strict clusters: {summary['sourceIncomingOnlyStrictClusterCount']}",
        f"- source outgoing strict clusters: {summary['sourceOutgoingStrictClusterCount']}",
        f"- target strict clusters: {summary['targetStrictClusterCount']}",
        f"- target selector-only clusters: {summary['targetSelectorOnlyClusterCount']}",
        f"- target selector-only clusters containing source: {summary['targetSelectorOnlySourceOverlapCount']}",
        f"- target selector-only source->target pair clusters: {summary['targetSelectorOnlySourceTargetRoutePairClusterCount']}",
        f"- current frontier selector-only route pairs/source outgoing/target incoming: "
        f"{summary['currentFrontierRoutePairCount']} / "
        f"{summary['currentFrontierSourceOutgoingRoutePairCount']} / "
        f"{summary['currentFrontierTargetIncomingRoutePairCount']}",
        f"- current frontier selector-only: {summary['currentFrontierClusterIsSelectorOnly']}",
        f"- strict target link found: {summary['strictTargetLinkFound']}",
        f"- proof found: {summary['proofFound']}",
        f"- strict target-link proof found: {summary['strictTargetLinkProofFound']}",
        f"- failed strict target-link gates: {', '.join(summary.get('failedStrictTargetLinkGateIds') or []) or '-'}",
        f"- missing evidence count: {len(summary.get('missingEvidence') or [])}",
        f"- evidence refs: {summary['evidenceRefCount']}",
        f"- promotion status: `{summary['promotionStatus']}`",
        "",
        summary["conclusion"],
        "",
        "## Missing Evidence",
        "",
    ]
    lines.extend(f"- {item}" for item in summary.get("missingEvidence") or [])
    lines.extend([
        "",
        "## Remaining Proofs",
        "",
    ])
    lines.extend(f"- {item}" for item in summary.get("remainingProofs") or [])
    lines.extend([
        "",
        "## Evidence Refs",
        "",
        "| path | fields |",
        "| --- | --- |",
    ])
    for ref in summary.get("evidenceRefs") or []:
        lines.append(f"| `{ref.get('path')}` | {csv(ref.get('fields') or [])} |")
    lines.extend([
        "",
        "## Source Strict Clusters",
        "",
        "| cluster | class | event sources | event links | event records | role |",
        "| --- | --- | --- | --- | --- | --- |",
    ])
    for cluster in summary["sourceStrictClusters"]:
        records = ", ".join(record.get("recordVaHex") or "-" for record in cluster.get("eventRecords") or [])
        role = "incoming-only" if cluster.get("incomingOnlyForSource") else "outgoing" if cluster.get("outgoingFromSource") else "-"
        lines.append(
            f"| `{cluster['clusterRangeHex']}` | {cluster['classification']} | "
            f"{', '.join(cluster['eventSources']) or '-'} | "
            f"{', '.join(cluster['eventFieldLinks']) or '-'} | {records or '-'} | {role} |"
        )
    lines.extend([
        "",
        "## Target Selector-Only Clusters",
        "",
        "| cluster | class | selector refs | maps | route pairs | source outgoing | target incoming | source->target | current frontier |",
        "| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | --- |",
    ])
    for cluster in summary["targetSelectorOnlyClusters"]:
        lines.append(
            f"| `{cluster['clusterRangeHex']}` | {cluster['classification']} | "
            f"{cluster['saveSelectorRefCount']} | {cluster['manifestMapCount']} | "
            f"{cluster['routePairCount']} | {cluster['sourceOutgoingRoutePairCount']} | "
            f"{cluster['targetIncomingRoutePairCount']} | {cluster['sourceTargetRoutePairCount']} | "
            f"{cluster['hasCurrentFrontierPair']} |"
        )
    lines.append("")
    return "\n".join(lines)


def html_page(summary: dict) -> str:
    failed_gates = "".join(
        f"<li>{html.escape(item)}</li>" for item in summary.get("failedStrictTargetLinkGateIds") or []
    )
    missing = "".join(f"<li>{html.escape(item)}</li>" for item in summary.get("missingEvidence") or [])
    remaining = "".join(f"<li>{html.escape(item)}</li>" for item in summary.get("remainingProofs") or [])
    evidence_ref_rows = "\n".join(
        "<tr>"
        f"<td><code>{html.escape(str(ref.get('path')))}</code></td>"
        f"<td>{html.escape(csv(ref.get('fields') or []))}</td>"
        "</tr>"
        for ref in summary.get("evidenceRefs") or []
    )
    source_rows = "\n".join(
        "<tr>"
        f"<td><code>{html.escape(cluster['clusterRangeHex'])}</code></td>"
        f"<td>{html.escape(str(cluster['classification']))}</td>"
        f"<td>{html.escape(', '.join(cluster['eventSources']) or '-')}</td>"
        f"<td>{html.escape(', '.join(cluster['eventFieldLinks']) or '-')}</td>"
        f"<td>{html.escape(', '.join(record.get('recordVaHex') or '-' for record in cluster.get('eventRecords') or []) or '-')}</td>"
        f"<td>{'incoming-only' if cluster.get('incomingOnlyForSource') else 'outgoing' if cluster.get('outgoingFromSource') else '-'}</td>"
        "</tr>"
        for cluster in summary["sourceStrictClusters"]
    )
    target_rows = "\n".join(
        "<tr>"
        f"<td><code>{html.escape(cluster['clusterRangeHex'])}</code></td>"
        f"<td>{html.escape(str(cluster['classification']))}</td>"
        f"<td>{cluster['saveSelectorRefCount']}</td>"
        f"<td>{cluster['manifestMapCount']}</td>"
        f"<td>{cluster['routePairCount']}</td>"
        f"<td>{cluster['sourceOutgoingRoutePairCount']}</td>"
        f"<td>{cluster['targetIncomingRoutePairCount']}</td>"
        f"<td>{cluster['sourceTargetRoutePairCount']}</td>"
        f"<td>{cluster['hasCurrentFrontierPair']}</td>"
        "</tr>"
        for cluster in summary["targetSelectorOnlyClusters"]
    )
    return "\n".join([
        "<!doctype html>",
        '<html lang="en">',
        "<head>",
        '  <meta charset="utf-8">',
        '  <meta name="viewport" content="width=device-width, initial-scale=1">',
        "  <title>map1_01a Strict Target Link Gap</title>",
        "  <style>body{font-family:system-ui,sans-serif;background:#111;color:#eee;margin:24px}table{border-collapse:collapse;width:100%;max-width:1200px}td,th{border:1px solid #333;padding:6px 8px;text-align:left;vertical-align:top}th{background:#1f1f1f}code{color:#9bd4ff}</style>",
        "</head>",
        "<body>",
        "  <h1>map1_01a Strict Target Link Gap</h1>",
        f"  <p>route <code>{summary['source']} -&gt; {summary['target']}</code>; "
        f"direct strict transitions {summary['directStrictEventTransitionCount']}; "
        f"target strict clusters {summary['targetStrictClusterCount']}; "
        "target selector-only clusters containing source "
        f"{summary['targetSelectorOnlySourceOverlapCount']}; "
        f"current frontier route pairs {summary['currentFrontierRoutePairCount']}; "
        f"strict target link found: {summary['strictTargetLinkFound']}; "
        f"proof found: {summary['proofFound']}; "
        f"strict target-link proof found: {summary['strictTargetLinkProofFound']}; "
        f"missing evidence count {len(summary.get('missingEvidence') or [])}; "
        f"evidence refs {summary.get('evidenceRefCount')}; "
        f"promotion status <code>{summary['promotionStatus']}</code>.</p>",
        f"  <p>{html.escape(summary['conclusion'])}</p>",
        f"  <h2>failed strict target-link gates</h2><ul>{failed_gates}</ul>",
        f"  <h2>Missing Evidence</h2><ul>{missing}</ul>",
        f"  <h2>Remaining Proofs</h2><ul>{remaining}</ul>",
        "  <h2>Evidence Refs</h2>",
        "  <table><thead><tr><th>path</th><th>fields</th></tr></thead><tbody>",
        evidence_ref_rows,
        "  </tbody></table>",
        "  <h2>Source Strict Clusters</h2>",
        "  <table><thead><tr><th>cluster</th><th>class</th><th>event sources</th><th>event links</th><th>event records</th><th>role</th></tr></thead><tbody>",
        source_rows,
        "  </tbody></table>",
        "  <h2>Target Selector-Only Clusters</h2>",
        "  <table><thead><tr><th>cluster</th><th>class</th><th>selector refs</th><th>maps</th><th>route pairs</th><th>source outgoing</th><th>target incoming</th><th>source-&gt;target</th><th>current frontier</th></tr></thead><tbody>",
        target_rows,
        "  </tbody></table>",
        "</body>",
        "</html>",
        "",
    ])


def write_outputs(summary: dict, out_dir: Path = OUT) -> None:
    out_dir.mkdir(parents=True, exist_ok=True)
    (out_dir / "map1_01a_strict_target_link_gap.json").write_text(
        json.dumps(summary, ensure_ascii=False, indent=2) + "\n",
        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(
        load_json(args.out_dir / "field_map_record_roots.json", {}),
        load_json(args.out_dir / "event_transitions.json", []),
    )
    write_outputs(summary, args.out_dir)
    print(f"wrote map1_01a strict target link gap -> {args.out_dir / 'map1_01a_strict_target_link_gap.json'}")


if __name__ == "__main__":
    main()
