"""s3 — 실외 체인: 뿌리 스케치 마킹(O1) → 배경 plate(O2) → 임시샷 → A/B/C → 최종.

fp 미사용 — 뿌리(조감 스케치)가 유일한 공간 control.
사용: .venv/bin/python s3_outdoor.py --root A|B [--only mark|plate|paths]
"""
import argparse
import json
import sys
from pathlib import Path

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

OUTDIR = F.OUT / "outdoor"  # --variant v2 시 main()에서 교체

MARK_PROMPT = (
    "The attached image is a TOP-DOWN aerial site sketch with circled capital"
    " letters marking site elements (and possibly a legend panel on the right)."
    " Keep the sketch EXACTLY as it is — same geometry, same circled letters,"
    " same legend, do not redraw or restyle anything.\n"
    "ADD exactly these annotations for ONE camera setup:\n"
    "- ONE green camera icon with a green triangular view-CONE showing where the"
    " camera stands and what it looks at, following this staging description:\n"
    "{cam}\n"
    "{figures}"
    "Add NOTHING else — no new structures, no new text other than the new"
    " capital letters in their small circles."
)


def figures_clause(s) -> str:
    st = s.get("staging") or {}
    cons = ((st.get("frame_spatial_contract") or {}).get("constraints") or [])
    n = sum(1 for c in cons if isinstance(c, dict)
            and c.get("target_kind") == "character")
    if n <= 0:
        n = len(st.get("character_angles") or [])
    if n <= 0:
        return "- This shot has NO people — add no lettered person circles.\n"
    letters = " ".join(f"({chr(80 + i)})" for i in range(n))  # P, Q, R... (요소 A~와 구분)
    return (f"- {n} red circle(s) with capital letters {letters} inside the"
            f" view-cone where the staging places the people:\n"
            + "\n".join("  * " + json.dumps(ca, ensure_ascii=False)
                        for ca in (st.get("character_angles") or [])) + "\n")


def o1_mark(recon, sk, root_png):
    s = recon["shots"][sk]
    cam = str((s.get("staging") or {}).get("camera_direction") or "")
    prompt = MARK_PROMPT.format(cam=cam, figures=figures_clause(s))
    return F.img_gpt(f"{sk}_rootmark", prompt, refs=[root_png],
                     out_path=OUTDIR / sk / "root_marked.png")


def o2_plate(recon, sk, root_plan, extra: str = ""):
    s = recon["shots"][sk]
    marked = OUTDIR / sk / "root_marked.png"
    locs = [m for m in recon["members"] if m["loc_id"] in s["loc_ids"]]
    loc_lines = "\n".join(f"- {m.get('label')}: {m.get('summary')}" for m in locs)
    prompt = "\n".join([
        "The attached image is a TOP-DOWN aerial site sketch of one real place,",
        "with a green camera icon and view-cone marking ONE camera position and",
        "its viewing direction. Circled letters mark site elements; red lettered",
        "circles (if any) mark where people stand — but render NO people.",
        "",
        "Draw the PHOTOREALISTIC outdoor view that THAT camera sees from its",
        "marked position at eye level, looking along the view-cone: the",
        "structures, ground surfaces, openings and approaches the sketch places",
        "in front of that camera, at real-world human scale. This is an EMPTY",
        "background plate: absolutely no people, no text, no letters, no",
        "circles, no arrows, no sketch lines — pure photographic realism.",
        "",
        "WHAT THIS PLACE LOOKS LIKE (production data):",
        loc_lines,
        "",
        "Site orientation notes:", str(root_plan.get("sketch_notes", "")),
        "", C.staging_lines(s),
    ] + ([extra] if extra else []))
    return F.img_gpt(f"{sk}_plate{'_v2' if extra else ''}", prompt, refs=[marked],
                     out_path=OUTDIR / sk / "plate.png")


