"""场景层地图构建(agent 活动的精细地图,docs/island-design-v3.md §6 地图分层架构)。

与导航图共用 compose 原语,差异:
- 逻辑格 16×12(32px 渲染后整体 2× 最近邻放大 → 1024×768),物件同步 2×,
  与 64px chibi 角色的身量比例匹配(房屋约 3-4 个角色高)。
- 每个场景一个声明式 SceneSpec:地形绘制函数 + 物件摆位 + 光晕 + region 锚点。

运行:python -m genesis.mapgen.scenes [场景名…];不带参数构建全部。
"""

from __future__ import annotations

import logging
import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Callable

from PIL import Image

from genesis.mapgen.compose import (
    Placement,
    WangTileSheet,
    add_glow,
    grade_night,
    paste_objects,
    render_terrain,
    smooth_levels,
    suppress_warm,
)
from genesis.mapgen.island import OBJECT_DIR, TILESET_DIR, _draw_path, _fill_ellipse

logger = logging.getLogger(__name__)

ROOT = Path(__file__).resolve().parents[3]
OUT_DIR = ROOT / "assets/sprites/maps/ravenisle"

CELLS_W, CELLS_H, TILE, ZOOM = 16, 12, 32, 2  # → 1024×768


@dataclass
class SceneSpec:
    """一张场景图的全部声明:地形、物件(2× 摆放)、光晕、viz region 锚点。"""

    name: str
    paint: Callable[[list[list[int]]], None]          # 顶点等级绘制(16×12 逻辑格)
    placements: list[tuple[str, int, int]]            # (物件文件, 锚点x, 脚底y)——最终像素坐标
    glows: list[tuple[int, int, int, tuple[int, int, int], float]] = field(default_factory=list)
    regions: dict[str, tuple[int, int]] = field(default_factory=dict)


def _scene_sheets() -> list[WangTileSheet]:
    return [
        WangTileSheet.from_files(TILESET_DIR / "ocean_ground_metadata.json", TILESET_DIR / "ocean_ground_image.png"),
        WangTileSheet.from_files(TILESET_DIR / "ground_grass_metadata.json", TILESET_DIR / "ground_grass_image.png"),
        WangTileSheet.from_files(TILESET_DIR / "grass_path_metadata.json", TILESET_DIR / "grass_path_image.png"),
    ]


def build_scene(spec: SceneSpec) -> Image.Image:
    levels = [[0] * (CELLS_W + 1) for _ in range(CELLS_H + 1)]
    spec.paint(levels)
    smooth_levels(levels)
    terrain = suppress_warm(render_terrain(levels, _scene_sheets()))
    canvas = terrain.resize((terrain.width * ZOOM, terrain.height * ZOOM), Image.NEAREST)

    placements = []
    for fname, x, y in spec.placements:
        path = OBJECT_DIR / fname
        if not path.exists():
            logger.warning("[%s] 缺物件 %s,跳过", spec.name, fname)
            continue
        img = Image.open(path).convert("RGBA")
        img = img.resize((img.width * ZOOM, img.height * ZOOM), Image.NEAREST)
        placements.append(Placement(img, x, y))
    paste_objects(canvas, placements)

    canvas = grade_night(canvas)
    for x, y, r, color, s in spec.glows:
        add_glow(canvas, x, y, r, color, s)
    return canvas


# ──────────────────────────── ⚓ 码头湾(dock / beach / boathouse) ────────────────────────────

def _paint_dock_bay(levels: list[list[int]]) -> None:
    """北侧荒草缓坡 → 中部灰石滩 → 南侧夜海;一条泥径从坡顶通向栈桥头。"""
    for vy in range(len(levels)):
        for vx in range(len(levels[0])):
            if vy <= 4:
                levels[vy][vx] = 2          # 北岸荒草
            elif vy <= 8:
                levels[vy][vx] = 1          # 灰石滩
    _fill_ellipse(levels, 4, 8.5, 3.2, 1.6, 1)    # 西侧石滩向海凸出一角
    _fill_ellipse(levels, 13, 3.5, 3.5, 1.8, 2)   # 东北草坡外扩
    _draw_path(levels, [(6.5, 0), (6.5, 3.5)])     # 泥径自北而下
    _draw_path(levels, [(6.5, 3.5), (6.2, 4)])


