#!/usr/bin/env python3
"""Scan all save-scene selector map sets for recomposition patterns."""
from __future__ import annotations

import argparse
import html
import itertools
import json
from pathlib import Path
from typing import Any


ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "out"
SOURCE = "map1_01a"
TARGET = "map2_02d"
SOURCE_SELECTOR = "0:0"
PREDECESSOR_SELECTOR = "1:0"
CURRENT_SELECTOR = "2:0"


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


def selector_key(row: dict) -> str:
    return f"{row.get('group')}:{row.get('slot')}"


def ordered_field_rows(selectors: list[dict]) -> list[dict]:
    return sorted(
        [row for row in selectors if row.get("fieldMaps")],
        key=lambda row: (int(row.get("group") or 0), int(row.get("slot") or 0)),
    )


def find_selector(rows: list[dict], key: str) -> dict:
    for row in rows:
        if selector_key(row) == key:
            return row
    raise ValueError(f"selector {key} not found")


def compact_selector(row: dict) -> dict:
    return {
        "selector": selector_key(row),
        "rootHex": row.get("selectedPointerHex"),
        "fieldMaps": row.get("fieldMaps") or [],
        "fieldMapCount": len(row.get("fieldMaps") or []),
    }


def one_map_augmentation_rows(rows: list[dict]) -> list[dict]:
    output = []
    for index, row in enumerate(rows):
        current_maps = set(row.get("fieldMaps") or [])
        for previous in rows[:index]:
            previous_maps = set(previous.get("fieldMaps") or [])
            added = sorted(current_maps - previous_maps)
            removed = sorted(previous_maps - current_maps)
            if len(added) == 1 and not removed:
                output.append({
                    "selector": selector_key(row),
                    "rootHex": row.get("selectedPointerHex"),
                    "baseSelector": selector_key(previous),
                    "baseRootHex": previous.get("selectedPointerHex"),
                    "addedMap": added[0],
                    "currentMapCount": len(current_maps),
                    "baseMapCount": len(previous_maps),
                })
    return output


def exact_pair_union_rows(rows: list[dict]) -> list[dict]:
    output = []
    for index, row in enumerate(rows):
        current_maps = set(row.get("fieldMaps") or [])
        for left, right in itertools.combinations(rows[:index], 2):
            union = set(left.get("fieldMaps") or []) | set(right.get("fieldMaps") or [])
            if union == current_maps:
                output.append({
                    "selector": selector_key(row),
                    "rootHex": row.get("selectedPointerHex"),
                    "leftSelector": selector_key(left),
                    "rightSelector": selector_key(right),
                    "leftRootHex": left.get("selectedPointerHex"),
                    "rightRootHex": right.get("selectedPointerHex"),
                    "mapCount": len(current_maps),
                })
    return output


def covering_pair_rows(rows: list[dict], selector: str) -> list[dict]:
    current_row = find_selector(rows, selector)
    current_order = (int(current_row.get("group") or 0), int(current_row.get("slot") or 0))
    current_maps = set(current_row.get("fieldMaps") or [])
    previous_rows = [
        row
        for row in rows
        if (int(row.get("group") or 0), int(row.get("slot") or 0)) < current_order
    ]
    output = []
    for left, right in itertools.combinations(previous_rows, 2):
        union = set(left.get("fieldMaps") or []) | set(right.get("fieldMaps") or [])
        if current_maps <= union:
            output.append({
                "leftSelector": selector_key(left),
                "rightSelector": selector_key(right),
                "extraVsCurrent": sorted(union - current_maps),
                "missingVsCurrent": sorted(current_maps - union),
                "unionEqualsCurrent": union == current_maps,
                "unionMapCount": len(union),
            })
    return sorted(output, key=lambda row: (len(row["extraVsCurrent"]), row["leftSelector"], row["rightSelector"]))


def selectors_containing(rows: list[dict], *maps: str) -> list[dict]:
    wanted = set(maps)
    return [
        compact_selector(row)
        for row in rows
        if wanted <= set(row.get("fieldMaps") or [])
    ]


