"""s21 remake 2차 — 배경 참조 완전 제외 재생성 (프롬프트=수동 수정본).

  - 대상: remake 1차와 같은 5샷. 참조=배경 완전 제외, 엔티티(passport)만
    유지(구조 필드 판단) — 4샷=참조 0(순수 T2I), S13류 passport 샷=1장.
  - 프롬프트: plans/nobg_prompts/<샷키>.txt (Claude 수동 수정본 — 장면
    본문 문단 불변, 배경 참조[image 1] 지시 제거/축약, 참조 0 샷은 참조
    일반 지시도 정리, passport 샷은 image 2→1 재번호). 코드는 로드+검증만.
  - 검증(assert): 참조 0 샷=프롬프트에 reference/image N 토큰 0 /
    passport 샷=구 번호 잔재 0+참조 1장 / 전 샷=원본 장면 문단 보존
    (passport 샷은 재번호 치환 후 비교).
  - 산출: out/shots_outdoor/remake/<샷키>_nobg.png
    + plans/outdoor_remake_nobg.json + remake_nobg.html (3열 비교).
  - DB write 0, 커밋 금지(scratchpad).
사용: backend/.venv/bin/python s21_remake_nobg.py
"""
import html as html_mod
import json
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent))
import forest_lib as F  # noqa: E402

ROOT = Path("/Users/manta/Documents/Projects/TheRoad-I1")
EXP = Path(__file__).parent
OUT = F.OUT / "shots_outdoor" / "remake"
PROMPTS = EXP / "plans" / "nobg_prompts"
PAGE = EXP / "remake_nobg.html"


def scene_body(prompt):
    """원본 콜 프롬프트 고정 구조에서 장면 본문 문단 추출.

    구조: 헤더들 → 장면 문단 → 'Generate one image:' 불릿. 장면 문단 =
    'Generate one image:' 직전의 마지막 문단 (빈 줄 구분).
    """
    head = prompt.split("Generate one image:")[0]
    paras = [p.strip() for p in head.split("\n\n") if p.strip()]
    return paras[-1]


def main():
    base = json.loads((EXP / "plans" / "outdoor_remake_bg.json")
                      .read_text("utf-8"))
    OUT.mkdir(parents=True, exist_ok=True)
    results = []
    for j in base["jobs"]:
        key = j["shot_key"]
        manual = (PROMPTS / f"{key}.txt").read_text("utf-8").strip()
        # 유지 참조 = 배경(swapped)·스케치(dropped) 제외분 (passport 등)
        kept = [r for r in j["refs"]
                if not r["swapped"] and not r.get("dropped")]
        excluded = [r for r in j["refs"] if r not in kept]
        low = manual.lower()
        if not kept:
            for tok in ("reference image", "image 1", "image 2"):
                assert tok not in low, f"{key}: 참조 0인데 '{tok}' 잔재"
        else:
            assert f"image {len(kept) + 1}" not in low, \
                f"{key}: 구 번호 잔재 (참조 {len(kept)}장인데 초과 번호 언급)"
            assert "image 1" in low, f"{key}: 유지 참조 재번호 언급 없음"
        # 장면 본문 문단 보존 검증 (passport 샷은 재번호 치환 후 비교)
        body = scene_body(j["prompt"])
        body_renum = body.replace("Image 2", "Image 1")
        assert body in manual or body_renum in manual, \
            f"{key}: 원본 장면 문단이 수동본에서 변형됨"
        labeled = [(r["label"], ROOT / r["file_path"]) for r in kept]
        out = OUT / f"{key}_nobg.png"
        F.img_nb2(f"remake_nobg_{key}", manual, labeled, out_path=out)
        results.append({
            "shot_key": key, "asset_id": j["asset_id"],
            "orig_file": j["orig_file"], "bgswap_file": j["out"],
            "kept_refs": kept, "excluded_refs": excluded,
            "out": out.name, "prompt": manual})
    F.save_plan("outdoor_remake_nobg", {"jobs": results})
    build_page(results)
    print(f"nobg {len(results)}장 -> {OUT}")
    print(f"page -> {PAGE}")


