#!/usr/bin/env python3
"""Summarize map1_01a scene records and their source/target cluster context."""
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"


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


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


def cluster_maps(cluster: dict) -> list[str]:
    return list(cluster.get("manifestMaps") or [])


def contains_map(cluster: dict, map_name: str) -> bool:
    return map_name in cluster_maps(cluster)


def is_strict(cluster: dict) -> bool:
    return 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 cluster_for_record(field_map_roots: dict, record_va_hex: str) -> dict | None:
    for cluster in field_map_roots.get("clusters") or []:
        for record in cluster.get("manifestRecords") or []:
            if record.get("recordVaHex") == record_va_hex:
                return cluster
    return None


def compact_cluster(cluster: dict | None) -> dict | None:
    if not cluster:
        return None
    return {
        "clusterRangeHex": cluster_range(cluster),
        "classification": cluster.get("classification"),
        "manifestMaps": cluster_maps(cluster),
        "eventSources": cluster.get("eventSources") or [],
        "eventFieldLinks": cluster.get("eventFieldLinks") or [],
        "eventRecordCount": cluster.get("eventRecordCount", 0),
        "saveSelectorRefCount": cluster.get("saveSelectorRefCount", 0),
        "currentFrontierPairCount": cluster.get("currentFrontierPairCount", 0),
        "containsSource": contains_map(cluster, SOURCE),
        "containsTarget": contains_map(cluster, TARGET),
        "sourceOutgoingStrict": SOURCE in (cluster.get("eventSources") or []),
        "sourceIncomingStrict": SOURCE in (cluster.get("eventFieldLinks") or []),
        "targetLinkedStrict": TARGET in (cluster.get("eventFieldLinks") or []),
        "selectorOnly": is_selector_only(cluster),
        "strict": is_strict(cluster),
    }


def role_for_record(record: dict, cluster: dict | None) -> str:
    if not cluster:
        return "unlinked manifest scene record"
    if is_strict(cluster):
        if SOURCE in (cluster.get("eventFieldLinks") or []) and SOURCE not in (cluster.get("eventSources") or []):
            return "incoming strict entry context"
        if SOURCE in (cluster.get("eventSources") or []):
            return "outgoing strict source context"
        return "strict event-linked context"
    if cluster.get("currentFrontierPairCount", 0):
        return "current frontier selector-only scene list"
    if is_selector_only(cluster):
        return "selector-only scene list"
    return "manifest-only context"


def compact_scene_record(record: dict, field_map_roots: dict) -> dict:
    record_va_hex = record.get("recordVaHex")
    cluster = cluster_for_record(field_map_roots, record_va_hex)
    compact = compact_cluster(cluster)
    return {
        "recordVaHex": record_va_hex,
        "mapStringVaHex": record.get("mapStringVaHex"),
        "sceneIdHex": record.get("sceneIdHex"),
        "sceneGroup": record.get("sceneGroup"),
        "sceneSlot": record.get("sceneSlot"),
        "tilesets": record.get("tilesets") or [],
        "sprites": record.get("sprites") or [],
        "nextMapRefVaHex": record.get("nextMapRefVaHex"),
        "clusterRangeHex": (compact or {}).get("clusterRangeHex"),
        "classification": (compact or {}).get("classification") or "unlinked manifest record",
        "role": role_for_record(record, cluster),
        "hasStrictEventRecord": bool(cluster and is_strict(cluster)),
        "saveSelectorRefCount": (compact or {}).get("saveSelectorRefCount", 0),
        "currentFrontierPairCount": (compact or {}).get("currentFrontierPairCount", 0),
        "eventSources": (compact or {}).get("eventSources") or [],
        "eventFieldLinks": (compact or {}).get("eventFieldLinks") or [],
    }


