"""Opus 전담 파이프라인 결과 갤러리.

스틸마다 보여주는 것
  ① 후보 전체 — Opus 가 고른 것에 테두리, 기존(Gemini) 선정과 다르면 표시
  ② Opus 가 후보마다 쓴 세 축 읽기 (방향 · 복잡 구조물 내부 공간 · 엔티티)
  ③ 수정 판단 — 심각도별 결함 목록 + should_repair 사유 + Opus 가 저작한 수정문
  ④ 수정한 샷은 [수정 전 ↔ 수정 후] 대조 + 재판정 판정문
"""
from __future__ import annotations

import html
import json
import re
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
PROJ = "e716bafb-24bb-42b7-aea0-fdb383844ee8"
EPI = "d6a9aa85-b75e-400c-980c-4ee7e876a15b"
RECIPE_WEB = f"/projects/{PROJ}/images/{EPI}/scene/recipe"
RECIPE = ROOT / f"projects/{PROJ}/images/{EPI}/scene/recipe"

CSS = """
:root{--bg:#101319;--card:#191d26;--line:#2b313d;--fg:#e7eaf0;--dim:#98a1b3;
--pick:#ffd166;--win:#3ddc84;--lose:#ff6b6b;--warn:#ffa94d}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--fg);
font:14px/1.65 -apple-system,BlinkMacSystemFont,"Apple SD Gothic Neo",sans-serif}
header{position:sticky;top:0;z-index:9;background:#0b0d12ee;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:#101319;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:10px;align-items:baseline;flex-wrap:wrap;margin-bottom:3px}
.hd .id{font-size:16px;font-weight:700}.hd .meta{color:var(--dim);font-size:12px}
.tag{border-radius:5px;padding:2px 8px;font-size:11px;font-weight:700}
.t-chg{background:var(--warn);color:#101319}.t-fix{background:var(--win);color:#101319}
.t-rev{background:var(--lose);color:#fff}.t-non{background:#39404f;color:var(--dim)}
.shot{color:var(--dim);font-size:13px;margin:2px 0 12px}
.sec{font-size:11px;letter-spacing:.08em;color:var(--dim);text-transform:uppercase;
margin:15px 0 7px}
.rolls{display:flex;gap:12px;flex-wrap:wrap;align-items:flex-start}
.roll{width:300px}
.roll img{width:100%;border-radius:7px;display:block;border:2px solid transparent}
.roll.pick img{border-color:var(--pick)}
.roll .cap{font-size:12px;margin-top:5px;font-weight:600}
.roll .ax{font-size:11.5px;color:#c2cad8;margin-top:4px;line-height:1.55}
.roll .ax b{color:var(--dim);font-weight:600}
.roll .vio{font-size:11.5px;color:#ffb3b3;margin-top:4px}
.cmp{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.cmp img{width:100%;border-radius:8px;display:block;border:2px solid var(--line)}
figure{margin:0}figcaption{font-size:12px;margin-top:5px}
details{margin-top:11px;border-top:1px solid var(--line);padding-top:9px}
summary{cursor:pointer;color:var(--dim);font-size:12px}
ul{margin:8px 0 0;padding-left:18px;font-size:13px}li{margin-bottom:4px}
.sev-critical{color:#ff8080;font-weight:700}.sev-major{color:var(--warn)}
.sev-minor{color:var(--dim)}
pre{white-space:pre-wrap;background:#0b0d12;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:#c2cad8;margin-top:6px}
@media(max-width:900px){.cmp{grid-template-columns:1fr}}
"""

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


def key(s: str):
    m = re.findall(r"\d+", s)
    return (int(m[0]), int(m[1])) if len(m) >= 2 else (9999, 9999)


def esc(x) -> str:
    return html.escape(str(x or ""))


