"""室内场景合成 v2:SNES RPG 式城堡内景。

层次感的来源(对照 v1 的平面色块墙):
1. 墙 = 「墙顶盖 cap」+「墙立面 face」两种贴图:朝南的墙露出一格高的立面
   (石砌缝/木裙板),其余画顶盖——墙因此有了"高度"。
2. 投影:墙体向南在地板上投渐变阴影;家具脚下垫椭圆软影;贴墙地板压暗(AO)。
3. 光照:底色压暗,烛光池放大,亮暗对比拉开纵深。

全部以 32px tile 网格定位,房间矩形 + 门洞在网格上雕刻,外墙自动加厚。
"""

from __future__ import annotations

import logging
import random
from dataclasses import dataclass, field
from pathlib import Path

from PIL import Image, ImageDraw, ImageFilter

from genesis.mapgen.compose import Placement, add_glow, grade_night, paste_objects

logger = logging.getLogger(__name__)

T = 32  # tile 尺寸


@dataclass
class Room:
    """一个房间:tile 矩形(含两端)+ 地板/墙面风格 + 家具(px 坐标)/光晕。"""

    id: str
    name: str
    rect: tuple[int, int, int, int]                  # tx0,ty0,tx1,ty1(地板 tile,含端点)
    floor: str
    face: str = "wall_stone"                         # 该房间北墙立面的贴图风格
    furniture: list[tuple[str, int, int]] = field(default_factory=list)
    glows: list[tuple[int, int, int, tuple[int, int, int], float]] = field(default_factory=list)
    anchor: tuple[int, int] | None = None


@dataclass
class InteriorSpec:
    name: str
    size_tiles: tuple[int, int]
    rooms: list[Room]
    doors: list[tuple[int, int, int, int, str]]      # 门洞 tile 矩形(含端点)+ 补地风格
    patches: list[tuple[int, int, int, int, str]] = field(default_factory=list)  # 地毯等(tile 矩形)
    wall_objects: list[tuple[str, int, int]] = field(default_factory=list)       # 贴墙物件(窗/壁挂,px)
    veils: list[tuple[int, int, int, int, int]] = field(default_factory=list)    # 暗纱(tile 矩形+alpha):上锁房间


def _load_strip(path: Path) -> list[Image.Image]:
    sheet = Image.open(path).convert("RGBA")
    n = sheet.width // sheet.height
    tw = sheet.height
    return [sheet.crop((i * tw, 0, (i + 1) * tw, tw)) for i in range(n)]


def _tiles_in(rect: tuple[int, int, int, int]):
    x0, y0, x1, y1 = rect
    for ty in range(y0, y1 + 1):
        for tx in range(x0, x1 + 1):
            yield tx, ty


