"""수정 전/후 비교 갤러리 — 생성된 전체 롤 · 선정본 · 수정본 · 선정본↔수정본 대조.

8940 이 프로젝트 루트를 서빙하므로 이미지는 루트 기준 절대경로로 건다.
재판정 결과 파일이 있으면 판정(원본승/수정본승)까지 함께 표기한다.
"""
from __future__ import annotations

import html
import json
import re
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
PROJ = "e716bafb-24bb-42b7-aea0-fdb383844ee8"
EPI = "d6a9aa85-b75e-400c-980c-4ee7e876a15b"
RECIPE = ROOT / f"projects/{PROJ}/images/{EPI}/scene/recipe"
WEB = f"/projects/{PROJ}/images/{EPI}/scene/recipe"
OUTDIR = ROOT / "artifact/20260806_수정본_재판정"
REJUDGE = OUTDIR / "rejudge_result.json"

CSS = """
:root{--bg:#12141a;--card:#1b1f28;--line:#2c323f;--fg:#e6e9ef;--dim:#98a1b3;
--win:#3ddc84;--lose:#ff6b6b;--sel:#ffd166}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--fg);
font:14px/1.6 -apple-system,BlinkMacSystemFont,"Apple SD Gothic Neo",sans-serif}
header{position:sticky;top:0;z-index:9;background:#0e1015ee;
backdrop-filter:blur(8px);border-bottom:1px solid var(--line);padding:14px 20px}
h1{margin:0 0 6px;font-size:17px}
.sum{color:var(--dim);font-size:13px}
.sum b{color:var(--fg)}
.filters{margin-top:10px;display:flex;gap:8px;flex-wrap:wrap}
button{background:var(--card);color:var(--fg);border:1px solid var(--line);
border-radius:999px;padding:5px 13px;cursor:pointer;font-size:12px}
button.on{background:var(--fg);color:#12141a;font-weight:600}
main{padding:18px 20px 80px;max-width:1700px;margin:0 auto}
.still{background:var(--card);border:1px solid var(--line);border-radius:12px;
padding:16px;margin-bottom:22px}
.hd{display:flex;gap:12px;align-items:baseline;flex-wrap:wrap;margin-bottom:4px}
.hd .id{font-size:16px;font-weight:700}
.hd .meta{color:var(--dim);font-size:12px}
.desc{color:var(--dim);font-size:13px;margin:2px 0 12px}
.tag{display:inline-block;border-radius:5px;padding:2px 8px;font-size:11px;
font-weight:700;letter-spacing:.02em}
.t-sel{background:var(--sel);color:#12141a}
.t-win{background:var(--win);color:#12141a}
.t-lose{background:var(--lose);color:#fff}
.t-none{background:#39404f;color:var(--dim)}
.sec{font-size:11px;letter-spacing:.08em;color:var(--dim);
text-transform:uppercase;margin:14px 0 7px}
.rolls{display:flex;gap:10px;flex-wrap:wrap}
.roll{width:250px}
.roll img{width:100%;border-radius:7px;display:block;border:2px solid transparent}
.roll.selected img{border-color:var(--sel)}
.roll .cap{font-size:11px;color:var(--dim);margin-top:4px}
.cmp{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.cmp figure{margin:0}
.cmp img{width:100%;border-radius:8px;display:block;border:2px solid var(--line)}
.cmp .a img{border-color:#5b6478}
figcaption{font-size:12px;margin-top:5px;display:flex;gap:7px;align-items:center}
details{margin-top:11px;border-top:1px solid var(--line);padding-top:9px}
summary{cursor:pointer;color:var(--dim);font-size:12px}
ul.iss{margin:8px 0 0;padding-left:18px;font-size:13px}
ul.iss li{margin-bottom:4px}
pre{white-space:pre-wrap;background:#0e1015;border:1px solid var(--line);
border-radius:7px;padding:10px;font-size:11.5px;color:#c8cfdc;overflow-x:auto}
.verd{font-size:12.5px;color:var(--dim);margin-top:6px}
.verd b{color:var(--fg)}
@media(max-width:900px){.cmp{grid-template-columns:1fr}}
"""

