"""s10 — 6라운드: 뿌리 v3 = 두 검증 접근의 결합 (fp 기하 앵커 × fp형 도면+입체감).

사용자 재정의(2026-07-05): 뿌리 = "전체 샷을 한꺼번에 넣은" 건물 전체(옥탑 거주부
+옥상+건물 몸체+지상 진입 마당/골목) 도면. 형태 = 실내 fp 의 도면 언어(가는 윤곽선
/플랫 색 채움/설비 기호) + 약간의 입체감(수직에서 ~30° 축측). 검증 소스 결합:
  - 기하: 실외 fp 3장 I2I 앵커 (4라운드 root_v2 검증 — s8)
  - 형태: fp 도면 언어+30° 축측 + 간결·중립(로케이션 문서) 계약 (5라운드 p1 검증 — s9)
  - 마커/범례: 원문자 12개+우측 영어 범례 (root_plan_v2 재사용 — 검증 요소/23샷 커버)
변형 3종(여러 방법 시도):
  a) 템플릿 결합 — p1 스타일 언어 + root_v2 의 참조/마커/범례 블록 (코드 저작 코어)
  b) LLM 저작 코어 — 스타일/내용 서술만 LLM(s9 패턴, EN+KO), 공통 블록은 코드 부착
  c) 2-hop 재스타일 — 검증된 root_v2.png 단일 참조로 형태 전환+기울임 (p4 패턴 응용)
사용: .venv/bin/python s10_root_v3.py [--only core|images|readback|html]
산출: out/root_v3/root_v3{a,b,c}.png, plans/root_v3b_core.json,
      plans/root_readback_v3{a,b,c}.json, round6.html
"""
import argparse
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent))
import forest_lib as F  # noqa: E402
import s1_root as S1  # noqa: E402
import s8_root_v2 as S8  # noqa: E402
import r6_page  # noqa: E402

OUTR = F.OUT / "root_v3"
VARIANTS = ("a", "b", "c")

# ── 공통 블록 (a/b 공유 — 검증된 root_v2 템플릿 요소를 그대로 계승) ──

STYLE_CORE_A = "\n".join([
    "ONE technical architectural SITE PLAN drawing of a single real-world",
    "property, in the exact drafting language of a clean floor plan — thin dark",
    "outlines, flat muted colour fills, simple fixture symbols — drawn with",
    "slight axonometric depth, viewed from high above at an oblique angle tilted",
    "about 30 degrees from vertical. NOT straight down, NOT a photograph, NOT an",
    "artistic ink sketch. Neutral document presentation: no mood, no weather,",
    "no story props, no vehicles, no people.",
    "",
    "Draw the WHOLE property in one coherent drawing:",
    "- the multi-storey BUILDING body (its roof plane and the walls the tilt",
    "  reveals),",
    "- any upper-level enclosed unit on that roof with its open deck and fixed",
    "  roof-level fixtures (only what its plan shows — never interior rooms),",
    "- the exterior STAIR route connecting the ground to the top level,",
    "- the GROUND level around the building: the entry yard, the narrow alley",
    "  approach and the street-side entrance,",
    "- a modest margin of neighbouring structures.",
    "Wall heights, the stair run and the ground-to-roof level difference must",
    "read clearly through the slight axonometric depth.",
])

STYLE_CORE_A_KO = (
    "실제 부지 하나의 기술적 건축 배치 도면 한 장 — 깔끔한 평면도의 도면 언어"
    "(가는 어두운 윤곽선, 플랫한 차분한 색 채움, 단순 설비 기호) 그대로, 수직에서"
    " 약 30도 기울인 높은 사선 시점의 약한 축측 깊이로 그린다. 완전 수직 부감도,"
    " 사진도, 예술적 잉크 스케치도 아님. 중립 문서 표현: 무드/날씨/이야기 소품/차량"
    "/사람 없음.\n\n부지 전체를 하나의 정합된 도면에: 다층 건물 몸체(지붕면+기울임이"
    " 드러내는 벽), 그 지붕 위 상층 거주부 외피+테라스+고정 설비(도면에 있는 것만 —"
    " 실내 금지), 지상↔상층을 잇는 외부 계단, 지상부(진입 마당·좁은 골목 접근로·"
    "도로변 출입구), 이웃 구조물의 적당한 여백. 벽 높이/계단/지상-옥상 레벨 차가"
    " 약한 축측 깊이로 명확히 읽혀야 함."
)