DOCK_BAY = SceneSpec(
    name="dock_bay",
    paint=_paint_dock_bay,
    placements=[
        # 物件按 2× 后的最终像素摆放(画布 1024×768)
        ("pine_big.png", 150, 185),
        ("pine_small.png", 330, 150),
        ("pine_big.png", 880, 165),
        ("pine_small.png", 990, 215),
        ("pier.png", 416, 856),        # 栈桥:上端搭在石滩、下端伸入夜海(2×=192×352)
        ("boathouse.png", 780, 600),   # 船屋立在水线上
        ("rowboat.png", 180, 560),     # 凿穿的小艇搁浅在西滩
        ("barrels.png", 925, 470),     # 半埋的空油桶
        ("dolly_track.png", 610, 430), # 当年剧组卸货留下的轨道残骸,散落滩上
    ],
    glows=[
        (780, 520, 30, (170, 195, 215), 0.25),    # 船屋窗的冷微光
    ],
    regions={"码头": (430, 700), "沙滩": (200, 480), "船屋": (780, 560)},
)


def _set_rect(levels: list[list[int]], x0: float, y0: float, x1: float, y1: float, value: int) -> None:
    """矩形内顶点直接设为 value(可降级,用于在草地中开石板广场等)。"""
    for vy in range(len(levels)):
        for vx in range(len(levels[0])):
            if x0 <= vx <= x1 and y0 <= vy <= y1:
                levels[vy][vx] = value


def _fill_all(levels: list[list[int]], value: int) -> None:
    for row in levels:
        for i in range(len(row)):
            row[i] = value


# ──────────────────────────── 🚪 前院(forecourt) ────────────────────────────

def _paint_forecourt(levels: list[list[int]]) -> None:
    """鸦堡正门外:中央湿苔石广场(剧组大本营旧址),两侧荒草,南端铁门。"""
    _fill_all(levels, 2)
    _set_rect(levels, 5, 0, 11, 12, 1)        # 中央石板广场
    _draw_path(levels, [(2, 9), (4.5, 9)])     # 西侧草地小径汇入广场
    _draw_path(levels, [(14, 5), (11.5, 5)])


FORECOURT = SceneSpec(
    name="forecourt",
    paint=_paint_forecourt,
    placements=[
        ("castle_facade.png", 512, 356),   # 鸦堡正立面压住北缘(进门即切场景)
        ("fountain.png", 512, 540),        # 干涸喷泉居中
        ("dolly_track.png", 290, 610),     # 摄影轨道残骸
        ("crates.png", 724, 596),          # 当年剧组卸货的木箱
        ("iron_gate.png", 512, 752),       # 南端锈蚀铁门(鸦雕石柱)
        ("pine_big.png", 120, 320), ("pine_small.png", 935, 300),
        ("pine_small.png", 165, 712), ("pine_big.png", 880, 700),
    ],
    glows=[
        (438, 300, 30, (255, 185, 80), 0.42),   # 堡内窗光透出
        (588, 300, 30, (255, 185, 80), 0.42),
    ],
    regions={"前院": (512, 600)},
)


# ──────────────────────────── 🌹 花园·温室(garden_green) ────────────────────────────

def _paint_garden(levels: list[list[int]]) -> None:
    """荒废法式花园:荒草为底,正交折线泥径绕园(对角线会断成补丁),东北温室。"""
    _fill_all(levels, 2)
    _draw_path(levels, [(2, 10), (6, 10), (6, 7), (10, 7), (10, 4), (12.5, 4)])  # 绕园主径
    _draw_path(levels, [(6, 7), (3, 7), (3, 9)])                                  # 西支径(井)


GARDEN = SceneSpec(
    name="garden_green",
    paint=_paint_garden,
    placements=[
        ("greenhouse.png", 800, 350),      # 玻璃温室(雨点敲穹顶)
        ("statue.png", 510, 400),          # 断臂女神像守在主径转角
        ("well.png", 195, 590),            # 古井(木盖被挪开过)
        ("flowerbed.png", 330, 560), ("flowerbed.png", 620, 560),   # 枯死的花圃对称分布
        ("flowerbed.png", 330, 700), ("flowerbed.png", 620, 700),
        ("rose_bush.png", 250, 320), ("rose_bush.png", 470, 250),
        ("rose_bush.png", 760, 560), ("rose_bush.png", 150, 450),
        ("rose_bush.png", 880, 690),
        ("boulder.png", 590, 180),
        ("pine_small.png", 100, 250), ("pine_big.png", 950, 600),
        ("pine_small.png", 250, 170), ("pine_big.png", 60, 700),
    ],
    glows=[
        (800, 270, 36, (170, 200, 220), 0.30),  # 温室冷白微光
    ],
    regions={"花园": (380, 500), "温室": (800, 300)},
)


# ──────────────────────────── ⚰️ 墓园·教堂(grave_chapel) ────────────────────────────

def _paint_graveyard(levels: list[list[int]]) -> None:
    """荒坡墓园:西侧石地承教堂,余为荒草,一条泥径自东南通来。"""
    _fill_all(levels, 2)
    _set_rect(levels, 0, 0, 5.5, 5.5, 1)       # 教堂下的石地
    _draw_path(levels, [(15, 10), (10, 10), (10, 7), (7, 7), (7, 5)])  # 东南来的泥径(正交)


