# -*- coding: utf-8 -*-
"""数据模型与领域常量。

本模块只定义「是什么」——资产类型、状态、字段约束——不定义「怎么存」。
存储细节在 db.py 中，避免 UI 层与数据库耦合。
"""
from __future__ import annotations

import enum
import time
from dataclasses import dataclass, field, asdict
from typing import Any


# ---------------------------------------------------------------------------
# 枚举：资产类型
# ---------------------------------------------------------------------------
class AssetType(str, enum.Enum):
    """资产类型。值为英文标识（跨平台稳定），显示名由 ASSET_TYPE_META 提供。"""

    COMIC_PAGE = "comic_page"       # 漫画页（正文页面图片）
    COVER = "cover"                 # 封面
    CHARACTER = "character"         # 角色设定图
    SCENE = "scene"                 # 场景设定图
    REFERENCE = "reference"         # 参考图 / 素材
    SCRIPT = "script"               # 剧本文本
    STORYBOARD = "storyboard"       # 分镜文本
    AUDIO = "audio"                 # 音频（配音 / 音效 / 音乐）
    NOTE = "note"                   # 备注 / 文字卡片

    @classmethod
    def from_string(cls, value: str | None) -> "AssetType":
        if not value:
            return cls.COMIC_PAGE
        try:
            return cls(value)
        except ValueError:
            return cls.COMIC_PAGE


# 文本类资产：内容存在 content 字段，可无外部文件
TEXT_TYPES = frozenset({AssetType.SCRIPT, AssetType.STORYBOARD, AssetType.NOTE})
# 文件类资产：必须有外部文件
FILE_TYPES = frozenset({
    AssetType.COMIC_PAGE, AssetType.COVER, AssetType.CHARACTER,
    AssetType.SCENE, AssetType.REFERENCE, AssetType.AUDIO,
})
# 媒体类型（需要缩略图 / 播放器）
MEDIA_TYPES = frozenset(FILE_TYPES)


# ---------------------------------------------------------------------------
# 资产类型元数据（供 UI 显示图标、颜色、分组）
# ---------------------------------------------------------------------------
@dataclass(frozen=True)
class TypeMeta:
    type: AssetType
    label: str
    icon: str          # Noto/emoji 风格图标（Qt 用文字 emoji 渲染，零依赖）
    color: str         # #RRGGBB
    group: str         # 分组名（侧边栏聚合用）
    exts: tuple[str, ...]  # 常见扩展名（导入识别用）


ASSET_TYPE_META: dict[AssetType, TypeMeta] = {
    AssetType.COMIC_PAGE: TypeMeta(AssetType.COMIC_PAGE, "漫画页", "📖", "#4A90D9", "漫画内容",
                                   (".png", ".jpg", ".jpeg", ".webp", ".tiff", ".gif", ".bmp", ".pdf")),
    AssetType.COVER: TypeMeta(AssetType.COVER, "封面", "🖼️", "#E67E22", "漫画内容",
                              (".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp")),
    AssetType.CHARACTER: TypeMeta(AssetType.CHARACTER, "角色设定", "👤", "#9B59B6", "设定集",
                                  (".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp")),
    AssetType.SCENE: TypeMeta(AssetType.SCENE, "场景设定", "🏞️", "#27AE60", "设定集",
                              (".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp")),
    AssetType.REFERENCE: TypeMeta(AssetType.REFERENCE, "参考素材", "📎", "#95A5A6", "设定集",
                                  (".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp", ".svg")),
    AssetType.SCRIPT: TypeMeta(AssetType.SCRIPT, "剧本", "📜", "#F39C12", "文本创作",
                               (".txt", ".md", ".mdx", ".docx", ".json")),
    AssetType.STORYBOARD: TypeMeta(AssetType.STORYBOARD, "分镜", "🎬", "#16A085", "文本创作",
                                   (".txt", ".md", ".json", ".csv")),
    AssetType.AUDIO: TypeMeta(AssetType.AUDIO, "音频", "🔊", "#E74C3C", "音频",
                              (".mp3", ".wav", ".m4a", ".aac", ".ogg", ".flac")),
    AssetType.NOTE: TypeMeta(AssetType.NOTE, "备注", "📝", "#7F8C8D", "文本创作", (".txt",)),
}

# 按展示顺序排列的类型（侧边栏、下拉框统一用此顺序）
ASSET_TYPES_ORDERED: list[AssetType] = [
    AssetType.COMIC_PAGE, AssetType.COVER, AssetType.CHARACTER, AssetType.SCENE,
    AssetType.REFERENCE, AssetType.SCRIPT, AssetType.STORYBOARD, AssetType.AUDIO, AssetType.NOTE,
]

# 系列状态
SERIES_STATUS = {
    "ongoing": "连载中",
    "complete": "已完结",
    "paused": "暂停",
    "planned": "企划中",
}

