#!/usr/bin/env python3
"""Compare renderer target plans with current web battle animation support."""

from __future__ import annotations

import html
import json
from collections import Counter
from pathlib import Path
from typing import Any


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

PLAN_JSON = OUT / "battle_child_target_renderer_plan_review.json"
ENGINE_JS = WEB / "engine" / "battle" / "animation.js"
TIMELINE_HTML = WEB / "battle_skill_timeline_review.html"
FORMULA_HTML = WEB / "battle_formula_calculator.html"
SIMULATOR_HTML = WEB / "battle_simulator.html"

OUT_JSON = OUT / "battle_child_target_renderer_gap_review.json"


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


def read(path: Path) -> str:
    return path.read_text(encoding="utf-8")


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


def classify_requirements(row: dict[str, Any]) -> list[str]:
    cls = row.get("rendererPlanClass")
    gaps = ["consume-target-renderer-plan-json"]
    if cls == "nested-child-spawner":
        gaps.append("apply-nested-repeat-counter")
        gaps.append("recursive-child-spawn-lifetime")
    elif cls == "motion-boundary-loop":
        gaps.append("apply-boundary-loop-lifetime")
        gaps.append("apply-motion-step-until-branch")
    elif cls == "counter-held-direct-frame":
        gaps.append("apply-counter-held-lifetime")
    return gaps


def fulfilled_requirements(row: dict[str, Any], engine: str, plan_token: str, page_load_count: int) -> list[str]:
    cls = row.get("rendererPlanClass")
    fulfilled: list[str] = []
    engine_can_consume = (
        "indexTargetRendererPlans" in engine
        and "targetRendererPlanRow" in engine
        and "options.targetRendererPlanIndex" in engine
    )
    if engine_can_consume and page_load_count:
        fulfilled.append("consume-target-renderer-plan-json")
    if cls == "nested-child-spawner" and "nestedRepeatPlanForChild" in engine and "counterInitialValue(nestedPlan" in engine:
        fulfilled.append("apply-nested-repeat-counter")
        fulfilled.append("recursive-child-spawn-lifetime")
    elif cls == "motion-boundary-loop" and "planClass === \"motion-boundary-loop\"" in engine:
        fulfilled.append("apply-boundary-loop-lifetime")
        if "motionBoundaryLoopProjection" in engine and "projectedFormula: boundaryProjection" in engine:
            fulfilled.append("apply-motion-step-until-branch")
    elif cls == "counter-held-direct-frame" and "planClass === \"counter-held-direct-frame\"" in engine:
        fulfilled.append("apply-counter-held-lifetime")
    return fulfilled


def build_report() -> dict[str, Any]:
    plan = load_json(PLAN_JSON)
    engine = read(ENGINE_JS)
    pages = {
        "timeline": read(TIMELINE_HTML),
        "formula": read(FORMULA_HTML),
        "simulator": read(SIMULATOR_HTML),
    }
    plan_token = "battle_child_target_renderer_plan_review.json"
    engine_token = "targetRendererPlan"
    page_load_count = sum(1 for text in pages.values() if plan_token in text)
    rows = []
    requirement_counts: Counter[str] = Counter()
    fulfilled_counts: Counter[str] = Counter()
    remaining_counts: Counter[str] = Counter()
    class_counts: Counter[str] = Counter()

    for row in plan.get("rows", []):
        requirements = classify_requirements(row)
        fulfilled = fulfilled_requirements(row, engine, plan_token, page_load_count)
        remaining = [item for item in requirements if item not in set(fulfilled)]
        requirement_counts.update(requirements)
        fulfilled_counts.update(fulfilled)
        remaining_counts.update(remaining)
        class_counts.update([str(row.get("rendererPlanClass"))])
        rows.append(
            {
                "helperId": row.get("helperId"),
                "targetVaHex": row.get("targetVaHex"),
                "rendererPlanClass": row.get("rendererPlanClass"),
                "skillLabels": row.get("skillLabels") or [],
                "frames": row.get("frames") or [],
                "rendererAction": row.get("rendererAction"),
                "implementationRequirements": requirements,
                "fulfilledRequirements": fulfilled,
                "remainingGaps": remaining,
                "engineEvidence": {
                    "planJsonLoadedByEngine": plan_token in engine,
                    "planRuntimeApiPresent": engine_token in engine,
                    "engineCanConsumePageLoadedPlan": "indexTargetRendererPlans" in engine
                        and "targetRendererPlanRow" in engine
                        and "options.targetRendererPlanIndex" in engine,
                    "targetPlanIndexerPresent": "indexTargetRendererPlans" in engine,
                    "targetPlanLookupPresent": "targetRendererPlanRow" in engine,
                    "targetPlanPassedToHelperFrames": "options.targetRendererPlanIndex" in engine,
                    "counterHeldLifetimePresent": "planClass === \"counter-held-direct-frame\"" in engine,
                    "boundaryLoopLifetimePresent": "planClass === \"motion-boundary-loop\"" in engine,
                    "boundaryLoopMotionProjectionPresent": "motionBoundaryLoopProjection" in engine
                        and "projectedFormula: boundaryProjection" in engine,
                    "nestedRepeatPresent": "nestedRepeatPlanForChild" in engine,
                    "currentHelperEffectFramesUsesVisual": "function helperEffectFrames" in engine
                        and "visual?.directSpawn?.events" in engine,
                },
            }
        )

    page_load_status = {
        name: {
            "loadsTargetRendererPlan": plan_token in text,
            "loadsHelperVisual": "battle_helper_visual_behavior_review.json" in text,
            "loadsHelperPosition": "battle_helper_position_motion_review.json" in text,
            "loadsHelperSync": "battle_helper_sync_timing_review.json" in text,
        }
        for name, text in pages.items()
    }

    return {
        "version": 1,
        "kind": "hwanse-battle-child-target-renderer-gap-review",
        "source": "tools/build_battle_child_target_renderer_gap_review.py",
        "runtimeUsed": False,
        "inputs": [
            str(PLAN_JSON.relative_to(ROOT)),
            str(ENGINE_JS.relative_to(ROOT)),
            str(TIMELINE_HTML.relative_to(ROOT)),
            str(FORMULA_HTML.relative_to(ROOT)),
            str(SIMULATOR_HTML.relative_to(ROOT)),
        ],
        "status": "renderer-plan-consumed-with-partial-motion-semantics" if not remaining_counts else "renderer-plan-partially-consumed",
        "summary": {
            "targetRendererPlanRows": len(rows),
            "rendererPlanClassCounts": dict(sorted(class_counts.items())),
            "implementationRequirementCounts": dict(sorted(requirement_counts.items())),
            "fulfilledRequirementCounts": dict(sorted(fulfilled_counts.items())),
            "remainingGapCounts": dict(sorted(remaining_counts.items())),
            "engineLoadsTargetRendererPlan": plan_token in engine,
            "engineCanConsumePageLoadedTargetRendererPlan": "indexTargetRendererPlans" in engine
                and "targetRendererPlanRow" in engine
                and "options.targetRendererPlanIndex" in engine,
            "activePagesLoadingTargetRendererPlan": sum(1 for item in page_load_status.values() if item["loadsTargetRendererPlan"]),
        },
        "pageLoadStatus": page_load_status,
        "interpretationNotes": [
            "The EXE-derived target renderer plan is now loaded by the active timeline/calculator/simulator pages and consumed by the common battle animation engine API.",
            "Counter-held lifetimes and nested repeat counters are consumed in preview generation.",
            "Boundary-loop rows are previewed with the extracted spawn schedule, lifetime gate, and the common opcode 0x2d mode 0x09 x-axis projection. Exact phase/amplitude remains marked inside the motion boundary review as partial when runtime-specific values are not statically fixed.",
        ],
        "rows": rows,
    }