def run_shot(recon, sk, mannequin_preview: bool, v2: bool = False):
    s = recon["shots"][sk]
    d = OUTDIR / sk
    env = d / "plate.png"
    control = d / "root_marked.png"
    passports = C.passports_for_shot(recon, s)
    tagv = "_v2" if v2 else ""
    contracts = ([C.people_contract(s), C.MARKER_NOT_PEOPLE,
                  C.EYE_LEVEL_CONTRACT] if v2 else None)

    temp = C.temp_shot(f"{sk}_temp{tagv}", s, env, control, d / "temp.png",
                       contracts=contracts)
    # 경로 A: 직행
    C.final_shot(f"{sk}_final_A{tagv}", s, env, passports, d / "final_A.png",
                 control_png=control, contracts=contracts)
    # 경로 B: VLM 구도 → 배경 재생성 → 최종
    comp = C.vlm_composition(sk + tagv, temp)
    bg_b = C.bg_regen(f"{sk}_bgB{tagv}", s, env, d / "bg_B.png", comp=comp,
                      contracts=contracts)
    C.final_shot(f"{sk}_final_B{tagv}", s, bg_b, passports, d / "final_B.png",
                 contracts=contracts)
    # 경로 C: 윤곽 → 배경 재생성 → 최종
    cont = C.contour(f"{sk}_contour{tagv}", temp, d / "contour.png")
    bg_c = C.bg_regen(f"{sk}_bgC{tagv}", s, env, d / "bg_C.png", contour_png=cont,
                      contracts=contracts)
    C.final_shot(f"{sk}_final_C{tagv}", s, bg_c, passports, d / "final_C.png",
                 contracts=contracts)
    if mannequin_preview:
        bg_cm = C.bg_regen(f"{sk}_bgC_m{tagv}", s, env, d / "bg_C_m.png",
                           contour_png=cont, mannequin=True, contracts=contracts)
        C.final_shot(f"{sk}_final_C_m{tagv}", s, bg_cm, passports,
                     d / "final_C_m.png", contracts=contracts)


def main():
    global OUTDIR
    ap = argparse.ArgumentParser()
    ap.add_argument("--root", required=True, choices=["A", "B"],
                    help="s1 육안 선정 root 변형")
    ap.add_argument("--only", default="all", choices=["all", "mark", "plate",
                                                      "paths"])
    ap.add_argument("--variant", default="v1", choices=["v1", "v2"],
                    help="v2 = 인원/마커/eye-level 계약 추가 (1.5라운드)")
    args = ap.parse_args()
    v2 = args.variant == "v2"
    if v2:
        OUTDIR = F.OUT / "outdoor_v2"
    recon = F.load_recon()
    root_plan = F.load_plan("root_plan")
    root_png = F.OUT / "root" / f"root_{args.root}.png"
    if not root_png.exists():
        raise SystemExit(f"root image missing: {root_png}")
    subset = F.load_plan("subset")["outdoor"]

    for i, sk in enumerate(subset):
        if v2:  # 마킹은 v1 산출 재사용 (동일 입력 — 재생성 불필요)
            src = F.OUT / "outdoor" / sk / "root_marked.png"
            dst = OUTDIR / sk / "root_marked.png"
            if src.exists() and not dst.exists():
                dst.parent.mkdir(parents=True, exist_ok=True)
                import shutil
                shutil.copyfile(src, dst)
        if args.only in ("all", "mark") and not v2:
            o1_mark(recon, sk, root_png)
        if args.only in ("all", "plate"):
            o2_plate(recon, sk, root_plan,
                     extra=(C.EYE_LEVEL_CONTRACT if v2 else ""))
        if args.only in ("all", "paths"):
            run_shot(recon, sk, mannequin_preview=(i == 0), v2=v2)
    F.runlog({"kind": "stage", "stage": "s3_outdoor", "done": args.only,
              "root": args.root, "variant": args.variant, "subset": subset})


if __name__ == "__main__":
    main()
