#!/usr/bin/env python3
"""s33 — 직행 합성 (2026-07-09, 실험 전용·커밋 금지).

사용자 지시: "마네킹은 효과적이지 않네 — 그냥 최초 bg + fp 로 바로
엔티티와 함께 몇가지 샷만". 중간 단계(스테이징/플레이트) 없이 한 번에:
  참조 = 최초 bg(룩 SOT) + 맵/fp(배치 SOT) + 캐릭터 passport
  프롬프트 = 샷 원문 + SPOT 고정(s30 grounding) + TIME_LOCK + 세계 사실
  카메라 = t2i 재량 (지시 없음)
대상 4샷=s32 동일(S13_3/S11_3/S13_5/S17_1) × i2/nb2 = 8장.
사용: backend/.venv/bin/python s33_direct_compose.py --only <stage>
      [--engines i2,nb2] [--shots ...]
산출: out/direct_compose/*.png + plans/s33_direct_v1.json + direct_compose.html
"""
import argparse
import html as _html
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent))
import forest_lib as F  # noqa: E402
import s27_forest_map as S27  # noqa: E402
import s30_shot_apply as S30  # noqa: E402
import s31_mannequin_first as S31  # noqa: E402 (place_desc 재사용)
import s32_grounded_staging as S32  # noqa: E402 (TIME_LOCK·샷 목록 재사용)

OUTD = F.OUT / "direct_compose"
PAGE = F.EXP / "direct_compose.html"
PLAN = "s33_direct_v1"

SHOTS = F.load_plan(S30.PLAN)["selected_shots"]  # 실외 7샷 전체 (07-09 확장)
ENGINES = ["i2", "nb2"]

DIRECT_HEAD = "\n".join([
    "Create ONE FINAL photorealistic live-action film still of the moment",
    "below, shot ON LOCATION at the real filming property shown in the",
    "attached references.",
])

FREE_CAMERA = "\n".join([
    "YOU choose the camera: pick the angle, distance, height, lens and",
    "composition that realise the shot text most powerfully — the camera",
    "may stand anywhere plausible AT THE STATED SPOT of this property.",
    "No composition instructions are given on purpose.",
])

REF_NOTE = "\n".join([
    "REFERENCES: the attached PHOTOGRAPH shows this same property — the",
    "sole source of how everything looks (building, materials, aging,",
    "colours, surroundings). The attached flat SITE-PLAN DRAWING shows",
    "where things are on the property — layout source only; never draw",
    "the plan itself, its colours, circles or labels. The remaining",
    "attached images are CHARACTER REFERENCES — the exact people who",
    "appear in the shot.",
])


def _prompt(spec, sk, s, ground):
    chars = s.get("characters") or []
    lines = ["SHOT TEXT (authoritative, Korean):",
             f"scene_heading: {s.get('scene_heading')}",
             f"shot: {s.get('description')}"]
    if chars:
        lines.append("people in shot: " + ", ".join(chars))
    parts = [
        DIRECT_HEAD,
        "SPOT (fixed): this shot happens at "
        + S31._place_desc(spec, ground) + ".",
        "\n".join(lines),
        FREE_CAMERA,
    ]
    if chars:
        parts.append(
            "CHARACTERS: only the listed reference people appear — "
            + ", ".join(chars) + ". Match each reference person's"
            " identity exactly (face, hair, build); dress and pose them"
            " as the moment describes.")
    else:
        parts.append("No people appear unless the moment itself says so.")
    parts += [S32.TIME_LOCK, S30.WORLD_FACTS, REF_NOTE, S30.NO_ANNOTATION]
    return "\n\n".join(parts)


def stage_prompts():
    spec = F.load_plan(S27.SPEC)
    s30 = F.load_plan(S30.PLAN)
    shots = S30._load_shots()
    plan = {"selected_shots": SHOTS, "shots": {}}
    for sk in SHOTS:
        s, g = shots[sk], s30["ground"][sk]
        plan["shots"][sk] = {
            "prompt": _prompt(spec, sk, s, g),
            "place_desc": S31._place_desc(spec, g),
            **{eng: f"direct_{sk}_{eng}.png" for eng in ENGINES}}
    F.save_plan(PLAN, plan)
    print(f"[prompts] {len(SHOTS)}샷 저장")


