#!/usr/bin/env python3
"""Summarize likely story-progress jumps from save scene selector rows."""
from __future__ import annotations

import argparse
import html
import json
from pathlib import Path


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


def row_key(row: dict) -> tuple[int, int]:
    return int(row["group"]), int(row["slot"])


def selector_label(row: dict) -> str:
    return f"{row['group']}:{row['slot']}"


def unique(values: list[str]) -> list[str]:
    seen = set()
    result = []
    for value in values:
        if value in seen:
            continue
        seen.add(value)
        result.append(value)
    return result


def build_summary(rows: list[dict]) -> dict:
    field_rows = [row for row in sorted(rows, key=row_key) if row.get("fieldMaps")]
    transitions = []
    for index, row in enumerate(field_rows):
        current_maps = unique(row.get("fieldMaps") or [])
        current_set = set(current_maps)
        previous_candidates = []
        for previous in field_rows[:index]:
            previous_maps = unique(previous.get("fieldMaps") or [])
            previous_set = set(previous_maps)
            shared = [name for name in previous_maps if name in current_set]
            if not shared:
                continue
            introduced = [name for name in current_maps if name not in previous_set]
            dropped = [name for name in previous_maps if name not in current_set]
            previous_candidates.append(
                {
                    "fromGroup": previous["group"],
                    "fromSlot": previous["slot"],
                    "fromLabel": selector_label(previous),
                    "sharedMaps": shared,
                    "introducedMaps": introduced,
                    "droppedMaps": dropped,
                    "sharedCount": len(shared),
                    "introducedCount": len(introduced),
                    "droppedCount": len(dropped),
                    "fromFieldMaps": previous_maps,
                    "fromSelectedPointerHex": previous.get("selectedPointerHex"),
                }
            )
        previous_candidates.sort(
            key=lambda item: (
                -item["sharedCount"],
                item["introducedCount"],
                item["droppedCount"],
                item["fromGroup"],
                item["fromSlot"],
            )
        )
        best = previous_candidates[0] if previous_candidates else None
        transitions.append(
            {
                "group": row["group"],
                "slot": row["slot"],
                "label": selector_label(row),
                "selectedPointerHex": row.get("selectedPointerHex"),
                "fieldMaps": current_maps,
                "fieldMapCount": len(current_maps),
                "bestPrevious": best,
                "previousCandidates": previous_candidates[:5],
            }
        )
    return {
        "selectorCount": len(rows),
        "fieldSelectorCount": len(field_rows),
        "progressRows": transitions,
    }


def open_selector_link(row: dict, web_prefix: str = "../web") -> str:
    return f"{web_prefix}/game.html?saveGroup={row['group']}&saveSlot={row['slot']}"


def map_links(names: list[str], web_prefix: str = "../web") -> str:
    if not names:
        return "-"
    return ", ".join(f"[{name}]({web_prefix}/game.html?map={name})" for name in names)


def markdown(summary: dict) -> str:
    lines = [
        "# Scene Selector Progress",
        "",
        "Generated from `out/save_scene_selectors.json`.",
        "",
        "This is not a confirmed map-transition graph. It compares save selector rows that load field maps and highlights rows that share at least one map with an earlier selector while introducing new field maps. That is useful when strict event-transition extraction misses story or script-driven progress.",
        "",
        f"Selector rows: {summary['selectorCount']}.",
        f"Field selector rows: {summary['fieldSelectorCount']}.",
        "",
        "| selector | best previous | alternate previous | shared maps | introduced maps | dropped maps | open selector |",
        "| --- | --- | --- | --- | --- | --- | --- |",
    ]
    for row in summary["progressRows"]:
        best = row.get("bestPrevious")
        if not best or not best.get("introducedMaps"):
            continue
        alternate = next(
            (
                item
                for item in row.get("previousCandidates", [])
                if item.get("fromLabel") != best.get("fromLabel") and item.get("introducedMaps")
            ),
            None,
        )
        alternate_label = "-"
        if alternate:
            alternate_label = (
                f"`{alternate['fromLabel']}` via {', '.join(alternate.get('sharedMaps') or [])}: "
                f"{', '.join(alternate.get('introducedMaps') or [])}"
            )
        lines.append(
            "| {selector} | {previous} | {alternate} | {shared} | {introduced} | {dropped} | [open]({open}) |".format(
                selector=f"`{row['label']}`",
                previous=f"`{best['fromLabel']}`",
                alternate=alternate_label,
                shared=", ".join(best.get("sharedMaps") or []) or "-",
                introduced=map_links(best.get("introducedMaps") or []),
                dropped=", ".join(best.get("droppedMaps") or []) or "-",
                open=open_selector_link(row),
            )
        )
    lines.append("")
    return "\n".join(lines)


def html_page(summary: dict) -> str:
    rows = []
    for row in summary["progressRows"]:
        best = row.get("bestPrevious")
        if not best or not best.get("introducedMaps"):
            continue
        alternate = next(
            (
                item
                for item in row.get("previousCandidates", [])
                if item.get("fromLabel") != best.get("fromLabel") and item.get("introducedMaps")
            ),
            None,
        )
        alternate_text = "-"
        if alternate:
            alternate_text = (
                f"{alternate['fromLabel']} via {', '.join(alternate.get('sharedMaps') or [])}: "
                f"{', '.join(alternate.get('introducedMaps') or [])}"
            )
        introduced_links = " ".join(
            f'<a href="../web/game.html?map={html.escape(name)}">{html.escape(name)}</a>'
            for name in best.get("introducedMaps") or []
        )
        rows.append(
            "\n".join(
                [
                    "<tr>",
                    f"  <td><code>{html.escape(row['label'])}</code></td>",
                    f"  <td><code>{html.escape(best['fromLabel'])}</code></td>",
                    f"  <td>{html.escape(alternate_text)}</td>",
                    f"  <td>{html.escape(', '.join(best.get('sharedMaps') or []) or '-')}</td>",
                    f'  <td class="links">{introduced_links or "-"}</td>',
                    f"  <td>{html.escape(', '.join(best.get('droppedMaps') or []) or '-')}</td>",
                    f'  <td><a href="{html.escape(open_selector_link(row))}">open selector</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>Scene Selector Progress</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; }",
            "    code { color: #ffd447; }",
            "    .links { display: flex; flex-wrap: wrap; gap: 6px 10px; }",
            "  </style>",
            "</head>",
            "<body>",
            "  <h1>Scene Selector Progress</h1>",
            "  <p>Compares save selector rows by shared and newly introduced field maps. These are progress hints, not confirmed transitions.</p>",
            f"  <p>Selector rows: {summary['selectorCount']}. Field selector rows: {summary['fieldSelectorCount']}.</p>",
            "  <table>",
            "    <thead><tr><th>selector</th><th>best previous</th><th>alternate previous</th><th>shared maps</th><th>introduced maps</th><th>dropped maps</th><th>open</th></tr></thead>",
            "    <tbody>",
            *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 / "scene_selector_progress.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("--selectors", type=Path, default=OUT / "save_scene_selectors.json")
    parser.add_argument("--out-dir", type=Path, default=OUT)
    args = parser.parse_args()
    summary = build_summary(json.loads(args.selectors.read_text(encoding="utf-8")))
    write_outputs(summary, args.out_dir)
    print(f"wrote scene selector progress -> {args.out_dir / 'scene_selector_progress.json'}")


if __name__ == "__main__":
    main()
