#!/usr/bin/env python3
"""Summarize why map1_01a still has no strict hotspot for the current route."""
from __future__ import annotations

import argparse
import html
import json
from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "out"
MAP = "map1_01a"
TARGET = "map2_02d"


def record_hex(row: dict) -> str:
    if row.get("recordVaHex"):
        return row["recordVaHex"]
    record_va = row.get("recordVa")
    if isinstance(record_va, int):
        return f"0x{record_va:08x}"
    return "none"


def target_context_summary(field_map_roots: dict | None) -> dict:
    contexts = []
    for cluster in (field_map_roots or {}).get("clusters") or []:
        manifest_maps = cluster.get("manifestMaps") or []
        route_pairs = cluster.get("routePairs") or []
        contains_target = TARGET in manifest_maps or any(
            row.get("source") == TARGET or row.get("target") == TARGET
            for row in route_pairs
        )
        if not contains_target:
            continue
        current_pair = next(
            (
                row for row in route_pairs
                if row.get("source") == MAP and row.get("target") == TARGET and row.get("isCurrentFrontier")
            ),
            None,
        )
        contexts.append({
            "clusterStartHex": cluster.get("clusterStartHex"),
            "clusterEndHex": cluster.get("clusterEndHex"),
            "classification": cluster.get("classification"),
            "eventRecordCount": cluster.get("eventRecordCount", 0),
            "saveSelectorRefCount": cluster.get("saveSelectorRefCount", 0),
            "manifestMaps": manifest_maps,
            "manifestMapCount": len(manifest_maps),
            "hasCurrentFrontierPair": current_pair is not None,
        })
    strict_contexts = [row for row in contexts if row["eventRecordCount"]]
    selector_only_contexts = [row for row in contexts if row["saveSelectorRefCount"] and not row["eventRecordCount"]]
    current_context = next((row for row in contexts if row["hasCurrentFrontierPair"]), None)
    return {
        "target": TARGET,
        "contextCount": len(contexts),
        "strictEventContextCount": len(strict_contexts),
        "selectorOnlyContextCount": len(selector_only_contexts),
        "currentFrontierContext": current_context,
        "contexts": contexts,
    }