def build_summary(
    field_map_roots: dict,
    hotspot_gap: dict,
    strict_target_link_gap: dict,
    record_pattern_contrast: dict,
) -> dict:
    clusters = field_map_roots.get("clusters") or []
    source_scene_records = [
        compact_scene_record(record, field_map_roots)
        for record in hotspot_gap.get("sceneManifestRows") or []
        if record.get("map") == SOURCE
    ]
    source_strict_incoming_count = strict_target_link_gap.get("sourceIncomingOnlyStrictClusterCount", 0)
    source_strict_outgoing_count = strict_target_link_gap.get("sourceOutgoingStrictClusterCount", 0)
    source_current_frontier_clusters = [
        cluster for cluster in clusters
        if contains_map(cluster, SOURCE) and cluster.get("currentFrontierPairCount", 0) > 0
    ]
    target_selector_only_clusters = strict_target_link_gap.get("targetSelectorOnlyClusters") or []
    source_target_shared_clusters = [
        cluster for cluster in clusters
        if contains_map(cluster, SOURCE) and contains_map(cluster, TARGET)
    ]
    source_target_shared_strict_clusters = [cluster for cluster in source_target_shared_clusters if is_strict(cluster)]
    source_target_shared_selector_only_clusters = [
        cluster for cluster in source_target_shared_clusters if is_selector_only(cluster)
    ]
    current_frontier = strict_target_link_gap.get("currentFrontierCluster") or {}
    confirmed = record_pattern_contrast.get("confirmedPattern") or {}
    frontier = record_pattern_contrast.get("frontierPattern") or {}
    summary = {
        "source": SOURCE,
        "target": TARGET,
        "sourceSceneRecordCount": len(source_scene_records),
        "sourceSceneRecords": source_scene_records,
        "sourceSceneRecordClusteredCount": sum(1 for row in source_scene_records if row.get("clusterRangeHex")),
        "sourceSceneRecordUnlinkedCount": sum(1 for row in source_scene_records if not row.get("clusterRangeHex")),
        "sourceStrictIncomingClusterCount": source_strict_incoming_count,
        "sourceStrictOutgoingClusterCount": source_strict_outgoing_count,
        "sourceCurrentFrontierClusterCount": len(source_current_frontier_clusters),
        "targetStrictClusterCount": strict_target_link_gap.get("targetStrictClusterCount", 0),
        "targetSelectorOnlyClusterCount": strict_target_link_gap.get("targetSelectorOnlyClusterCount", 0),
        "targetSelectorOnlyClusters": target_selector_only_clusters,
        "sourceTargetSharedClusterCount": len(source_target_shared_clusters),
        "sourceTargetSharedStrictClusterCount": len(source_target_shared_strict_clusters),
        "sourceTargetSharedSelectorOnlyClusterCount": len(source_target_shared_selector_only_clusters),
        "sourceTargetSharedClusters": [compact_cluster(cluster) for cluster in source_target_shared_clusters],
        "confirmedEntryClusterHex": confirmed.get("clusterRangeHex"),
        "confirmedEntryEventRecordHex": confirmed.get("eventRecordHex"),
        "confirmedEntryPointCount": confirmed.get("eventPointCount", 0),
        "currentFrontierClusterHex": current_frontier.get("clusterRangeHex"),
        "currentFrontierSourceRecordHex": frontier.get("nearestSourceRecordAfterBranch"),
        "currentFrontierTargetRecordHex": frontier.get("nearestTargetRecordAfterBranch"),
        "currentFrontierEventRecordCount": current_frontier.get("eventRecordCount", 0),
        "currentFrontierSaveSelectorRefCount": current_frontier.get("saveSelectorRefCount", 0),
        "currentFrontierRoutePairCount": current_frontier.get("currentFrontierPairCount", 0),
        "strictTargetLinkFound": strict_target_link_gap.get("strictTargetLinkFound"),
        "promotionStatus": "blocked",
        "conclusion": (
            "map1_01a has three scene records in the manifest scan: one unlinked resource record, one incoming "
            "strict entry cluster from map1_02b, and one current selector-only frontier cluster shared with "
            "map2_02d. The only source/target shared cluster is selector-only, while the strict cluster is "
            "incoming-only and map2_02d has no strict target cluster. This keeps map1_01a -> map2_02d blocked "
            "until a strict source hotspot, runtime trace, or equivalent control-flow proof is found."
        ),
    }
    return summary