def current_summary(rows: list[dict]) -> dict:
    source_row = find_selector(rows, SOURCE_SELECTOR)
    predecessor_row = find_selector(rows, PREDECESSOR_SELECTOR)
    current_row = find_selector(rows, CURRENT_SELECTOR)
    source_maps = set(source_row.get("fieldMaps") or [])
    predecessor_maps = set(predecessor_row.get("fieldMaps") or [])
    current_maps = set(current_row.get("fieldMaps") or [])
    covering_pairs = covering_pair_rows(rows, CURRENT_SELECTOR)
    one_map_rows = [
        row for row in one_map_augmentation_rows(rows)
        if row["selector"] == CURRENT_SELECTOR
    ]
    exact_pair_rows = [
        row for row in exact_pair_union_rows(rows)
        if row["selector"] == CURRENT_SELECTOR
    ]
    return {
        "sourceSelector": compact_selector(source_row),
        "predecessorSelector": compact_selector(predecessor_row),
        "currentSelector": compact_selector(current_row),
        "selectorsContainingSourceAndTarget": selectors_containing(rows, SOURCE, TARGET),
        "selectorsContainingSource": selectors_containing(rows, SOURCE),
        "selectorsContainingTarget": selectors_containing(rows, TARGET),
        "currentEqualsPredecessorPlusSource": current_maps == (predecessor_maps | {SOURCE}),
        "currentEqualsSourcePredecessorUnion": current_maps == (source_maps | predecessor_maps),
        "sourcePredecessorUnionCoversCurrent": current_maps <= (source_maps | predecessor_maps),
        "sourcePredecessorUnionExtraMaps": sorted((source_maps | predecessor_maps) - current_maps),
        "sourcePredecessorUnionMissingMaps": sorted(current_maps - (source_maps | predecessor_maps)),
        "currentOneMapAugmentations": one_map_rows,
        "currentExactPairUnions": exact_pair_rows,
        "currentCoveringPairUnions": covering_pairs,
    }


def build_summary(selectors: list[dict]) -> dict:
    rows = ordered_field_rows(selectors)
    one_map_rows = one_map_augmentation_rows(rows)
    exact_pair_rows = exact_pair_union_rows(rows)
    current = current_summary(rows)
    route_pair_selectors = current["selectorsContainingSourceAndTarget"]
    route_pair_only_current = [row["selector"] for row in route_pair_selectors] == [CURRENT_SELECTOR]
    current_covering_pairs = current["currentCoveringPairUnions"]
    conclusion = (
        f"Across {len(rows)} selectors with field maps, only selector {CURRENT_SELECTOR} contains both "
        f"{SOURCE} and {TARGET}. Its map set equals predecessor {PREDECESSOR_SELECTOR} plus {SOURCE}, "
        f"while the source+predecessor union over-covers it by "
        f"{', '.join(current['sourcePredecessorUnionExtraMaps']) or 'no maps'}. "
        "This strengthens the selector-recomposition diagnosis, but it still does not prove runtime execution "
        "order, selected-pointer activation, or a strict map1_01a source hotspot."
    )
    return {
        "source": SOURCE,
        "target": TARGET,
        "selectorWithFieldMapCount": len(rows),
        "oneMapAugmentationCount": len(one_map_rows),
        "exactPairUnionCount": len(exact_pair_rows),
        "exactPairUnionSelectorCount": len({row["selector"] for row in exact_pair_rows}),
        "routePairSelectorCount": len(route_pair_selectors),
        "routePairOnlyCurrentSelector": route_pair_only_current,
        "current": current,
        "oneMapAugmentations": one_map_rows,
        "exactPairUnions": exact_pair_rows,
        "promotionStatus": "blocked",
        "executionOrderProven": False,
        "strictHotspotFound": False,
        "conclusion": conclusion,
        "remainingProofs": [
            "capture a real selector 2:0 gameplay savedata or selected-pointer runtime trace",
            "prove VM/control-flow execution into selector 2:0 on the confirmed route",
            "find a strict map1_01a source coordinate or hotspot for map2_02d",
        ],
        "currentTopCoveringPairs": current_covering_pairs[:10],
    }


