#!/usr/bin/env python3
"""Build an intake checklist for original-runtime battle effect captures.

This does not complete the visual fidelity goal by itself.  It makes the
remaining blocker explicit: which original-game captures are still needed and
which local capture manifest entries, if any, can be validated.
"""

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"
DATA = ROOT / "data"

MANIFEST = DATA / "battle_effect_capture_manifest.json"
ORIGINAL_INBOX = Path("captures") / "battle_effect_original_capture_inbox"
OUT_JSON = OUT / "battle_effect_capture_checklist.json"
OUT_HTML = OUT / "battle_effect_capture_checklist.html"


def load_json(path: Path) -> dict[str, Any]:
    return json.loads(path.read_text(encoding="utf-8"))


def safe_row_dir(value: str | None) -> str:
    text = str(value or "row")
    return "".join(ch if ch.isalnum() or ch in "._-" else "_" for ch in text).strip("._") or "row"


def expected_row(row: dict[str, Any], priority: str) -> dict[str, Any]:
    row_id = row["id"]
    return {
        "id": row_id,
        "priority": priority,
        "ownerKey": row.get("ownerKey"),
        "ownerName": row.get("ownerName"),
        "skillIdHex": row.get("skillIdHex"),
        "levelOrFixed": row.get("levelOrFixed"),
        "skillName": row.get("skillName"),
        "effectAnimationClass": row.get("effectAnimationClass"),
        "executionRequirements": row.get("executionRequirements") or [],
        "helperBehaviorClasses": row.get("helperBehaviorClasses") or [],
        "originalInboxDirectory": str(ORIGINAL_INBOX / safe_row_dir(row_id) / "original"),
    }


def load_manifest() -> dict[str, Any]:
    if not MANIFEST.exists():
        return {
            "exists": False,
            "rows": {},
            "path": str(MANIFEST.relative_to(ROOT)),
        }
    raw = load_json(MANIFEST)
    rows: dict[str, dict[str, Any]] = {}
    if isinstance(raw.get("captures"), list):
        for item in raw["captures"]:
            if isinstance(item, dict) and item.get("id"):
                rows[str(item["id"])] = item
    elif isinstance(raw.get("captures"), dict):
        for key, item in raw["captures"].items():
            if isinstance(item, dict):
                rows[str(key)] = {"id": str(key), **item}
    return {
        "exists": True,
        "rows": rows,
        "path": str(MANIFEST.relative_to(ROOT)),
    }


def resolve_capture_path(value: str | None) -> tuple[str | None, bool]:
    if not value:
        return None, False
    path = Path(value)
    if not path.is_absolute():
        path = ROOT / path
    return str(path.relative_to(ROOT)) if path.exists() and path.is_relative_to(ROOT) else str(path), path.exists()


def count_png_frames(value: str | None) -> int:
    if not value:
        return 0
    path = Path(value)
    if not path.is_absolute():
        path = ROOT / path
    if not path.exists():
        return 0
    if path.is_file():
        return 1 if path.suffix.lower() == ".png" else 0
    return sum(1 for item in path.iterdir() if item.is_file() and item.suffix.lower() == ".png")