# 导入扫描时按扩展名自动推断资产类型。
#
# 注意：ASSET_TYPE_META[t].exts 是「该类型 UI 文件过滤器可接受的扩展名」，
# 图片类扩展名在 COMIC_PAGE / COVER / CHARACTER / SCENE / REFERENCE 间严重重叠，
# 直接推导会得到「后定义者胜出」的错误结果（例如 .png → REFERENCE）。
# 这里用显式清单，取「最可能」的归属：普通图片默认视为漫画页，
# 文本类与音频类无歧义。用户可在导入向导中覆盖推断结果。
_EXT_INFERENCE: list[tuple[AssetType, frozenset[str]]] = [
    (AssetType.COMIC_PAGE, frozenset({".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp", ".tiff", ".tif", ".pdf"})),
    (AssetType.AUDIO, frozenset({".mp3", ".wav", ".m4a", ".aac", ".ogg", ".flac"})),
    (AssetType.SCRIPT, frozenset({".txt", ".md", ".mdx", ".docx", ".json"})),
    (AssetType.STORYBOARD, frozenset({".csv"})),
]

EXT_TO_TYPE: dict[str, AssetType] = {
    ext: t for t, exts in _EXT_INFERENCE for ext in exts
}


# ---------------------------------------------------------------------------
# 数据对象
# ---------------------------------------------------------------------------
def now() -> float:
    """当前时间戳（秒，浮点）。统一用 POSIX 时间，便于排序与迁移。"""
    return time.time()


@dataclass
class Series:
    id: int | None = None
    name: str = ""
    author: str = ""
    status: str = "ongoing"
    cover_path: str = ""
    created_at: float = field(default_factory=now)
    updated_at: float = field(default_factory=now)

    @property
    def status_label(self) -> str:
        return SERIES_STATUS.get(self.status, self.status)

    def to_dict(self) -> dict[str, Any]:
        return asdict(self)


@dataclass
class Chapter:
    id: int | None = None
    series_id: int | None = None
    volume: int | None = None      # 卷号，None 表示无分卷
    number: int | None = None      # 章号
    title: str = ""
    description: str = ""
    created_at: float = field(default_factory=now)

    @property
    def label(self) -> str:
        """卷章的人类可读标签，例如 "第3卷 · 第12话" / "第12话" / "番外"。"""
        if self.volume and self.number is not None:
            return f"第{self.volume}卷 · 第{self.number}话"
        if self.number is not None:
            return f"第{self.number}话"
        return self.title or "未命名章节"

    def to_dict(self) -> dict[str, Any]:
        return asdict(self)


@dataclass
class Tag:
    id: int | None = None
    name: str = ""
    color: str = "#4A90D9"
    created_at: float = field(default_factory=now)

    def to_dict(self) -> dict[str, Any]:
        return asdict(self)


@dataclass
class Asset:
    """核心资产实体。

    file_path 指向原始文件（图片/音频/文本，存在项目外部），
    content 存放文本类资产的正文（剧本、分镜、备注），
    两者至少一个非空；FILE_TYPES 要求 file_path，TEXT_TYPES 允许仅 content。
    """

    id: int | None = None
    series_id: int | None = None
    chapter_id: int | None = None
    type: AssetType = AssetType.COMIC_PAGE
    title: str = ""
    description: str = ""
    file_path: str = ""
    content: str = ""
    page_index: int | None = None    # 在章节内的页序（漫画页排版用）
    tags: list[str] = field(default_factory=list)
    sort_order: int = 0
    created_at: float = field(default_factory=now)
    updated_at: float = field(default_factory=now)

    @property
    def type_meta(self) -> TypeMeta:
        return ASSET_TYPE_META[self.type]

    @property
    def is_text(self) -> bool:
        return self.type in TEXT_TYPES

    @property
    def is_file(self) -> bool:
        return self.type in FILE_TYPES

    def to_dict(self) -> dict[str, Any]:
        d = asdict(self)
        d["type"] = self.type.value
        return d

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "Asset":
        """从字典/JSON 构造，容忍缺失字段（用于导入、导出还原）。"""
        known = {f.name for f in cls.__dataclass_fields__.values()}  # type: ignore[attr-defined]
        kwargs = {k: v for k, v in data.items() if k in known}
        t = kwargs.pop("type", AssetType.COMIC_PAGE)
        kwargs["type"] = AssetType.from_string(t if isinstance(t, str) else getattr(t, "value", t))
        if isinstance(kwargs.get("tags"), str):
            import json
            try:
                kwargs["tags"] = json.loads(kwargs["tags"])
            except Exception:
                kwargs["tags"] = [s.strip() for s in kwargs["tags"].split(",") if s.strip()]
        return cls(**kwargs)
