"""goal#4 프로토타입 (커밋 금지) — 실내 FP-blocking A/B.

B방식(사용자 요구 구조): FP(top-down 도면) → 카메라+엔티티 blocking(I2I) →
eye-level 마네킹 스케치(I2I) → [최종샷 입력 guide].
A방식(현 production): bg plate(실사) → faint underlay → 마네킹 등록.

같은 L04 두 샷(s5/12, s14/5)에 대해 A·B guide 를 만들어 육안 비교(8911).
generic 프롬프트(시나리오 토큰 0, slot/spatial descriptor 만). main 미수정.
"""
import os
import sys
import uuid

os.environ["INDOOR_SHARED_POSE_GUIDE_ENABLED"] = "true"
os.environ["INDOOR_SHARED_POSE_GUIDE_JUDGE_ENABLED"] = "true"
_BACKEND = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "backend"))
os.chdir(_BACKEND)
sys.path.insert(0, _BACKEND)

PID = "7f325c39-7478-4386-a562-27daadd44353"
EID = "fbf15266-989c-4cc8-8d82-699ea5f628aa"
OUT = os.path.abspath(os.path.join(_BACKEND, "..", "scratchpad", "wave5_integration", "goal4_gallery"))
FP_PNG = os.path.join(
    "/Users/manta/Documents/Projects/TheRoad-I1/projects", PID, "episodes", EID,
    "images", "floor_plan", "fp_l04_main_living_space.png")

# 대상 L04 샷 (FP 존재)
SHOTS = [(5, 12), (14, 5)]


def _slot_lines(brief):
    lines = []
    letters = "ABCDEFGH"
    for i, f in enumerate(brief.get("figures") or []):
        L = letters[i]
        desc = f.get("slot") or "in the room"
        extra = []
        if f.get("pose"):
            extra.append(f"posture: {f['pose']}")
        if f.get("gesture"):
            extra.append(f["gesture"])
        tail = (" — " + "; ".join(extra)) if extra else ""
        lines.append(f"   - circle {L}: a figure positioned {desc}{tail}.")
    return "\n".join(lines) or "   - (none)"


def _blocking_prompt(brief):
    framing = brief.get("framing") or "medium"
    return (
        "The attached image is a TOP-DOWN schematic floor plan of an interior space. "
        "KEEP the whole plan EXACTLY as it is — same room outlines, zones, colours, "
        "proportions and orientation; do NOT redraw, move, recolour or restyle anything.\n"
        "ADD on top, for ONE camera shot only:\n"
        "1) FIGURES — for each, draw a small CIRCLE with a CAPITAL LETTER inside, placed "
        "in an open area of the room consistent with this spatial brief (screen-left = "
        "left side of the plan from the camera, foreground = nearer the camera):\n"
        f"{_slot_lines(brief)}\n"
        "2) THE CAMERA — exactly ONE recognizable CAMERA icon at a sensible eye-level "
        "vantage inside or at the edge of the room, with a BOLD direction ARROW and a "
        f"translucent triangular view-CONE aimed to frame the figures as a {framing} shot.\n"
        "Add NO text other than these new capital letters.")


_SKETCH_PROMPT = (
    "The attached image is a TOP-DOWN floor plan of one interior location seen from "
    "directly above. Lettered circles mark the figures present in this shot, and a "
    "camera icon with a view-cone shows the camera position and the direction it looks. "
    "Read it ONLY to understand which figures are in shot and their relative placement "
    "and the camera viewpoint.\n"
    "Now draw a ROUGH eye-level STORYBOARD COMPOSITION SKETCH of what THAT camera sees "
    "— a clean MONOCHROME pencil/marker line-art storyboard frame, NOT a finished "
    "render and NOT a top-down map. Use a normal horizontal EYE-LEVEL interior "
    "viewpoint, never a bird's-eye.\n"
    "Lay out the frame by this depth / left-right brief:\n{brief}\n"
    "Draw each figure as a simple featureless mannequin/placeholder at its mapped screen "
    "position and depth, resting on the floor/furniture surfaces beneath it (nothing "
    "floats). Draw room elements as simple line shapes only. This sketch defines ONLY "
    "framing, placement, depth and viewpoint — do NOT invent materials, colours, style, "
    "faces or surface finish.\n"
    "The plan's lettered circles, camera icon, view-cone and arrow are spatial "
    "annotations only — do NOT copy ANY of them into the sketch. The output must contain "
    "NO numbers, letters, circles, arrows, labels or text anywhere.")