def compare_expected_to_manifest(expected: list[dict[str, Any]], manifest: dict[str, Any]) -> list[dict[str, Any]]:
    rows = manifest["rows"]
    out = []
    for row in expected:
        item = rows.get(row["id"])
        capture_path, capture_exists = resolve_capture_path((item or {}).get("originalCapture"))
        web_path, web_exists = resolve_capture_path((item or {}).get("webReferenceCapture"))
        original_frame_count = count_png_frames((item or {}).get("originalCapture"))
        web_frame_count = count_png_frames((item or {}).get("webReferenceCapture"))
        capture_ticks = (item or {}).get("captureTicks") or []
        expected_web_frame_count = len(capture_ticks) if isinstance(capture_ticks, list) else 0
        web_count_matches_ticks = (
            not expected_web_frame_count
            or web_frame_count == expected_web_frame_count
        )
        ready = bool(item and capture_exists and web_exists and web_count_matches_ticks)
        if ready:
            status = "ready-for-compare"
        elif not capture_exists:
            status = "missing-original-capture"
        elif not web_exists:
            status = "missing-web-reference-capture"
        elif not web_count_matches_ticks:
            status = "web-reference-frame-count-mismatch"
        else:
            status = "missing-capture"
        out.append(
            {
                **row,
                "manifestEntry": bool(item),
                "originalCapture": capture_path,
                "originalCaptureExists": capture_exists,
                "originalFrameCount": original_frame_count,
                "webReferenceCapture": web_path,
                "webReferenceCaptureExists": web_exists,
                "webReferenceFrameCount": web_frame_count,
                "expectedWebFrameCount": expected_web_frame_count,
                "webReferenceFrameCountMatchesTicks": web_count_matches_ticks,
                "captureTicks": capture_ticks,
                "runtimeVersion": (item or {}).get("runtimeVersion"),
                "captureNotes": (item or {}).get("notes"),
                "readyForPixelCompare": ready,
                "status": status,
            }
        )
    return out


def build_template(first_pass_rows: list[dict[str, Any]]) -> dict[str, Any]:
    return {
        "version": 1,
        "kind": "hwanse-battle-effect-capture-manifest",
        "notes": [
            "Place this as data/battle_effect_capture_manifest.json when original runtime captures are available.",
            "originalCapture should point to a local 640x480 capture file or frame directory. Do not commit large capture binaries.",
        ],
        "captures": [
            {
                "id": row["id"],
                "originalCapture": f"captures/battle_effect_pixel_oracle/{row['id'].replace(':', '_')}/original/",
                "webReferenceCapture": "",
                "runtimeVersion": "original-game",
                "notes": "",
            }
            for row in first_pass_rows
        ],
    }


def build_report() -> dict[str, Any]:
    plan = load_json(OUT / "battle_effect_pixel_oracle_plan.json")
    manifest = load_manifest()
    first_pass_expected = [expected_row(row, "first-pass") for row in plan.get("recommendedFirstPassRows") or []]
    full_expected = [expected_row(row, "full-oracle") for row in plan.get("fullOracleRows") or []]
    first_pass = compare_expected_to_manifest(first_pass_expected, manifest)
    full_oracle = compare_expected_to_manifest(full_expected, manifest)
    first_ready = sum(1 for row in first_pass if row["readyForPixelCompare"])
    full_ready = sum(1 for row in full_oracle if row["readyForPixelCompare"])
    first_original_ready = sum(1 for row in first_pass if row["originalCaptureExists"])
    first_web_ready = sum(1 for row in first_pass if row["webReferenceCaptureExists"])
    first_web_count_mismatches = sum(1 for row in first_pass if row["webReferenceCaptureExists"] and not row["webReferenceFrameCountMatchesTicks"])
    full_original_ready = sum(1 for row in full_oracle if row["originalCaptureExists"])
    full_web_ready = sum(1 for row in full_oracle if row["webReferenceCaptureExists"])
    full_web_count_mismatches = sum(1 for row in full_oracle if row["webReferenceCaptureExists"] and not row["webReferenceFrameCountMatchesTicks"])
    status = "ready-for-first-pass-compare" if first_ready == len(first_pass) else "waiting-for-captures"
    return {
        "version": 1,
        "kind": "hwanse-battle-effect-capture-checklist",
        "source": "tools/build_battle_effect_capture_checklist.py",
        "status": status,
        "inputs": [
            "out/battle_effect_pixel_oracle_plan.json",
            "data/battle_effect_capture_manifest.json",
        ],
        "summary": {
            "manifestPath": manifest["path"],
            "manifestExists": manifest["exists"],
            "firstPassRows": len(first_pass),
            "firstPassReady": first_ready,
            "firstPassMissing": len(first_pass) - first_ready,
            "firstPassOriginalReady": first_original_ready,
            "firstPassWebReferenceReady": first_web_ready,
            "firstPassWebReferenceFrameCountMismatches": first_web_count_mismatches,
            "fullOracleRows": len(full_oracle),
            "fullOracleReady": full_ready,
            "fullOracleMissing": len(full_oracle) - full_ready,
            "fullOracleOriginalReady": full_original_ready,
            "fullOracleWebReferenceReady": full_web_ready,
            "fullOracleWebReferenceFrameCountMismatches": full_web_count_mismatches,
            "completionDecision": "do-not-call-update-goal",
        },
        "captureCommands": {
            "prepareFirstPassInbox": "python3 tools/prepare_battle_effect_original_capture_dirs.py --scope first-pass --write",
            "prepareFullInbox": "python3 tools/prepare_battle_effect_original_capture_dirs.py --scope full --write",
            "importFirstPass": "python3 tools/import_battle_effect_original_captures.py captures/battle_effect_original_capture_inbox --scope first-pass --copy",
            "importFull": "python3 tools/import_battle_effect_original_captures.py captures/battle_effect_original_capture_inbox --scope full --copy",
            "compare": "python3 tools/build_battle_effect_capture_checklist.py && python3 tools/compare_battle_effect_pixel_oracle.py && python3 tools/build_battle_skill_goal_audit.py",
        },
        "captureManifestTemplate": build_template(first_pass_expected),
        "firstPassRows": first_pass,
        "fullOracleRows": full_oracle,
    }


