"""spec.json → top-down schematic PNG (matplotlib).

base floor plan (Layer 1) + shot-spec overlay (Layer 2) 둘 다 지원.
"""
from __future__ import annotations

import json
import math
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib import font_manager
from matplotlib.patches import FancyArrowPatch, Polygon, Rectangle, Wedge

# 한글 폰트 — macOS AppleGothic 우선, 없으면 NanumGothic, 없으면 default
for _fname in ("AppleGothic", "NanumGothic", "Malgun Gothic"):
    try:
        font_manager.findfont(_fname, fallback_to_default=False)
        plt.rcParams["font.family"] = _fname
        break
    except Exception:
        pass
plt.rcParams["axes.unicode_minus"] = False


# ───────── color palette ─────────

ROOM_COLORS = {
    "living": "#fef3c7",       # amber-100
    "main_bedroom": "#fce7f3", # pink-100
    "daughter_bedroom": "#dbeafe", # blue-100
    "bathroom": "#cffafe",     # cyan-100
    "entryway": "#e5e7eb",     # gray-200
    "kitchen": "#fef3c7",      # same as living if combined
    "default": "#f3f4f6",
}

FURNITURE_COLORS = {
    "tv": "#1f2937",
    "sofa": "#7c3aed",
    "table": "#92400e",
    "sink": "#0891b2",
    "bed": "#be185d",
    "mirror": "#e5e7eb",
    "toilet": "#f3f4f6",
    "curtain": "#fbcfe8",
    "stove": "#dc2626",
    "refrigerator": "#1d4ed8",
    "chair": "#a16207",
    "default": "#9ca3af",
}

EXTERIOR_COLORS = {
    "rooftop_concrete": "#d6d3d1",
    "balcony": "#fed7aa",
    "street": "#94a3b8",
    "default": "#e7e5e4",
}


def _room_color(room_id: str) -> str:
    rid = room_id.lower()
    for k, v in ROOM_COLORS.items():
        if k in rid:
            return v
    return ROOM_COLORS["default"]


def _furniture_color(ftype: str) -> str:
    return FURNITURE_COLORS.get(ftype.lower(), FURNITURE_COLORS["default"])


def _exterior_color(etype: str) -> str:
    et = etype.lower()
    for k, v in EXTERIOR_COLORS.items():
        if k in et:
            return v
    return EXTERIOR_COLORS["default"]


# ───────── Layer 1: base floor plan ─────────

