#!/usr/bin/env python3
"""Classify route-overlap secondaryBranchState fill roots by selector order."""
from __future__ import annotations

import argparse
import html
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 selector_key(row: dict) -> str:
    return f"{row.get('group')}:{row.get('slot')}"


def selector_sort_key(row: dict) -> tuple[int, int, int]:
    return (
        int(row.get("group") or 0),
        int(row.get("slot") or 0),
        int(row.get("_inputIndex") or 0),
    )


def ordered_field_selectors(selectors: list[dict]) -> list[dict]:
    rows = []
    for index, row in enumerate(selectors):
        if row.get("fieldMaps"):
            rows.append({**row, "_inputIndex": index})
    return sorted(rows, key=selector_sort_key)


def selector_index(rows: list[dict]) -> dict[str, int]:
    return {selector_key(row): index for index, row in enumerate(rows)}


def selector_lookup(rows: list[dict]) -> dict[str, dict]:
    return {selector_key(row): row for row in rows}


def root_lookup(rows: list[dict]) -> dict[str, dict]:
    return {row.get("selectedPointerHex"): row for row in rows if row.get("selectedPointerHex")}


def fill_count(row: dict | None) -> int:
    return len((row or {}).get("fills") or [])


def after_frontier_count(row: dict | None) -> int:
    return sum(1 for fill in (row or {}).get("fills") or [] if fill.get("afterCurrentFrontierReader") is True)


def before_frontier_count(row: dict | None) -> int:
    return sum(1 for fill in (row or {}).get("fills") or [] if fill.get("beforeCurrentFrontierReader") is True)


def row_role(selector: str, index: int | None, current_index: int) -> str:
    if selector == SOURCE_SELECTOR:
        return "source-side previous selector"
    if selector == PREDECESSOR_SELECTOR:
        return "only pre-current route-overlap fill root"
    if selector == CURRENT_SELECTOR:
        return "current selector; fill is after frontier"
    if index is not None and index > current_index:
        return "post-current route-overlap fill root"
    if index is not None and index < current_index:
        return "other pre-current route-overlap fill root"
    return "unmapped route-overlap fill root"


