#!/usr/bin/env python3
"""Summarize broad coordinate candidates that are not strict transition records."""
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 manifest_index(scene_manifest: list[dict] | None) -> dict[tuple[str, int], dict]:
    index = {}
    for record in scene_manifest or []:
        map_name = record.get("map")
        record_va = record.get("recordVa")
        if isinstance(map_name, str) and isinstance(record_va, int):
            index[(map_name, record_va)] = record
    return index


def build_rows(
    coordinate_candidates: list[dict],
    event_transitions: list[dict],
    scene_links: dict,
    scene_manifest: list[dict] | None = None,
) -> list[dict]:
    strict_records = {
        (row.get("map"), row.get("recordVa"))
        for row in event_transitions
    }
    manifest_records = manifest_index(scene_manifest)
    rows = []
    for record in coordinate_candidates:
        key = (record.get("map"), record.get("recordVa"))
        if key in strict_records:
            continue
        candidates = record.get("candidates") or []
        usable = [
            candidate
            for candidate in candidates
            if candidate.get("pointCount", 0) >= 2
        ]
        if not usable:
            continue
        manifest = manifest_records.get(key)
        link_info = {} if manifest else scene_links.get(record["map"], {})
        record_type = "scene manifest payload" if manifest else "coordinate candidate"
        reason = (
            "scene manifest resource payload without strict event dispatch"
            if manifest
            else "coordinate candidate without strict event target"
        )
        first_point = None
        for candidate in usable:
            points = candidate.get("points") or []
            if points:
                first_point = {"x": points[0]["x"], "y": points[0]["y"]}
                break
        tilesets = link_info.get("tilesets") or (manifest.get("tilesets", []) if manifest else [])
        rows.append({
            "map": record["map"],
            "sceneIdHex": record.get("sceneIdHex"),
            "recordVa": record.get("recordVa"),
            "recordVaHex": f"0x{record['recordVa']:08x}" if isinstance(record.get("recordVa"), int) else "",
            "candidateCount": len(usable),
            "maxPointCount": max(candidate.get("pointCount", 0) for candidate in usable),
            "fieldMaps": link_info.get("fieldMaps") or [],
            "tilesets": tilesets,
            "recordType": record_type,
            "strictTransitionCandidate": not manifest,
            "firstPoint": first_point,
            "firstCandidates": [
                {
                    "fieldIndex": candidate.get("fieldIndex"),
                    "pointCount": candidate.get("pointCount"),
                    "points": candidate.get("points", [])[:6],
                }
                for candidate in usable[:4]
            ],
            "reason": reason,
        })
    return sorted(
        rows,
        key=lambda row: (
            len(row["fieldMaps"]) == 0,
            -row["maxPointCount"],
            row["map"],
            row["recordVa"] or 0,
        ),
    )


def point_text(points: list[dict]) -> str:
    return ", ".join(f"{point['x']},{point['y']}" for point in points) if points else "-"


def markdown(rows: list[dict], web_prefix: str = "../web") -> str:
    lines = [
        "# Transition Extraction Gaps",
        "",
        "Broad coordinate candidates that are not strict event transition records yet.",
        "",
        f"Rows: {len(rows)}.",
        "",
        "| map | type | candidates | max points | field maps | tilesets | scene | record | sample | reason | debug open |",
        "| --- | --- | ---: | ---: | --- | --- | --- | --- | --- | --- | --- |",
    ]
    for row in rows:
        params = {"map": row["map"], "events": "1", "overview": "1"}
        if row.get("firstPoint"):
            point = row["firstPoint"]
            params["startTile"] = f"{point['x']},{point['y']}"
            params["focusTile"] = f"{point['x']},{point['y']}"
        href = f"{web_prefix}/game.html?{urlencode(params)}"
        samples = "; ".join(
            f"i{candidate['fieldIndex']} n={candidate['pointCount']} {point_text(candidate['points'])}"
            for candidate in row["firstCandidates"]
        )
        lines.append(
            f"| {row['map']} | {row.get('recordType') or '-'} | "
            f"{row['candidateCount']} | {row['maxPointCount']} | "
            f"{', '.join(row['fieldMaps']) or '-'} | {', '.join(row['tilesets']) or '-'} | "
            f"{row.get('sceneIdHex') or '-'} | `{row.get('recordVaHex') or '-'}` | {samples} | "
            f"{row.get('reason') or '-'} | [debug]({href}) |"
        )
    if not rows:
        lines.append("| - | - | - | - | - | - | - | - | - | - | - |")
    lines.append("")
    return "\n".join(lines)