def _brief_text(brief):
    parts = []
    for i, f in enumerate(brief.get("figures") or []):
        seg = f.get("slot") or "in frame"
        if f.get("pose"):
            seg += f", {f['pose']}"
        parts.append(f"figure {i+1}: {seg}")
    parts.append(brief.get("support_clause", ""))
    return " | ".join([p for p in parts if p])


def main():
    os.makedirs(OUT + "/img", exist_ok=True)
    from app.core.config import settings
    from app.core.database import SessionLocal, register_models
    register_models()
    from app.modules.pipeline.location_floor_plan import generate_floor_plan_image
    from app.modules.pipeline.indoor_shared_pose_plan import build_pose_brief
    from app.services.registered_pose_guide_service import _resolve_openai_client
    from app.services.scene_checkpoint_loaders import load_background_chain_bg_map, load_shot_staging_map
    from app.services.scene_persistence_service import ScenePersistenceService
    from app.core.steps.indoor_shared_pose_guide_context import _load_name_by_short_id

    db = SessionLocal()
    persistence = ScenePersistenceService(db, PID)
    stills, _ = persistence.load_episode_still_dicts(EID)
    staging_map = load_shot_staging_map(settings.projects_dir, PID, EID)
    bgmap = load_background_chain_bg_map(settings.projects_dir, PID, EID)
    name_by_id = _load_name_by_short_id(db, PID, EID)
    client = _resolve_openai_client()

    shot_by_key = {(s.get("scene_index"), s.get("shot_index")): (staging_map.get(f"{s.get('scene_index')}_{s.get('shot_index')}") or {})
                   for s in stills}

    import shutil
    shutil.copy(FP_PNG, OUT + "/img/FP_L04.png")

    rows = []
    for (si, shi) in SHOTS:
        key = f"{si}_{shi}"
        brief = build_pose_brief(shot_by_key.get((si, shi)) or {}, name_by_id=name_by_id)
        bg_bytes = (bgmap.get(key) or {}).get("image_bytes")
        # bg plate 저장(참조)
        if bg_bytes:
            open(f"{OUT}/img/{key}_bg.png", "wb").write(bg_bytes)

        # Stage B: FP → blocking
        print(f"[{key}] Stage B blocking...", flush=True)
        blocking = generate_floor_plan_image(
            prompt=_blocking_prompt(brief), openai_client=client,
            ref_paths=[__import__("pathlib").Path(FP_PNG)], model="gpt-image-2",
            capture_role=None)
        bpath = f"{OUT}/img/{key}_B1_blocking.png"; open(bpath, "wb").write(blocking)

        # Stage C: blocking → eye-level sketch
        print(f"[{key}] Stage C sketch...", flush=True)
        btmp = f"{OUT}/img/.{key}.blk.{uuid.uuid4().hex}.png"; open(btmp, "wb").write(blocking)
        sketch = generate_floor_plan_image(
            prompt=_SKETCH_PROMPT.format(brief=_brief_text(brief)), openai_client=client,
            ref_paths=[__import__("pathlib").Path(btmp)], model="gpt-image-2",
            capture_role=None)
        os.unlink(btmp)
        open(f"{OUT}/img/{key}_B2_sketch.png", "wb").write(sketch)
        print(f"[{key}] done. figures={len(brief.get('figures') or [])} framing={brief.get('framing')}", flush=True)
        rows.append((key, len(brief.get("figures") or []), brief.get("framing"), _brief_text(brief)))

    db.close()
    print("\n=== summary ===")
    for r in rows:
        print(" ", r)


if __name__ == "__main__":
    main()