def build_interior(spec: InteriorSpec, floors_dir: Path, objects_dir: Path,
                   seed: int = 7) -> Image.Image:
    rng = random.Random(seed)
    tiles_w, tiles_h = spec.size_tiles
    width, height = tiles_w * T, tiles_h * T

    # ── 1. 网格建模:floor / wall(含外墙加厚) ──────────────────────────
    floor: dict[tuple[int, int], str] = {}
    room_of: dict[tuple[int, int], Room] = {}
    for room in spec.rooms:
        for t in _tiles_in(room.rect):
            floor[t] = room.floor
            room_of[t] = room
    for x0, y0, x1, y1, style in spec.doors:
        for t in _tiles_in((x0, y0, x1, y1)):
            floor[t] = style

    def dilate(cells: set) -> set:
        out = set(cells)
        for (tx, ty) in cells:
            for dy in (-1, 0, 1):
                for dx in (-1, 0, 1):
                    out.add((tx + dx, ty + dy))
        return out

    fset = set(floor)
    walls = {t for t in dilate(dilate(fset)) - fset
             if 0 <= t[0] < tiles_w and 0 <= t[1] < tiles_h}

    # ── 2. 铺地板 ──────────────────────────────────────────────────────
    styles = {floor[t] for t in floor} | {p[4] for p in spec.patches}
    tilelib = {s: _load_strip(floors_dir / f"{s}.png") for s in styles}
    walllib = {s: _load_strip(floors_dir / f"{s}.png")
               for s in {"wall_cap"} | {r.face for r in spec.rooms}}

    canvas = Image.new("RGBA", (width, height), (8, 7, 12, 255))
    for (tx, ty), style in floor.items():
        canvas.paste(rng.choice(tilelib[style]), (tx * T, ty * T))
    for x0, y0, x1, y1, style in spec.patches:
        for tx, ty in _tiles_in((x0, y0, x1, y1)):
            canvas.paste(rng.choice(tilelib[style]), (tx * T, ty * T))

    # ── 3. 墙:朝南立面 / 顶盖 ─────────────────────────────────────────
    for (tx, ty) in sorted(walls, key=lambda t: (t[1], t[0])):
        below = (tx, ty + 1)
        if below in floor:  # 立面:露出"高度"
            face_style = room_of.get(below, spec.rooms[0]).face
            canvas.paste(rng.choice(walllib[face_style]), (tx * T, ty * T))
        else:
            canvas.paste(rng.choice(walllib["wall_cap"]), (tx * T, ty * T))

    # ── 4. 阴影层:墙投影 + 贴墙 AO ────────────────────────────────────
    shade = Image.new("L", (width, height), 0)
    sdraw = ImageDraw.Draw(shade)
    for (tx, ty) in floor:
        if (tx, ty - 1) in walls:           # 北侧是墙 → 接墙投影带
            for i in range(14):
                sdraw.line([(tx * T, ty * T + i), (tx * T + T - 1, ty * T + i)],
                           fill=int(110 * (1 - i / 14)))
        if (tx - 1, ty) in walls:           # 侧向 AO
            sdraw.rectangle((tx * T, ty * T, tx * T + 5, ty * T + T - 1), fill=45)
        if (tx + 1, ty) in walls:
            sdraw.rectangle((tx * T + T - 6, ty * T, tx * T + T - 1, ty * T + T - 1), fill=45)
    shade = shade.filter(ImageFilter.GaussianBlur(2))
    canvas.paste(Image.new("RGB", (width, height), (4, 3, 8)), (0, 0), shade)

    # ── 5. 贴墙物件(窗/壁挂)→ 家具软影 → 家具 ──────────────────────
    for fname, x, y in spec.wall_objects:
        img = Image.open(objects_dir / fname).convert("RGBA")
        canvas.alpha_composite(img, (x - img.width // 2, y - img.height))

    placements: list[Placement] = []
    for room in spec.rooms:
        for fname, x, y in room.furniture:
            path = objects_dir / fname
            if not path.exists():
                logger.warning("[%s/%s] 缺家具 %s,跳过", spec.name, room.id, fname)
                continue
            placements.append(Placement(Image.open(path).convert("RGBA"), x, y))

    shadow = Image.new("RGBA", (width, height), (0, 0, 0, 0))
    shdraw = ImageDraw.Draw(shadow)
    for p in placements:
        w = int(p.image.width * 0.42)
        h = max(8, int(p.image.width * 0.13))
        shdraw.ellipse((p.x - w, p.y - h, p.x + w, p.y + h // 2), fill=(0, 0, 0, 92))
    canvas.alpha_composite(shadow.filter(ImageFilter.GaussianBlur(4)))
    paste_objects(canvas, placements)

    # ── 6. 夜色 + 光池 ─────────────────────────────────────────────────
    canvas = grade_night(canvas, darken=0.16, tint=(22, 24, 48), tint_strength=0.11)
    for room in spec.rooms:
        for x, y, radius, color, strength in room.glows:
            add_glow(canvas, x, y, radius, color, strength)

    # 暗纱:上锁/不可进入的房间罩一层近黑,只留轮廓隐约可辨
    for x0, y0, x1, y1, alpha in spec.veils:
        veil = Image.new("RGBA", ((x1 - x0 + 1) * T, (y1 - y0 + 1) * T), (6, 5, 10, alpha))
        canvas.alpha_composite(veil, (x0 * T, y0 * T))
    return canvas