def build_summary(
    scene_manifest: list[dict],
    scene_coordinates: list[dict],
    extraction_gaps: list[dict],
    event_transitions: list[dict],
    playable: dict,
    manifest_point_scan: dict | None = None,
    field_map_roots: dict | None = None,
    resource_ref_scan: dict | None = None,
) -> dict:
    manifest_rows = [row for row in scene_manifest if row.get("map") == MAP]
    coordinate_rows = [row for row in scene_coordinates if row.get("map") == MAP]
    gap_rows = [row for row in extraction_gaps if row.get("map") == MAP]
    transition_rows = [row for row in event_transitions if row.get("map") == MAP]
    blocker = next((row for row in playable.get("confirmedRouteBlockers") or [] if row.get("map") == MAP), {})
    save_selector_targets = blocker.get("saveSelectorFrontierTargets") or []
    manifest_point_scan = manifest_point_scan or {}
    manifest_point_sources = sum(
        record.get("promotableSourcePointCount", 0)
        for record in manifest_point_scan.get("records") or []
    )
    manifest_point_incoming = manifest_point_scan.get("incomingPointTableCount", 0)
    target_context = target_context_summary(field_map_roots)
    target_context_status = (
        "target appears only in selector-only scene-list clusters"
        if target_context["contextCount"] and target_context["strictEventContextCount"] == 0
        else "target has strict event context"
    )
    resource_ref_scan = resource_ref_scan or {}
    resource_ref_count = resource_ref_scan.get("resourceReferenceCount", 0)
    resource_point_count = resource_ref_scan.get("pointCandidateCount", 0)
    resource_exit_point_count = resource_ref_scan.get("routeExitPointCandidateCount", 0)
    resource_strict_count = resource_ref_scan.get("strictSourceTargetCandidateCount", 0)
    resource_current_ref = resource_ref_scan.get("currentFrontierReference") or {}
    evidence = [
        {
            "source": "scene_manifest",
            "count": len(manifest_rows),
            "status": "selector-only scene rows",
            "detail": "; ".join(
                f"{row.get('sceneIdHex')} record={record_hex(row)} tilesets={','.join(row.get('tilesets') or [])}"
                for row in manifest_rows
            )
            or "none",
        },
        {
            "source": "map1_01a_manifest_point_scan",
            "count": manifest_point_sources,
            "status": "no promotable source point table",
            "detail": (
                f"incoming strict-event point tables={manifest_point_incoming}; "
                "0x00503364->0x0050336c belongs to map1_02b->map1_01a, not a map1_01a source hotspot"
            ),
        },
        {
            "source": "map1_01a_resource_ref_scan",
            "count": resource_strict_count,
            "status": "direct resource refs still non-promotable",
            "detail": (
                f"resource refs={resource_ref_count}; point-like payloads={resource_point_count}; "
                f"route-exit point hits={resource_exit_point_count}; "
                f"current frontier ref={resource_current_ref.get('refVaHex', 'none')}"
            ),
        },
        {
            "source": "event_transitions",
            "count": len(transition_rows),
            "status": "no strict event transition",
            "detail": "no strict hotspot record extracted for map1_01a",
        },
        {
            "source": "scene_coordinate_candidates",
            "count": len(coordinate_rows),
            "status": "no broad coordinate candidate",
            "detail": "broad coordinate scan has no map1_01a row to manually review",
        },
        {
            "source": "transition_extraction_gaps",
            "count": len(gap_rows),
            "status": "no extraction-gap row",
            "detail": "the gap report has no map1_01a coordinate candidate waiting for promotion",
        },
        {
            "source": "playable_progress",
            "count": len(save_selector_targets),
            "status": "save-selector frontier only",
            "detail": f"confirmed route blocker points to {', '.join(save_selector_targets) or 'no save selector target'}",
        },
        {
            "source": "field_map_record_roots",
            "count": target_context["contextCount"],
            "status": target_context_status,
            "detail": (
                f"strict event contexts={target_context['strictEventContextCount']}; "
                f"selector-only contexts={target_context['selectorOnlyContextCount']}; "
                f"current cluster={(target_context['currentFrontierContext'] or {}).get('clusterStartHex', 'none')}"
            ),
        },
    ]
    conclusion = (
        "map1_01a still has no strict source hotspot for map2_02d. The current evidence is selector-only: "
        "playable progress sees a save-selector frontier, but neither strict event extraction nor broad coordinate "
        "candidate extraction produces a map1_01a coordinate row, and the manifest point scan only confirms an incoming "
        "map1_02b->map1_01a point table. The target map2_02d appears in selector-only scene-list clusters with no strict "
        "event-linked target context, and the current cluster also lists many neighboring map2_* records. The save-selector "
        "branch context therefore points at resource/scene-list loading rather than a map-transition target. The next "
        "productive step is to find a strict event/hotspot source or a "
        "non-coordinate runtime source for the branch gate, not to promote a transition from existing selector adjacency."
    )
    return {
        "map": MAP,
        "target": TARGET,
        "sceneManifestRows": manifest_rows,
        "eventTransitionCount": len(transition_rows),
        "coordinateCandidateCount": len(coordinate_rows),
        "extractionGapCount": len(gap_rows),
        "manifestPointPromotableSourceCount": manifest_point_sources,
        "manifestPointIncomingCount": manifest_point_incoming,
        "resourceRefScan": {
            "resourceReferenceCount": resource_ref_count,
            "pointCandidateCount": resource_point_count,
            "routeExitPointCandidateCount": resource_exit_point_count,
            "strictSourceTargetCandidateCount": resource_strict_count,
            "currentFrontierReferenceFound": resource_ref_scan.get("currentFrontierReferenceFound"),
            "currentFrontierReferenceHex": resource_current_ref.get("refVaHex"),
        },
        "targetContext": target_context,
        "saveSelectorFrontierTargets": save_selector_targets,
        "strictHotspotFound": False,
        "promotionStatus": "blocked",
        "evidence": evidence,
        "nextActions": [
            "look outside the scanned manifest point tables for a strict event/hotspot source",
            "find a non-coordinate source for the map1_01a branch gate",
            "keep map1_01a->map2_02d out of normal confirmed transitions until a strict source exists",
        ],
        "conclusion": conclusion,
    }


def markdown(summary: dict) -> str:
    lines = [
        "# map1_01a Hotspot Gap",
        "",
        f"- target: `{summary['target']}`",
        f"- strict hotspot found: {summary['strictHotspotFound']}",
        f"- promotion status: {summary['promotionStatus']}",
        f"- save-selector frontier targets: {', '.join(summary['saveSelectorFrontierTargets']) or 'none'}",
        f"- target strict event contexts: {summary['targetContext']['strictEventContextCount']}",
        f"- target selector-only contexts: {summary['targetContext']['selectorOnlyContextCount']}",
        "",
        summary["conclusion"],
        "",
        "## Evidence",
        "",
        "| source | count | status | detail |",
        "| --- | ---: | --- | --- |",
    ]
    for row in summary["evidence"]:
        lines.append(f"| {row['source']} | {row['count']} | {row['status']} | {row['detail']} |")
    lines.extend([
        "",
        "## Target Contexts",
        "",
        "| cluster | class | events | selector refs | maps | current frontier |",
        "| --- | --- | ---: | ---: | --- | --- |",
    ])
    for row in summary["targetContext"]["contexts"]:
        lines.append(
            f"| `{row['clusterStartHex']}`..`{row['clusterEndHex']}` | {row['classification']} | "
            f"{row['eventRecordCount']} | {row['saveSelectorRefCount']} | "
            f"{', '.join(row['manifestMaps'])} | {row['hasCurrentFrontierPair']} |"
        )
    lines.extend(["", "## Next Actions", ""])
    lines.extend(f"- {item}" for item in summary["nextActions"])
    lines.append("")
    return "\n".join(lines)


