#!/usr/bin/env python3
"""Summarize save-selector frontier edges for transition research."""
from __future__ import annotations

import argparse
import html
import json
from pathlib import Path
from urllib.parse import urlencode


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


def web_href(params: dict[str, str]) -> str:
    return f"../web/game.html?{urlencode(params)}"


def rows_by_map(rows: list[dict], key: str = "map") -> dict[str, list[dict]]:
    result: dict[str, list[dict]] = {}
    for row in rows:
        result.setdefault(row.get(key), []).append(row)
    return result


def hex_value(row: dict, key: str) -> str | None:
    hex_key = f"{key}Hex"
    if row.get(hex_key):
        return row[hex_key]
    value = row.get(key)
    return f"0x{value:08x}" if isinstance(value, int) else None


def build_rows(
    playable_progress: dict,
    selector_links: dict[str, dict],
    scene_manifest: list[dict],
    scene_events: list[dict],
    event_transitions: list[dict],
) -> list[dict]:
    manifest_by_map = rows_by_map(scene_manifest)
    events_by_map = rows_by_map(scene_events)
    transitions_by_map = rows_by_map(event_transitions)
    result = []
    for edge in playable_progress.get("saveSelectorCandidateFrontier") or []:
        source = edge["source"]
        target = edge["target"]
        link_records = [
            record
            for record in (selector_links.get(source) or {}).get("records", [])
            if target in (record.get("targets") or record.get("fieldMaps") or [])
        ]
        direct_transitions = [
            row
            for row in transitions_by_map.get(source, [])
            if target in (row.get("targets") or [])
        ]
        scene_pairs = []
        for record in link_records:
            by_scene_map = {scene.get("map"): scene for scene in record.get("scenes") or []}
            scene_pairs.append({
                "selector": record.get("selector"),
                "leafPointerHex": record.get("leafPointerHex"),
                "sourceRecordVaHex": (by_scene_map.get(source) or {}).get("recordVaHex"),
                "sourceSceneIdHex": (by_scene_map.get(source) or {}).get("sceneIdHex"),
                "targetRecordVaHex": (by_scene_map.get(target) or {}).get("recordVaHex"),
                "targetSceneIdHex": (by_scene_map.get(target) or {}).get("sceneIdHex"),
                "sourceTilesets": (by_scene_map.get(source) or {}).get("tilesets") or [],
                "targetTilesets": (by_scene_map.get(target) or {}).get("tilesets") or [],
            })
        result.append({
            "source": source,
            "target": target,
            "selectors": edge.get("selectors") or [],
            "leafPointers": edge.get("leafPointers") or [],
            "recordCount": edge.get("recordCount", len(link_records)),
            "directEventTransition": bool(direct_transitions),
            "directTransitionRecords": [
                {
                    "recordVaHex": row.get("recordVaHex"),
                    "sceneIdHex": row.get("sceneIdHex"),
                    "eventKind": row.get("eventKind"),
                    "pointCount": len(row.get("points") or []),
                }
                for row in direct_transitions
            ],
            "sourceEventCount": len(events_by_map.get(source, [])),
            "targetEventCount": len(events_by_map.get(target, [])),
            "sourceManifestRecords": [
                {
                    "recordVaHex": hex_value(row, "recordVa"),
                    "sceneIdHex": row.get("sceneIdHex"),
                    "tilesets": row.get("tilesets") or [],
                    "sprites": row.get("sprites") or [],
                }
                for row in manifest_by_map.get(source, [])
            ],
            "targetManifestRecords": [
                {
                    "recordVaHex": hex_value(row, "recordVa"),
                    "sceneIdHex": row.get("sceneIdHex"),
                    "tilesets": row.get("tilesets") or [],
                    "sprites": row.get("sprites") or [],
                }
                for row in manifest_by_map.get(target, [])
            ],
            "scenePairs": scene_pairs,
            "sourceOpen": web_href({"map": source, "events": "1", "overview": "1"}),
            "targetOpen": web_href({"map": target, "events": "1", "overview": "1"}),
        })
    return result


def markdown(rows: list[dict]) -> str:
    lines = [
        "# Save Selector Frontier",
        "",
        "Frontier edges are save-selector scene-link candidates that leave the currently confirmed playable route. They are not confirmed tile transitions.",
        "",
        f"Frontier edges: {len(rows)}.",
        "",
        "| source | target | direct event? | source events | selector leaves | scene pairs | open |",
        "| --- | --- | --- | ---: | --- | --- | --- |",
    ]
    for row in rows:
        pairs = ", ".join(
            f"{pair.get('sourceRecordVaHex')}->{pair.get('targetRecordVaHex')}"
            for pair in row["scenePairs"]
        )
        leaves = ", ".join(
            f"{selector} {leaf}"
            for selector, leaf in zip(row["selectors"], row["leafPointers"])
        ) or ", ".join(row["leafPointers"])
        lines.append(
            f"| {row['source']} | {row['target']} | {'yes' if row['directEventTransition'] else 'no'} | "
            f"{row['sourceEventCount']} | {leaves or '-'} | {pairs or '-'} | "
            f"[source]({row['sourceOpen']}) / [target]({row['targetOpen']}) |"
        )
    if not rows:
        lines.append("| - | - | - | - | - | - | - |")
    lines.append("")
    return "\n".join(lines)