def markdown(summary: dict) -> str:
    current = summary["current"]
    lines = [
        "# Save Selector Recomposition Lattice",
        "",
        f"- route: `{summary['source']} -> {summary['target']}`",
        f"- selectors with field maps: {summary['selectorWithFieldMapCount']}",
        f"- one-map augmentation rows: {summary['oneMapAugmentationCount']}",
        f"- exact previous pair unions: {summary['exactPairUnionCount']} across {summary['exactPairUnionSelectorCount']} selector(s)",
        f"- route-pair selector count: {summary['routePairSelectorCount']}",
        f"- route pair only current selector: {summary['routePairOnlyCurrentSelector']}",
        f"- current equals predecessor plus source: {current['currentEqualsPredecessorPlusSource']}",
        f"- current exact pair union count: {len(current['currentExactPairUnions'])}",
        f"- current source+predecessor extra maps: {', '.join(current['sourcePredecessorUnionExtraMaps']) or 'none'}",
        f"- promotion status: `{summary['promotionStatus']}`",
        "",
        summary["conclusion"],
        "",
        "## Route Pair Selectors",
        "",
        "| selector | root | map count | maps |",
        "| --- | --- | ---: | --- |",
    ]
    for row in current["selectorsContainingSourceAndTarget"]:
        lines.append(
            f"| `{row['selector']}` | `{row['rootHex']}` | {row['fieldMapCount']} | {', '.join(row['fieldMaps'])} |"
        )
    lines.extend([
        "",
        "## Current Covering Pair Unions",
        "",
        "| left | right | equals current | extra | missing |",
        "| --- | --- | --- | --- | --- |",
    ])
    for row in summary["currentTopCoveringPairs"]:
        lines.append(
            f"| `{row['leftSelector']}` | `{row['rightSelector']}` | {row['unionEqualsCurrent']} | "
            f"{', '.join(row['extraVsCurrent']) or '-'} | {', '.join(row['missingVsCurrent']) or '-'} |"
        )
    lines.extend([
        "",
        "## One-Map Augmentations",
        "",
        "| selector | base | added map | current maps | base maps |",
        "| --- | --- | --- | ---: | ---: |",
    ])
    for row in summary["oneMapAugmentations"]:
        lines.append(
            f"| `{row['selector']}` | `{row['baseSelector']}` | `{row['addedMap']}` | "
            f"{row['currentMapCount']} | {row['baseMapCount']} |"
        )
    lines.extend([
        "",
        "## Exact Pair Unions",
        "",
        "| selector | left | right | map count |",
        "| --- | --- | --- | ---: |",
    ])
    for row in summary["exactPairUnions"]:
        lines.append(
            f"| `{row['selector']}` | `{row['leftSelector']}` | `{row['rightSelector']}` | {row['mapCount']} |"
        )
    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:
    current = summary["current"]

    def route_pair_rows() -> str:
        return "\n".join(
            "<tr>"
            f"<td><code>{html.escape(row['selector'])}</code></td>"
            f"<td><code>{html.escape(row['rootHex'])}</code></td>"
            f"<td>{row['fieldMapCount']}</td>"
            f"<td>{html.escape(', '.join(row['fieldMaps']))}</td>"
            "</tr>"
            for row in current["selectorsContainingSourceAndTarget"]
        )

    def covering_rows() -> str:
        return "\n".join(
            "<tr>"
            f"<td><code>{html.escape(row['leftSelector'])}</code></td>"
            f"<td><code>{html.escape(row['rightSelector'])}</code></td>"
            f"<td>{row['unionEqualsCurrent']}</td>"
            f"<td>{html.escape(', '.join(row['extraVsCurrent']) or '-')}</td>"
            f"<td>{html.escape(', '.join(row['missingVsCurrent']) or '-')}</td>"
            "</tr>"
            for row in summary["currentTopCoveringPairs"]
        )

    def one_map_rows() -> str:
        return "\n".join(
            "<tr>"
            f"<td><code>{html.escape(row['selector'])}</code></td>"
            f"<td><code>{html.escape(row['baseSelector'])}</code></td>"
            f"<td><code>{html.escape(row['addedMap'])}</code></td>"
            f"<td>{row['currentMapCount']}</td>"
            f"<td>{row['baseMapCount']}</td>"
            "</tr>"
            for row in summary["oneMapAugmentations"]
        )

    def exact_rows() -> str:
        return "\n".join(
            "<tr>"
            f"<td><code>{html.escape(row['selector'])}</code></td>"
            f"<td><code>{html.escape(row['leftSelector'])}</code></td>"
            f"<td><code>{html.escape(row['rightSelector'])}</code></td>"
            f"<td>{row['mapCount']}</td>"
            "</tr>"
            for row in summary["exactPairUnions"]
        )

    proof_items = "".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>Save Selector Recomposition Lattice</title>",
        "  <style>body{font-family:system-ui,sans-serif;background:#111;color:#eee;margin:24px}table{border-collapse:collapse;width:100%;max-width:1280px}td,th{border:1px solid #333;padding:6px 8px;text-align:left;vertical-align:top}th{background:#1f1f1f}code{color:#9bd4ff}</style>",
        "</head>",
        "<body>",
        "  <h1>Save Selector Recomposition Lattice</h1>",
        f"  <p>route <code>{summary['source']} -&gt; {summary['target']}</code>; selectors with field maps {summary['selectorWithFieldMapCount']}; one-map augmentations {summary['oneMapAugmentationCount']}; exact previous pair unions {summary['exactPairUnionCount']}; route pair selector count {summary['routePairSelectorCount']}; route pair only current selector {summary['routePairOnlyCurrentSelector']}; promotion <code>{summary['promotionStatus']}</code>.</p>",
        f"  <p>current equals predecessor plus source: {current['currentEqualsPredecessorPlusSource']}; current exact pair union count: {len(current['currentExactPairUnions'])}; current source+predecessor extra maps: {html.escape(', '.join(current['sourcePredecessorUnionExtraMaps']) or 'none')}.</p>",
        f"  <p>{html.escape(summary['conclusion'])}</p>",
        "  <h2>Route Pair Selectors</h2>",
        "  <table><thead><tr><th>selector</th><th>root</th><th>map count</th><th>maps</th></tr></thead><tbody>",
        route_pair_rows(),
        "  </tbody></table>",
        "  <h2>Current Covering Pair Unions</h2>",
        "  <table><thead><tr><th>left</th><th>right</th><th>equals current</th><th>extra</th><th>missing</th></tr></thead><tbody>",
        covering_rows(),
        "  </tbody></table>",
        "  <h2>One-Map Augmentations</h2>",
        "  <table><thead><tr><th>selector</th><th>base</th><th>added map</th><th>current maps</th><th>base maps</th></tr></thead><tbody>",
        one_map_rows(),
        "  </tbody></table>",
        "  <h2>Exact Pair Unions</h2>",
        "  <table><thead><tr><th>selector</th><th>left</th><th>right</th><th>map count</th></tr></thead><tbody>",
        exact_rows(),
        "  </tbody></table>",
        "  <h2>Remaining Proofs</h2>",
        f"  <ul>{proof_items}</ul>",
        "</body>",
        "</html>",
        "",
    ])


def write_outputs(summary: dict, out_dir: Path = OUT) -> None:
    out_dir.mkdir(parents=True, exist_ok=True)
    (out_dir / "save_selector_recomposition_lattice.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 / "save_scene_selectors.json", []))
    write_outputs(summary, args.out_dir)
    print(f"wrote save selector recomposition lattice -> {args.out_dir / 'save_selector_recomposition_lattice.json'}")


if __name__ == "__main__":
    main()
