"""s15 — fp 재생성 (사용자 3단 지시, 2026-07-05 — fp 만 먼저):

  ① 블록 도면 = **참조만** — 모사/트레이스 금지, 무엇이 있고 대략 어디인지의
     레퍼런스로만 쓰고 구도/비율은 모델이 실제 장소답게 자유 작도.
  ② **LLM 이 건물/도면 상세 설명을 생성**해 프롬프트에 주입 — 특히 상부 유닛
     아래 건물 몸체의 층수 구조(데이터에 없으면 가장 그럴듯한 층수를 LLM 이
     선택하고 그렇게 밝힘), 외피/개구부/지상 접근 전부 상세히. EN+KO(갤러리).
  ③ 코드 렌더 도면 자체가 어색 → 참조 이미지 없이 **추출 내용 텍스트만으로
     T2I 가 알아서** 작도(순수 생성).
표시 순서 = ①→②→③(=r1/r2/r0). 각 gpt/nb2. 시나리오 중립: 구체 내용은 전부
데이터(씬 원문/layout JSON/loc/override) — 템플릿은 kind·일반 어휘만.
사용: .venv/bin/python s15_fp_redo.py [--only desc|images|html]
산출: plans/fp_place_desc.json, plans/fp_redo_prompts.json,
      out/blockset/fp_r{1,2,0}_{gpt,nb2}.png, blockgen.html 갱신
"""
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 s13_blockset_v2 as S13  # noqa: E402
import s14_blockgen as S14  # noqa: E402

OUTB = F.OUT / "blockset"

DESC_SYSTEM = """You write the PLACE DESCRIPTION for an architectural SITE-PLAN
asset in film pre-production. The place is one real-world location: a building
and its grounds with connected indoor and outdoor parts. You get production
data: member descriptions, the FULL original scene texts, a structured block
layout (elements with kinds and height layers) and any production-owner
corrections. Every specific must come from that data.

Write a DETAILED, CONCRETE description for the plan-drawing model:
- building_en: the main building itself — ★its storey structure explicitly
  (how many storeys the body has BELOW any upper-level unit; if the texts do
  not state a count, choose the most plausible one for this type of building
  and say it plainly as a chosen assumption), massing, envelope materials as
  the data gives them, facade opening pattern per storey, roof condition.
- upper_en: any topmost enclosed unit and its open deck level — envelope,
  boundary openings (counts per the data), fixed fixtures around it.
- grounds_en: ground level — approach, boundary, entrances, any exterior
  level-connecting route (honour production-owner corrections about it).
- plan_en: what the finished TOP-DOWN site plan must therefore show, in
  drawing terms (2-4 sentences).
Each field 40-90 words, plain factual tone, present-tense physical state
only — never past or one-off events, actions, usage stories or story
moments. NO story props, no vehicles, no people, no mood/lighting words, no
proper names, no text/labels in the image.
Also output faithful Korean translations (same keys with _ko) for human
review. notes: which data grounded the storey choice and opening counts."""

DESC_SCHEMA = {
    "type": "object",
    "properties": {k: {"type": "string"} for k in (
        "building_en", "building_ko", "upper_en", "upper_ko",
        "grounds_en", "grounds_ko", "plan_en", "plan_ko", "notes")},
    "required": ["building_en", "building_ko", "upper_en", "upper_ko",
                 "grounds_en", "grounds_ko", "plan_en", "plan_ko", "notes"],
    "additionalProperties": False,
}


def gen_desc(recon, layout):
    members = F.members_block(recon["members"])
    idxs, scenes = S13.scene_texts(recon)
    ov = S14.load_overrides()
    user = ("PLACE MEMBERS:\n" + members
            + "\n\nFULL ORIGINAL SCENE TEXTS:\n\n" + scenes
            + "\n\nSTRUCTURED BLOCK LAYOUT (extracted):\n"
            + json.dumps(layout, ensure_ascii=False)
            + "\n\nPRODUCTION-OWNER CORRECTIONS:\n"
            + "\n".join(ov.get("facts_extra") or ["(none)"])
            + "\n\nWrite the place description now.")
    out = F.llm("forest_fp_place_desc", DESC_SYSTEM, user, DESC_SCHEMA,
                model="gpt")
    F.save_plan("fp_place_desc", out)
    print("desc words:", sum(len(out[k].split()) for k in
                             ("building_en", "upper_en", "grounds_en", "plan_en")))
    return out


LOOSE_REF = "\n".join([
    "The FIRST attached image is a rough BLOCK REFERENCE of one real property:",
    "it tells you only WHAT exists and the APPROXIMATE arrangement. Do NOT",
    "trace, copy or reproduce its composition, block shapes, proportions or",
    "spacing. Compose the site plan yourself so the place reads as a real,",
    "believable property that satisfies the facts below. Its fill colours",
    "encode height layers only — never surface colours. Any letters/numbers",
    "on it are annotations — never draw them.",
])

