"""s18 — A‴ 블록 다이어그램 최종형 (사용자 확정 조합, 2026-07-05):

  - **스타일 = s16 A 형** (코드 렌더 같은 순수 플랫 블록) + 잔여 3D 완전 금지
    (베벨/가장자리 하이라이트/그라데이션/입체감 0) + 라벨 텍스트 0.
  - **내용 = LLM 추출 구조 데이터의 lean 전달**: layout JSON 에서 상대 위치·
    크기·중요 배치만 결정론 변환(이름+모양+크기어+위치어+부착변+연결) —
    [kind] 괄호/마커 토큰/높이 이론 등 라벨 유발 요소 제거. 높이는 톤 규칙 1줄.
  - 그 A‴ 를 참조로 **fp(B′)** 생성 (확립된 2단계 fp 계약 재사용).
사용: .venv/bin/python s18_blocks_lean.py [--only blocks|fp|html]
산출: out/blockset/blocks4_{nb2,gpt}.png, out/blockset/fp2_{nb2,gpt}.png,
      blockgen.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 s13_blockset_v2 as S13  # noqa: E402
import s14_blockgen as S14  # noqa: E402
import s15_fp_redo as S15  # noqa: E402
import s16_nb2_chain as S16  # noqa: E402

OUTB = F.OUT / "blockset"

STYLE_FLAT = "\n".join([
    "Draw ONE SCHEMATIC BLOCK DIAGRAM of a real property's layout — the kind",
    "of flat figure a computer program draws: strict TOP-DOWN 2D, plain",
    "solid-colour rectangles and circles on a plain light background, uniform",
    "thin dark outlines. ABSOLUTELY FLAT: no bevels, no edge highlights, no",
    "gradients, no shadows, no shading, no texture, no perspective, no 3D",
    "feel of any kind — every block is one uniform colour region with a plain",
    "outline. PURPOSE: structural comprehension only. Every building is ONE",
    "solid block — never any interior rooms, inner walls or furniture.",
    "No text, letters, numbers, labels, markers, legend or compass anywhere.",
    "No people, no props, no vehicles.",
])


def _size_word(e, cw, ch):
    area = (e["w"] * e["h"]) / (cw * ch) * 100.0
    aspect = max(e["w"], e["h"]) / max(0.001, min(e["w"], e["h"]))
    if aspect >= 4:
        return "thin strip"
    if area >= 15:
        return "large"
    if area >= 4:
        return "medium"
    if area >= 1:
        return "small"
    return "tiny"


def lean_structure_lines(layout):
    """layout JSON → 상대 위치·크기·중요 배치만 (라벨 유발 토큰 0, 결정론)."""
    cw = float(layout["canvas"]["width"]) or 100.0
    ch = float(layout["canvas"]["height"]) or 100.0
    blocks = [e for e in layout["elements"] if e["kind"] in S13.BLOCK_KINDS]
    lines = []
    for e in layout["elements"]:
        shape = "round" if e["shape"] == "circle" else "rectangular"
        pos = S14._pos_words(e, cw, ch)
        size = _size_word(e, cw, ch)
        extra = ""
        if e["kind"] in S13.POINT_KINDS:
            side = S14._nearest_block_side(e, blocks)
            if side:
                extra = f", {side}"
        if e["kind"] == "stair":
            extra = ", connecting the ground level up to the deck level"
        if e["kind"] == "neighbor_mass":
            extra = ", plain simplified block"
        rep = f" ({e['repeat']} in a row)" if e.get("repeat", 1) > 1 else ""
        where = f"{pos} area"
        if not extra:
            inside = S14._containment_phrase(e, blocks)
            if inside:
                where = inside
        lines.append(f"- {e['name_en']}: {size} {shape} block, {where}"
                     f"{extra}{rep}")
    lay = sorted(layout["layers"], key=lambda l: l["rel_height"])
    tint = " -> ".join(l["name_en"] for l in lay)
    lines.append(f"- one flat tint rule only, to tell height levels apart,"
                 f" low to high: {tint} (darker = higher)")
    return "\n".join(lines)


def blocks_prompt(layout):
    return (STYLE_FLAT
            + "\n\nSTRUCTURE (relative positions, sizes, key arrangement):\n"
            + lean_structure_lines(layout))


def run_blocks(layout, recon):
    p = blocks_prompt(layout)
    try:
        prompts = F.load_plan("nb2_chain_prompts")
    except Exception:
        prompts = {}
    prompts["blocks4_nb2.png"] = p
    prompts["blocks4_gpt.png"] = p
    F.save_plan("nb2_chain_prompts", prompts)
    F.img_nb2("s18_blocks4_nb2", p, [], out_path=OUTB / "blocks4_nb2.png")
    S14.build_html(layout, recon)
    F.img_gpt("s18_blocks4_gpt", p, refs=None,
              out_path=OUTB / "blocks4_gpt.png")
    S14.build_html(layout, recon)


def run_fp(layout, recon):
    desc = F.load_plan("fp_place_desc")
    fp_style = S14.indoor_fp(recon)
    p = S16.fp_prompt(layout, recon, desc)
    try:
        prompts = F.load_plan("nb2_chain_prompts")
    except Exception:
        prompts = {}
    prompts["fp2_nb2.png"] = p
    prompts["fp2_gpt.png"] = p
    F.save_plan("nb2_chain_prompts", prompts)
    blocks = OUTB / "blocks4_nb2.png"
    refs_nb2 = [(S16.NB2_FP_BLOCKS_LABEL, blocks)]
    if fp_style:
        refs_nb2.append((S14.NB2_STYLE_LABEL, fp_style))
    F.img_nb2("s18_fp2_nb2", p, refs_nb2, out_path=OUTB / "fp2_nb2.png")
    S14.build_html(layout, recon)
    F.img_gpt("s18_fp2_gpt", p,
              refs=[blocks] + ([fp_style] if fp_style else []),
              out_path=OUTB / "fp2_gpt.png")
    S14.build_html(layout, recon)


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--only", default="all",
                    choices=["all", "blocks", "fp", "html"])
    args = ap.parse_args()
    recon = F.load_recon()
    layout = F.load_plan("blockset_layout_v2")
    if args.only in ("all", "blocks"):
        run_blocks(layout, recon)
    if args.only in ("all", "fp"):
        run_fp(layout, recon)
    if args.only in ("all", "html"):
        S14.build_html(layout, recon)
    F.runlog({"kind": "stage", "stage": "s18_blocks_lean", "done": args.only})


if __name__ == "__main__":
    main()