def markdown(summary: dict) -> str:
    lines = [
        "# map1_01a Scene Record Cluster Context",
        "",
        f"- route under test: `{summary['source']} -> {summary['target']}`",
        f"- source scene records: {summary['sourceSceneRecordCount']}",
        f"- clustered source records: {summary['sourceSceneRecordClusteredCount']}",
        f"- unlinked source records: {summary['sourceSceneRecordUnlinkedCount']}",
        f"- source incoming strict clusters: {summary['sourceStrictIncomingClusterCount']}",
        f"- source outgoing strict clusters: {summary['sourceStrictOutgoingClusterCount']}",
        f"- target strict clusters: {summary['targetStrictClusterCount']}",
        f"- target selector-only clusters: {summary['targetSelectorOnlyClusterCount']}",
        f"- source/target shared clusters: {summary['sourceTargetSharedClusterCount']}",
        f"- source/target shared strict clusters: {summary['sourceTargetSharedStrictClusterCount']}",
        f"- source/target shared selector-only clusters: {summary['sourceTargetSharedSelectorOnlyClusterCount']}",
        f"- current frontier cluster: `{summary['currentFrontierClusterHex']}`",
        f"- current frontier event records: {summary['currentFrontierEventRecordCount']}",
        f"- current frontier selector refs: {summary['currentFrontierSaveSelectorRefCount']}",
        f"- strict target link found: {summary['strictTargetLinkFound']}",
        f"- promotion status: `{summary['promotionStatus']}`",
        "",
        summary["conclusion"],
        "",
        "## Source Scene Records",
        "",
        "| record | cluster | class | role | strict events | selector refs | next map ref | tilesets |",
        "| --- | --- | --- | --- | ---: | ---: | --- | --- |",
    ]
    for record in summary["sourceSceneRecords"]:
        lines.append(
            f"| `{record['recordVaHex']}` | `{record.get('clusterRangeHex') or '-'}` | "
            f"{record['classification']} | {record['role']} | "
            f"{record['hasStrictEventRecord']} | {record['saveSelectorRefCount']} | "
            f"`{record.get('nextMapRefVaHex') or '-'}` | {', '.join(record['tilesets']) or '-'} |"
        )
    lines.extend([
        "",
        "## Source/Target Shared Clusters",
        "",
        "| cluster | class | events | selector refs | current frontier | maps |",
        "| --- | --- | ---: | ---: | ---: | --- |",
    ])
    for cluster in summary["sourceTargetSharedClusters"]:
        lines.append(
            f"| `{cluster['clusterRangeHex']}` | {cluster['classification']} | "
            f"{cluster['eventRecordCount']} | {cluster['saveSelectorRefCount']} | "
            f"{cluster['currentFrontierPairCount']} | {', '.join(cluster['manifestMaps'])} |"
        )
    lines.extend([
        "",
        "## Target Selector-Only Clusters",
        "",
        "| cluster | class | selector refs | current frontier |",
        "| --- | --- | ---: | --- |",
    ])
    for cluster in summary["targetSelectorOnlyClusters"]:
        lines.append(
            f"| `{cluster['clusterRangeHex']}` | {cluster['classification']} | "
            f"{cluster['saveSelectorRefCount']} | {cluster['hasCurrentFrontierPair']} |"
        )
    lines.append("")
    return "\n".join(lines)