STYLE_2D = "\n".join([
    "Draw ONE strict TOP-DOWN 2D architectural SITE PLAN in the drafting",
    "language of a clean floor plan — thin dark outlines, flat muted colour",
    "fills, detailed plan symbols (double-line walls, door leaf + swing arc,",
    "window frames set in walls, stair treads with stringers and landings,",
    "base rings for round fixtures, paving joints, kerb lines). Height",
    "levels appear only as flat fill tints (darker = higher). Flat",
    "orthographic plan view: NO 3D, NO axonometric, NO perspective, no",
    "shadows. Neutral document: no people, no story props, no vehicles, no",
    "posted papers, no text, no letters, no numbers, no markers, no legend,",
    "no compass. Never expose interior rooms or furniture of any enclosed",
    "unit — outer shell and boundary openings only.",
])

STYLE_REF_NOTE = "\n".join([
    "The LAST attached image is an interior floor plan from the same",
    "production — use it ONLY as the DRAFTING-STYLE reference (line weight,",
    "fill language, symbol vocabulary). Never copy its rooms or content.",
])


def desc_block(desc):
    return "\n".join([
        "THE PLACE (authored description — the content ground truth):",
        "BUILDING: " + desc["building_en"],
        "TOP UNIT & DECK: " + desc["upper_en"],
        "GROUNDS: " + desc["grounds_en"],
        "THE PLAN MUST SHOW: " + desc["plan_en"],
    ])


def facts_block(layout, ids):
    return ("LAYOUT RELATIONS (approximate — honour what exists and rough"
            " relations, NOT exact geometry):\n"
            + S14.summary_lines(layout, ids=ids))


def r1_prompt(layout, recon, ids):
    return "\n\n".join([
        LOOSE_REF, facts_block(layout, ids),
        "WHAT THIS PLACE LOOKS LIKE (production data):\n"
        + S14.loc_lines(recon),
        STYLE_REF_NOTE, STYLE_2D,
    ])


def r2_prompt(layout, recon, desc, ids):
    return "\n\n".join([
        LOOSE_REF, desc_block(desc), facts_block(layout, ids),
        STYLE_REF_NOTE, STYLE_2D,
    ])


def r0_prompt(layout, recon, desc, ids):
    return "\n\n".join([
        "Compose the site plan of one real property entirely from the text",
        "below — there is NO layout reference image; you decide the whole",
        "composition so the place reads as real and believable. If a",
        "drafting-style reference image is attached, copy ONLY its drawing",
        "style (line weight, fills, symbols) — never its rooms or content.",
        desc_block(desc), facts_block(layout, ids),
        STYLE_2D,
    ])


def images(recon, layout, desc):
    fp = S14.indoor_fp(recon)
    clean = OUTB / "blockset_v2_canvas_clean.png"
    prompts = {}

    def run(tag, fn, prompt, gpt_refs, nb2_refs):
        prompts[fn] = prompt
        if fn.endswith("_gpt.png"):
            F.img_gpt(tag, prompt, refs=gpt_refs, out_path=OUTB / fn)
        else:
            F.img_nb2(tag, prompt, nb2_refs, out_path=OUTB / fn)
        F.save_plan("fp_redo_prompts", prompts)
        S14.build_html(layout, recon)

    style_gpt = [fp] if fp else []
    style_nb2 = [(S14.NB2_STYLE_LABEL, fp)] if fp else []
    # ① 참조 약화 (구도 자유)
    run("fp_r1_gpt", "fp_r1_gpt.png", r1_prompt(layout, recon, True),
        [S14.CANVAS_PNG] + style_gpt, None)
    run("fp_r1_nb2", "fp_r1_nb2.png", r1_prompt(layout, recon, False),
        None, [(S14.NB2_LABEL, clean)] + style_nb2)
    # ② + LLM 상세 설명 (층수 포함)
    run("fp_r2_gpt", "fp_r2_gpt.png", r2_prompt(layout, recon, desc, True),
        [S14.CANVAS_PNG] + style_gpt, None)
    run("fp_r2_nb2", "fp_r2_nb2.png", r2_prompt(layout, recon, desc, False),
        None, [(S14.NB2_LABEL, clean)] + style_nb2)
    # ⓪ T2I 단독 (레이아웃 참조 0 — 추출 내용 텍스트만, 스타일 fp 는 도면 언어용)
    run("fp_r0_gpt", "fp_r0_gpt.png", r0_prompt(layout, recon, desc, True),
        style_gpt or None, None)
    run("fp_r0_nb2", "fp_r0_nb2.png", r0_prompt(layout, recon, desc, False),
        None, style_nb2 or None)


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--only", default="all", choices=["all", "desc", "images",
                                                      "html"])
    args = ap.parse_args()
    recon = F.load_recon()
    layout = F.load_plan("blockset_layout_v2")
    if args.only in ("all", "desc"):
        desc = gen_desc(recon, layout)
    else:
        desc = F.load_plan("fp_place_desc")
    if args.only in ("all", "images"):
        images(recon, layout, desc)
    if args.only in ("all", "html"):
        S14.build_html(layout, recon)
    F.runlog({"kind": "stage", "stage": "s15_fp_redo", "done": args.only})


if __name__ == "__main__":
    main()