def small_json(value: Any) -> str:
    if value in (None, [], {}):
        return "<span class='muted'>none</span>"
    return f"<pre>{html_escape(json.dumps(value, ensure_ascii=False, indent=2))}</pre>"


def html_page(report: dict[str, Any]) -> str:
    cards = "".join(
        f"<div class='card'><b>{html_escape(key)}</b>{small_json(value)}</div>"
        for key, value in report["summary"].items()
    )
    notes = "".join(f"<li>{html_escape(note)}</li>" for note in report["interpretationNotes"])
    rows = []
    for row in report["rows"]:
        rows.append(
            "<tr>"
            f"<td><b>#{row['helperId']}</b><br><code>{html_escape(row['targetVaHex'])}</code><br>{html_escape(row['rendererPlanClass'])}</td>"
            f"<td>{'<br>'.join(html_escape(label) for label in row['skillLabels'])}</td>"
            f"<td>{html_escape(row['rendererAction'])}</td>"
            f"<td>{'<br>'.join(f'<span class=\"badge\">{html_escape(gap)}</span>' for gap in row['remainingGaps']) or '<span class=\"muted\">none</span>'}</td>"
            f"<td>{small_json(row['engineEvidence'])}</td>"
            "</tr>"
        )
    return f"""<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Battle Child Target Renderer Gap Review</title>
  <style>
    body {{ margin: 0; padding: 24px; background: #101218; color: #edf1f7; font: 14px/1.5 system-ui, sans-serif; }}
    a {{ color: #8ec5ff; }}
    h1 {{ margin: 0 0 8px; font-size: 24px; }}
    .muted {{ color: #9aa4b2; }}
    .cards {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 12px; margin: 16px 0; }}
    .card {{ border: 1px solid #2c3444; border-radius: 8px; padding: 12px; background: #171c25; }}
    .badge {{ display: inline-block; margin: 2px 4px 2px 0; padding: 2px 7px; border-radius: 999px; background: #283348; color: #cfe3ff; font-size: 12px; }}
    table {{ width: 100%; border-collapse: collapse; margin-top: 16px; }}
    th, td {{ border-top: 1px solid #2c3444; padding: 10px; vertical-align: top; }}
    th {{ text-align: left; position: sticky; top: 0; background: #101218; }}
    pre {{ margin: 6px 0 0; white-space: pre-wrap; color: #cbd5e1; font-size: 12px; }}
    code {{ color: #ffd58c; }}
  </style>
</head>
<body>
  <h1>Battle Child Target Renderer Gap Review</h1>
  <p class="muted">EXE 기반 target renderer plan과 현재 웹 전투 렌더러 구현 사이의 남은 차이를 정리합니다.</p>
  <p>
    <a href="../web/index.html">index</a> ·
    <a href="battle_child_target_renderer_plan_review.json">target renderer plan JSON</a> ·
    <a href="../web/battle_skill_timeline_review.html">skill timeline</a> ·
    <a href="battle_child_target_renderer_gap_review.json">JSON</a>
  </p>
  <div class="cards">{cards}</div>
  <h2>Page Load Status</h2>
  {small_json(report["pageLoadStatus"])}
  <ul>{notes}</ul>
  <table>
    <thead><tr><th>helper/target</th><th>기술</th><th>EXE renderer action</th><th>남은 gap</th><th>engine evidence</th></tr></thead>
    <tbody>{''.join(rows)}</tbody>
  </table>
</body>
</html>
"""


def main() -> None:
    report = build_report()
    OUT_JSON.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    print("wrote out/battle_child_target_renderer_gap_review.json")
    print(json.dumps(report["summary"], ensure_ascii=False, indent=2))


if __name__ == "__main__":
    main()