def build_summary(
    selectors: list[dict],
    secondary_fill_roots: dict | None = None,
    predecessor_persistence_gap: dict | None = None,
) -> dict:
    secondary_fill_roots = secondary_fill_roots or {}
    predecessor_persistence_gap = predecessor_persistence_gap or {}
    ordered = ordered_field_selectors(selectors)
    by_selector = selector_lookup(ordered)
    by_root = root_lookup(ordered)
    indexes = selector_index(ordered)
    source_selector = predecessor_persistence_gap.get("sourceRoutePreviousSelector") or SOURCE_SELECTOR
    predecessor_selector = predecessor_persistence_gap.get("predecessorSelector") or PREDECESSOR_SELECTOR
    current_selector = predecessor_persistence_gap.get("currentSelector") or CURRENT_SELECTOR
    current_index = indexes[current_selector]
    fill_roots_by_root = {
        row.get("rootHex"): row
        for row in secondary_fill_roots.get("routeOverlapRoots") or []
        if row.get("rootHex")
    }
    source_row = by_selector.get(source_selector) or {}
    source_fill_root = fill_roots_by_root.get(source_row.get("selectedPointerHex"))
    rows = []
    if source_row:
        rows.append({
            "selector": source_selector,
            "selectorIndex": indexes.get(source_selector),
            "rootHex": source_row.get("selectedPointerHex"),
            "fieldMaps": source_row.get("fieldMaps") or [],
            "routeMapOverlap": [
                item for item in source_row.get("fieldMaps") or []
                if item in set(secondary_fill_roots.get("routeMaps") or [])
            ],
            "fillCount": fill_count(source_fill_root),
            "beforeCurrentFrontierFillCount": before_frontier_count(source_fill_root),
            "afterCurrentFrontierFillCount": after_frontier_count(source_fill_root),
            "fills": (source_fill_root or {}).get("fills") or [],
            "role": row_role(source_selector, indexes.get(source_selector), current_index),
            "promotionUse": "no secondary fill evidence in the source-side previous selector",
        })
    for fill_root in secondary_fill_roots.get("routeOverlapRoots") or []:
        root_hex = fill_root.get("rootHex")
        selector_row = by_root.get(root_hex) or {}
        selector = selector_key(selector_row) if selector_row else f"{fill_root.get('group')}:{fill_root.get('slot')}"
        if selector == source_selector:
            continue
        index = indexes.get(selector)
        role = row_role(selector, index, current_index)
        promotion_use = "blocked"
        if selector == predecessor_selector:
            promotion_use = "candidate only after runtime order and persistence are proven"
        elif selector == current_selector:
            promotion_use = "not an initializer; all valid fills are after the current frontier reader"
        elif index is not None and index > current_index:
            promotion_use = "post-current selector table order cannot initialize current selector 2:0"
        rows.append({
            "selector": selector,
            "selectorIndex": index,
            "rootHex": root_hex,
            "fieldMaps": fill_root.get("fieldMaps") or [],
            "routeMapOverlap": fill_root.get("routeMapOverlap") or [],
            "fillCount": fill_root.get("fillCount", fill_count(fill_root)),
            "beforeCurrentFrontierFillCount": before_frontier_count(fill_root),
            "afterCurrentFrontierFillCount": after_frontier_count(fill_root),
            "fills": fill_root.get("fills") or [],
            "role": role,
            "promotionUse": promotion_use,
        })
    pre_current_rows = [
        row for row in rows
        if row.get("selector") != source_selector
        and row.get("selectorIndex") is not None
        and row.get("selectorIndex") < current_index
        and row.get("fillCount", 0) > 0
    ]
    current_rows = [row for row in rows if row.get("selector") == current_selector]
    post_current_rows = [
        row for row in rows
        if row.get("selectorIndex") is not None
        and row.get("selectorIndex") > current_index
        and row.get("fillCount", 0) > 0
    ]
    source_side_previous_fill_count = fill_count(source_fill_root)
    current_after_count = sum(row.get("afterCurrentFrontierFillCount", 0) for row in current_rows)
    current_before_count = sum(row.get("beforeCurrentFrontierFillCount", 0) for row in current_rows)
    conclusion = (
        "Route-overlap secondaryBranchState fills do not close the selector-merge gap. "
        "The source-side previous selector 0:0 has no secondary fill evidence, the only pre-current "
        "route-overlap fill root is predecessor selector 1:0, the current 2:0 fill is after the frontier reader, "
        "and later route-overlap fill roots appear after selector 2:0 in table order."
    )
    return {
        "source": SOURCE,
        "target": TARGET,
        "sourceSelector": source_selector,
        "predecessorSelector": predecessor_selector,
        "currentSelector": current_selector,
        "currentSelectorIndex": current_index,
        "routeOrderProven": predecessor_persistence_gap.get("routeOrderProven"),
        "selectorMergeGapOpen": predecessor_persistence_gap.get("selectorMergeGapOpen"),
        "routeOverlapRootCount": secondary_fill_roots.get("routeOverlapRootCount", len(fill_roots_by_root)),
        "preCurrentRouteOverlapFillRootCount": len(pre_current_rows),
        "candidatePredecessorRootCount": sum(1 for row in pre_current_rows if row.get("selector") == predecessor_selector),
        "sourceSidePreviousSecondaryFillCount": source_side_previous_fill_count,
        "currentRootBeforeFrontierFillCount": current_before_count,
        "currentRootAfterFrontierFillCount": current_after_count,
        "postCurrentRouteOverlapRootCount": len(post_current_rows),
        "postCurrentRouteOverlapFillCount": sum(int(row.get("fillCount") or 0) for row in post_current_rows),
        "secondaryRouteOverlapPromotesRoute": False,
        "promotionStatus": "blocked",
        "rows": sorted(
            rows,
            key=lambda row: (
                row.get("selectorIndex") is None,
                row.get("selectorIndex") if row.get("selectorIndex") is not None else 9999,
                row.get("selector") or "",
            ),
        ),
        "remainingProofs": [
            "prove predecessor selector 1:0 executes before current selector 2:0 in the route path",
            "prove secondaryBranchState is not globally reset before the current frontier reader",
            "find a strict map1_01a source coordinate or hotspot",
        ],
        "conclusion": conclusion,
    }


def fill_brief(row: dict) -> str:
    fills = row.get("fills") or []
    if not fills:
        return "-"
    return ", ".join(f"`{fill.get('vaHex')}={fill.get('valueHex')}`" for fill in fills[:5])


