"""최종 변환 문안 A/B 갤러리 — 광원과 전경 물체를 되찾는가.

실측(`ab_cine_drift.py`, 완주 판 7쌍·좌우 2회):

    light_source  7/7 바뀜   ← 꺼져 있던 손전등이 켜진 샷이 둘
    framing       7/7 바뀜   ← 문안이 명시로 허락한다
    objects       4/7 바뀜   ← 문안이 **금지**하는데도 생긴다
    place  0/7 · people 0/7  ← 장소·인물은 흔들리지 않는다

★현행 문안 한 문단이 자기모순이다 — 「사물을 더하지 마라」 바로 다음 줄에
 「전경 배치는 바꿔도 된다」가 있고, 허가 쪽이 이겼다.

    arm A  현행 (기준선 — 완주 판 1장 + 새로 2회, 흔들림을 먼저 본다)
    arm C  광원을 소스에 고정 (대비·falloff·공기감은 그대로 재량)
    arm E  전경 배치 허가만 제거 (카메라 자유는 유지)
    arm D  C+E

★봐 주실 물음은 「달라졌는가」가 아니다 — **소스에 없던 빛과 물건이
 생겼는가**다. 카메라가 움직이는 것은 설계상 허용된 자유라 따로 본다.

usage: build_cine_clause_gallery.py
"""
import html
import subprocess
from pathlib import Path

ROOT = Path("/Users/manta/Documents/Projects/TheRoad-I1")
AB = ROOT / "artifact" / "20260825_cine_clause_ab"
RECIPE = (ROOT / "projects/da049582-2c6d-492c-979d-f468d61bab6e/images"
          "/fb7a883f-baac-4145-9131-732ce628d474/scene/recipe")
ARM_NOTE = {
    "A": "현행 그대로",
    "C": "광원을 소스에 고정",
    "E": "전경 배치 허가 제거",
    "D": "C+E",
}
# 축별 판정(ab_cine_drift.py 산출) — 있으면 그림 밑에 얹는다. 그림만 보면
# 어느 축이 걸렸는지 눈으로 다 못 가른다.
DRIFT = Path("/private/tmp/claude-501/-Users-manta-Documents-Projects-"
             "TheRoad-I1/45f0211b-1a4b-42fe-817f-737e399ebc44/scratchpad"
             "/cine_clause_drift.json")
AXIS_KO = {"place": "장소", "people": "인물", "objects": "사물",
           "moment": "순간", "light_source": "광원", "framing": "구도"}


def load_drift():
    """{이미지 stem: [(축, 판정, 설명)]} — 없으면 빈 dict."""
    import json
    if not DRIFT.is_file():
        return {}
    out = {}
    for r in json.loads(DRIFT.read_text(encoding="utf-8")):
        out.setdefault(r["shot"], []).append(
            (r["axis"], r["verdict"], r["note"]))
    return out


def verdict_html(rows):
    if not rows:
        return ""
    chips = []
    for axis, verdict, note in rows:
        if verdict == "같음":
            continue
        cls = "vch" if verdict == "★바뀜" else "vsp"
        chips.append(f'<span class="{cls}" title="{html.escape(note)}">'
                     f'{AXIS_KO.get(axis, axis)}</span>')
    if not chips:
        return '<div class="vv"><span class="vok">여섯 축 모두 같음</span></div>'
    return '<div class="vv">' + "".join(chips) + '</div>'


def lan_ip() -> str:
    try:
        return subprocess.run(["ipconfig", "getifaddr", "en0"],
                              capture_output=True, text=True,
                              timeout=5).stdout.strip() or "127.0.0.1"
    except Exception:  # noqa: BLE001
        return "127.0.0.1"