def html_page(summary: dict) -> str:
    source_rows = "\n".join(
        "<tr>"
        f"<td><code>{html.escape(str(record['recordVaHex']))}</code></td>"
        f"<td><code>{html.escape(str(record.get('clusterRangeHex') or '-'))}</code></td>"
        f"<td>{html.escape(str(record['classification']))}</td>"
        f"<td>{html.escape(str(record['role']))}</td>"
        f"<td>{html.escape(str(record['hasStrictEventRecord']))}</td>"
        f"<td>{html.escape(str(record['saveSelectorRefCount']))}</td>"
        f"<td><code>{html.escape(str(record.get('nextMapRefVaHex') or '-'))}</code></td>"
        f"<td>{html.escape(', '.join(record['tilesets']) or '-')}</td>"
        "</tr>"
        for record in summary["sourceSceneRecords"]
    )
    shared_rows = "\n".join(
        "<tr>"
        f"<td><code>{html.escape(str(cluster['clusterRangeHex']))}</code></td>"
        f"<td>{html.escape(str(cluster['classification']))}</td>"
        f"<td>{html.escape(str(cluster['eventRecordCount']))}</td>"
        f"<td>{html.escape(str(cluster['saveSelectorRefCount']))}</td>"
        f"<td>{html.escape(str(cluster['currentFrontierPairCount']))}</td>"
        f"<td>{html.escape(', '.join(cluster['manifestMaps']))}</td>"
        "</tr>"
        for cluster in summary["sourceTargetSharedClusters"]
    )
    target_rows = "\n".join(
        "<tr>"
        f"<td><code>{html.escape(str(cluster['clusterRangeHex']))}</code></td>"
        f"<td>{html.escape(str(cluster['classification']))}</td>"
        f"<td>{html.escape(str(cluster['saveSelectorRefCount']))}</td>"
        f"<td>{html.escape(str(cluster['hasCurrentFrontierPair']))}</td>"
        "</tr>"
        for cluster in summary["targetSelectorOnlyClusters"]
    )
    return "\n".join([
        "<!doctype html><meta charset='utf-8'>",
        "<title>map1_01a Scene Record Cluster Context</title>",
        "<style>body{font-family:system-ui,sans-serif;margin:24px;line-height:1.45}table{border-collapse:collapse;width:100%;margin:16px 0}th,td{border:1px solid #ddd;padding:6px 8px;text-align:left;vertical-align:top}th{background:#f4f4f4}code{white-space:nowrap}.summary{max-width:980px}</style>",
        "<h1>map1_01a Scene Record Cluster Context</h1>",
        "<div class='summary'>",
        f"<p><b>Route:</b> <code>{summary['source']} -&gt; {summary['target']}</code></p>",
        f"<p><b>source scene records:</b> {summary['sourceSceneRecordCount']}; "
        f"<b>source/target shared strict clusters:</b> {summary['sourceTargetSharedStrictClusterCount']}; "
        f"<b>source/target shared selector-only clusters:</b> {summary['sourceTargetSharedSelectorOnlyClusterCount']}; "
        f"<b>promotion:</b> {html.escape(str(summary['promotionStatus']))}</p>",
        f"<p>{html.escape(summary['conclusion'])}</p>",
        "</div>",
        "<h2>Source Scene Records</h2>",
        "<table><thead><tr><th>record</th><th>cluster</th><th>class</th><th>role</th><th>strict events</th><th>selector refs</th><th>next map ref</th><th>tilesets</th></tr></thead><tbody>",
        source_rows,
        "</tbody></table>",
        "<h2>Source/Target Shared Clusters</h2>",
        "<table><thead><tr><th>cluster</th><th>class</th><th>events</th><th>selector refs</th><th>current frontier</th><th>maps</th></tr></thead><tbody>",
        shared_rows,
        "</tbody></table>",
        "<h2>Target Selector-Only Clusters</h2>",
        "<table><thead><tr><th>cluster</th><th>class</th><th>selector refs</th><th>current frontier</th></tr></thead><tbody>",
        target_rows,
        "</tbody></table>",
    ])


def write_outputs(summary: dict, out_dir: Path) -> None:
    out_dir.mkdir(parents=True, exist_ok=True)
    (out_dir / "map1_01a_scene_record_cluster_context.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 / "map1_01a_hotspot_gap.json", {}),
        load_json(args.out_dir / "map1_01a_strict_target_link_gap.json", {}),
        load_json(args.out_dir / "map1_01a_record_pattern_contrast.json", {}),
    )
    write_outputs(summary, args.out_dir)
    print(f"wrote scene record cluster context -> {args.out_dir / 'map1_01a_scene_record_cluster_context.json'}")


if __name__ == "__main__":
    main()