def html_page(summary: dict) -> str:
    rows = "\n".join(
        "<tr>"
        f"<td>{html.escape(row['source'])}</td>"
        f"<td>{row['count']}</td>"
        f"<td>{html.escape(row['status'])}</td>"
        f"<td>{html.escape(row['detail'])}</td>"
        "</tr>"
        for row in summary["evidence"]
    )
    actions = "".join(f"<li>{html.escape(item)}</li>" for item in summary["nextActions"])
    target_rows = "\n".join(
        "<tr>"
        f"<td><code>{html.escape(row['clusterStartHex'] or '-')}</code>..<code>{html.escape(row['clusterEndHex'] or '-')}</code></td>"
        f"<td>{html.escape(row['classification'] or '-')}</td>"
        f"<td>{row['eventRecordCount']}</td>"
        f"<td>{row['saveSelectorRefCount']}</td>"
        f"<td>{html.escape(', '.join(row['manifestMaps']))}</td>"
        f"<td>{row['hasCurrentFrontierPair']}</td>"
        "</tr>"
        for row in summary["targetContext"]["contexts"]
    )
    return "\n".join([
        "<!doctype html><meta charset=\"utf-8\"><title>map1_01a Hotspot Gap</title>",
        "<style>body{font-family:system-ui,sans-serif;background:#111;color:#eee;max-width:1100px;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 Hotspot Gap</h1>",
        f"<p>target <code>{summary['target']}</code>; strict hotspot found: {summary['strictHotspotFound']}; promotion status: {html.escape(summary['promotionStatus'])}</p>",
        f"<p>{html.escape(summary['conclusion'])}</p>",
        "<table><thead><tr><th>source</th><th>count</th><th>status</th><th>detail</th></tr></thead><tbody>",
        rows,
        "</tbody></table>",
        "<h2>Target Contexts</h2>",
        "<table><thead><tr><th>cluster</th><th>class</th><th>events</th><th>selector refs</th><th>maps</th><th>current frontier</th></tr></thead><tbody>",
        target_rows,
        "</tbody></table>",
        "<h2>Next Actions</h2>",
        f"<ul>{actions}</ul>",
    ])


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


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--scene-manifest", type=Path, default=OUT / "scene_manifest.json")
    parser.add_argument("--scene-coordinates", type=Path, default=OUT / "scene_coordinate_candidates.json")
    parser.add_argument("--extraction-gaps", type=Path, default=OUT / "transition_extraction_gaps.json")
    parser.add_argument("--event-transitions", type=Path, default=OUT / "event_transitions.json")
    parser.add_argument("--playable", type=Path, default=OUT / "playable_progress.json")
    parser.add_argument("--manifest-point-scan", type=Path, default=OUT / "map1_01a_manifest_point_scan.json")
    parser.add_argument("--field-map-roots", type=Path, default=OUT / "field_map_record_roots.json")
    parser.add_argument("--resource-ref-scan", type=Path, default=OUT / "map1_01a_resource_ref_scan.json")
    parser.add_argument("--out-dir", type=Path, default=OUT)
    args = parser.parse_args()
    manifest_point_scan = (
        json.loads(args.manifest_point_scan.read_text(encoding="utf-8"))
        if args.manifest_point_scan.exists()
        else None
    )
    summary = build_summary(
        json.loads(args.scene_manifest.read_text(encoding="utf-8")),
        json.loads(args.scene_coordinates.read_text(encoding="utf-8")),
        json.loads(args.extraction_gaps.read_text(encoding="utf-8")),
        json.loads(args.event_transitions.read_text(encoding="utf-8")),
        json.loads(args.playable.read_text(encoding="utf-8")),
        manifest_point_scan,
        json.loads(args.field_map_roots.read_text(encoding="utf-8")) if args.field_map_roots.exists() else None,
        json.loads(args.resource_ref_scan.read_text(encoding="utf-8")) if args.resource_ref_scan.exists() else None,
    )
    write_outputs(summary, args.out_dir)
    print(f"wrote map1_01a hotspot gap -> {args.out_dir / 'map1_01a_hotspot_gap.html'}")


if __name__ == "__main__":
    main()