def main() -> None:
    res_path = Path(sys.argv[1])
    outdir = res_path.parent
    data = json.loads(res_path.read_text("utf-8"))
    items = sorted(((k, v) for k, v in data.items() if "select" in v),
                   key=lambda kv: key(kv[0]))
    errs = [k for k, v in data.items() if "select" not in v]

    n_chg = sum(1 for _, v in items
                if v["select"]["winner"] != v.get("prior_selected_roll"))
    n_fix = sum(1 for _, v in items if v.get("final_is_repaired"))
    n_try = sum(1 for _, v in items if v.get("rejudge"))
    n_rev = n_try - n_fix

    P = ['<meta charset="utf-8">',
         "<title>Opus 전담 판정 — 후보 선정 · 수정 지휘</title>",
         f"<style>{CSS}</style>", "<header>",
         "<h1>Opus 전담 판정 — 후보 선정 · 결함 · 수정 지휘</h1>",
         f'<div class="sum">스틸 <b>{len(items)}</b> · '
         f'기존 선정과 달라짐 <b>{n_chg}</b> · '
         f'수정 시도 <b>{n_try}</b> → 채택 <b>{n_fix}</b> / 개악 복귀 <b>{n_rev}</b>'
         + (f" · 오류 <b>{len(errs)}</b>" if errs else "") + "</div>",
         '<div class="filters"><button data-f="all" class="on">전체</button>'
         '<button data-f="changed">선정 바뀜</button>'
         '<button data-f="fixed">수정 확정</button>'
         '<button data-f="reverted">수정 개악·복귀</button>'
         '<button data-f="untouched">손대지 않음</button></div></header><main>']

    for stem, v in items:
        sel, crit = v["select"], v["critique"]
        pick, prior = sel["winner"], v.get("prior_selected_roll")
        flags = []
        flags.append("changed" if pick != prior else "same")
        if v.get("final_is_repaired"):
            flags.append("fixed")
        elif v.get("rejudge"):
            flags.append("reverted")
        else:
            flags.append("untouched")

        badges = ""
        if pick != prior:
            badges += (f'<span class="tag t-chg">선정 바뀜 '
                       f'{esc(prior)} → {esc(pick)}</span> ')
        if v.get("final_is_repaired"):
            badges += '<span class="tag t-fix">수정 확정</span> '
        elif v.get("rejudge"):
            badges += '<span class="tag t-rev">수정 개악 → 원본 복귀</span> '
        else:
            badges += '<span class="tag t-non">손대지 않음</span> '

        P.append(f'<section class="still" data-f="{" ".join(flags)}">')
        P.append(f'<div class="hd"><span class="id">{esc(stem)}</span>'
                 f'<span class="meta">Opus 선정 {esc(pick)} · '
                 f'기존 {esc(prior)}</span>{badges}</div>')
        P.append(f'<div class="shot">{esc(sel.get("why"))}</div>')

        P.append('<div class="sec">① 후보 전체 — 노란 테두리 = Opus 선정</div>'
                 '<div class="rolls">')
        for r in sel["readings"]:
            lab = r["label"]
            cls = "roll pick" if lab == pick else "roll"
            img = f"{RECIPE_WEB}/{stem}_{lab.lower()}.png"
            vio = ("".join(f"<div class='vio'>★ {esc(x)}</div>"
                           for x in r.get("hard_violations") or []))
            P.append(
                f'<div class="{cls}"><img loading="lazy" src="{img}">'
                f'<div class="cap">후보 {esc(lab)} · {r["score"]}점'
                + (" — 선정" if lab == pick else "") + "</div>"
                f'<div class="ax"><b>방향</b> {esc(r["direction"])}</div>'
                f'<div class="ax"><b>공간</b> {esc(r["built_space"])}</div>'
                f'<div class="ax"><b>엔티티</b> {esc(r["entities"])}</div>'
                f'{vio}</div>')
        P.append("</div>")

        if v.get("rejudge"):
            P.append('<div class="sec">② 수정 전 ↔ 수정 후</div><div class="cmp">')
            fx = Path(v["fixed"]).name
            P.append(
                f'<figure><img loading="lazy" '
                f'src="{RECIPE_WEB}/{stem}_{pick.lower()}.png">'
                f'<figcaption>수정 전 (후보 {esc(pick)})</figcaption></figure>'
                f'<figure><img loading="lazy" src="images/{fx}">'
                f'<figcaption>Opus 지휘 수정본'
                + (" — 채택" if v.get("final_is_repaired") else " — 개악 판정, 폐기")
                + "</figcaption></figure></div>")
            rj = v["rejudge"]
            rows = []
            for tag, k in (("정순", "forward"), ("역순", "reverse")):
                name = ({"A": "원본", "B": "수정본"} if tag == "정순"
                        else {"A": "수정본", "B": "원본"})
                for c in rj[k]["verdicts"]:
                    rows.append(f'{tag} · {name[c["label"]]} {c["score"]}점 — '
                                f'{esc(c["verdict_ko"])}')
            P.append(f'<div class="verd"><b>재판정</b> 수정본 득표 '
                     f'{rj["fix_votes"]}/2<br>' + "<br>".join(rows) + "</div>")

        iss = crit.get("issues") or []
        P.append(f"<details><summary>결함 {len(iss)}건 · 수정 판단 "
                 f"(should_repair={crit.get('should_repair')})</summary><ul>")
        for i in iss:
            sv = i.get("severity", "minor")
            rg = " · 재생성 필요" if i.get("regenerate_needed") else ""
            P.append(f'<li><span class="sev-{sv}">[{sv}]</span>{rg} '
                     f'{esc(i.get("issue_ko"))}</li>')
        P.append("</ul>")
        P.append(f'<div class="verd"><b>판단 사유</b> '
                 f'{esc(crit.get("decision_reason"))}</div>')
        if crit.get("fix_prompt_en"):
            P.append("<div class='verd'><b>Opus 가 저작한 수정 프롬프트</b></div>"
                     f"<pre>{esc(crit['fix_prompt_en'])}</pre>")
        P.append("</details></section>")

    P.append(f"</main><script>{JS}</script>")
    (outdir / "index.html").write_text("\n".join(P), "utf-8")
    print(f"작성 완료: {outdir/'index.html'}  (스틸 {len(items)} · "
          f"선정 바뀜 {n_chg} · 수정 채택 {n_fix} · 개악 복귀 {n_rev})")


if __name__ == "__main__":
    main()