def build_page(results):
    def _esc(t):
        return html_mod.escape(str(t or ""))

    def _ref_src(x):
        return f"out/shots_outdoor/refs/{x['pipeline_role']}_{x['id'][:8]}.png"

    secs = []
    for r in results:
        kept_html = "".join(
            f"<div class='ref'><img src='{_esc(_ref_src(x))}'>"
            f"<p class='rlabel'>유지 · {_esc(x['pipeline_role'])}</p></div>"
            for x in r["kept_refs"]) + "".join(
            f"<div class='ref dropped'><img src='{_esc(_ref_src(x))}'>"
            f"<p class='rlabel'>제외 · {_esc(x['pipeline_role'])}</p></div>"
            for x in r["excluded_refs"])
        n_kept = len(r["kept_refs"])
        secs.append(f"""
<h2>{_esc(r['shot_key'])} <span class='sub'>(무배경 참조 {n_kept}장{
    '' if n_kept else ' — 순수 T2I'})</span></h2>
<div class='trio'>
  <div><img src='out/shots_outdoor/{_esc(r['orig_file'])}'>
    <p class='cap'>원본 최신 스틸 (기존 배경 plate)</p></div>
  <div><img src='out/shots_outdoor/remake/{_esc(r['bgswap_file'])}'>
    <p class='cap'>배경 교체 (stairfix_nb2)</p></div>
  <div><img src='out/shots_outdoor/remake/{_esc(r['out'])}'>
    <p class='cap'>배경 참조 제외 (수동 수정 프롬프트)</p></div>
</div>
<div class='refrow'>{kept_html}</div>
<details><summary>무배경 전송 프롬프트 (수동 수정 전문)</summary>
<pre>{_esc(r['prompt'])}</pre></details>
""")
    PAGE.write_text(f"""<!doctype html><meta charset='utf-8'>
<title>s21 remake — 무배경 3열 비교</title>
<style>
body{{font-family:system-ui,'Apple SD Gothic Neo',sans-serif;margin:24px;
background:#fafafa;color:#222;max-width:1600px}}
h1{{font-size:20px}} h2{{font-size:16px;margin-top:32px;border-bottom:2px
solid #ddd;padding-bottom:4px}} .sub{{font-size:12px;color:#777}}
img{{width:100%;display:block;background:#fff;border:1px solid #ddd;
padding:3px;box-sizing:border-box}}
.trio{{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px}}
.cap{{font-size:12px;color:#333;margin:4px 0 10px;font-weight:600}}
.refrow{{display:grid;grid-template-columns:repeat(6,1fr);gap:8px;
margin:6px 0}}
.rlabel{{font-size:10.5px;color:#666;margin:2px 0 0;line-height:1.3}}
.dropped img{{opacity:.35;border:2px dashed #c62828}}
.dropped .rlabel{{color:#c62828}}
pre{{white-space:pre-wrap;font-size:11px;background:#fff;border:1px solid
#e5e5e5;padding:10px}}
details{{margin:8px 0}} summary{{font-size:12px;color:#557;cursor:pointer}}
</style>
<h1>s21 remake 2차 — 배경 참조 완전 제외 ({len(results)}샷 3열 비교)</h1>
<p style='font-size:12.5px;color:#556'>좌=원본 / 중=배경만 stairfix_nb2로
교체(1차) / 우=배경 참조 자체를 제외(2차, 프롬프트 수동 수정 —
장면 본문 불변·배경 지시 제거·passport 샷은 image 재번호). 엔티티
passport 는 유지. 매핑=plans/outdoor_remake_nobg.json</p>
{''.join(secs)}
""", encoding="utf-8")


if __name__ == "__main__":
    main()