def ref_block(n: int) -> str:
    lines = [
        "The attached images are the property's real top-down partial plans —",
        "compose them into this ONE drawing, keeping each part's geometry,",
        "proportions and opening positions EXACTLY (do not move, resize, mirror",
        "or invent):",
    ]
    lines += [f"- attached image #{i + 1}: top-down plan of one part of the"
              f" property (its geometry for that part is ground truth)"
              for i in range(n)]
    return "\n".join(lines)


def data_block(recon) -> str:
    return "THE PLACE (production data):\n" + F.members_block(recon["members"])


def marker_block(plan) -> str:
    el_lines = "\n".join(f"({e['id']}) {e['name_en']} — {e['placement']}"
                         for e in plan["elements"])
    return ("Mark each element with a small circle containing its capital"
            " letter:\n" + el_lines)


def legend_block(plan) -> str:
    legend_lines = ", ".join(f"{e['id']}: {e['name_en'].upper()}"
                             for e in plan["elements"])
    return "\n".join([
        "On the RIGHT side, add a clean legend panel listing each circled",
        "letter with its English name in small neat capital letters:",
        legend_lines,
        "",
        "The circled letters and the legend are the ONLY text and the ONLY",
        "circular marks allowed. No zone captions, no numbers, no other labels.",
    ])


# ── 변형 b: LLM 저작 코어 (s9 GEN_SYSTEM 계약 계승 — 결합 뿌리용 단일 코어) ──

B_CORE_SYSTEM = """You write the CORE of one image prompt for a LOCATION SITE-PLAN
ASSET in film pre-production. The place is one scouted real-world location: a
building and its grounds with connected indoor and outdoor parts — every
specific comes from the attached production data (member descriptions + full
shot contents). Extract ONLY the permanent physical anatomy of the place.

HARD STYLE RULES:
- CONCISE. 70-130 words. List what must be IN the drawing — nothing else.
- The drawing form: a technical architectural SITE PLAN in the exact drafting
  language of a clean floor plan (thin dark outlines, flat muted colour fills,
  simple fixture symbols), drawn with slight axonometric depth, viewed from high
  above at an oblique angle tilted about 30 degrees from vertical — NOT straight
  down, NOT a photograph, NOT an artistic ink sketch.
- One drawing covers the WHOLE property: the building body, any upper-level
  enclosed unit with its deck and fixed fixtures, any exterior level-connecting
  route, the ground approach spaces and entrances the data gives, and a modest
  margin of neighbours. Levels and wall heights must read.
- NEUTRAL location document: no storytelling, no mood, no weather, no lighting
  effects, no story props, no vehicles. Never expose interior rooms/furniture.
  No people. Do not ask for any text in the image.
- Do NOT mention attached reference images, markers, letters or legends — those
  instructions are appended separately by the pipeline.

Output the core in ENGLISH plus a faithful KOREAN translation (human review
only; the image model receives the English)."""

B_CORE_SCHEMA = {
    "type": "object",
    "properties": {
        "core_en": {"type": "string"},
        "core_ko": {"type": "string"},
        "notes": {"type": "string",
                  "description": "what data grounded the anatomy choices"},
    },
    "required": ["core_en", "core_ko", "notes"],
    "additionalProperties": False,
}


def gen_b_core(recon):
    members = F.members_block(recon["members"])
    shot_blocks = "\n\n".join(
        F.shot_block(k, s) for k, s in sorted(recon["shots"].items()))
    user = ("PLACE MEMBERS:\n" + members
            + "\n\nALL SHOTS AT THIS PLACE (full content):\n\n" + shot_blocks
            + "\n\nWrite the site-plan prompt core now.")
    out = F.llm("forest_r6_root_core", B_CORE_SYSTEM, user, B_CORE_SCHEMA,
                model="gpt")
    F.save_plan("root_v3b_core", out)
    print(f"b core: {len(out['core_en'].split())} words")
    return out