def html_page(rows: list[dict]) -> str:
    body_rows = []
    for row in rows:
        pairs = "<br>".join(
            html.escape(
                f"{pair.get('selector')} {pair.get('leafPointerHex')}: "
                f"{pair.get('sourceRecordVaHex')} ({pair.get('sourceSceneIdHex')}) -> "
                f"{pair.get('targetRecordVaHex')} ({pair.get('targetSceneIdHex')})"
            )
            for pair in row["scenePairs"]
        )
        leaves = "<br>".join(html.escape(leaf) for leaf in row["leafPointers"])
        body_rows.append(
            "<tr>"
            f"<td>{html.escape(row['source'])}</td>"
            f"<td>{html.escape(row['target'])}</td>"
            f"<td>{'yes' if row['directEventTransition'] else 'no'}</td>"
            f"<td>{row['sourceEventCount']}</td>"
            f"<td>{row['targetEventCount']}</td>"
            f"<td>{html.escape(', '.join(row['selectors']) or '-')}<br>{leaves or '-'}</td>"
            f"<td>{pairs or '-'}</td>"
            f'<td><a href="{html.escape(row["sourceOpen"])}">source</a> '
            f'<a href="{html.escape(row["targetOpen"])}">target</a></td>'
            "</tr>"
        )
    return "\n".join([
        "<!doctype html>",
        '<html lang="en">',
        "<head>",
        '  <meta charset="utf-8">',
        '  <meta name="viewport" content="width=device-width, initial-scale=1">',
        "  <title>Save Selector Frontier</title>",
        "  <style>",
        "    body { margin: 24px; background: #101010; color: #eee; font: 14px system-ui, sans-serif; }",
        "    table { border-collapse: collapse; width: 100%; }",
        "    th, td { border: 1px solid #333; padding: 6px 8px; vertical-align: top; }",
        "    th { background: #1d1d1d; position: sticky; top: 0; }",
        "    a { color: #9bd4ff; }",
        "  </style>",
        "</head>",
        "<body>",
        "  <h1>Save Selector Frontier</h1>",
        "  <p>Frontier edges are save-selector scene-link candidates that leave the currently confirmed playable route. They are not confirmed tile transitions.</p>",
        f"  <p>Frontier edges: {len(rows)}.</p>",
        "  <table>",
        "    <thead><tr><th>source</th><th>target</th><th>direct event?</th><th>source events</th><th>target events</th><th>selector leaves</th><th>scene pairs</th><th>open</th></tr></thead>",
        "    <tbody>",
        "\n".join(body_rows) or '<tr><td colspan="8">No frontier edges.</td></tr>',
        "    </tbody>",
        "  </table>",
        "</body>",
        "</html>",
        "",
    ])


def write_outputs(rows: list[dict], out_dir: Path = OUT) -> None:
    out_dir.mkdir(parents=True, exist_ok=True)
    (out_dir / "save_selector_frontier.json").write_text(
        json.dumps(rows, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--playable-progress", type=Path, default=OUT / "playable_progress.json")
    parser.add_argument("--selector-links", type=Path, default=OUT / "save_selector_scene_links.json")
    parser.add_argument("--scene-manifest", type=Path, default=OUT / "scene_manifest.json")
    parser.add_argument("--scene-events", type=Path, default=OUT / "scene_events.json")
    parser.add_argument("--event-transitions", type=Path, default=OUT / "event_transitions.json")
    parser.add_argument("--out-dir", type=Path, default=OUT)
    args = parser.parse_args()
    rows = build_rows(
        json.loads(args.playable_progress.read_text(encoding="utf-8")),
        json.loads(args.selector_links.read_text(encoding="utf-8")),
        json.loads(args.scene_manifest.read_text(encoding="utf-8")),
        json.loads(args.scene_events.read_text(encoding="utf-8")),
        json.loads(args.event_transitions.read_text(encoding="utf-8")),
    )
    write_outputs(rows, args.out_dir)
    print(f"wrote {len(rows)} save selector frontier rows -> {args.out_dir / 'save_selector_frontier.json'}")


if __name__ == "__main__":
    main()