def badge(text: str) -> str:
    cls = "ok" if text == "ready-for-compare" else "bad"
    return f"<span class='badge {cls}'>{html.escape(str(text))}</span>"


def row_table(rows: list[dict[str, Any]], compact: bool = False) -> str:
    out = []
    for row in rows:
        reqs = " ".join(f"<span class='tag'>{html.escape(req)}</span>" for req in row["executionRequirements"])
        cells = [
            f"<td><code>{html.escape(row['id'])}</code></td>",
            f"<td>{html.escape(str(row.get('ownerName') or row.get('ownerKey')))}</td>",
            f"<td>{html.escape(str(row.get('skillName')))}</td>",
            f"<td>{html.escape(str(row.get('levelOrFixed') or ''))}</td>",
            f"<td>{html.escape(str(row.get('effectAnimationClass')))}</td>",
        ]
        if not compact:
            cells.append(f"<td>{reqs}</td>")
        cells.extend(
            [
                f"<td>{badge(row['status'])}</td>",
                f"<td><code>{html.escape(str(row.get('originalInboxDirectory') or ''))}</code></td>",
                f"<td>{html.escape(str(row.get('originalCapture') or ''))}<br><span class='muted'>{row.get('originalFrameCount') or 0} PNG</span></td>",
                f"<td>{html.escape(str(row.get('webReferenceCapture') or ''))}<br><span class='muted'>{row.get('webReferenceFrameCount') or 0}/{row.get('expectedWebFrameCount') or '-'} PNG</span></td>",
            ]
        )
        out.append("<tr>" + "".join(cells) + "</tr>")
    return "\n".join(out)