def main() -> int:
    if not AB.is_dir():
        print(f"★산출이 없다 — 먼저 ab_cine_clause.py 를 돌려라 ({AB})")
        return 1
    imgs = sorted(AB.glob("S*_?_r?.png"))
    shots = sorted({p.name.split("_", 1)[0] for p in imgs})
    if not shots:
        print("★A/B 산출 png 가 없다")
        return 1

    # 원본과 완주 판 변환본을 갤러리 안으로 복사한다 — 같은 자리에서 본다.
    for tag in shots:
        for src, dst in ((RECIPE / f"{tag}_sel.png", AB / f"{tag}_00src.png"),
                         (RECIPE / f"{tag}_cine.png", AB / f"{tag}_01run.png")):
            if src.is_file() and not dst.is_file():
                dst.write_bytes(src.read_bytes())

    drift = load_drift()
    rows = []
    for tag in shots:
        cells = [
            f'<figure><span class="tag tS">원본 sel</span>'
            f'<img src="{tag}_00src.png"><figcaption>변환에 들어간 그림'
            f'</figcaption></figure>',
            f'<figure><span class="tag tR">완주 판</span>'
            f'<img src="{tag}_01run.png"><figcaption>현행 문안으로 실제 나온 '
            f'최종본</figcaption></figure>',
        ]
        for p in sorted(AB.glob(f"{tag}_?_r?.png")):
            arm = p.name.split("_")[1]
            rnd = p.name.split("_")[2].split(".")[0]
            cells.append(
                f'<figure><span class="tag t{arm}">{arm} {rnd}</span>'
                f'<img src="{p.name}"><figcaption>'
                f'{html.escape(ARM_NOTE.get(arm, arm))}</figcaption>'
                f'{verdict_html(drift.get(p.stem, []))}</figure>')
        rows.append(f'<section class="shot"><h2>{html.escape(tag)}</h2>'
                    f'<div class="grid">{"".join(cells)}</div></section>')

    doc = f"""<meta charset="utf-8">
<title>최종 변환 문안 A/B</title>
<style>
  body {{ background:#0e1116; color:#e6e9ef; margin:0; padding:28px 32px 60px;
          font:14px/1.6 -apple-system,'Apple SD Gothic Neo',sans-serif; }}
  h1 {{ font-size:21px; margin:0 0 8px; }}
  .lede {{ color:#aab2c0; max-width:900px; margin:0 0 16px; }}
  .ask {{ background:#1b2430; border-left:3px solid #4a90d9; padding:12px 16px;
          border-radius:0 8px 8px 0; max-width:900px; margin:0 0 22px; }}
  table {{ border-collapse:collapse; margin:0 0 24px; font-size:13px; }}
  td,th {{ border:1px solid #2a2f3a; padding:5px 12px; text-align:left; }}
  th {{ color:#8f97a8; font-weight:600; }}
  .shot {{ margin:0 0 34px; }}
  h2 {{ font-size:17px; margin:0 0 10px; }}
  .grid {{ display:grid; grid-template-columns:repeat(5,1fr); gap:10px; }}
  @media (max-width:1200px) {{ .grid {{ grid-template-columns:repeat(2,1fr); }} }}
  figure {{ margin:0; position:relative; }}
  figure img {{ width:100%; border-radius:6px; display:block; background:#000; }}
  .tag {{ position:absolute; top:8px; left:8px; z-index:1; font-size:12px;
          font-weight:700; padding:3px 9px; border-radius:5px; color:#fff; }}
  .tS {{ background:#555c6b; }} .tR {{ background:#8a5a2b; }}
  .tA {{ background:#2b6cb0; }} .tC {{ background:#38795b; }}
  .tE {{ background:#7a3f8f; }} .tD {{ background:#a03a4a; }}
  figcaption {{ font-size:12px; color:#c3c9d6; padding:6px 2px 0; }}
  .vv {{ padding:5px 2px 0; display:flex; flex-wrap:wrap; gap:4px; }}
  .vv span {{ font-size:11px; font-weight:700; padding:2px 7px;
              border-radius:9px; cursor:help; }}
  .vch {{ background:#4a1d24; color:#f0a0ab; }}
  .vsp {{ background:#3a3320; color:#d8c68a; }}
  .vok {{ background:#1d3524; color:#7fd39b; }}
</style>
<h1>최종 변환 문안 A/B — 없던 빛과 물건이 생기는 것</h1>
<p class="lede">
  최종 i2i 변환은 <b>아무 판정 없이</b> 최종본이 됩니다
  (<code>still_recipe_service.py:4536</code>). 완주 판 7쌍을 축별로 재니
  <b>장소와 인물은 7/7 유지</b>되는데 <b>광원이 7/7 바뀌고</b> 없던 전경 물건이
  <b>4/7</b> 에서 생겼습니다. 손전등이 꺼져 있던 샷 둘은 변환본에서 켜져 있습니다.
</p>
<div class="ask">
  <b>봐 주실 물음</b> — 「달라졌는가」가 아니라
  <b>「소스에 없던 빛과 물건이 생겼는가」</b>입니다.
  카메라가 움직이는 것은 문안이 명시로 허락한 자유라 이번 물음 밖입니다.
  <br><br>
  그림 밑의 <span class="vch">붉은 딱지</span>는 VLM 이 <b>바뀌었다</b>고
  본 축, <span class="vsp">노란 딱지</span>는 좌우를 바꿔 물었을 때
  <b>판정이 엇갈린</b> 축입니다(딱지에 마우스를 올리면 무엇이 달라졌다는
  설명이 나옵니다). 실측 결론은 <b>광원 잠금 문안이 통하지 않았다</b>는
  것입니다 — arm C·D 에 <i>"keep … whether it is switched on"</i> 을
  넣었는데도 16칸 중 13칸에서 광원이 바뀌었고, 그 중 14칸의 정체가 같습니다:
  <b>꺼져 있던 손전등이 켜진다</b>.
</div>
<table>
  <tr><th>arm</th><th>무엇을 바꿨나</th></tr>
  <tr><td>A</td><td>현행 그대로 — 기준선(모델 흔들림을 먼저 본다)</td></tr>
  <tr><td>C</td><td>「소스에 보이는 빛이 이 씬의 빛이다 — 자리·종류·색·켜짐
      여부를 지키고 대비와 falloff 만 만든다」</td></tr>
  <tr><td>E</td><td>「전경 배치를 바꿔도 된다」는 허가만 제거 — 카메라 각도·
      높이·거리 자유는 그대로</td></tr>
  <tr><td>D</td><td>C+E</td></tr>
</table>
{''.join(rows)}
"""
    (AB / "index.html").write_text(doc, encoding="utf-8")
    print(f"샷 {len(shots)} · A/B 이미지 {len(imgs)}장")
    print(f"→ http://{lan_ip()}:8940/artifact/20260825_cine_clause_ab/")
    return 0


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