"""s5 — 2라운드: (A) 실외 sketch-hop-before-plate, (B) 마네킹 인물샷 재검.

A. wide/무인물 establishing 한정: root_marked → eye-level LINE-ART camview 스케치
   → photoreal empty plate → (S4_Shot1 만) temp→VLM→bg→final (B 경로, v2 계약).
   산출: out/outdoor_v3/<sk>/
B. 마네킹 효용: 인물 샷 2개 — 실내 S5_Shot3(기존 contour 재사용),
   실외 S11_Shot3(v2 contour 재사용) 에 bg_C_m + final_C_m 추가.

사용: .venv/bin/python s5_round2.py [--only hop|mannequin]
"""
import argparse
import shutil
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

V3 = F.OUT / "outdoor_v3"
HOP_SHOTS = ["S4_Shot1", "S10_Shot6"]   # wide/무인물 + medium/무인물 (게이트 검증)
FULLCHAIN_SHOT = "S4_Shot1"

SKETCH_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 are site markers, never people).",
    "",
    "Draw the clean LINE-ART sketch of the EYE-LEVEL view that THAT camera sees",
    "from its marked position: thin dark ink lines on a plain white background,",
    "no colour, no shading, no photo texture. The camera stands ON the ground of",
    "its marked spot at human eye height (about 1.6 m) INSIDE the scene — NOT",
    "above it. Show the structures, ground surfaces, openings and approaches the",
    "site sketch places in front of that camera, in correct perspective at",
    "real-world human scale, with the horizon at a natural eye-level height.",
    "Do NOT draw any overhead or bird's-eye view. No people, no text, no",
    "letters, no circles, no arrows — pure line drawing of the environment.",
])


def plate_prompt(recon, s, root_plan) -> str:
    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)
    return "\n".join([
        "The FIRST attached image is a LINE-ART sketch of one camera's eye-level",
        "view of a real outdoor place — it is the COMPOSITION AND PERSPECTIVE",
        "ground truth. The SECOND attached image is the top-down site sketch of",
        "the same place — use it ONLY to understand materials, fixtures and",
        "spatial relations; never copy its top-down viewpoint or its markers.",
        "",
        "Render the PHOTOREALISTIC empty background plate of exactly the view the",
        "line-art sketch shows: same framing, same perspective, same structures,",
        "at real-world human scale. This is an EMPTY plate: no people, no text,",
        "no letters, no circles, 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), "", C.EYE_LEVEL_CONTRACT,
    ])


def hop(recon, root_plan):
    for sk in HOP_SHOTS:
        s = recon["shots"][sk]
        d = V3 / sk
        d.mkdir(parents=True, exist_ok=True)
        src_mark = F.OUT / "outdoor" / sk / "root_marked.png"
        mark = d / "root_marked.png"
        if not mark.exists():
            shutil.copyfile(src_mark, mark)
        sketch = F.img_gpt(f"{sk}_camview_sketch", SKETCH_PROMPT, refs=[mark],
                           out_path=d / "sketch.png")
        plate = F.img_gpt(f"{sk}_plate_v3", plate_prompt(recon, s, root_plan),
                          refs=[sketch, mark], out_path=d / "plate.png")
        if sk != FULLCHAIN_SHOT:
            continue
        contracts = [C.people_contract(s), C.MARKER_NOT_PEOPLE,
                     C.EYE_LEVEL_CONTRACT]
        passports = C.passports_for_shot(recon, s)
        temp = C.temp_shot(f"{sk}_temp_v3", s, plate, mark, d / "temp.png",
                           contracts=contracts)
        comp = C.vlm_composition(sk + "_v3", temp)
        bg_b = C.bg_regen(f"{sk}_bgB_v3", s, plate, d / "bg_B.png", comp=comp,
                          contracts=contracts)
        C.final_shot(f"{sk}_final_B_v3", s, bg_b, passports, d / "final_B.png",
                     contracts=contracts)


MANNEQUIN_TARGETS = [  # (lane_dir, shot_key, v2 계약 여부)
    ("indoor", "S5_Shot3", False),
    ("outdoor_v2", "S11_Shot3", True),
]


def mannequin(recon):
    for lane, sk, use_contracts in MANNEQUIN_TARGETS:
        s = recon["shots"][sk]
        d = F.OUT / lane / sk
        cont = d / "contour.png"
        if not cont.exists():
            F.runlog({"kind": "warn", "stage": "s5", "msg": f"no contour {lane}/{sk}"})
            continue
        env = (d / "plate.png") if lane.startswith("outdoor") else (
            F.OUT / "indoor" / "top_layers" /
            f"{__import__('s2_indoor').assigned_top(F.load_plan('indoor_plan'), sk)}.png")
        contracts = ([C.people_contract(s), C.MARKER_NOT_PEOPLE,
                      C.EYE_LEVEL_CONTRACT] if use_contracts else None)
        passports = C.passports_for_shot(recon, s)
        bg_m = C.bg_regen(f"{sk}_bgC_m_r2", s, env, d / "bg_C_m.png",
                          contour_png=cont, mannequin=True, contracts=contracts)
        C.final_shot(f"{sk}_final_C_m_r2", s, bg_m, passports,
                     d / "final_C_m.png", contracts=contracts)


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--only", default="all", choices=["all", "hop", "mannequin"])
    args = ap.parse_args()
    recon = F.load_recon()
    root_plan = F.load_plan("root_plan")
    if args.only in ("all", "hop"):
        hop(recon, root_plan)
    if args.only in ("all", "mannequin"):
        mannequin(recon)
    F.runlog({"kind": "stage", "stage": "s5_round2", "done": args.only})


if __name__ == "__main__":
    main()