def stage_gen(engines, shots_filter):
    plan = F.load_plan(PLAN)
    shots = S30._load_shots()
    passports = F.query_passports()
    name_to_sid = F.load_recon()["character_name_to_sid"]
    sel = [k for k in plan["selected_shots"]
           if not shots_filter or k in shots_filter]
    engs = [e for e in ENGINES if e in engines]
    bg, mp = S30.PHOTO_PNG, S30.MAP_PNG
    for sk in sel:
        e = plan["shots"][sk]
        s = shots[sk]
        pp = [(f"CHARACTER REFERENCE — {n}: the exact person in the shot.",
               passports[name_to_sid[n]])
              for n in (s.get("characters") or [])
              if name_to_sid.get(n) and passports.get(name_to_sid.get(n))]
        for eng in engs:
            out = OUTD / e[eng]
            if eng == "i2":
                F.img_gpt(f"s33_direct_{sk}_i2", e["prompt"],
                          refs=[bg, mp] + [p for _, p in pp],
                          size="1536x1024", out_path=out)
            else:
                refs = [("LOCATION PHOTOGRAPH — the same property; sole"
                         " source of how everything looks.", bg),
                        ("SITE PLAN — layout source only; never draw this"
                         " drawing or its markers.", mp)] + pp
                F.img_nb2(f"s33_direct_{sk}_nb2", e["prompt"], refs,
                          aspect_ratio="16:9", out_path=out)
        print(f"[gen] {sk}: {'+'.join(engs)} 완료")


def stage_html():
    plan = F.load_plan(PLAN)
    shots = S30._load_shots()
    s30 = F.load_plan(S30.PLAN)
    s32 = F.load_plan(S32.PLAN)

    def esc(t):
        return _html.escape(str(t))

    def fig(rel, cap, width=23):
        return (f"<figure style='width:{width}%'><a href='{rel}'>"
                f"<img src='{rel}' loading='lazy'></a>"
                f"<figcaption>{esc(cap)}</figcaption></figure>")

    secs = ""
    for sk in plan["selected_shots"]:
        s = shots[sk]
        e = plan["shots"][sk]
        figs = "".join(fig(f"out/direct_compose/{e[eng]}", f"직행 {eng}")
                       for eng in ENGINES if (OUTD / e[eng]).exists())
        cmp_figs = ""
        c32 = ((s32.get("compose") or {}).get(sk) or {})
        f32 = (c32.get("files") or {}).get("nb2_by_nb2")
        if f32 and (F.OUT / "grounded_staging" / f32).exists():
            cmp_figs += fig(f"out/grounded_staging/{f32}",
                            "비교: s32(접지 스테이징, nb2→nb2)")
        s30f = ((s30.get("compose") or {}).get(sk) or {}).get("file")
        if s30f and (F.OUT / "shot_apply" / s30f).exists():
            cmp_figs += fig(f"out/shot_apply/{s30f}", "비교: s30(플레이트 우선)")
        base = F.OUT / "shot_apply" / "baseline" / f"{sk}.png"
        if base.exists():
            cmp_figs += fig(f"out/shot_apply/baseline/{sk}.png",
                            "비교: 기존 production 스틸")
        secs += f"""
<h2>{esc(sk)} — {esc(s.get('scene_heading'))}</h2>
<p>{esc(s.get('description'))}</p>
<p><b>고정 지점</b>: {esc(e.get('place_desc'))}</p>
{figs}
<h3>비교</h3>{cmp_figs}
<details><summary>프롬프트</summary><pre>{esc(e['prompt'])}</pre></details>
"""

    doc = f"""<!doctype html><html lang=ko><head><meta charset=utf-8>
<title>s33 — 직행 합성 (최초 bg+맵+passport 한 번에)</title><style>
body{{font-family:'Apple SD Gothic Neo',sans-serif;margin:24px;max-width:1500px}}
figure{{display:inline-block;margin:1%;vertical-align:top}}
img{{width:100%;border:1px solid #ccc}} figcaption{{font-size:13px;text-align:center}}
pre{{font-size:11px;background:#f7f7f7;border:1px solid #ddd;padding:8px;
white-space:pre-wrap;max-height:320px;overflow:auto}}
h2{{border-bottom:2px solid #333;padding-bottom:4px;margin-top:36px}}
h3{{margin:14px 0 4px}}</style></head><body>
<h1>s33 — 직행 합성 (2026-07-09)</h1>
<p>중간 단계 없이 한 번에: 참조=최초 bg(룩)+맵(배치)+passport,
프롬프트=샷 원문+지점 고정+시간 고정, 카메라=t2i 재량.
s30/s32/기존 스틸과 비교.</p>
{fig('out/forest_map/top2/top2_photo_fixed_nb2.png', '촬영지 사진(룩 SOT)', 31)}
{fig('out/forest_map/top2/top2_map_gpt.png', '약도(배치 SOT)', 31)}
{secs}
</body></html>"""
    PAGE.write_text(doc, encoding="utf-8")
    print(f"[html] {PAGE}")


if __name__ == "__main__":
    ap = argparse.ArgumentParser()
    ap.add_argument("--only", required=True, choices=["prompts", "gen", "html"])
    ap.add_argument("--engines", default="i2,nb2")
    ap.add_argument("--shots", default="")
    a = ap.parse_args()
    OUTD.mkdir(parents=True, exist_ok=True)
    engines = [e.strip() for e in a.engines.split(",") if e.strip()]
    shots_filter = {s.strip() for s in a.shots.split(",") if s.strip()}
    if a.only == "prompts":
        stage_prompts()
    elif a.only == "gen":
        stage_gen(engines, shots_filter)
    else:
        stage_html()
