#!/usr/bin/env python3
"""Separate confirmed map1_01a entry context from selector-only frontier context."""
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"
CONFIRMED_PREVIOUS = "map1_02b"
FRONTIER_TARGET = "map2_02d"


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


def cluster_for_record(field_roots: dict, record_va_hex: str) -> dict | None:
    for cluster in field_roots.get("clusters") or []:
        if any(record.get("recordVaHex") == record_va_hex for record in cluster.get("manifestRecords") or []):
            return cluster
    return None


def cluster_for_event(field_roots: dict, event_record_va_hex: str) -> dict | None:
    for cluster in field_roots.get("clusters") or []:
        if any(record.get("recordVaHex") == event_record_va_hex for record in cluster.get("eventRecords") or []):
            return cluster
    return None


def records_for_scene(save_selector_scene_links: dict, record_va_hex: str) -> list[dict]:
    rows = []
    for row in (save_selector_scene_links.get(MAP) or {}).get("records") or []:
        if any(scene.get("recordVaHex") == record_va_hex for scene in row.get("scenes") or []):
            rows.append(row)
    return rows


def selector_targets(rows: list[dict]) -> list[str]:
    seen = set()
    targets = []
    for row in rows:
        for target in row.get("targets") or []:
            if target in seen:
                continue
            seen.add(target)
            targets.append(target)
    return targets


def selector_labels(rows: list[dict]) -> list[str]:
    labels = []
    seen = set()
    for row in rows:
        label = f"{row.get('selector')}:{row.get('leafPointerHex')}"
        if label in seen:
            continue
        seen.add(label)
        labels.append(label)
    return labels


def confirmed_incoming(event_transitions: list[dict]) -> list[dict]:
    rows = []
    for event in event_transitions:
        if event.get("map") != CONFIRMED_PREVIOUS or MAP not in (event.get("targets") or []):
            continue
        rows.append({
            "source": event.get("map"),
            "target": MAP,
            "recordVaHex": record_hex(event),
            "sceneIdHex": event.get("sceneIdHex"),
            "activePoints": event.get("activePoints") or [],
            "targets": event.get("targets") or [],
            "conditionPayloads": [
                {
                    "conditionVaHex": choice.get("conditionVaHex"),
                    "payloadVaHex": choice.get("payloadVaHex"),
                    "linkedStrings": choice.get("linkedStrings") or [],
                }
                for choice in event.get("conditionChoices") or []
                if MAP in (choice.get("targets") or [])
            ],
        })
    return rows


def role_for_record(record_va_hex: str, cluster: dict | None, selector_rows: list[dict], confirmed_entry_record_hex: str, frontier_record_hex: str) -> str:
    targets = selector_targets(selector_rows)
    if record_va_hex == confirmed_entry_record_hex:
        return "confirmed-entry-context"
    if record_va_hex == frontier_record_hex or FRONTIER_TARGET in targets:
        return "selector-only-frontier-context"
    if cluster and cluster.get("classification") == "strict event-linked cluster":
        return "strict-event-linked-context"
    if selector_rows:
        return "selector-linked-context"
    return "manifest-only-context"


def build_record_contexts(
    scene_manifest: list[dict],
    field_roots: dict,
    save_selector_scene_links: dict,
    confirmed_entry_record_hex: str,
    frontier_record_hex: str,
) -> list[dict]:
    contexts = []
    for manifest_record in scene_manifest:
        if manifest_record.get("map") != MAP:
            continue
        record_va_hex = record_hex(manifest_record)
        cluster = cluster_for_record(field_roots, record_va_hex)
        selector_rows = records_for_scene(save_selector_scene_links, record_va_hex)
        contexts.append({
            "recordVaHex": record_va_hex,
            "sceneIdHex": manifest_record.get("sceneIdHex"),
            "tilesets": manifest_record.get("tilesets") or [],
            "resourceNames": [resource.get("name") for resource in manifest_record.get("resources") or []],
            "followingResourceNames": [resource.get("name") for resource in manifest_record.get("followingResources") or []],
            "clusterStartHex": (cluster or {}).get("clusterStartHex"),
            "clusterEndHex": (cluster or {}).get("clusterEndHex"),
            "clusterClassification": (cluster or {}).get("classification", "not-in-interesting-root-clusters"),
            "eventRecordCount": (cluster or {}).get("eventRecordCount", 0),
            "eventSources": (cluster or {}).get("eventSources") or [],
            "eventFieldLinks": (cluster or {}).get("eventFieldLinks") or [],
            "saveSelectorRefCount": (cluster or {}).get("saveSelectorRefCount", 0),
            "selectorTargets": selector_targets(selector_rows),
            "selectorLabels": selector_labels(selector_rows),
            "role": role_for_record(record_va_hex, cluster, selector_rows, confirmed_entry_record_hex, frontier_record_hex),
        })
    contexts.sort(key=lambda row: row["recordVaHex"])
    return contexts