GRAVE_CHAPEL = SceneSpec(
    name="grave_chapel",
    paint=_paint_graveyard,
    placements=[
        ("chapel.png", 250, 330),          # 塌顶石教堂(祭坛上七支新蜡烛)
        ("graves.png", 620, 470),          # 阮青之墓——唯一有白菊的一座
        ("graves_old.png", 850, 350),      # 灯塔看守人一家的旧墓
        ("graves_old.png", 470, 620), ("graves_old.png", 760, 690),
        ("graves_old.png", 880, 520),
        ("boulder.png", 520, 220),
        ("pine_big.png", 950, 190), ("pine_small.png", 130, 480),
        ("pine_small.png", 900, 660), ("pine_big.png", 60, 200),
        ("pine_small.png", 240, 690), ("pine_big.png", 360, 250),
    ],
    glows=[
        (262, 320, 26, (255, 200, 120), 0.26),  # 教堂里烛光隐约
        (660, 460, 30, (150, 175, 225), 0.20),  # 月光落在那块干净的碑上
    ],
    regions={"后山墓园": (680, 520), "荒废教堂": (260, 330)},
)


# ──────────────────────────── 🌲 风暴林·断崖(woods_cliff) ────────────────────────────

def _paint_woods_cliff(levels: list[list[int]]) -> None:
    """西北角断崖临海,黑松林覆盖其余;一条小径通向崖边。"""
    _fill_all(levels, 2)
    _set_rect(levels, 0, 0, 16, 1.2, 1)        # 北缘岩带
    _set_rect(levels, 0, 0, 5, 6, 1)           # 西北崖体
    _set_rect(levels, 0, 0, 3.2, 4, 0)         # 崖下夜海(更大,崖的"高差"看得见)
    _draw_path(levels, [(15, 9), (11, 9), (11, 6), (8, 6), (8, 5)])  # 通向崖边的小径(避开岩区防降级)


WOODS_CLIFF = SceneSpec(
    name="woods_cliff",
    paint=_paint_woods_cliff,
    placements=[
        ("warning_sign.png", 470, 296),    # "小心脚下"——立在小径尽头,油漆未干
        ("boulder.png", 240, 360), ("boulder.png", 640, 180),
        ("pine_big.png", 540, 300), ("pine_big.png", 760, 420),
        ("pine_small.png", 640, 520), ("pine_big.png", 880, 260),
        ("pine_small.png", 430, 440), ("pine_big.png", 950, 580),
        ("pine_small.png", 300, 560), ("pine_big.png", 150, 660),
        ("pine_small.png", 820, 700), ("pine_small.png", 980, 420),
    ],
    glows=[
        (180, 160, 44, (130, 160, 215), 0.22),  # 崖外海面月光
    ],
    regions={"断崖": (260, 250), "风暴林": (650, 450)},
)


# ──────────────────────────── 🗼 灯塔(lighthouse_pt) ────────────────────────────

def _paint_lighthouse(levels: list[list[int]]) -> None:
    """孤悬的裸岩岬:四面夜海,只有一条岩颈从西南接进来。"""
    _fill_all(levels, 0)
    _fill_ellipse(levels, 8, 5, 6.5, 4.2, 1)   # 主岩台
    _fill_ellipse(levels, 3, 9.5, 4, 2.6, 1)   # 西南岩颈(来路)
    _fill_ellipse(levels, 8, 4.5, 3.2, 2.0, 2) # 岩台心一撮荒草


LIGHTHOUSE_PT = SceneSpec(
    name="lighthouse_pt",
    paint=_paint_lighthouse,
    placements=[
        ("lighthouse.png", 512, 440),      # 灯不亮——油被搬空了
        ("boulder.png", 300, 420), ("boulder.png", 740, 380),
        ("barrels.png", 660, 480),         # 被搬空的油罐丢在塔脚
        ("pine_small.png", 200, 520),
    ],
    glows=[
        (512, 130, 50, (130, 160, 215), 0.24),  # 只有月光,没有塔光
    ],
    regions={"灯塔": (512, 420)},
)


SCENES: dict[str, SceneSpec] = {s.name: s for s in [
    DOCK_BAY, FORECOURT, GARDEN, GRAVE_CHAPEL, WOODS_CLIFF, LIGHTHOUSE_PT,
]}


def main() -> None:
    logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s %(message)s")
    names = sys.argv[1:] or list(SCENES)
    OUT_DIR.mkdir(parents=True, exist_ok=True)
    for name in names:
        img = build_scene(SCENES[name])
        out = OUT_DIR / f"{name}.png"
        img.save(out)
        logger.info("✅ 场景图 %s → %s (%dx%d)", name, out, *img.size)


if __name__ == "__main__":
    main()
