#!/usr/bin/env python3
"""lane 콘티가 선화로 나오는가 — STYLE 절만 바꿔 A/B.

문제: 실내 콘티(`shot_conti_light`)는 선화로 나오는데 lane 콘티만 **회색 음영
렌더 + 3D 목각 마네킹**으로 나온다. 젖은 노면 반사까지 그린다. 하류가 그 화풍을
물려받는다(배경 판의 마네킹이 3D 그대로다).

두 팩 문안을 대조해 찾은 차이:

    실내 light_frame.md      "Absolutely NO shading, NO tone, NO hatching, NO
                              texture, NO grayscale fill, NO light-and-shadow"
    lane mannequin_frame.md  그런 문구 없음 + **"like a wooden posing figure"**

앞엣것이 빠져 음영이 들어오고, 뒤엣것이 3D 입체를 부른다.

    arm A  기록된 프롬프트 그대로 (v14 STYLE)
    arm B  STYLE 절만 v15 로 교체 — 나머지 7,000여 자는 바이트 동일

★v15 는 새로 짓지 않고 **실내 팩에서 실증된 문안**을 가져왔다. 그 팩은 실제로
 선화를 만들어 내고 있다. 「wooden posing figure」만 뺐고 방향 문구
 (마네킹이 어느 쪽을 향하는가 — v14 가 막던 실패)는 한 줄도 안 건드렸다.

usage: ab_lane_sketch_style.py [--dry] [--rounds=3]
"""
import json
import sys
from pathlib import Path

sys.path.insert(0, "/Users/manta/Documents/Projects/TheRoad-I1/scratchpad")
import _opik_env  # noqa: E402,F401  ★cwd 를 backend 로 고정

ROOT = Path("/Users/manta/Documents/Projects/TheRoad-I1")
PROJ = "da049582-2c6d-492c-979d-f468d61bab6e"
EPI = "fb7a883f-baac-4145-9131-732ce628d474"
CONTI = ROOT / "projects" / PROJ / "images" / EPI / "conti"
OUT = ROOT / "artifact" / "20260825_lane_sketch_style_ab"
V15 = ROOT / "prompts/_base/marker_map_sketch/15.202608251258/mannequin_frame.md"
SHOT = "S2sh1"
HEAD = "STYLE — MANNEQUIN BLOCKING SKETCH"
NEXT = "STAGING GEOMETRY"


def swap_style(prompt: str, new_style: str) -> tuple[str, int]:
    """STYLE 절만 갈아 끼운다 (그 절은 STAGING GEOMETRY 앞에서 끝난다)."""
    i = prompt.find(HEAD)
    j = prompt.find(NEXT, i + 1)
    if i < 0 or j < 0:
        return prompt, 0
    return prompt[:i] + new_style.strip() + "\n\n" + prompt[j:], j - i


def main() -> int:
    dry = "--dry" in sys.argv
    rounds = 3
    for a in sys.argv[1:]:
        if a.startswith("--rounds="):
            rounds = int(a.split("=", 1)[1])

    recs = json.loads((CONTI / "lane_records.json").read_text(encoding="utf-8"))
    base = (recs.get(SHOT) or {}).get("prompt") or ""
    new_style = V15.read_text(encoding="utf-8")
    pb, swapped = swap_style(base, new_style)
    ref = CONTI / f"lane_marker_map_{SHOT}.png"

    print(f"{SHOT}  A {len(base):,}자 → B {len(pb):,}자  (STYLE {swapped}자 교체)")
    print(f"  참조: {ref.name} {'OK' if ref.exists() else '✘ 없음'}")
    if not ref.exists():
        return 1

    OUT.mkdir(parents=True, exist_ok=True)
    if dry:
        (OUT / "_prompt_A.txt").write_text(base, encoding="utf-8")
        (OUT / "_prompt_B.txt").write_text(pb, encoding="utf-8")
        print(f"\n--dry — 프롬프트 두 벌을 {OUT} 에 적어 뒀다.")
        return 0

    from app.core.config import settings
    from app.core.openai_keys import openai_client
    from app.modules.llm.gpt_image_primitive import call_gpt_image_bytes

    client = openai_client(timeout=float(settings.llm_timeout_image_gen))
    # ★1회로는 못 잰다 — ABBA 로 회차를 섞는다
    seq = []
    for r in range(1, rounds + 1):
        seq.extend((r, a) for a in (("A", "B") if r % 2 else ("B", "A")))

    for rnd, arm in seq:
        dst = OUT / f"{SHOT}_{arm}{rnd}.png"
        if dst.exists():
            print(f"  {SHOT}_{arm}{rnd} 이미 있음 — 건너뜀")
            continue
        try:
            png = call_gpt_image_bytes(
                client, mode="edit", prompt=(base if arm == "A" else pb),
                ref_paths=[ref],
                call_kwargs={"model": "gpt-image-2", "size": "1536x1024",
                             "quality": "high", "n": 1},
            )
            dst.write_bytes(png)
            print(f"  {SHOT}_{arm}{rnd} ✔ {len(png):,}B")
        except Exception as e:  # noqa: BLE001
            print(f"  {SHOT}_{arm}{rnd} ✘ {e}")
    print(f"\n→ {OUT}")
    return 0


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