def render_base_floor_plan(
    spec: Dict[str, Any],
    out_path: Path,
    title: str = "",
    figsize: Tuple[int, int] = (12, 12),
) -> Path:
    """base spec.json → top-down schematic PNG."""
    canvas = spec.get("canvas", {"width": 10, "height": 10})
    cw = float(canvas.get("width", 10))
    ch = float(canvas.get("height", 10))

    # canvas extension to include exterior zones
    pad = 1.5
    xmin, ymin = -pad, -pad
    xmax, ymax = cw + pad, ch + pad
    for ez in spec.get("exterior_adjacency_zones", []):
        for x, y in ez.get("polygon", []):
            xmin = min(xmin, x - 0.5)
            ymin = min(ymin, y - 0.5)
            xmax = max(xmax, x + 0.5)
            ymax = max(ymax, y + 0.5)

    fig, ax = plt.subplots(figsize=figsize)
    ax.set_xlim(xmin, xmax)
    ax.set_ylim(ymin, ymax)
    ax.set_aspect("equal")
    ax.set_facecolor("#fafafa")

    # exterior zones (그리기 먼저, room이 위에 덮음)
    for ez in spec.get("exterior_adjacency_zones", []):
        poly = ez.get("polygon", [])
        if len(poly) >= 3:
            patch = Polygon(
                poly, closed=True,
                facecolor=_exterior_color(ez.get("type", "")),
                edgecolor="#78716c", linewidth=1.5, hatch="//",
                alpha=0.5,
            )
            ax.add_patch(patch)
            cx = sum(p[0] for p in poly) / len(poly)
            cy = sum(p[1] for p in poly) / len(poly)
            label = ez.get("label_en") or ez.get("type", "exterior")
            ax.text(cx, cy, label.upper(), ha="center", va="center",
                    fontsize=10, weight="bold", color="#44403c")

    # rooms
    for room in spec.get("rooms", []):
        poly = room.get("polygon", [])
        if len(poly) < 3:
            continue
        patch = Polygon(
            poly, closed=True,
            facecolor=_room_color(room["id"]),
            edgecolor="#1f2937", linewidth=2.5,
        )
        ax.add_patch(patch)
        cx = sum(p[0] for p in poly) / len(poly)
        cy = sum(p[1] for p in poly) / len(poly)
        label_ko = room.get("label_ko", "")
        label_en = room.get("label_en", room["id"])
        ax.text(cx, cy + 0.4, label_ko, ha="center", va="center",
                fontsize=11, weight="bold", color="#111827")
        ax.text(cx, cy - 0.1, f"({label_en})", ha="center", va="center",
                fontsize=8, style="italic", color="#374151")

    # doors
    for door in spec.get("doors", []):
        x, y = door.get("position", [0, 0])
        kind = door.get("kind", "interior")
        if kind == "front_entry":
            color = "#dc2626"  # red
            size = 0.5
        elif kind == "window":
            color = "#3b82f6"  # blue
            size = 0.4
        else:
            color = "#16a34a"  # green
            size = 0.35
        # door arc (swing)
        wedge = Wedge((x, y), size, 0, 90, facecolor=color, alpha=0.3,
                      edgecolor=color, linewidth=1.2)
        ax.add_patch(wedge)
        ax.plot([x], [y], marker="o", markersize=6, color=color, zorder=10)
        ax.text(x + 0.15, y + 0.15, door.get("id", ""), fontsize=6, color=color)

    # furniture
    for f in spec.get("furniture", []):
        x, y = f.get("position", [0, 0])
        sw, sh = f.get("size", [0.5, 0.5])
        color = _furniture_color(f.get("type", ""))
        rect = Rectangle(
            (x - sw / 2, y - sh / 2), sw, sh,
            facecolor=color, edgecolor="#000",
            linewidth=1.0, alpha=0.85,
        )
        ax.add_patch(rect)
        label = f.get("label_en") or f.get("type", "")
        text_y = y + sh / 2 + 0.1
        ax.text(x, text_y, label.upper(), ha="center", va="bottom",
                fontsize=7, color="#111827", weight="bold")

    # north arrow
    nx, ny = xmax - 0.7, ymax - 0.7
    ax.annotate("N", xy=(nx, ny + 0.4), xytext=(nx, ny - 0.2),
                arrowprops={"arrowstyle": "->", "color": "#1f2937", "lw": 1.5},
                ha="center", fontsize=11, weight="bold", color="#1f2937")

    # title + grid
    ax.set_title(title, fontsize=13, weight="bold", pad=10)
    ax.grid(True, linestyle=":", alpha=0.3)
    ax.set_xlabel("x (m)")
    ax.set_ylabel("y (m)")

    # legend
    legend_handles = [
        mpatches.Patch(color="#dc2626", label="front entry"),
        mpatches.Patch(color="#16a34a", label="interior door"),
        mpatches.Patch(color="#3b82f6", label="window"),
    ]
    ax.legend(handles=legend_handles, loc="upper left", fontsize=8, framealpha=0.9)

    out_path.parent.mkdir(parents=True, exist_ok=True)
    plt.tight_layout()
    fig.savefig(out_path, dpi=120, bbox_inches="tight")
    plt.close(fig)
    return out_path


# ───────── Layer 2: shot-spec overlay ─────────