def html_page(report: dict[str, Any]) -> str:
    summary = report["summary"]
    commands = report["captureCommands"]
    template = html.escape(json.dumps(report["captureManifestTemplate"], ensure_ascii=False, indent=2))
    command_block = html.escape("\n".join(commands.values()))
    return f"""<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Battle Effect Capture Checklist</title>
  <style>
    body {{ margin: 0; padding: 24px; font-family: system-ui, sans-serif; background: #f8fafc; color: #172033; }}
    h1, h2 {{ margin: 0 0 12px; }}
    .muted {{ color: #64748b; font-size: 12px; }}
    .cards {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 10px; margin: 16px 0 24px; }}
    .card {{ background: white; border: 1px solid #dbe3ef; border-radius: 8px; padding: 12px; }}
    .value {{ font-size: 24px; font-weight: 800; }}
    table {{ width: 100%; border-collapse: collapse; background: white; border: 1px solid #dbe3ef; margin: 12px 0 28px; }}
    th, td {{ border-bottom: 1px solid #e2e8f0; padding: 8px 10px; text-align: left; vertical-align: top; }}
    th {{ background: #eaf0f8; font-size: 12px; text-transform: uppercase; letter-spacing: .04em; }}
    details {{ background: white; border: 1px solid #dbe3ef; border-radius: 8px; padding: 12px; margin: 12px 0 28px; }}
    summary {{ cursor: pointer; font-weight: 800; }}
    code, pre {{ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }}
    pre {{ white-space: pre-wrap; background: #111827; color: #e5e7eb; padding: 12px; border-radius: 8px; overflow: auto; }}
    .badge, .tag {{ display: inline-block; margin: 2px; padding: 2px 6px; border-radius: 999px; font-size: 12px; font-weight: 700; }}
    .badge.ok {{ background: #dcfce7; color: #166534; }}
    .badge.bad {{ background: #fee2e2; color: #991b1b; }}
    .tag {{ background: #e0f2fe; color: #075985; }}
  </style>
</head>
<body>
  <h1>Battle Effect Capture Checklist</h1>
  <p class="muted">source: <code>{html.escape(report['source'])}</code> · status: <code>{html.escape(report['status'])}</code></p>
  <div class="cards">
    <div class="card"><div class="muted">manifest</div><div class="value">{html.escape(str(summary['manifestExists']))}</div><div class="muted">{html.escape(summary['manifestPath'])}</div></div>
    <div class="card"><div class="muted">first pass compare-ready</div><div class="value">{summary['firstPassReady']}/{summary['firstPassRows']}</div><div class="muted">original {summary['firstPassOriginalReady']} · web {summary['firstPassWebReferenceReady']} · web mismatches {summary['firstPassWebReferenceFrameCountMismatches']}</div></div>
    <div class="card"><div class="muted">full oracle compare-ready</div><div class="value">{summary['fullOracleReady']}/{summary['fullOracleRows']}</div><div class="muted">original {summary['fullOracleOriginalReady']} · web {summary['fullOracleWebReferenceReady']} · web mismatches {summary['fullOracleWebReferenceFrameCountMismatches']}</div></div>
    <div class="card"><div class="muted">completion</div><div class="value">{html.escape(summary['completionDecision'])}</div></div>
  </div>
  <h2>Capture Commands</h2>
  <pre>{command_block}</pre>
  <h2>First-Pass Rows</h2>
  <table>
    <thead><tr><th>row</th><th>actor</th><th>skill</th><th>level</th><th>class</th><th>requirements</th><th>status</th><th>drop original PNGs here</th><th>original</th><th>web reference</th></tr></thead>
    <tbody>{row_table(report['firstPassRows'])}</tbody>
  </table>
  <details>
    <summary>Full Oracle Rows ({len(report['fullOracleRows'])})</summary>
    <table>
      <thead><tr><th>row</th><th>actor</th><th>skill</th><th>level</th><th>class</th><th>status</th><th>drop original PNGs here</th><th>original</th><th>web reference</th></tr></thead>
      <tbody>{row_table(report['fullOracleRows'], compact=True)}</tbody>
    </table>
  </details>
  <h2>Manifest Template</h2>
  <pre>{template}</pre>
</body>
</html>
"""


def write_outputs(report: dict[str, Any], json_out: Path = OUT_JSON, html_out: Path | None = None) -> None:
    json_out.parent.mkdir(parents=True, exist_ok=True)
    json_out.write_text(json.dumps(report, ensure_ascii=False, separators=(",", ":")), encoding="utf-8")
    if html_out is not None:
        html_out.parent.mkdir(parents=True, exist_ok=True)
        html_out.write_text(html_page(report), encoding="utf-8")


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--json-out", type=Path, default=OUT_JSON)
    parser.add_argument("--html-out", type=Path)
    args = parser.parse_args()
    report = build_report()
    write_outputs(report, args.json_out, args.html_out)
    print(json.dumps(report["summary"], ensure_ascii=False))
    return 0


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