"""插件 plugin.yaml 解析与校验。"""

from __future__ import annotations

import re
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any

import yaml

_ID_RE = re.compile(r"^[a-z][a-z0-9_-]{0,63}$")


@dataclass
class PluginManifest:
    schema_version: int = 1
    id: str = ""
    name: str = ""
    #: 展示用标题（可选）；空则与 name 相同
    display_name: str = ""
    version: str = "0.0.1"
    enabled: bool = True
    description: str = ""
    min_aicd_version: str | None = None
    entries: dict[str, str] = field(default_factory=dict)
    config: dict[str, Any] = field(default_factory=dict)
    raw: dict[str, Any] = field(default_factory=dict, repr=False)

    @classmethod
    def from_mapping(cls, data: dict[str, Any]) -> PluginManifest:
        if not isinstance(data, dict):
            raise ValueError("manifest root must be a mapping")
        ent_w = data.get("entries") or {}
        if not isinstance(ent_w, dict):
            ent_w = {}
        cfg_w = data.get("config") or {}
        if not isinstance(cfg_w, dict):
            cfg_w = {}
        mid = str(data.get("id") or "").strip()
        if not _ID_RE.match(mid):
            raise ValueError(
                f"invalid plugin id {mid!r}: use lowercase letters, digits, _, -"
            )
        mv = data.get("min_aicd_version")
        if mv is None:
            mv = data.get("minAicdVersion")
        min_ver = str(mv).strip() if mv not in (None, "") else None
        dn = (
            data.get("display_name")
            or data.get("displayName")
            or data.get("title")
        )
        display_name = str(dn).strip() if dn not in (None, "") else ""
        return cls(
            schema_version=int(data.get("schema_version") or 1),
            id=mid,
            name=str(data.get("name") or mid).strip(),
            display_name=display_name,
            version=str(data.get("version") or "0.0.1").strip(),
            enabled=bool(data.get("enabled", True)),
            description=str(data.get("description") or "").strip(),
            min_aicd_version=min_ver,
            entries={str(k): str(v) for k, v in ent_w.items() if v not in (None, "")},
            config=dict(cfg_w),
            raw=dict(data),
        )


def load_manifest_file(path: str | Path) -> PluginManifest:
    p = Path(path)
    raw = yaml.safe_load(p.read_text(encoding="utf-8"))
    if not isinstance(raw, dict):
        raise ValueError("plugin.yaml must be a YAML mapping")
    return PluginManifest.from_mapping(raw)
