"""s7 — 3라운드: root_C = 외부 fp(fp_l03류)를 기하 SOT 로 앵커한 뿌리 재구축.

진단: 기존 root_A(자체발명)/root_B(production 배치도 상속)가 외부 fp 와 배치
불일치(주거동 축소·계단/설비 위치 오류) → 실외 하류 전체 붕괴. 외부 fp 가 이미
정답 top-down 배치이므로 그것을 직접 재스타일(존→실표면, 실내 미노출)한다.

체인 재검증: root_C → S4_Shot1 마킹 → camview sketch → plate (직행 결론 반영).
산출: out/root/root_C.png, out/outdoor_c/S4_Shot1/
사용: .venv/bin/python s7_root_fp.py [--only root|chain|readback]
"""
import argparse
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
import s1_root as S1  # noqa: E402
import s3_outdoor as S3  # noqa: E402
import s5_round2 as S5  # noqa: E402

SK = "S4_Shot1"
OUTC = F.OUT / "outdoor_c" / SK


def outdoor_fp_for_anchor(recon):
    """앵커용 외부 fp 선택 — 데이터 기준: 실외(loc is_indoor=False) loc 의 fp 중
    그룹 실외 샷이 가장 많이 걸린 loc 의 첫 fp."""
    outdoor_locs = [m["loc_id"] for m in recon["members"] if not m["is_indoor"]]
    shot_count = {l: 0 for l in outdoor_locs}
    for s in recon["shots"].values():
        for l in s["loc_ids"]:
            if l in shot_count:
                shot_count[l] += 1
    best = max(shot_count, key=lambda l: shot_count[l])
    fps = recon["fp_by_loc"].get(best) or []
    if not fps:
        raise SystemExit(f"no exterior fp for loc {best}")
    return best, fps[0]["png_path"]


def indoor_fp_for_boundary(recon):
    anchor = recon.get("anchor_loc")
    fps = recon["fp_by_loc"].get(anchor) or []
    return fps[0]["png_path"] if fps else None


def root_c(recon, plan):
    loc, ext_fp = outdoor_fp_for_anchor(recon)
    in_fp = indoor_fp_for_boundary(recon)
    el_lines = "\n".join(f"({e['id']}) {e['name_en']} — {e['placement']}"
                         for e in plan["elements"])
    legend_lines = ", ".join(f"{e['id']}: {e['name_en'].upper()}"
                             for e in plan["elements"])
    members_text = F.members_block(recon["members"])
    prompt = "\n".join([
        "The FIRST attached image is the TOP-DOWN zone plan of one real place's",
        "outdoor level. Its GEOMETRY is the absolute ground truth: keep every",
        "boundary line, every zone's position, size and PROPORTION, the stair,",
        "the fixtures and every opening EXACTLY where and how large the plan",
        "puts them. Do not move, resize, mirror or re-arrange anything.",
        "",
        "Redraw that exact layout as ONE hand-drawn TOP-DOWN aerial SITE SKETCH:",
        "dark ink outlines with light flat colour washes, straight-down view,",
        "NOT a photograph, NOT 3D. Translate each zone into its real surface:",
        "an enclosed dwelling zone becomes that dwelling's plain ROOF PLANE",
        "(never draw its interior rooms or furniture); open zones become the",
        "worn open terrace surface; a service zone keeps its fixtures (tank,",
        "laundry line) as drawn; a threshold/entry zone keeps its door and the",
        "stair stays exactly where the plan places it.",
        "The SECOND attached image is the dwelling's indoor floor plan — use it",
        "ONLY to confirm the dwelling's outer boundary shape and where openings",
        "sit on that boundary. Never draw any interior layout.",
        "",
        "THE PLACE (production data):",
        members_text,
        "",
        "Mark each of the following elements at its position with a small circle",
        "containing its single capital letter:",
        el_lines,
        "",
        "On the RIGHT side of the image, add a clean legend panel listing each",
        "circled letter with its English name in small neat capital letters:",
        legend_lines,
        "",
        "Replace the plan's zone captions and numbers — the circled letters and",
        "the legend panel are the ONLY text and the ONLY circular marks allowed.",
        "Add a modest margin of the immediate surroundings (neighbouring roofs,",
        "the alley the stair descends into) so the place reads in context.",
    ])
    refs = [ext_fp] + ([in_fp] if in_fp else [])
    F.runlog({"kind": "info", "stage": "s7", "anchor_loc": loc, "refs": refs})
    return F.img_gpt("root_C_fp_anchor", prompt, refs=refs,
                     out_path=F.OUT / "root" / "root_C.png")


def chain(recon, root_plan):
    s = recon["shots"][SK]
    OUTC.mkdir(parents=True, exist_ok=True)
    root_png = F.OUT / "root" / "root_C.png"
    cam = str((s.get("staging") or {}).get("camera_direction") or "")
    mark_prompt = S3.MARK_PROMPT.format(cam=cam, figures=S3.figures_clause(s))
    mark = F.img_gpt(f"{SK}_rootmark_C", mark_prompt, refs=[root_png],
                     out_path=OUTC / "root_marked.png")
    sketch = F.img_gpt(f"{SK}_camview_C", S5.SKETCH_PROMPT, refs=[mark],
                       out_path=OUTC / "sketch.png")
    F.img_gpt(f"{SK}_plate_C", S5.plate_prompt(recon, s, root_plan),
              refs=[sketch, mark], out_path=OUTC / "plate.png")


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--only", default="all", choices=["all", "root", "chain",
                                                      "readback"])
    args = ap.parse_args()
    recon = F.load_recon()
    plan = F.load_plan("root_plan")
    if args.only in ("all", "root"):
        root_c(recon, plan)
    if args.only in ("all", "readback"):
        S1.readback(plan, "C")
    if args.only in ("all", "chain"):
        chain(recon, plan)
    F.runlog({"kind": "stage", "stage": "s7_root_fp", "done": args.only})


if __name__ == "__main__":
    main()