JS = """
const btns=[...document.querySelectorAll('button[data-f]')];
btns.forEach(b=>b.onclick=()=>{
  btns.forEach(x=>x.classList.toggle('on',x===b));
  const f=b.dataset.f;
  document.querySelectorAll('.still').forEach(s=>{
    s.style.display=(f==='all'||s.dataset.f.split(' ').includes(f))?'':'none';});
});
"""


def stem_key(s: str):
    m = re.match(r"S(\d+)sh(\d+)", s)
    return (int(m.group(1)), int(m.group(2))) if m else (9999, 9999)


def main() -> None:
    records = json.loads((RECIPE / "records.json").read_text("utf-8"))
    rej = {}
    if REJUDGE.exists():
        try:
            rej = json.loads(REJUDGE.read_text("utf-8")).get("results", {})
        except Exception:
            rej = {}

    # 샷 설명 (DB 없이 기록만으로) — prompt 의 SHOT TEXT 줄
    def shot_text(p: str) -> str:
        m = re.search(r"SHOT TEXT \(authoritative, Korean\):\s*(.+)", p or "")
        return m.group(1).strip() if m else ""

    items = []
    for stem, r in sorted(records.items(), key=lambda kv: stem_key(kv[0])):
        if not isinstance(r, dict) or "selected" not in r:
            continue
        if not re.match(r"S\d+sh\d+$", stem):
            continue
        items.append((stem, r))

    n_fix = sum(1 for _, r in items if "fix_prompt" in r)
    rj = [v for v in rej.values() if "winner" in v]
    fix_won = sum(1 for v in rj if v.get("fix_won"))
    orig_won = len(rj) - fix_won

    parts = [
        '<meta charset="utf-8">',
        "<title>수정 전/후 비교 — C 실행 (8/5~6)</title>",
        f"<style>{CSS}</style>",
        "<header>",
        "<h1>수정 전/후 비교 — C 실행 (금월도 v40, 8/5~6)</h1>",
        f'<div class="sum">스틸 <b>{len(items)}</b>개 · i2i 수정 거침 '
        f'<b>{n_fix}</b>개 · 재판정 완료 <b>{len(rj)}</b>개'
        + (f' → 수정본 승 <b>{fix_won}</b> / '
           f'<b>원본 승 {orig_won}</b>' if rj else " (재판정 진행 중)")
        + "</div>",
        '<div class="filters">'
        '<button data-f="all" class="on">전체</button>'
        '<button data-f="fixed">수정 있음</button>'
        '<button data-f="nofix">수정 없음</button>'
        '<button data-f="origwin">재판정: 원본 승</button>'
        '<button data-f="fixwin">재판정: 수정본 승</button>'
        "</div></header><main>",
    ]

    for stem, r in items:
        sel = str(r.get("selected", "")).strip()
        has_fix = "fix_prompt" in r
        v = rej.get(stem) or {}
        flags = ["fixed" if has_fix else "nofix"]
        if v.get("winner") == "A":
            flags.append("origwin")
        elif v.get("winner") == "B":
            flags.append("fixwin")

        crit = r.get("critique") or {}
        issues = crit.get("issues") or r.get("issues") or []
        st = html.escape(shot_text(
            (r.get("roll_prompts") or {}).get(sel) or r.get("prompt", "")))

        parts.append(f'<section class="still" data-f="{" ".join(flags)}">')
        badge = ""
        if v.get("winner") == "A":
            badge = '<span class="tag t-lose">재판정: 원본 승 — 수정본 개악</span>'
        elif v.get("winner") == "B":
            badge = '<span class="tag t-win">재판정: 수정본 승</span>'
        elif has_fix:
            badge = '<span class="tag t-none">재판정 대기</span>'
        parts.append(
            f'<div class="hd"><span class="id">{stem}</span>'
            f'<span class="meta">{html.escape(str(r.get("ref_mode","")))} '
            f'· 선정={sel} · 지적 {len(issues)}건</span>{badge}</div>')
        if st:
            parts.append(f'<div class="desc">{st}</div>')

        # ① 생성된 전체 롤
        rolls = sorted(RECIPE.glob(f"{stem}_[abc].png"))
        if rolls:
            parts.append('<div class="sec">① 생성된 전체 후보 (노란 테두리 = 선정)</div>')
            parts.append('<div class="rolls">')
            for p in rolls:
                lab = p.stem.split("_")[-1].upper()
                cls = "roll selected" if lab == sel else "roll"
                cap = f"후보 {lab}" + (" — 선정" if lab == sel else "")
                parts.append(
                    f'<div class="{cls}"><img loading="lazy" '
                    f'src="{WEB}/{p.name}"><div class="cap">{cap}</div></div>')
            parts.append("</div>")

        # ② 선정본 ↔ 수정본 대조
        if has_fix:
            orig = f"{WEB}/{stem}_{sel.lower()}.png"
            fixed = f"{WEB}/{stem}_fix.png"
            parts.append('<div class="sec">② 선정된 원본 ↔ 수정본 (최종 확정본)</div>')
            wa = ('<span class="tag t-win">승</span>' if v.get("winner") == "A"
                  else "")
            wb = ('<span class="tag t-win">승</span>' if v.get("winner") == "B"
                  else "")
            parts.append(
                f'<div class="cmp">'
                f'<figure class="a"><img loading="lazy" src="{orig}">'
                f'<figcaption>수정 전 — 선정 원본 (후보 {sel}){wa}</figcaption></figure>'
                f'<figure class="b"><img loading="lazy" src="{fixed}">'
                f'<figcaption>수정 후 — DB 최종본{wb}</figcaption></figure></div>')
        else:
            parts.append(
                '<div class="sec">② 수정 없음 — 선정본이 곧 최종본</div>')

        if v.get("combined"):
            tot = (v["combined"].get("totals") or {})
            agree = v["combined"].get("agreement")
            name = {"A": "원본", "B": "수정본"}
            line = (f'<b>재판정 합산</b> 원본 {tot.get("A","-")} : '
                    f'수정본 {tot.get("B","-")}'
                    f'{"  (정순·역순 일치)" if agree else "  (정순·역순 불일치)"}')
            rows = []
            for tag, key in (("정순", "forward_normalized"),
                             ("역순", "reverse_normalized")):
                for c in ((v.get(key) or {}).get("verdicts") or []):
                    rows.append(
                        f'{tag} · {name.get(c.get("label"), c.get("label"))} '
                        f'{c.get("score")}점 — '
                        f'{html.escape(str(c.get("verdict_ko", "")))}')
            parts.append(f'<div class="verd">{line}<br>'
                         + "<br>".join(rows) + "</div>")

        if issues:
            parts.append(
                f"<details><summary>심판이 지적한 결함 {len(issues)}건 "
                f"+ 수정 지시문</summary><ul class='iss'>")
            for i in issues:
                parts.append(f"<li>{html.escape(str(i.get('issue_ko','')))}</li>")
            parts.append("</ul>")
            if r.get("fix_prompt"):
                parts.append(
                    f"<pre>{html.escape(str(r['fix_prompt']))}</pre>")
            parts.append("</details>")
        parts.append("</section>")

    parts.append(f"</main><script>{JS}</script>")
    OUTDIR.mkdir(parents=True, exist_ok=True)
    (OUTDIR / "index.html").write_text("\n".join(parts), "utf-8")
    print(f"작성 완료: {OUTDIR/'index.html'}  (스틸 {len(items)} · 수정 {n_fix} "
          f"· 재판정 {len(rj)})")


if __name__ == "__main__":
    main()
