"""s21 pure T2I — 씬 원문에서 LLM 이 순수 샷 프롬프트 재추출, 참조 0 생성.

  - 사용자 지시: 배경/캐릭터 참조 이미지 전부 제외한 순수 생성, 일관성
    무시, i2(gpt-image-2)+nb2 생성해 기존 최신 스틸과 3열 비교.
  - 기존 t2i_prompt 미사용 — 분석에서 확인된 오염(owned 앵커 재서술·
    상류 카메라/조명 발명의 팩트 승격·fixed_element 무드 전파)을 원천
    배제. 씬 원문 전체+샷 순간 서술만 입력으로 LLM(gpt)이 새로 저작.
  - 계약: 원문 명시/가시적 함의만(조명색·날씨·안개·무드소품·치수 발명
    금지), 인물 이름 금지(원문의 인구학/역할 서술만), 시각적 현재만
    (s20 PHOTO_VISUAL_ONLY 재사용), 무텍스트, 단일 순간, 타 씬/샷
    일관성 무시. shot description 은 순간/프레이밍 선택용 — 내용이
    원문과 충돌하면 원문 우선(원문에 없는 시각 요소 채택 금지).
  - 산출: out/pure_t2i/<샷키>_{gpt,nb2}.png + plans/pure_shot_prompts.json
    (재실행 시 저작 재사용=이미지 캐시 정합) + pure_t2i.html.
  - DB write 0, 커밋 금지(scratchpad).
사용: backend/.venv/bin/python s21_pure_t2i.py
"""
import html as html_mod
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent))
import forest_lib as F  # noqa: E402
from s20_numbered_bd import PHOTO_VISUAL_ONLY  # noqa: E402  (계약 재사용)

EXP = Path(__file__).parent
OUT = F.OUT / "pure_t2i"
PAGE = EXP / "pure_t2i.html"
PLAN_NAME = "pure_shot_prompts"

PURE_SHOT_AUTHOR_SYSTEM = "\n".join([
    "You write ONE image-generation prompt (English) for a",
    "photorealistic cinematic still of ONE selected shot. There is NO",
    "reference image: your prompt is the model's ONLY source.",
    "",
    "You get the scene's original screenplay text (full) and the",
    "selected shot's moment/framing description.",
    "",
    "Your prompt must:",
    "- render exactly the selected shot's single moment — one instant,",
    "  one camera position,",
    "- use ONLY what the screenplay text states or visibly implies:",
    "  place, time of day, objects, people, actions. Do NOT invent",
    "  lighting colours, weather, fog, haze, mood props, measurements",
    "  or atmosphere the text does not state. Where the text is",
    "  silent, say nothing and trust the model — over-specification",
    "  breeds contradictions,",
    "- the shot description picks the moment and framing only; if it",
    "  contains visual content absent from the screenplay text, the",
    "  screenplay text wins — drop the extra content,",
    "- people: describe generically using only the text's own",
    "  demographic/role wording — NEVER use personal names,",
    PHOTO_VISUAL_ONLY,
    "- ignore continuity with any other scene or shot — this scene's",
    "  text is the whole world,",
    "- no readable text, signage or lettering anywhere in the image.",
    "Write 60-140 words of direct imperative prose, starting with",
    "'Photorealistic cinematic still.'",
    "used_from_text_ko: quotes from the given text you actually used",
    "(Korean, verbatim).",
    "prompt_ko: the same prompt in Korean (for the human reviewer).",
])

PURE_SHOT_SCHEMA = {
    "type": "object",
    "properties": {
        "prompt_en": {"type": "string"},
        "prompt_ko": {"type": "string"},
        "used_from_text_ko": {"type": "array", "items": {"type": "string"}},
    },
    "required": ["prompt_en", "prompt_ko", "used_from_text_ko"],
    "additionalProperties": False,
}


