#!/usr/bin/env python3
"""Match sampled runtime 37x21 layer buffers against static field maps."""
from __future__ import annotations

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

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

sys.path.insert(0, str(ROOT / "tools"))
from export_map_js import parse_map  # noqa: E402


VIEW_W = 37
VIEW_H = 21
VIEW_CELLS = VIEW_W * VIEW_H


def unique_word_states(report: dict[str, Any]) -> list[dict[str, Any]]:
    states: list[dict[str, Any]] = []
    seen: set[str] = set()
    for sample in report.get("samples", []):
        if sample.get("width") != VIEW_W or sample.get("height") != VIEW_H:
            continue
        if not sample.get("layer0Words") or not sample.get("layer1Words"):
            continue
        key = f"{sample.get('layer0Sha1')}:{sample.get('layer1Sha1')}"
        if key in seen:
            continue
        seen.add(key)
        states.append(sample)
    return states


def load_maps() -> list[tuple[str, dict[str, Any]]]:
    rows: list[tuple[str, dict[str, Any]]] = []
    for src in sorted(EXTRACT_FLD.glob("map[0-9]_*[a-z].cns")):
        try:
            info = parse_map(src)
        except Exception:
            continue
        if info["width"] >= VIEW_W and info["height"] >= VIEW_H:
            rows.append((src.stem, info))
    return rows


def compare_window(
    live0: list[int],
    live1: list[int],
    map_info: dict[str, Any],
    x: int,
    y: int,
) -> tuple[int, int]:
    layer0 = map_info["layers"][0]
    layer1 = map_info["layers"][1]
    score0 = 0
    score1 = 0
    width = map_info["width"]
    for yy in range(VIEW_H):
        map_offset = (y + yy) * width + x
        live_offset = yy * VIEW_W
        row0 = layer0[map_offset : map_offset + VIEW_W]
        row1 = layer1[map_offset : map_offset + VIEW_W]
        score0 += sum(1 for a, b in zip(live0[live_offset : live_offset + VIEW_W], row0) if a == b)
        score1 += sum(1 for a, b in zip(live1[live_offset : live_offset + VIEW_W], row1) if a == b)
    return score0, score1


def match_state(sample: dict[str, Any], maps: list[tuple[str, dict[str, Any]]]) -> dict[str, Any]:
    live0 = sample["layer0Words"]
    live1 = sample["layer1Words"]
    exact: list[dict[str, Any]] = []
    best: list[dict[str, Any]] = []
    for name, info in maps:
        best_row = {"score": -1, "x": None, "y": None, "layer0Score": 0, "layer1Score": 0}
        for y in range(info["height"] - VIEW_H + 1):
            for x in range(info["width"] - VIEW_W + 1):
                score0, score1 = compare_window(live0, live1, info, x, y)
                total = score0 + score1
                if total > best_row["score"]:
                    best_row = {
                        "score": total,
                        "x": x,
                        "y": y,
                        "layer0Score": score0,
                        "layer1Score": score1,
                    }
                if score0 == VIEW_CELLS and score1 == VIEW_CELLS:
                    exact.append({"map": name, "x": x, "y": y})
        best.append(
            {
                "map": name,
                "width": info["width"],
                "height": info["height"],
                **best_row,
            }
        )
    best.sort(key=lambda row: (row["score"], row["layer0Score"], row["layer1Score"]), reverse=True)
    return {
        "timeMs": sample.get("timeMs"),
        "layer0Sha1": sample.get("layer0Sha1"),
        "layer1Sha1": sample.get("layer1Sha1"),
        "layer1UniqueValues": sorted(set(live1)),
        "exactMatches": exact,
        "bestMatches": best[:20],
        "classification": "static-field-viewport-exact" if exact else "not-a-static-field-viewport-exact",
    }


def build_review(input_path: Path) -> dict[str, Any]:
    report = json.loads(input_path.read_text(encoding="utf-8"))
    states = unique_word_states(report)
    if not states and report.get("wordArraysStripped"):
        existing = OUT / "runtime_map_layer0_viewport_match_review.json"
        if existing.exists():
            preserved = json.loads(existing.read_text(encoding="utf-8"))
            preserved["preservedBecauseInputWordArraysWereStripped"] = True
            preserved["strippedInput"] = str(input_path)
            return preserved
    maps = load_maps()
    matches = [match_state(state, maps) for state in states]
    exact_states = [row for row in matches if row["exactMatches"]]
    return {
        "kind": "hwanse-runtime-map-layer0-viewport-match-review",
        "source": str(input_path),
        "runtimeStatus": report.get("status"),
        "keysAfterReady": report.get("keysAfterReady"),
        "keyEventCount": len(report.get("keyEvents", [])),
        "keyWriteOkCount": sum(1 for event in report.get("keyEvents", []) if (event.get("write") or {}).get("writeOk")),
        "sampleCount": report.get("sampleCount"),
        "uniqueMapSizes": report.get("uniqueMapSizes"),
        "uniqueLayer0States": report.get("uniqueLayer0States"),
        "matchedStateCount": len(matches),
        "exactMatchedStateCount": len(exact_states),
        "matches": matches,
        "conclusion": (
            "Runtime reached a 37x21 field viewport that exactly matches static map data."
            if exact_states
            else "Runtime sampled 37x21 buffers, but none exactly matched static field map layer0/layer1 data."
        ),
    }


