#!/usr/bin/env python3
"""Summarize current selector scene-record adjacency around the route blocker."""
from __future__ import annotations

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


ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "out"
SOURCE = "map1_01a"
TARGET = "map2_02d"
GEOMETRY_EXIT_SAMPLES = [
    {"side": "top", "x": 18, "y": 0},
    {"side": "bottom", "x": 16, "y": 47},
    {"side": "left", "x": 3, "y": 14},
    {"side": "right", "x": 34, "y": 19},
]
FIELD_MAP_RE = re.compile(r"^map\d+_\d+[a-z]\.cns$")


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


def cns_kind(name: str) -> str:
    if FIELD_MAP_RE.match(name):
        return "field-map"
    if name.startswith("map_"):
        return "tileset"
    if name.startswith("cara_"):
        return "character"
    if name.startswith("face_"):
        return "face"
    return "resource"


def geometry_word_hits(words: list[dict]) -> list[dict]:
    hits = []
    for word in words:
        lo = word.get("u16Lo")
        hi = word.get("u16Hi")
        if not isinstance(lo, int) or not isinstance(hi, int):
            continue
        for sample in GEOMETRY_EXIT_SAMPLES:
            x = sample["x"]
            y = sample["y"]
            if (lo, hi) == (x, y):
                hits.append({**sample, "vaHex": word.get("vaHex"), "valueHex": word.get("valueHex"), "encoding": "x,y"})
            elif (lo, hi) == (y, x):
                hits.append({**sample, "vaHex": word.get("vaHex"), "valueHex": word.get("valueHex"), "encoding": "y,x"})
    return hits


def summarize_segment(words: list[dict], start_index: int, end_index: int) -> dict:
    segment = words[start_index:end_index]
    start = segment[0] if segment else {}
    cns_entries = [
        {
            "vaHex": word.get("vaHex"),
            "name": word.get("cns"),
            "kind": cns_kind(word.get("cns") or ""),
            "fieldMapRecordStart": word.get("fieldMapRecordStart") is True,
        }
        for word in segment
        if word.get("cns")
    ]
    pointer_entries = [
        {
            "vaHex": word.get("vaHex"),
            "valueHex": word.get("valueHex"),
            "cns": word.get("cns"),
            "fieldMapRecordStart": word.get("fieldMapRecordStart") is True,
        }
        for word in segment
        if word.get("pointer") or word.get("cns")
    ]
    command_values = [
        {
            "vaHex": word.get("vaHex"),
            "valueHex": word.get("valueHex"),
            "u16Lo": word.get("u16Lo"),
            "u16Hi": word.get("u16Hi"),
        }
        for word in segment
        if not word.get("pointer") and not word.get("cns")
    ]
    return {
        "map": (start.get("cns") or "").removesuffix(".cns"),
        "recordVaHex": start.get("vaHex"),
        "sceneIdHex": start.get("sceneIdHex"),
        "wordStartIndex": start.get("index"),
        "wordEndIndexExclusive": segment[-1].get("index", 0) + 1 if segment else start_index,
        "dwordCount": len(segment),
        "cnsEntries": cns_entries,
        "cnsKinds": sorted({entry["kind"] for entry in cns_entries}),
        "fieldMapRecordCount": sum(1 for entry in cns_entries if entry["fieldMapRecordStart"]),
        "resourceCns": [entry["name"] for entry in cns_entries if not entry["fieldMapRecordStart"]],
        "pointerEntries": pointer_entries,
        "commandValueSample": command_values[:16],
        "geometryExitWordHits": geometry_word_hits(segment),
    }


def summarize_leaf_stream(row: dict) -> dict:
    words = sorted(row.get("words") or [], key=lambda item: item.get("index", 0))
    field_starts = [index for index, word in enumerate(words) if word.get("fieldMapRecordStart")]
    segments = []
    for offset, start in enumerate(field_starts):
        end = field_starts[offset + 1] if offset + 1 < len(field_starts) else len(words)
        segments.append(summarize_segment(words, start, end))
    by_map = {segment.get("map"): segment for segment in segments}
    source_index = next((index for index, segment in enumerate(segments) if segment.get("map") == SOURCE), None)
    target_index = next((index for index, segment in enumerate(segments) if segment.get("map") == TARGET), None)
    source_segment = by_map.get(SOURCE) or {}
    target_segment = by_map.get(TARGET) or {}
    source_to_target_adjacent = source_index is not None and target_index == source_index + 1
    between_words = []
    if source_index is not None and target_index is not None:
        source_end = source_segment.get("wordEndIndexExclusive")
        target_start = target_segment.get("wordStartIndex")
        if isinstance(source_end, int) and isinstance(target_start, int):
            between_words = [
                word for word in words
                if source_end <= word.get("index", -1) < target_start
            ]
    geometry_hits = geometry_word_hits(between_words) + source_segment.get("geometryExitWordHits", [])
    return {
        "leafPointerHex": row.get("leafPointerHex"),
        "selector": row.get("selector"),
        "source": row.get("source"),
        "target": row.get("target"),
        "sourceRecordVaHex": row.get("sourceRecordVaHex"),
        "targetRecordVaHex": row.get("targetRecordVaHex"),
        "sourceToTargetAdjacent": source_to_target_adjacent,
        "recordSequence": [segment.get("map") for segment in segments],
        "sourceSegment": source_segment,
        "targetSegment": target_segment,
        "betweenSourceAndTargetWordCount": len(between_words),
        "geometryExitWordHits": geometry_hits,
        "geometryExitWordHitCount": len(geometry_hits),
        "promotionStatus": "blocked",
        "classification": (
            "scene-record-adjacency-without-strict-hotspot"
            if source_to_target_adjacent else
            "scene-record-list-without-strict-hotspot"
        ),
    }