def markdown(summary: dict) -> str:
    lines = [
        "# Save Selector Secondary Route-Overlap Candidates",
        "",
        f"- route: {summary['source']} -> {summary['target']}",
        f"- route-overlap secondary fill roots: {summary['routeOverlapRootCount']}",
        f"- pre-current route-overlap fill roots: {summary['preCurrentRouteOverlapFillRootCount']}",
        f"- candidate predecessor roots: {summary['candidatePredecessorRootCount']}",
        f"- source-side previous secondary fills: {summary['sourceSidePreviousSecondaryFillCount']}",
        f"- current root before-frontier fills: {summary['currentRootBeforeFrontierFillCount']}",
        f"- current root after-frontier fills: {summary['currentRootAfterFrontierFillCount']}",
        f"- post-current route-overlap roots: {summary['postCurrentRouteOverlapRootCount']}",
        f"- secondary route-overlap promotes route: {summary['secondaryRouteOverlapPromotesRoute']}",
        f"- promotion status: `{summary['promotionStatus']}`",
        "",
        summary["conclusion"],
        "",
        "| selector | index | root | fills | before frontier | after frontier | route overlap | role | promotion use | first fills |",
        "| --- | ---: | --- | ---: | ---: | ---: | --- | --- | --- | --- |",
    ]
    for row in summary["rows"]:
        lines.append(
            f"| `{row.get('selector')}` | {row.get('selectorIndex')} | `{row.get('rootHex')}` | "
            f"{row.get('fillCount')} | {row.get('beforeCurrentFrontierFillCount')} | "
            f"{row.get('afterCurrentFrontierFillCount')} | {', '.join(row.get('routeMapOverlap') or []) or '-'} | "
            f"{row.get('role')} | {row.get('promotionUse')} | {fill_brief(row)} |"
        )
    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:
    body_rows = "\n".join(
        "<tr>"
        f"<td><code>{html.escape(str(row.get('selector')))}</code></td>"
        f"<td>{html.escape(str(row.get('selectorIndex')))}</td>"
        f"<td><code>{html.escape(str(row.get('rootHex')))}</code></td>"
        f"<td>{row.get('fillCount')}</td>"
        f"<td>{row.get('beforeCurrentFrontierFillCount')}</td>"
        f"<td>{row.get('afterCurrentFrontierFillCount')}</td>"
        f"<td>{html.escape(', '.join(row.get('routeMapOverlap') or []) or '-')}</td>"
        f"<td>{html.escape(str(row.get('role')))}</td>"
        f"<td>{html.escape(str(row.get('promotionUse')))}</td>"
        f"<td>{', '.join('<code>' + html.escape(str(fill.get('vaHex'))) + '=' + html.escape(str(fill.get('valueHex'))) + '</code>' for fill in (row.get('fills') or [])[:5]) or '-'}</td>"
        "</tr>"
        for row in summary["rows"]
    )
    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>Save Selector Secondary Route-Overlap Candidates</title>",
        "  <style>",
        "    :root { color-scheme: dark; font-family: system-ui, sans-serif; background: #101010; color: #eee; }",
        "    body { margin: 0; padding: 24px; }",
        "    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 { background: #181818; color: #ddd; }",
        "    code { color: #d7f0ff; }",
        "  </style>",
        "</head>",
        "<body>",
        "  <h1>Save Selector Secondary Route-Overlap Candidates</h1>",
        f"  <p>route {html.escape(summary['source'])} -&gt; {html.escape(summary['target'])}; route-overlap roots {summary['routeOverlapRootCount']}; pre-current roots {summary['preCurrentRouteOverlapFillRootCount']}; source-side fills {summary['sourceSidePreviousSecondaryFillCount']}; current after-frontier fills {summary['currentRootAfterFrontierFillCount']}; post-current roots {summary['postCurrentRouteOverlapRootCount']}; secondary route-overlap promotes route {summary['secondaryRouteOverlapPromotesRoute']}.</p>",
        "  <ul>",
        f"    <li>pre-current route-overlap fill roots: {summary['preCurrentRouteOverlapFillRootCount']}</li>",
        f"    <li>source-side previous secondary fills: {summary['sourceSidePreviousSecondaryFillCount']}</li>",
        f"    <li>current root after-frontier fills: {summary['currentRootAfterFrontierFillCount']}</li>",
        f"    <li>post-current route-overlap roots: {summary['postCurrentRouteOverlapRootCount']}</li>",
        "  </ul>",
        f"  <p>{html.escape(summary['conclusion'])}</p>",
        "  <table><thead><tr><th>selector</th><th>index</th><th>root</th><th>fills</th><th>before frontier</th><th>after frontier</th><th>route overlap</th><th>role</th><th>promotion use</th><th>first fills</th></tr></thead><tbody>",
        body_rows,
        "  </tbody></table>",
        f"  <h2>Remaining Proofs</h2><ul>{proofs}</ul>",
        "</body>",
        "</html>",
    ])


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


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


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--selectors", type=Path, default=OUT / "save_scene_selectors.json")
    parser.add_argument("--secondary-fill-roots", type=Path, default=OUT / "save_selector_secondary_fill_roots.json")
    parser.add_argument("--predecessor-persistence-gap", type=Path, default=OUT / "save_selector_predecessor_persistence_gap.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")),
        load_json(args.secondary_fill_roots, {}),
        load_json(args.predecessor_persistence_gap, {}),
    )
    write_outputs(summary, args.out_dir)
    print(f"wrote secondary route-overlap candidates -> {args.out_dir / 'save_selector_secondary_route_overlap_candidates.html'}")


if __name__ == "__main__":
    main()