def html_page(rows: list[dict], web_prefix: str = "../web") -> str:
    body = []
    for row in rows:
        params = {"map": row["map"], "events": "1", "overview": "1"}
        if row.get("firstPoint"):
            point = row["firstPoint"]
            params["startTile"] = f"{point['x']},{point['y']}"
            params["focusTile"] = f"{point['x']},{point['y']}"
        href = f"{web_prefix}/game.html?{urlencode(params)}"
        samples = "; ".join(
            f"i{candidate['fieldIndex']} n={candidate['pointCount']} {point_text(candidate['points'])}"
            for candidate in row["firstCandidates"]
        )
        body.append(
            "<tr>"
            f"<td>{html.escape(row['map'])}</td>"
            f"<td>{html.escape(row.get('recordType') or '-')}</td>"
            f"<td>{row['candidateCount']}</td>"
            f"<td>{row['maxPointCount']}</td>"
            f"<td>{html.escape(', '.join(row['fieldMaps']) or '-')}</td>"
            f"<td>{html.escape(', '.join(row['tilesets']) or '-')}</td>"
            f"<td>{html.escape(row.get('sceneIdHex') or '-')}</td>"
            f"<td><code>{html.escape(row.get('recordVaHex') or '-')}</code></td>"
            f"<td>{html.escape(samples)}</td>"
            f"<td>{html.escape(row.get('reason') or '-')}</td>"
            f'<td><a href="{html.escape(href)}">debug</a></td>'
            "</tr>"
        )
    return "\n".join([
        "<!doctype html>",
        '<meta charset="utf-8">',
        "<title>Transition Extraction Gaps</title>",
        "<style>body{font-family:system-ui,sans-serif;background:#111;color:#ddd;margin:24px}a{color:#8fd3ff}table{border-collapse:collapse;width:100%}td,th{border:1px solid #333;padding:6px 8px;text-align:left;vertical-align:top}th{background:#1f1f1f}</style>",
        "<h1>Transition Extraction Gaps</h1>",
        "<p>Broad coordinate candidates that are not strict event transition records yet. Scene manifest payload rows are kept as blockers, not auto-confirmable transitions.</p>",
        f"<p>Rows: {len(rows)}.</p>",
        "<table><thead><tr><th>map</th><th>type</th><th>candidates</th><th>max points</th><th>field maps</th><th>tilesets</th><th>scene</th><th>record</th><th>sample</th><th>reason</th><th>debug open</th></tr></thead><tbody>",
        "\n".join(body) if body else '<tr><td colspan="11">No extraction gaps.</td></tr>',
        "</tbody></table>",
    ])


def write_outputs(rows: list[dict], out_dir: Path = OUT, html_out: Path | None = None) -> None:
    out_dir.mkdir(parents=True, exist_ok=True)
    (out_dir / "transition_extraction_gaps.json").write_text(
        json.dumps(rows, ensure_ascii=False, separators=(",", ":")) + "\n",
        encoding="utf-8",
    )
    if html_out is not None:
        html_out.parent.mkdir(parents=True, exist_ok=True)
        html_out.write_text(html_page(rows), encoding="utf-8")


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--coordinates", type=Path, default=OUT / "scene_coordinate_candidates.json")
    parser.add_argument("--transitions", type=Path, default=OUT / "event_transitions.json")
    parser.add_argument("--scene-links", type=Path, default=OUT / "scene_links.json")
    parser.add_argument("--scene-manifest", type=Path, default=OUT / "scene_manifest.json")
    parser.add_argument("--out-dir", type=Path, default=OUT)
    parser.add_argument("--html-out", type=Path, default=None, help="Optional HTML report output path.")
    args = parser.parse_args()

    rows = build_rows(
        json.loads(args.coordinates.read_text(encoding="utf-8")),
        json.loads(args.transitions.read_text(encoding="utf-8")),
        json.loads(args.scene_links.read_text(encoding="utf-8")),
        json.loads(args.scene_manifest.read_text(encoding="utf-8")) if args.scene_manifest.exists() else None,
    )
    write_outputs(rows, args.out_dir, args.html_out)
    print(f"wrote {len(rows)} transition extraction gaps -> {args.out_dir / 'transition_extraction_gaps.json'}")


if __name__ == "__main__":
    main()
