#!/usr/bin/env python3
"""Summarize the battle skill animation goal completion state.

This is a completion-audit artifact, not a generator for gameplay data.  It
maps the user's battle-skill animation goal to concrete files and verifiers.
The goal is limited to EXE-derived canonical playback and browser-visible
runner behavior.  Original-runtime pixel capture is tracked separately as a
future parity check, because it is external evidence and should not block this
static-analysis goal.
"""
from __future__ import annotations

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

from summarize_battle_skill_timeline_browser import build_summary as build_timeline_browser_summary
from summarize_battle_skill_timeline_browser import write_outputs as write_timeline_browser_outputs


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

OUT_JSON = OUT / "battle_skill_goal_audit.json"


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


def exists(rel: str) -> bool:
    return (ROOT / rel).exists()


def check(
    check_id: str,
    requirement: str,
    status: str,
    artifacts: list[str],
    evidence: list[str],
    gaps: list[str],
    verifier: list[str],
) -> dict[str, Any]:
    return {
        "id": check_id,
        "requirement": requirement,
        "status": status,
        "artifacts": [{"path": path, "exists": exists(path)} for path in artifacts],
        "evidence": evidence,
        "gaps": gaps,
        "verifier": verifier,
    }


def build_report() -> dict[str, Any]:
    canonical = load_json(OUT / "battle_skill_timeline_canonical.json")
    canonical_summary = canonical.get("summary") or {}
    browser_summary = build_timeline_browser_summary(ROOT)
    write_timeline_browser_outputs(browser_summary, OUT)
    browser = browser_summary.get("timeline") or {}
    effect_binding = load_json(OUT / "battle_effect_runner_binding_review.json")
    effect_summary = effect_binding.get("summary") or {}
    effect_patterns = load_json(OUT / "battle_effect_animation_pattern_review.json")
    effect_pattern_summary = effect_patterns.get("summary") or {}
    effect_visual = load_json(OUT / "battle_effect_visual_assertion_review.json")
    effect_visual_summary = effect_visual.get("summary") or {}
    effect_oracle = load_json(OUT / "battle_effect_pixel_oracle_plan.json")
    effect_oracle_summary = effect_oracle.get("summary") or {}
    effect_capture = load_json(OUT / "battle_effect_capture_checklist.json")
    effect_capture_summary = effect_capture.get("summary") or {}
    effect_compare = load_json(OUT / "battle_effect_pixel_compare_report.json")
    effect_compare_summary = effect_compare.get("summary") or {}
    complete = load_json(OUT / "battle_skill_complete_pattern_review.json")
    complete_summary = complete.get("summary") or {}
    web_reference_tool_exists = exists("tools/capture_battle_effect_web_reference.py")
    web_reference_doc_exists = exists("docs/BATTLE_EFFECT_WEB_REFERENCE_CAPTURE.md")
    original_capture_prepare_tool_exists = exists("tools/prepare_battle_effect_original_capture_dirs.py")
    original_capture_import_tool_exists = exists("tools/import_battle_effect_original_captures.py")
    original_capture_inbox_exists = exists("captures/battle_effect_original_capture_inbox")

    criteria = [
        check(
            "canonical-action-rows",
            "주인공/몬스터 기술 row, frame sequence, gate, WLK evidence가 EXE canonical bundle로 정리되어야 한다.",
            "complete" if canonical_summary.get("canonicalRowsReady") is True else "missing",
            [
                "out/battle_skill_timeline_canonical.json",
                "out/battle_skill_timeline_canonical.js",
                "tools/build_battle_skill_timeline_canonical.py",
            ],
            [
                f"playerRows={canonical_summary.get('playerRows')}, monsterRows={canonical_summary.get('monsterRows')}, totalRows={canonical_summary.get('totalRows')}",
                f"wlkEvidenceUnresolvedRows={canonical_summary.get('wlkEvidenceUnresolvedRows')}",
                f"playerTimelineStatusCounts={canonical_summary.get('playerTimelineStatusCounts')}",
            ],
            [] if canonical_summary.get("canonicalRowsReady") is True else ["canonicalRowsReady is not true"],
            ["python3 tools/build_battle_skill_timeline_canonical.py", "python3 tools/verify_battle_skill_playback_regressions.py"],
        ),
        check(
            "shared-battle-pages",
            "battle_skill_timeline_review, battle_formula_calculator, battle_simulator가 같은 canonical playback을 써야 한다.",
            "complete" if effect_summary.get("pageChecksPassed") == effect_summary.get("pageChecksTotal") else "partial",
            [
                "web/battle_skill_timeline_review.html",
                "web/battle_formula_calculator.html",
                "web/battle_simulator.html",
                "web/engine/battle/animation.js",
                "out/battle_effect_runner_binding_review.json",
            ],
            [
                f"pageChecks={effect_summary.get('pageChecksPassed')}/{effect_summary.get('pageChecksTotal')}",
                f"pagesUsingPlaybackEffectFrames={effect_summary.get('pagesUsingPlaybackEffectFrames')}",
                f"pagesRenderingHelperEffectLayer={effect_summary.get('pagesRenderingHelperEffectLayer')}",
            ],
            [] if effect_summary.get("pageChecksPassed") == effect_summary.get("pageChecksTotal") else ["shared page binding check is not fully green"],
            ["python3 tools/build_battle_effect_runner_binding_review.py", "python3 tools/verify_web_assets.py"],
        ),
        check(
            "known-browser-regressions",
            "룬룬권, 안면백조권, 도발, 꼬챙이꿰기처럼 과거에 밀리던 기술이 실제 브라우저 프레임스트립에서 canonical과 일치해야 한다.",
            "complete" if browser.get("status") == "ok" and browser.get("checkedRows", 0) >= 6 else "missing",
            [
                "tools/verify_battle_skill_timeline_browser.py",
                "out/battle_skill_timeline_browser_summary.json",
                "web/battle_skill_timeline_review.html",
                "out/battle_effect_visual_assertion_review.json",
                "out/battle_effect_visual_assertion_review.html",
            ],
            [
                f"browserSmokeStatus={browser.get('status')}",
                f"checkedRows={browser.get('checkedRows')}",
                f"checkedEffectRows={browser.get('checkedEffectRows')}",
                f"checkedExecutionRequirementRows={browser.get('checkedExecutionRequirementRows')}",
                f"checkedExecutionRequirementBuckets={browser.get('checkedExecutionRequirementBuckets')}",
                "checked: Rinshan 도발/꼬챙이꿰기/안면백조권 Lv1,Lv4 + Ataho 룬룬권 Lv1,Lv4",
                "effect checked: Ataho 호격권/맹호비상각/폭전축/맹호스페셜 Lv4 + Rinshan 선렬각/영상승룡파 Lv4",
                "execution requirement rows expose browser effect diagnostics for transform/range, child spawn timing, loop/motion, and palette buckets",
                "effect rows with CNS frames pass a browser canvas non-empty pixel guard",
                "effect rows render at an active tick in the combined actor/effect preview canvas with non-empty pixels",
                f"visualAssertionStatus={effect_visual.get('status')}",
                f"visualAssertionRows={effect_visual_summary.get('requirementRows')}, failedGroups={effect_visual_summary.get('failedGroups')}",
                f"pixelOracleStatus={effect_visual_summary.get('pixelOracleStatus')}",
            ],
            [] if browser.get("status") == "ok" else ["browser smoke is not passing"],
            [
                "xvfb-run -a python3 tools/verify_battle_skill_timeline_browser.py",
                "python3 tools/build_battle_effect_visual_assertion_review.py",
            ],
        ),
        check(
            "effect-runner-binding",
            "EXE helper/effect 요구가 공통 러너에 결속되어 있고, active pages가 helper effect layer를 그려야 한다.",
            "complete" if (
                effect_summary.get("engineChecksPassed") == effect_summary.get("engineChecksTotal")
                and effect_summary.get("pageChecksPassed") == effect_summary.get("pageChecksTotal")
                and effect_summary.get("skillsWithReviewFlags") == 0
            ) else "partial",
            [
                "out/battle_effect_animation_pattern_review.json",
                "out/battle_effect_runner_binding_review.json",
                "web/engine/battle/animation.js",
            ],
            [
                f"effectSkillRows={effect_summary.get('effectSkillRows')}",
                f"skillsWithEffectFrames={effect_summary.get('skillsWithEffectFrames')}",
                f"skillsWithExecutionRequirements={effect_summary.get('skillsWithExecutionRequirements')}",
                f"engineChecks={effect_summary.get('engineChecksPassed')}/{effect_summary.get('engineChecksTotal')}",
            ],
            [] if effect_summary.get("skillsWithReviewFlags") == 0 else ["effect pattern review flags remain"],
            ["python3 tools/build_battle_effect_animation_pattern_review.py", "python3 tools/build_battle_effect_runner_binding_review.py"],
        ),
    ]
    complete_count = sum(1 for item in criteria if item["status"] == "complete")
    missing = [item["id"] for item in criteria if item["status"] != "complete"]
    future_verification = {
        "id": "original-runtime-pixel-parity",
        "requirement": "원본 게임 런타임 캡처와 웹 러너 프레임을 픽셀 비교해 최종 시각 parity를 확인한다.",
        "blockingCurrentGoal": False,
        "status": effect_compare.get("status"),
        "artifacts": [
            {"path": path, "exists": exists(path)}
            for path in [
                "out/battle_effect_pixel_oracle_plan.json",
                "out/battle_effect_capture_checklist.json",
                "out/battle_effect_pixel_compare_report.json",
                "tools/capture_battle_effect_web_reference.py",
                "tools/prepare_battle_effect_original_capture_dirs.py",
                "tools/import_battle_effect_original_captures.py",
                "docs/BATTLE_EFFECT_WEB_REFERENCE_CAPTURE.md",
            ]
        ],
        "evidence": [
            f"pixelOraclePlanStatus={effect_oracle.get('status')}",
            f"pixelOracleFullRows={effect_oracle_summary.get('requiredFullOracleRows')}, firstPassRows={effect_oracle_summary.get('recommendedFirstPassRows')}",
            f"captureChecklistStatus={effect_capture.get('status')}",
            f"captureChecklistFirstPassReady={effect_capture_summary.get('firstPassReady')}/{effect_capture_summary.get('firstPassRows')}",
            f"pixelCompareStatus={effect_compare.get('status')}",
            f"pixelCompareFullPassed={effect_compare_summary.get('fullRowsPassed')}/{effect_compare_summary.get('fullRowsExpected')}",
            f"webReferenceCaptureToolExists={web_reference_tool_exists}",
            f"webReferenceCaptureDocExists={web_reference_doc_exists}",
            f"originalCapturePrepareToolExists={original_capture_prepare_tool_exists}",
            f"originalCaptureImportToolExists={original_capture_import_tool_exists}",
            f"originalCaptureInboxExists={original_capture_inbox_exists}",
        ],
        "note": (
            "원본 런타임 캡처는 최종 parity 검증용 외부 증거다. "
            "EXE 정적 분석/canonical playback goal의 완료 조건에는 포함하지 않는다."
        ),
    }
    return {
        "version": 1,
        "kind": "hwanse-battle-skill-goal-audit",
        "source": "tools/build_battle_skill_goal_audit.py",
        "status": "not-complete" if missing else "complete",
        "summary": {
            "criteria": len(criteria),
            "complete": complete_count,
            "notComplete": len(missing),
            "missingOrIncomplete": missing,
            "canonicalRowsReady": canonical_summary.get("canonicalRowsReady"),
            "canonicalGoalReady": canonical_summary.get("goalReady"),
            "browserSmokeStatus": browser.get("status"),
            "browserSmokeCheckedRows": browser.get("checkedRows"),
            "browserSmokeCheckedEffectRows": browser.get("checkedEffectRows"),
            "browserSmokeCheckedExecutionRequirementRows": browser.get("checkedExecutionRequirementRows"),
            "effectExecutionRequirementRows": effect_summary.get("skillsWithExecutionRequirements"),
            "effectVisualAssertionStatus": effect_visual.get("status"),
            "effectVisualAssertionFailedGroups": effect_visual_summary.get("failedGroups"),
            "effectVisualPixelOracleStatus": effect_visual_summary.get("pixelOracleStatus"),
            "effectPixelOraclePlanStatus": effect_oracle.get("status"),
            "effectPixelOracleFullRows": effect_oracle_summary.get("requiredFullOracleRows"),
            "effectPixelOracleFirstPassRows": effect_oracle_summary.get("recommendedFirstPassRows"),
            "effectCaptureChecklistStatus": effect_capture.get("status"),
            "effectCaptureFirstPassReady": effect_capture_summary.get("firstPassReady"),
            "effectCaptureFirstPassRows": effect_capture_summary.get("firstPassRows"),
            "effectPixelCompareStatus": effect_compare.get("status"),
            "effectPixelCompareFullPassed": effect_compare_summary.get("fullRowsPassed"),
            "effectPixelCompareFullExpected": effect_compare_summary.get("fullRowsExpected"),
            "webReferenceCaptureToolExists": web_reference_tool_exists,
            "webReferenceCaptureDocExists": web_reference_doc_exists,
            "originalCapturePrepareToolExists": original_capture_prepare_tool_exists,
            "originalCaptureImportToolExists": original_capture_import_tool_exists,
            "originalCaptureInboxExists": original_capture_inbox_exists,
            "originalRuntimePixelParityBlockingCurrentGoal": False,
        },
        "criteria": criteria,
        "futureVerification": [future_verification],
        "completionDecision": "do-not-call-update-goal" if missing else "eligible-for-update-goal",
    }


