"""실험 — interior 도면을 reference로 4방향(N/E/S/W) 사진 멀티턴 생성.

사용:
  python scripts/experiment_360_interior.py \
    --plan-png <path> --out-dir <dir> [--run-dir <v4 run dir>]

run-dir 지정 시 step1_spatial.json에서 environment_canon 추출하여 prompt에 자동 주입.
"""
from __future__ import annotations

import argparse
import base64
import json
import logging
import os
import sys
from pathlib import Path
from typing import Optional

BACKEND = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(BACKEND))

from dotenv import load_dotenv  # noqa: E402
load_dotenv(BACKEND / ".env")

from openai import OpenAI  # noqa: E402

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logger = logging.getLogger("360")


def compact_canon_text(canon: dict) -> str:
    parts = []
    b = canon.get("building") or {}
    if b:
        bp = []
        for k in ("stories", "primary_material", "exterior_stairs",
                  "rooftop_features", "window_pattern", "weathering"):
            v = b.get(k)
            if v:
                if isinstance(v, list): v = ", ".join(map(str, v))
                bp.append(f"{k}={v}")
        if b.get("color_palette"):
            bp.append("colors=" + ", ".join(map(str, b["color_palette"])))
        if bp: parts.append("Building: " + "; ".join(bp))
    i = canon.get("interior") or {}
    if i:
        ip = []
        for k in ("wall_finish", "floor_finish", "ceiling",
                  "lighting_fixtures", "general_clutter_level"):
            v = i.get(k)
            if v:
                if isinstance(v, list): v = ", ".join(map(str, v))
                ip.append(f"{k}={v}")
        if ip: parts.append("Interior: " + "; ".join(ip))
    return ". ".join(parts).strip()


VIEW_PROMPTS = [
    ("N", "facing NORTH, looking toward the north wall of the room"),
    ("E", "facing EAST (90 degrees clockwise rotation from the previous N view), looking toward the east wall"),
    ("S", "facing SOUTH (180 degrees from the N view, opposite the entry), looking toward the south wall"),
    ("W", "facing WEST (270 degrees clockwise from the N view), looking toward the west wall"),
]


def build_prompt(direction_desc: str, canon_text: str, is_first: bool) -> str:
    base = (
        "Photorealistic cinematic interior eye-level still, 35mm film aesthetic. "
        "Same room as the architectural floor plan reference image. "
        f"Camera position: standing roughly at the room's center, {direction_desc}, "
        "approx 1.6m camera height (human eye-level), slight wide angle ~28mm.\n\n"
        "**THIS IS A DIFFERENT CAMERA ANGLE — show the wall and furniture that fall in "
        f"the {direction_desc.split(',')[0]} half of the room. The composition must clearly "
        "differ from any previous view photo (different walls, different furniture in frame).**\n\n"
        "Strict requirements:\n"
        "- Maintain EXACT wall layout, door positions, window positions, and furniture "
        "placement as shown in the floor plan reference. Do not invent walls or furniture.\n"
        "- Visual identity (wall finish, floor finish, ceiling, lighting fixtures, "
        "color palette, weathering) consistent with canon below "
        + ("AND with previous view photo references provided (but DIFFERENT camera angle)"
           if not is_first else "")
        + ".\n"
        "- No people, no characters, no figures, empty room, uninhabited.\n"
        "- Soft natural daylight from window mixed with dim domestic practicals "
        "(weak floor lamp, faint old television glow if visible). Realistic shadows.\n"
        "- Subtle depth of field, slight film grain, lived-in details "
        "(dust motes, scuff marks, subtle wear).\n"
        "- No overlaid text, no diagram lines, no architectural label letters, "
        "no captions, no watermarks.\n"
        "- 35-degree horizontal field of view fits one major wall and partial side walls."
    )
    if canon_text:
        base += f"\n\nCanon: {canon_text}"
    return base


def main() -> int:
    p = argparse.ArgumentParser()
    p.add_argument("--plan-png", required=True, help="interior floor plan PNG (reference)")
    p.add_argument("--out-dir", required=True)
    p.add_argument("--run-dir", default=None,
                   help="(선택) v4 run dir — step1_spatial.json에서 canon 자동 추출")
    p.add_argument("--canon", default="", help="canon spec 영문 한 단락 (직접 지정)")
    p.add_argument("--model", default="gpt-image-2")
    p.add_argument("--size", default="1536x1024")
    p.add_argument("--quality", default="high")
    p.add_argument("--no-prev-chain", action="store_true",
                   help="prev view photo를 ref에서 제외 (시점이 같아지는 문제 회피)")
    args = p.parse_args()

    plan_png = Path(args.plan_png).resolve()
    if not plan_png.exists():
        logger.error("plan-png 없음: %s", plan_png); return 1

    out_dir = Path(args.out_dir).resolve()
    out_dir.mkdir(parents=True, exist_ok=True)

    canon_text = args.canon
    if not canon_text and args.run_dir:
        sp_path = Path(args.run_dir) / "step1_spatial.json"
        if sp_path.exists():
            spatial = json.loads(sp_path.read_text(encoding="utf-8"))
            canon_text = compact_canon_text(spatial.get("environment_canon") or {})
            logger.info("canon from run-dir: %d chars", len(canon_text))

    if not os.getenv("OPENAI_API_KEY"):
        logger.error("OPENAI_API_KEY not set"); return 1

    client = OpenAI()
    prev_photo: Optional[Path] = None

    for tag, direction_desc in VIEW_PROMPTS:
        is_first = (prev_photo is None) or args.no_prev_chain
        prompt = build_prompt(direction_desc, canon_text, is_first)
        # refs: --no-prev-chain이면 plan만 (시점 다양화 우선),
        # 아니면 prev photo + plan (visual identity 우선)
        if args.no_prev_chain:
            refs = [plan_png]
        else:
            refs = ([prev_photo] if prev_photo else []) + [plan_png]
        logger.info("[view %s] refs=%d (prev=%s, plan=%s, no_chain=%s)",
                    tag, len(refs), bool(prev_photo) and not args.no_prev_chain,
                    plan_png.name, args.no_prev_chain)
        files = [open(p, "rb") for p in refs]
        try:
            resp = client.images.edit(
                model=args.model, image=files, prompt=prompt,
                size=args.size, quality=args.quality, n=1,
            )
        finally:
            for f in files:
                f.close()
        out_p = out_dir / f"interior_view_{tag}.png"
        b64 = resp.data[0].b64_json
        if not b64:
            logger.error("[view %s] empty b64", tag); continue
        out_p.write_bytes(base64.b64decode(b64))
        logger.info("[view %s] saved: %s (%d KB)", tag, out_p.name, out_p.stat().st_size // 1024)
        prev_photo = out_p

    logger.info("=== DONE === %s", out_dir)
    return 0


if __name__ == "__main__":
    sys.exit(main())
