#!/usr/bin/env python3
"""nb2 구본/신구조 vs grok 2.0 vs i2i 변환 v1/v2 — 23샷 총대조 (2026-08-13).

lane 6줄 (전부 staging v22 위):
- 직전본: grok 갤러리의 prev 사본 (nb2, staging v20 시절)
- nb2 구본: 08:08~10:09 런(구 구조 — 멀티롤 3롤/2택1) — img/ 에 이미
  변환해 둔 JPG 사본 + 동결 승자 맵(nb2_old_winners.json). ★재실행이
  recipe 를 덮으므로 이 lane 은 recipe 를 다시 읽지 않는다.
- grok 신본: artifact/20260813_grok23샷_전롤갤러리/img/ (05:15 런 사본)
- i2i v1: 구본 선정 → grok 시네마틱 변환 (20260813_grok시네마틱변환23)
- nb2 신구조: recipe 현재 파일(재실행 — 2롤 ab·b=구도 변주) — 라이브
  변환 {tag}_nb2v2_{roll}.jpg + sel 바이트 대조.
- i2i v2: 신구조 선정 → grok 시네마틱 변환 (20260813_grok시네마틱변환23v2)
"""
from __future__ import annotations

import html
import json
import re
from io import BytesIO
from pathlib import Path

from PIL import Image

ROOT = Path(__file__).resolve().parent.parent
PID = "c7e3b2e7-c545-4516-93b2-62a51a74d794"
EID = "7c902020-4451-4967-9eb6-1e53c2b9b717"
RECIPE = ROOT / "projects" / PID / "images" / EID / "scene" / "recipe"
GROK_IMG = ROOT / "artifact" / "20260813_grok23샷_전롤갤러리" / "img"
CINE_DIR = "20260813_grok시네마틱변환23"
CINE_IMG = ROOT / "artifact" / CINE_DIR / "img"
CINE2_DIR = "20260813_grok시네마틱변환23v2"
CINE2_IMG = ROOT / "artifact" / CINE2_DIR / "img"
OUT = ROOT / "artifact" / "20260813_nb2_vs_grok_3자대조"
IMG = OUT / "img"
# 구본 nb2(08:08~10:09 런) 승자·경로 동결 — 재실행 직전 recipe 바이트
# 대조로 기록(build 시점의 recipe 는 이미 신구조 산출).
NB2_OLD_WINNERS = OUT / "nb2_old_winners.json"

TARGET_SCENES = {1, 4, 5, 9, 37, 39, 56, 64, 65}
# grok 런(05:15) 완료 직후 바이트 대조로 확정한 승자 (recipe 는 이후
# nb2 런이 덮어씀 — 당시 기록의 이관)
GROK_WINNERS = {
    "S1sh1": "fix", "S1sh9": "b", "S1sh11": "a", "S4sh4": "fix",
    "S4sh9": "a", "S5sh6": "fix", "S5sh10": "fix", "S5sh13": "b",
    "S9sh10": "b", "S9sh13": "fix", "S9sh21": "fix", "S37sh1": "a",
    "S37sh4": "b", "S37sh6": "fix", "S39sh2": "a", "S39sh4": "fix",
    "S56sh1": "b", "S64sh1": "fix", "S64sh4": "a", "S64sh6": "b",
    "S65sh6": "b", "S65sh7": "fix", "S65sh10": "fix",
}


def to_jpg(src: Path, dst: Path, q: int = 88) -> None:
    im = Image.open(src)
    if im.mode != "RGB":
        im = im.convert("RGB")
    im.save(dst, "JPEG", quality=q)


def cells_html(cells, cls: str = "") -> str:
    return "".join(
        '<div class="cell{w}{c}"><a href="{s}" target="_blank">'
        '<img loading="lazy" src="{s}"></a><div class="cap">{n}'
        '{b}</div></div>'.format(
            w=" win" if win else "", c=f" {cls}" if cls else "",
            s=html.escape(src), n=html.escape(name),
            b=(' <span class="badge">선정</span>' if win else ""),
        )
        for src, name, win in cells
    )