# ── 변형 c: 2-hop 재스타일 (root_v2.png 단일 참조) ──

C_RESTYLE_PROMPT = "\n".join([
    "Image edit. Redraw the attached TOP-DOWN site plan in the exact drafting",
    "language of a clean architectural floor plan — thin dark outlines, flat",
    "muted colour fills, simple fixture symbols — and give it slight axonometric",
    "depth: tilt the view about 30 degrees from vertical so wall heights, the",
    "exterior stair run and the ground-to-roof level difference read clearly.",
    "",
    "Keep EVERYTHING in the drawing exactly where it is: the same layout,",
    "proportions and opening positions, the same circled capital-letter markers",
    "at the same spots, and the same right-side legend panel with identical",
    "text. Do not add, remove, move or rename anything.",
    "Never expose interior rooms or furniture. No people, no story props, no",
    "vehicles, no other text.",
])

C_RESTYLE_PROMPT_KO = (
    "이미지 편집. 첨부된 완전 수직 배치도를 깔끔한 건축 평면도의 도면 언어(가는"
    " 어두운 윤곽선, 플랫한 차분한 색 채움, 단순 설비 기호)로 다시 그리고, 약한"
    " 축측 깊이를 부여: 수직에서 약 30도 기울여 벽 높이/외부 계단/지상-옥상 레벨"
    " 차가 명확히 읽히게. 배치·비율·개구부 위치·원문자 마커 위치·우측 범례 텍스트"
    " 전부 그대로 유지(추가/제거/이동/개명 금지). 실내/가구 노출 금지, 사람/이야기"
    " 소품/차량/기타 텍스트 금지."
)


def build_prompts(recon, plan, b_core):
    fps = S8.exterior_fps(recon)
    shared = "\n\n".join([ref_block(len(fps)), data_block(recon),
                          marker_block(plan), legend_block(plan)])
    return {
        "a": (STYLE_CORE_A + "\n\n" + shared, [p for _, _, p in fps]),
        "b": (b_core["core_en"] + "\n\n" + shared, [p for _, _, p in fps]),
        "c": (C_RESTYLE_PROMPT, [F.OUT / "root_v2" / "root_v2.png"]),
    }


def images(recon, plan, b_core):
    OUTR.mkdir(parents=True, exist_ok=True)
    prompts = build_prompts(recon, plan, b_core)
    for v in VARIANTS:
        prompt, refs = prompts[v]
        F.img_gpt(f"r6_root_v3{v}", prompt, refs=refs,
                  out_path=OUTR / f"root_v3{v}.png")
        r6_page.build()


def readback(plan):
    for v in VARIANTS:
        png = OUTR / f"root_v3{v}.png"
        if not png.exists():
            continue
        user = [{"type": "text", "text":
                 "This is an architectural site plan drawn at a high oblique"
                 " angle, with circled capital letters and a legend panel."
                 " List every circled capital letter, list the legend entries"
                 " as written, and say whether any interior room layout or"
                 " furniture is exposed."},
                F.png_data_url(png)]
        rb = F.llm("forest_r6_root_readback",
                   "You read technical plans precisely.",
                   user, S1.READBACK_SCHEMA, model="gpt")
        F.save_plan(f"root_readback_v3{v}", rb)
        want = {e["id"] for e in plan["elements"]}
        got = set(rb.get("letters_found") or [])
        print(f"readback v3{v}: {len(got & want)}/{len(want)} letters,"
              f" interior={rb.get('interior_layout_exposed')}")
    r6_page.build()


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--only", default="all",
                    choices=["all", "core", "images", "readback", "html"])
    args = ap.parse_args()
    recon = F.load_recon()
    plan = F.load_plan("root_plan_v2")  # 검증된 요소/커버리지 재사용 (결정론)
    if args.only in ("all", "core"):
        b_core = gen_b_core(recon)
    else:
        b_core = F.load_plan("root_v3b_core")
    if args.only in ("all", "images"):
        images(recon, plan, b_core)
    if args.only in ("all", "readback"):
        readback(plan)
    if args.only in ("all", "html"):
        r6_page.build()
    F.runlog({"kind": "stage", "stage": "s10_root_v3", "done": args.only})


if __name__ == "__main__":
    main()