def author_prompts(jobs):
    """plan 존재 시 재사용(이미지 캐시와 정합), 없으면 LLM 저작."""
    try:
        plan = F.load_plan(PLAN_NAME)
        if set(plan["shots"]) == {j["shot_key"] for j in jobs}:
            return plan["shots"]
    except FileNotFoundError:
        pass
    save = {s["scene_index"]: s for s in F.load_step("scene_save")["segments"]}
    sx = {s["scene_index"]: s for s in F.load_step("shot_extract")["scenes"]}
    out = {}
    for j in jobs:
        si, shi = j["scene_index"], j["shot_index"]
        scene_text = save[si]["text"]  # 전체 원문 — 자르기 금지
        shot = next(s for s in sx[si]["shots"] if s["shot_index"] == shi)
        user = (
            "[SCENE TEXT — full original screenplay scene]\n"
            f"{scene_text}\n\n"
            "[SELECTED SHOT — moment/framing]\n"
            f"{shot['description']}"
        )
        res = F.llm(f"pure_shot_author_{j['shot_key']}",
                    PURE_SHOT_AUTHOR_SYSTEM, user, PURE_SHOT_SCHEMA,
                    model="gpt")
        out[j["shot_key"]] = {
            "scene_index": si, "shot_index": shi,
            "shot_description": shot["description"], **res}
    F.save_plan(PLAN_NAME, {"shots": out})
    return out


def main():
    base = F.load_plan("outdoor_remake_bg")
    jobs = []
    for j in base["jobs"]:
        si, shi = j["shot_key"].split("_Shot")
        jobs.append({"shot_key": j["shot_key"],
                     "scene_index": int(si[1:]), "shot_index": int(shi),
                     "orig_file": j["orig_file"]})
    prompts = author_prompts(jobs)
    OUT.mkdir(parents=True, exist_ok=True)
    for j in jobs:
        p = prompts[j["shot_key"]]["prompt_en"]
        F.img_gpt(f"pure_t2i_{j['shot_key']}_gpt", p,
                  out_path=OUT / f"{j['shot_key']}_gpt.png")
        F.img_nb2(f"pure_t2i_{j['shot_key']}_nb2", p, [],
                  out_path=OUT / f"{j['shot_key']}_nb2.png")
    build_page(jobs, prompts)
    print(f"pure {len(jobs)}샷 x2 -> {OUT}")
    print(f"page -> {PAGE}")


def build_page(jobs, prompts):
    def _esc(t):
        return html_mod.escape(str(t or ""))

    secs = []
    for j in jobs:
        k = j["shot_key"]
        p = prompts[k]
        quotes = "".join(f"<li>{_esc(q)}</li>"
                         for q in p.get("used_from_text_ko", []))
        secs.append(f"""
<h2>{_esc(k)}</h2>
<div class='trio'>
  <div><img src='out/shots_outdoor/{_esc(j['orig_file'])}'>
    <p class='cap'>기존 (production 최신 스틸)</p></div>
  <div><img src='out/pure_t2i/{_esc(k)}_gpt.png'>
    <p class='cap'>순수 추출 T2I — i2 (gpt-image-2)</p></div>
  <div><img src='out/pure_t2i/{_esc(k)}_nb2.png'>
    <p class='cap'>순수 추출 T2I — nb2</p></div>
</div>
<p class='ko'>{_esc(p['prompt_ko'])}</p>
<details><summary>영문 전송 프롬프트 + 원문 사용 구절</summary>
<pre>{_esc(p['prompt_en'])}</pre>
<ul class='q'>{quotes}</ul></details>
""")
    PAGE.write_text(f"""<!doctype html><meta charset='utf-8'>
<title>s21 pure T2I — 순수 재추출 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}}
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}}
.ko{{font-size:12.5px;color:#345;background:#fff;border:1px solid #e5e5e5;
padding:8px 10px}}
pre{{white-space:pre-wrap;font-size:11px;background:#fff;border:1px solid
#e5e5e5;padding:10px}}
.q{{font-size:11.5px;color:#666}}
details{{margin:8px 0}} summary{{font-size:12px;color:#557;cursor:pointer}}
</style>
<h1>s21 pure T2I — 씬 원문 순수 재추출, 참조 0 ({len(jobs)}샷)</h1>
<p style='font-size:12.5px;color:#556'>프롬프트=씬 원문 전체+샷 순간
서술만으로 LLM(gpt) 신규 저작 — 기존 t2i_prompt 미사용(owned 앵커·상류
무드 발명·fixed_element 배제), 발명 금지 계약, 타 샷 일관성 무시(지시).
참조 이미지 0(배경·캐릭터 전부 제외). 좌=기존 production 최신 스틸 /
중=i2 / 우=nb2. 저작=plans/pure_shot_prompts.json</p>
{''.join(secs)}
""", encoding="utf-8")


if __name__ == "__main__":
    main()