def main() -> None:
    IMG.mkdir(parents=True, exist_ok=True)
    records = json.loads((RECIPE / "records.json").read_text(encoding="utf-8"))
    old_win = json.loads(NB2_OLD_WINNERS.read_text(encoding="utf-8"))
    tags = sorted(
        (t for t in GROK_WINNERS
         if (RECIPE / f"{t}_sel.png").is_file()),
        key=lambda t: (int(re.match(r"S(\d+)", t).group(1)),
                       int(t.split("sh")[1])),
    )
    rows = []
    for tag in tags:
        rec = records.get(tag) or {}
        prompt = str(rec.get("prompt") or "")
        m = re.search(r"SHOT TEXT \(authoritative, Korean\): (.+)", prompt)
        shot_text = m.group(1).strip() if m else ""
        sel_bytes = (RECIPE / f"{tag}_sel.png").read_bytes()

        # nb2 구본 — 동결 lane: 캐시된 JPG + 동결 승자 맵 (recipe 무접촉)
        ow = old_win.get(tag) or {}
        nb2_cells = []
        for roll in ("a", "b", "c", "fix", "sel"):
            dst = IMG / f"{tag}_nb2_{roll}.jpg"
            if not dst.is_file():
                continue
            name = ("nb2 최종(후처리)" if roll == "sel" else f"nb2 {roll}")
            nb2_cells.append(
                (f"img/{dst.name}", name,
                 roll == (ow.get("winner") or "sel")))

        # nb2 신구조 — 라이브 lane: 재실행 recipe 롤 + sel 바이트 대조
        v2_cells = []
        for roll in ("a", "b", "c", "fix"):
            p = RECIPE / f"{tag}_{roll}.png"
            if not p.is_file():
                continue
            dst = IMG / f"{tag}_nb2v2_{roll}.jpg"
            if not dst.is_file():
                to_jpg(p, dst)
            win = p.read_bytes() == sel_bytes
            v2_cells.append((f"img/{dst.name}", f"신구조 {roll}"
                             + (" (변주)" if roll == "b" else ""), win))
        if v2_cells and not any(w for _, _, w in v2_cells):
            dst = IMG / f"{tag}_nb2v2_sel.jpg"
            if not dst.is_file():
                to_jpg(RECIPE / f"{tag}_sel.png", dst)
            v2_cells.append((f"img/{dst.name}", "신구조 최종(후처리)", True))

        # grok 신본 — 기존 갤러리 사본을 상대 경로로 재사용
        gwin = GROK_WINNERS.get(tag, "?")
        grok_cells = []
        for roll in ("a", "b", "fix"):
            p = GROK_IMG / f"{tag}_{roll}.png"
            if not p.is_file():
                continue
            grok_cells.append((
                f"../20260813_grok23샷_전롤갤러리/img/{tag}_{roll}.png",
                f"grok {roll}" + (" (변주)" if roll == "b" else ""),
                roll == gwin,
            ))

        # i2i 변환 lane 2줄 — v1(구본 선정 기반)·v2(신구조 선정 기반)
        cine_cells = [
            (f"../{CINE_DIR}/img/{p.name}", "grok 시네마틱 변환 v1", False)
            for p in sorted(CINE_IMG.glob(f"{tag}_cine.*"))
        ]
        cine_row = (cells_html(cine_cells, cls="cine") if cine_cells
                    else '<div class="cap">(변환 산출 없음)</div>')
        cine2_cells = [
            (f"../{CINE2_DIR}/img/{p.name}", "grok 시네마틱 변환 v2", False)
            for p in sorted(CINE2_IMG.glob(f"{tag}_cine.*"))
        ] if CINE2_IMG.is_dir() else []
        cine2_row = (cells_html(cine2_cells, cls="cine") if cine2_cells
                     else '<div class="cap">(변환 산출 없음)</div>')
        v2_row = (cells_html(v2_cells) if v2_cells
                  else '<div class="cap">(재실행 산출 대기)</div>')

        prev_rel = f"../20260813_grok23샷_전롤갤러리/img/{tag}_prev.png"
        prev_cell = (
            f'<div class="cell"><a href="{prev_rel}" target="_blank">'
            f'<img loading="lazy" src="{prev_rel}"></a>'
            f'<div class="cap">직전본 (nb2·staging v20)</div></div>'
            if (GROK_IMG / f"{tag}_prev.png").is_file() else ""
        )

        # 구본 경로 라벨 — 동결 맵(재실행 후 records 는 신구조 것)
        route = ("2택1 (a=콘티 체인 / b=무콘티)"
                 if ow.get("route") == "2택1"
                 else "멀티롤 3롤 (같은 프롬프트 주사위)")

        rows.append(
            f'<h2>{html.escape(tag)}</h2>'
            f'<p class="shot">{html.escape(shot_text)}</p>'
            f'<div class="lane"><span class="lab">직전본</span>'
            f'<div class="row">{prev_cell}</div></div>'
            f'<div class="lane"><span class="lab">nb2 구본 — 구 구조 · '
            f'{route}</span><div class="row">{cells_html(nb2_cells)}</div></div>'
            f'<div class="lane"><span class="lab">grok 신본 — 컴팩트'
            f'·2롤 ab 변주</span><div class="row">{cells_html(grok_cells)}'
            f'</div></div>'
            f'<div class="lane"><span class="lab">구본 선정 → grok 시네마틱'
            f' i2i 변환 v1</span><div class="row">{cine_row}</div></div>'
            f'<div class="lane"><span class="lab">nb2 신구조 — 2롤 ab'
            f'(b=구도 변주)·심각도 게이트·v18 절</span>'
            f'<div class="row">{v2_row}</div></div>'
            f'<div class="lane"><span class="lab">신구조 선정 → grok 시네마틱'
            f' i2i 변환 v2</span><div class="row">{cine2_row}</div></div>'
        )

    page = (
        '<meta charset="utf-8">\n'
        "<title>nb2 vs grok 2.0 — 23샷 3자 대조</title>\n"
        "<style>\n"
        "body{margin:0;padding:24px;background:#14161a;color:#e8e8e8;"
        "font:15px/1.6 -apple-system,'Apple SD Gothic Neo',sans-serif}\n"
        "h1{font-size:20px} h2{font-size:17px;margin:38px 0 2px;"
        "color:#ffd479}\n"
        ".shot{font-size:13px;color:#aab;margin:2px 0 8px}\n"
        ".lane{margin:8px 0} .lab{font-size:12.5px;color:#8a94a6}\n"
        ".row{display:flex;gap:10px;flex-wrap:wrap;margin-top:3px}\n"
        ".cell{flex:1 1 240px;max-width:430px}\n"
        ".cell img{width:100%;border:1px solid #3a3f47;border-radius:4px;"
        "cursor:zoom-in}\n"
        ".cell.win img{border:3px solid #8aff9e}\n"
        ".cell.cine img{border:3px solid #ff5c5c}\n"
        ".cap{font-size:12.5px;color:#aab;margin-top:3px}\n"
        ".badge{background:#8aff9e;color:#10251a;border-radius:3px;"
        "padding:0 6px;font-weight:700;font-size:12px}\n"
        "</style>\n"
        "<h1>nb2 구본/신구조 vs grok 2.0 + 시네마틱 i2i v1/v2 — 23샷 "
        "총대조 (2026-08-13)</h1>\n"
        "<p>전부 staging v22(포즈≠앵글) 위. 샷마다 6줄: ①직전본 ②nb2 "
        "구본(08:08 런 — 구 구조: 멀티롤 3롤 또는 2택1, a·b 구도 수렴 "
        "가능) ③grok 신본(컴팩트·2롤 ab 변주) ④구본 선정→grok i2i 변환 "
        "v1(붉은 테두리) ⑤<b>nb2 신구조</b>(재실행 — 전 경로 2롤 ab·b="
        "구도 변주 무조건, critical 만 fix, identity 참조 가림 우선, "
        "몸-지지 절) ⑥신구조 선정→grok i2i 변환 v2(붉은 테두리). 초록 "
        "테두리=각 런의 최종 선정. i2i 변환=장소·인물·순간 고정, 영화 "
        "키프레임처럼 재구성·재조명(원본 프롬프트 미사용).</p>\n"
        + "\n".join(rows)
    )
    (OUT / "index.html").write_text(page, encoding="utf-8")
    print(f"3자 대조 갤러리: 샷 {len(tags)}개 → {OUT}")


if __name__ == "__main__":
    main()