def esc(value: Any) -> str:
    return html.escape(str(value))


def render_html(review: dict[str, Any]) -> str:
    cards = [
        ("runtime", review.get("runtimeStatus")),
        ("keys", f"{review.get('keyWriteOkCount')}/{review.get('keyEventCount')}"),
        ("states", review.get("matchedStateCount")),
        ("exact states", review.get("exactMatchedStateCount")),
    ]
    card_html = "".join(f"<div class='card'><b>{esc(k)}</b><span>{esc(v)}</span></div>" for k, v in cards)
    state_sections = []
    for state in review.get("matches", []):
        exact = state.get("exactMatches") or []
        best_rows = "".join(
            "<tr>"
            f"<td><code>{esc(row.get('map'))}</code></td>"
            f"<td>{esc(row.get('x'))},{esc(row.get('y'))}</td>"
            f"<td>{esc(row.get('score'))}/1554</td>"
            f"<td>{esc(row.get('layer0Score'))}/777</td>"
            f"<td>{esc(row.get('layer1Score'))}/777</td>"
            f"<td>{esc(row.get('width'))}x{esc(row.get('height'))}</td>"
            "</tr>"
            for row in state.get("bestMatches", [])
        )
        exact_text = ", ".join(f"{row['map']}@{row['x']},{row['y']}" for row in exact) or "-"
        state_sections.append(
            f"""
            <section>
              <h2>state {esc(state.get('timeMs'))}ms · <code>{esc(state.get('layer0Sha1'))}</code></h2>
              <p>classification: <b>{esc(state.get('classification'))}</b></p>
              <p>exact: {esc(exact_text)}</p>
              <p>layer1 values: <code>{esc(state.get('layer1UniqueValues'))}</code></p>
              <table><thead><tr><th>map</th><th>x,y</th><th>total</th><th>layer0</th><th>layer1</th><th>size</th></tr></thead><tbody>{best_rows}</tbody></table>
            </section>
            """
        )
    payload = json.dumps(review, ensure_ascii=False)
    return f"""<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Runtime Map Viewport Match</title>
  <style>
    body {{ margin:0; background:#101318; color:#edf1f7; font-family:system-ui,sans-serif; }}
    main {{ max-width:1240px; margin:0 auto; padding:24px; }}
    a {{ color:#8ecbff; }}
    .summary {{ display:grid; grid-template-columns:repeat(auto-fit,minmax(160px,1fr)); gap:12px; margin:16px 0; }}
    .card, section {{ border:1px solid #2b3544; border-radius:8px; background:#161b22; }}
    .card {{ padding:12px; }}
    .card b {{ display:block; color:#9fb1c9; font-size:12px; text-transform:uppercase; }}
    .card span {{ display:block; margin-top:8px; font-size:18px; overflow-wrap:anywhere; }}
    section {{ padding:16px; margin:16px 0; overflow:auto; }}
    table {{ border-collapse:collapse; width:100%; min-width:760px; font-size:13px; }}
    th,td {{ border-bottom:1px solid #283342; padding:8px; text-align:left; }}
    th {{ color:#b9c7dc; background:#111722; }}
    code {{ color:#dbeafe; }}
  </style>
</head>
<body>
<main>
  <p><a href="runtime_map_layer0_poll.json">runtime poll JSON</a> · <a href="../web/map_animation_execution_boundary_review.html">static boundary</a></p>
  <h1>Runtime Map Viewport Match</h1>
  <p>{esc(review.get('conclusion'))}</p>
  <div class="summary">{card_html}</div>
  {''.join(state_sections)}
</main>
<script>window.HWANSE_RUNTIME_MAP_VIEWPORT_MATCH = {payload};</script>
</body>
</html>
"""


def write_outputs(review: dict[str, Any], out_dir: Path) -> None:
    out_dir.mkdir(parents=True, exist_ok=True)
    (out_dir / "runtime_map_layer0_viewport_match_review.json").write_text(
        json.dumps(review, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )
    html_text = render_html(review)
    (out_dir / "runtime_map_layer0_viewport_match_review.html").write_text(html_text, encoding="utf-8")


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--input", type=Path, default=OUT / "runtime_map_layer0_poll.json")
    parser.add_argument("--out-dir", type=Path, default=OUT)
    args = parser.parse_args()
    review = build_review(args.input)
    write_outputs(review, args.out_dir)
    print("runtime_map_layer0_viewport_match_review ok")
    print(json.dumps({k: review.get(k) for k in ["runtimeStatus", "matchedStateCount", "exactMatchedStateCount", "conclusion"]}, ensure_ascii=False, indent=2))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