def build_summary(
    scene_manifest: list[dict],
    event_transitions: list[dict],
    field_roots: dict,
    save_selector_scene_links: dict,
) -> dict:
    incoming = confirmed_incoming(event_transitions)
    incoming_event_hexes = {row["recordVaHex"] for row in incoming}
    confirmed_entry_record_hex = "none"
    confirmed_entry_cluster = None
    for event_hex in incoming_event_hexes:
        cluster = cluster_for_event(field_roots, event_hex)
        if not cluster:
            continue
        for record in cluster.get("manifestRecords") or []:
            if record.get("map") == MAP:
                confirmed_entry_record_hex = record.get("recordVaHex")
                confirmed_entry_cluster = cluster
                break
        if confirmed_entry_record_hex != "none":
            break

    frontier_rows = [
        row
        for row in (save_selector_scene_links.get(MAP) or {}).get("records") or []
        if FRONTIER_TARGET in (row.get("targets") or [])
    ]
    frontier_record_hex = "none"
    if frontier_rows:
        for scene in frontier_rows[0].get("scenes") or []:
            if scene.get("map") == MAP:
                frontier_record_hex = scene.get("recordVaHex")
                break

    record_contexts = build_record_contexts(
        scene_manifest,
        field_roots,
        save_selector_scene_links,
        confirmed_entry_record_hex,
        frontier_record_hex,
    )
    frontier_cluster = cluster_for_record(field_roots, frontier_record_hex) if frontier_record_hex != "none" else None
    frontier_proven = (
        confirmed_entry_record_hex != "none"
        and frontier_record_hex != "none"
        and confirmed_entry_record_hex == frontier_record_hex
        and (frontier_cluster or {}).get("classification") == "strict event-linked cluster"
    )
    conclusion = (
        "Confirmed play reaches map1_01a through the strict map1_02b event cluster at 0x005032d8/0x00503350, "
        "while the map2_02d successor hint lives in the separate 0x00542b44 selector-only cluster. "
        "Those are different scene clusters, so the save-selector adjacency is not proven to be the current "
        "map1_01a runtime context or a normal transition target."
    )
    return {
        "map": MAP,
        "frontierTarget": FRONTIER_TARGET,
        "confirmedPrevious": CONFIRMED_PREVIOUS,
        "confirmedIncomingTransitions": incoming,
        "confirmedEntryRecordHex": confirmed_entry_record_hex,
        "confirmedEntryClusterHex": (confirmed_entry_cluster or {}).get("clusterStartHex"),
        "frontierRecordHex": frontier_record_hex,
        "frontierClusterHex": (frontier_cluster or {}).get("clusterStartHex"),
        "frontierSelectors": [
            {
                "selector": row.get("selector"),
                "leafPointerHex": row.get("leafPointerHex"),
                "targets": row.get("targets") or [],
                "sceneRecords": [scene.get("recordVaHex") for scene in row.get("scenes") or []],
            }
            for row in frontier_rows
        ],
        "frontierProvenFromConfirmedEntry": frontier_proven,
        "promotionStatus": "blocked",
        "recordContexts": record_contexts,
        "remainingProofs": [
            "prove selector 2:0 / 0x00542b44 is reached from the confirmed map1_02b -> map1_01a entry path",
            "or find a strict map1_01a source coordinate/hotspot in the confirmed entry context",
            "or keep map1_01a -> map2_02d as selector-only trial data",
        ],
        "conclusion": conclusion,
    }


def markdown(summary: dict) -> str:
    lines = [
        "# map1_01a Entry Context",
        "",
        f"- confirmed previous: `{summary['confirmedPrevious']}`",
        f"- frontier target: `{summary['frontierTarget']}`",
        f"- confirmed entry record: `{summary['confirmedEntryRecordHex']}`",
        f"- frontier record: `{summary['frontierRecordHex']}`",
        f"- frontier proven from confirmed entry: {summary['frontierProvenFromConfirmedEntry']}",
        f"- promotion status: `{summary['promotionStatus']}`",
        "",
        summary["conclusion"],
        "",
        "## Record Contexts",
        "",
        "| record | role | cluster | events | selector targets | selectors | resources | following |",
        "| --- | --- | --- | --- | --- | --- | --- | --- |",
    ]
    for row in summary["recordContexts"]:
        events = f"{row['eventRecordCount']} src={','.join(row['eventSources']) or '-'} links={','.join(row['eventFieldLinks']) or '-'}"
        lines.append(
            f"| `{row['recordVaHex']}` | {row['role']} | {row['clusterClassification']} `{row.get('clusterStartHex') or '-'}` | "
            f"{events} | {', '.join(row['selectorTargets']) or '-'} | {', '.join(row['selectorLabels']) or '-'} | "
            f"{', '.join(row['resourceNames']) or '-'} | {', '.join(row['followingResourceNames']) or '-'} |"
        )
    lines.extend(["", "## Confirmed Incoming", ""])
    for row in summary["confirmedIncomingTransitions"]:
        points = ", ".join(f"{point.get('x')},{point.get('y')}" for point in row.get("activePoints") or [])
        payloads = ", ".join(payload.get("payloadVaHex") or "-" for payload in row.get("conditionPayloads") or [])
        lines.append(f"- `{row['source']}@{row['recordVaHex']}` -> `{row['target']}` active={points or '-'} payloads={payloads or '-'}")
    lines.extend(["", "## Remaining Proofs", ""])
    lines.extend(f"- {item}" for item in summary["remainingProofs"])
    lines.append("")
    return "\n".join(lines)