def render_shot_overlay(
    base_spec: Dict[str, Any],
    shot_layout: Dict[str, Any],
    out_path: Path,
    title: str = "",
    figsize: Tuple[int, int] = (12, 12),
) -> Path:
    """base + shot-spec overlay (camera cone + characters + active props)."""
    base_path = out_path.with_suffix(".__base__.png")
    render_base_floor_plan(base_spec, base_path, title="", figsize=figsize)

    canvas = base_spec.get("canvas", {"width": 10, "height": 10})
    cw = float(canvas.get("width", 10))
    ch = float(canvas.get("height", 10))

    pad = 1.5
    xmin, ymin = -pad, -pad
    xmax, ymax = cw + pad, ch + pad
    for ez in base_spec.get("exterior_adjacency_zones", []):
        for x, y in ez.get("polygon", []):
            xmin = min(xmin, x - 0.5)
            ymin = min(ymin, y - 0.5)
            xmax = max(xmax, x + 0.5)
            ymax = max(ymax, y + 0.5)

    # re-render with overlay
    fig, ax = plt.subplots(figsize=figsize)
    ax.set_xlim(xmin, xmax)
    ax.set_ylim(ymin, ymax)
    ax.set_aspect("equal")
    ax.set_facecolor("#fafafa")

    # base layer (faded)
    for ez in base_spec.get("exterior_adjacency_zones", []):
        poly = ez.get("polygon", [])
        if len(poly) >= 3:
            ax.add_patch(Polygon(
                poly, closed=True,
                facecolor=_exterior_color(ez.get("type", "")),
                edgecolor="#78716c", linewidth=1.0, alpha=0.4,
            ))
    for room in base_spec.get("rooms", []):
        poly = room.get("polygon", [])
        if len(poly) >= 3:
            ax.add_patch(Polygon(
                poly, closed=True,
                facecolor=_room_color(room["id"]),
                edgecolor="#1f2937", linewidth=2.0, alpha=0.7,
            ))
            cx = sum(p[0] for p in poly) / len(poly)
            cy = sum(p[1] for p in poly) / len(poly)
            ax.text(cx, cy, room.get("label_ko", room["id"]),
                    ha="center", va="center", fontsize=9,
                    color="#374151", alpha=0.7)
    for f in base_spec.get("furniture", []):
        x, y = f.get("position", [0, 0])
        sw, sh = f.get("size", [0.5, 0.5])
        ax.add_patch(Rectangle(
            (x - sw / 2, y - sh / 2), sw, sh,
            facecolor=_furniture_color(f.get("type", "")),
            edgecolor="#000", linewidth=0.5, alpha=0.5,
        ))
        ax.text(x, y + sh / 2 + 0.05, f.get("label_en", "").upper(),
                ha="center", va="bottom", fontsize=6, alpha=0.6)

    # ── overlay: camera ──
    cam = shot_layout.get("camera", {})
    if cam:
        cx, cy = cam.get("position", [0, 0])
        facing_deg = cam.get("facing_deg", 90)  # 0=east, 90=north
        cone_deg = cam.get("cone_angle_deg", 60)
        cone_len = cam.get("focal_distance", 4)
        # camera marker
        ax.plot(cx, cy, marker="o", markersize=14,
                markerfacecolor="#dc2626", markeredgecolor="#7f1d1d",
                markeredgewidth=2, zorder=20)
        ax.text(cx, cy - 0.4, "CAM", ha="center", fontsize=9,
                weight="bold", color="#7f1d1d", zorder=21)
        # cone wedge
        start = facing_deg - cone_deg / 2
        end = facing_deg + cone_deg / 2
        cone = Wedge((cx, cy), cone_len, start, end,
                     facecolor="#dc2626", alpha=0.18,
                     edgecolor="#dc2626", linewidth=1.5, zorder=15)
        ax.add_patch(cone)
        # facing arrow
        rad = math.radians(facing_deg)
        ax.annotate("", xy=(cx + math.cos(rad) * cone_len * 0.7,
                            cy + math.sin(rad) * cone_len * 0.7),
                    xytext=(cx, cy),
                    arrowprops={"arrowstyle": "->", "color": "#7f1d1d",
                                "lw": 2.0}, zorder=22)

    # ── overlay: characters ──
    for ch_pos in shot_layout.get("characters", []):
        x, y = ch_pos.get("position", [0, 0])
        cid = ch_pos.get("id", "?")
        facing = ch_pos.get("facing_deg", 90)
        # body marker
        ax.plot(x, y, marker="o", markersize=18,
                markerfacecolor="#facc15", markeredgecolor="#854d0e",
                markeredgewidth=2, zorder=20)
        ax.text(x, y, cid, ha="center", va="center",
                fontsize=9, weight="bold", color="#1f2937", zorder=21)
        # facing arrow (small)
        rad = math.radians(facing)
        ax.annotate("", xy=(x + math.cos(rad) * 0.7, y + math.sin(rad) * 0.7),
                    xytext=(x, y),
                    arrowprops={"arrowstyle": "->", "color": "#854d0e",
                                "lw": 1.2}, zorder=22)
        # state label
        st = ch_pos.get("state", "")
        if st:
            ax.text(x, y - 0.6, st, ha="center", fontsize=7,
                    color="#374151", style="italic", zorder=21)

    # ── overlay: active props ──
    for p in shot_layout.get("active_props", []):
        x, y = p.get("position", [0, 0])
        pid = p.get("id", "?")
        ax.plot(x, y, marker="s", markersize=11,
                markerfacecolor="#2563eb", markeredgecolor="#1e3a8a",
                markeredgewidth=1.5, zorder=20)
        ax.text(x, y + 0.3, pid, ha="center", fontsize=8,
                weight="bold", color="#1e3a8a", zorder=21)

    # focus lines (gaze, action)
    for fl in shot_layout.get("focus_lines", []):
        a, b = fl.get("from", [0, 0]), fl.get("to", [0, 0])
        ax.annotate("", xy=b, xytext=a,
                    arrowprops={"arrowstyle": "->", "linestyle": "--",
                                "color": "#7c3aed", "lw": 1.0, "alpha": 0.7},
                    zorder=23)

    # title
    ax.set_title(title, fontsize=13, weight="bold", pad=10)
    ax.grid(True, linestyle=":", alpha=0.3)
    ax.set_xlabel("x (m)")
    ax.set_ylabel("y (m)")

    out_path.parent.mkdir(parents=True, exist_ok=True)
    plt.tight_layout()
    fig.savefig(out_path, dpi=120, bbox_inches="tight")
    plt.close(fig)
    if base_path.exists():
        base_path.unlink()
    return out_path


# ───────── CLI ─────────

if __name__ == "__main__":
    import sys
    here = Path(__file__).parent
    results = here / "results"

    for name in ("gpt", "gemini"):
        spec_path = results / f"spec_{name}.json"
        if not spec_path.exists():
            print(f"[SKIP] {spec_path} not found")
            continue
        spec = json.loads(spec_path.read_text())
        out = results / f"floor_plan_{name}.png"
        render_base_floor_plan(spec, out,
                               title=f"Base Floor Plan ({name.upper()})")
        print(f"[OK] {name} → {out}")