def badge(status: str) -> str:
    cls = {
        "complete": "ok",
        "partial": "warn",
        "incomplete": "bad",
        "missing": "bad",
    }.get(status, "warn")
    return f"<span class='badge {cls}'>{html.escape(status)}</span>"


def html_list(items: list[str]) -> str:
    if not items:
        return "<span class='muted'>없음</span>"
    return "<ul>" + "".join(f"<li>{html.escape(str(item))}</li>" for item in items) + "</ul>"


def html_page(report: dict[str, Any]) -> str:
    rows = []
    for item in report["criteria"]:
        artifacts = "<br>".join(
            f"{'✓' if artifact['exists'] else '✗'} <code>{html.escape(artifact['path'])}</code>"
            for artifact in item["artifacts"]
        )
        rows.append(
            "<tr>"
            f"<td><code>{html.escape(item['id'])}</code></td>"
            f"<td>{html.escape(item['requirement'])}</td>"
            f"<td>{badge(item['status'])}</td>"
            f"<td>{artifacts}</td>"
            f"<td>{html_list(item['evidence'])}</td>"
            f"<td>{html_list(item['gaps'])}</td>"
            f"<td>{html_list(item['verifier'])}</td>"
            "</tr>"
        )
    future_rows = []
    for item in report.get("futureVerification", []):
        artifacts = "<br>".join(
            f"{'✓' if artifact['exists'] else '✗'} <code>{html.escape(artifact['path'])}</code>"
            for artifact in item["artifacts"]
        )
        future_rows.append(
            "<tr>"
            f"<td><code>{html.escape(item['id'])}</code></td>"
            f"<td>{html.escape(item['requirement'])}</td>"
            f"<td>{html.escape(str(item['blockingCurrentGoal']))}</td>"
            f"<td>{html.escape(str(item['status']))}</td>"
            f"<td>{artifacts}</td>"
            f"<td>{html_list(item['evidence'])}</td>"
            f"<td>{html.escape(item['note'])}</td>"
            "</tr>"
        )
    summary = report["summary"]
    return f"""<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Battle Skill Goal Audit</title>
  <style>
    body {{ margin:0; padding:24px; background:#f8fafc; color:#172033; font-family:system-ui, sans-serif; }}
    h1 {{ margin:0 0 8px; font-size:24px; }}
    .muted {{ color:#64748b; }}
    .cards {{ display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:10px; margin:16px 0; }}
    .card {{ background:#fff; border:1px solid #dbe3ef; border-radius:8px; padding:12px; }}
    .value {{ font-size:22px; font-weight:800; }}
    table {{ width:100%; border-collapse:collapse; background:#fff; border:1px solid #dbe3ef; }}
    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; }}
    code {{ font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }}
    ul {{ margin:0; padding-left:18px; }}
    .badge {{ display:inline-block; padding:3px 8px; border-radius:999px; font-weight:700; font-size:12px; }}
    .badge.ok {{ background:#dcfce7; color:#166534; }}
    .badge.warn {{ background:#fef3c7; color:#92400e; }}
    .badge.bad {{ background:#fee2e2; color:#991b1b; }}
  </style>
</head>
<body>
  <h1>Battle Skill Goal Audit</h1>
  <p class="muted">기술 애니메이션 goal의 완료 조건을 artifact와 verifier에 직접 매핑한다.</p>
  <p><a href="../web/index.html">홈</a> · <a href="../web/battle_skill_timeline_review.html">timeline review</a> · <a href="battle_effect_runner_binding_review.html">effect binding</a></p>
  <div class="cards">
    <div class="card"><div class="muted">status</div><div class="value">{html.escape(report['status'])}</div></div>
    <div class="card"><div class="muted">criteria</div><div class="value">{summary['complete']}/{summary['criteria']}</div></div>
    <div class="card"><div class="muted">canonical rows ready</div><div class="value">{summary['canonicalRowsReady']}</div></div>
    <div class="card"><div class="muted">browser smoke</div><div class="value">{html.escape(str(summary['browserSmokeStatus']))}</div></div>
    <div class="card"><div class="muted">effect requirement rows</div><div class="value">{summary['effectExecutionRequirementRows']}</div></div>
  </div>
  <table>
    <thead><tr><th>id</th><th>requirement</th><th>status</th><th>artifacts</th><th>evidence</th><th>gaps</th><th>verifier</th></tr></thead>
    <tbody>{''.join(rows)}</tbody>
  </table>
  <h2>Future Runtime Parity Check</h2>
  <p class="muted">원본 런타임 캡처 기반 픽셀 비교는 별도 검증이며 현재 EXE canonical goal을 막지 않는다.</p>
  <table>
    <thead><tr><th>id</th><th>requirement</th><th>blocks current goal</th><th>status</th><th>artifacts</th><th>evidence</th><th>note</th></tr></thead>
    <tbody>{''.join(future_rows)}</tbody>
  </table>
</body>
</html>
"""


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--out-json", type=Path, default=OUT_JSON)
    parser.add_argument("--html-out", type=Path)
    args = parser.parse_args()
    report = build_report()
    args.out_json.parent.mkdir(parents=True, exist_ok=True)
    args.out_json.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    if args.html_out is not None:
        args.html_out.parent.mkdir(parents=True, exist_ok=True)
        args.html_out.write_text(html_page(report), encoding="utf-8")
    print(f"wrote {args.out_json}")
    return 0


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