def html_page(summary: dict) -> str:
    rows = []
    for row in summary["recordContexts"]:
        events = f"{row['eventRecordCount']} src={','.join(row['eventSources']) or '-'} links={','.join(row['eventFieldLinks']) or '-'}"
        rows.append(
            "<tr>"
            f"<td><code>{html.escape(row['recordVaHex'])}</code></td>"
            f"<td>{html.escape(row['role'])}</td>"
            f"<td>{html.escape(row['clusterClassification'])} <code>{html.escape(row.get('clusterStartHex') or '-')}</code></td>"
            f"<td>{html.escape(events)}</td>"
            f"<td>{html.escape(', '.join(row['selectorTargets']) or '-')}</td>"
            f"<td>{html.escape(', '.join(row['selectorLabels']) or '-')}</td>"
            f"<td>{html.escape(', '.join(row['resourceNames']) or '-')}</td>"
            f"<td>{html.escape(', '.join(row['followingResourceNames']) or '-')}</td>"
            "</tr>"
        )
    incoming = "".join(
        "<li>"
        f"<code>{html.escape(row['source'])}@{html.escape(row['recordVaHex'])}</code> -> "
        f"<code>{html.escape(row['target'])}</code>"
        "</li>"
        for row in summary["confirmedIncomingTransitions"]
    )
    proofs = "".join(f"<li>{html.escape(item)}</li>" for item in summary["remainingProofs"])
    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 Entry Context</title>",
        "  <style>",
        "    :root { color-scheme: dark; font-family: system-ui, sans-serif; background: #101010; color: #eee; }",
        "    body { margin: 0; padding: 24px; }",
        "    h1 { margin: 0 0 8px; font-size: 24px; }",
        "    h2 { margin: 24px 0 8px; font-size: 18px; }",
        "    p { max-width: 1100px; color: #bbb; line-height: 1.45; }",
        "    table { width: 100%; border-collapse: collapse; margin: 12px 0 20px; font-size: 13px; }",
        "    th, td { border-bottom: 1px solid #303030; padding: 7px 8px; text-align: left; vertical-align: top; }",
        "    th { position: sticky; top: 0; background: #181818; color: #ddd; }",
        "    code { color: #d7f0ff; }",
        "  </style>",
        "</head>",
        "<body>",
        "  <h1>map1_01a Entry Context</h1>",
        f"  <p>confirmed entry <code>{html.escape(summary['confirmedEntryRecordHex'])}</code>; "
        f"frontier record <code>{html.escape(summary['frontierRecordHex'])}</code>; "
        f"frontier proven from confirmed entry: {summary['frontierProvenFromConfirmedEntry']}; "
        f"promotion status <code>{html.escape(summary['promotionStatus'])}</code></p>",
        f"  <p>{html.escape(summary['conclusion'])}</p>",
        "  <h2>Record Contexts</h2>",
        "  <table><thead><tr><th>record</th><th>role</th><th>cluster</th><th>events</th><th>selector targets</th><th>selectors</th><th>resources</th><th>following</th></tr></thead><tbody>",
        *rows,
        "  </tbody></table>",
        "  <h2>Confirmed Incoming</h2>",
        f"  <ul>{incoming}</ul>",
        "  <h2>Remaining Proofs</h2>",
        f"  <ul>{proofs}</ul>",
        "</body>",
        "</html>",
        "",
    ])


def write_outputs(summary: dict, out_dir: Path = OUT) -> None:
    out_dir.mkdir(parents=True, exist_ok=True)
    (out_dir / "map1_01a_entry_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("--scene-manifest", type=Path, default=OUT / "scene_manifest.json")
    parser.add_argument("--event-transitions", type=Path, default=OUT / "event_transitions.json")
    parser.add_argument("--field-roots", type=Path, default=OUT / "field_map_record_roots.json")
    parser.add_argument("--save-selector-scene-links", type=Path, default=OUT / "save_selector_scene_links.json")
    parser.add_argument("--out-dir", type=Path, default=OUT)
    args = parser.parse_args()
    summary = build_summary(
        json.loads(args.scene_manifest.read_text(encoding="utf-8")),
        json.loads(args.event_transitions.read_text(encoding="utf-8")),
        json.loads(args.field_roots.read_text(encoding="utf-8")),
        json.loads(args.save_selector_scene_links.read_text(encoding="utf-8")),
    )
    write_outputs(summary, args.out_dir)
    print(f"wrote map1_01a entry context -> {args.out_dir / 'map1_01a_entry_context.json'}")


if __name__ == "__main__":
    main()