def build_summary(leaf_streams: list[dict]) -> dict:
    rows = [
        summarize_leaf_stream(row)
        for row in leaf_streams
        if row.get("source") == SOURCE and row.get("target") == TARGET
    ]
    adjacent_count = sum(1 for row in rows if row.get("sourceToTargetAdjacent"))
    geometry_hit_count = sum(row.get("geometryExitWordHitCount", 0) for row in rows)
    return {
        "scope": "current selector 2:0 scene-record sequence around map1_01a -> map2_02d",
        "source": SOURCE,
        "target": TARGET,
        "rowCount": len(rows),
        "adjacentSceneRecordCount": adjacent_count,
        "geometryExitWordHitCount": geometry_hit_count,
        "rows": rows,
        "promotionStatus": "blocked",
        "conclusion": (
            "map1_01a and map2_02d appear as adjacent field-map scene records in the current selector leaf streams, "
            "but the source record has no geometry-exit coordinate word matching the routeAssist samples and no strict "
            "hotspot row between the two field-map records. This remains selector scene-list adjacency, not a normal "
            "tile transition."
        ),
    }


def markdown(summary: dict) -> str:
    lines = [
        "# Save Selector Scene Record Sequence",
        "",
        f"- scope: {summary['scope']}",
        f"- source-target: `{summary['source']} -> {summary['target']}`",
        f"- leaf streams: {summary['rowCount']}",
        f"- adjacent scene records: {summary['adjacentSceneRecordCount']}",
        f"- geometry exit word hits: {summary['geometryExitWordHitCount']}",
        f"- promotion: `{summary['promotionStatus']}`",
        f"- conclusion: {summary['conclusion']}",
        "",
        "| leaf | sequence | source record | target record | source resources | target resources | coord hits | class |",
        "| --- | --- | --- | --- | --- | --- | ---: | --- |",
    ]
    for row in summary["rows"]:
        source = row.get("sourceSegment") or {}
        target = row.get("targetSegment") or {}
        source_resources = ", ".join(source.get("resourceCns") or [])
        target_resources = ", ".join(target.get("resourceCns") or [])
        lines.append(
            f"| `{row.get('leafPointerHex')}` | {' -> '.join(row.get('recordSequence') or [])} | "
            f"`{source.get('recordVaHex')}` `{source.get('sceneIdHex')}` | "
            f"`{target.get('recordVaHex')}` `{target.get('sceneIdHex')}` | "
            f"{source_resources or '-'} | {target_resources or '-'} | "
            f"{row.get('geometryExitWordHitCount')} | {row.get('classification')} |"
        )
    lines.append("")
    return "\n".join(lines)


def html_page(summary: dict) -> str:
    rows = []
    for row in summary["rows"]:
        source = row.get("sourceSegment") or {}
        target = row.get("targetSegment") or {}
        rows.append(
            "<tr>"
            f"<td><code>{html.escape(row.get('leafPointerHex') or '-')}</code></td>"
            f"<td>{html.escape(' -> '.join(row.get('recordSequence') or []))}</td>"
            f"<td><code>{html.escape(source.get('recordVaHex') or '-')}</code><br>{html.escape(source.get('sceneIdHex') or '-')}</td>"
            f"<td><code>{html.escape(target.get('recordVaHex') or '-')}</code><br>{html.escape(target.get('sceneIdHex') or '-')}</td>"
            f"<td>{html.escape(', '.join(source.get('resourceCns') or []) or '-')}</td>"
            f"<td>{html.escape(', '.join(target.get('resourceCns') or []) or '-')}</td>"
            f"<td>{row.get('geometryExitWordHitCount')}</td>"
            f"<td>{html.escape(row.get('classification') or '-')}</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 Scene Record Sequence</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; }",
        "    code { color: #f5d76e; }",
        "  </style>",
        "</head>",
        "<body>",
        "  <h1>Save Selector Scene Record Sequence</h1>",
        f"  <p>Scope: {html.escape(summary['scope'])}</p>",
        f"  <p>Adjacent scene records: <code>{summary['adjacentSceneRecordCount']}</code>. Geometry exit word hits: <code>{summary['geometryExitWordHitCount']}</code>. Promotion: <code>{html.escape(summary['promotionStatus'])}</code>.</p>",
        f"  <p>{html.escape(summary['conclusion'])}</p>",
        "  <table><thead><tr><th>leaf</th><th>sequence</th><th>source</th><th>target</th><th>source resources</th><th>target resources</th><th>coord hits</th><th>class</th></tr></thead>",
        f"  <tbody>{''.join(rows)}</tbody></table>",
        "</body>",
        "</html>",
        "",
    ])


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


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--leaf-streams", type=Path, default=OUT / "save_selector_leaf_streams.json")
    parser.add_argument("--out-dir", type=Path, default=OUT)
    args = parser.parse_args()
    summary = build_summary(load_json(args.leaf_streams, []))
    write_outputs(summary, args.out_dir)
    print(f"wrote save selector scene record sequence -> {args.out_dir / 'save_selector_scene_record_sequence.html'}")


if __name__ == "__main__":
    main()
