"""能力面注册表 — 读/写 plane_registry.yaml · 构建 PlanePack。"""

from __future__ import annotations

import json
import shutil
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

import yaml

from niannian.cognitive.planes import (
    DEFAULT_COMPANION_PLANES,
    DEFAULT_EDUCATION_PLANES,
    DEFAULT_HYBRID_PLANES,
    DEFAULT_WORK_PLANES,
    PLANE_IDS,
    PlanePack,
    Regime,
)


def _now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


def _registry_path(config: dict[str, Any], home: Path | None = None) -> Path:
    cognitive = config.get("cognitive") or {}
    raw = cognitive.get("plane_registry") or "config/cognitive/plane_registry.yaml"
    path = Path(raw)
    if path.is_file():
        return path
    if home is not None:
        candidate = home / raw
        if candidate.is_file():
            return candidate
    return path


def _legacy_pack(regime: Regime) -> PlanePack:
    if regime == "work":
        return PlanePack(regime=regime, active=DEFAULT_WORK_PLANES, skip=frozenset({"P8", "P14"}))
    if regime == "hybrid":
        return PlanePack(regime=regime, active=DEFAULT_HYBRID_PLANES, skip=frozenset({"P8"}))
    if regime == "education":
        return PlanePack(regime=regime, active=DEFAULT_EDUCATION_PLANES, skip=frozenset({"P8", "P12", "P14"}))
    return PlanePack(regime=regime, active=DEFAULT_COMPANION_PLANES, skip=frozenset({"P8", "P12"}))


class PlaneRegistry:
    def __init__(self, config: dict[str, Any], *, home: Path | None = None) -> None:
        self._config = config
        self._home = home
        self._path = _registry_path(config, home)

    @property
    def path(self) -> Path:
        return self._path

    def exists(self) -> bool:
        return self._path.is_file()

    def load(self) -> dict[str, Any]:
        if not self._path.is_file():
            return {"planes": [], "packs": {}, "rules": {}}
        with self._path.open(encoding="utf-8") as f:
            data = yaml.safe_load(f) or {}
        return data if isinstance(data, dict) else {"planes": [], "packs": {}, "rules": {}}

    def save(self, data: dict[str, Any]) -> None:
        self._path.parent.mkdir(parents=True, exist_ok=True)
        backup_dir = self._path.parent.parent / "self" / "history"
        if self._path.is_file():
            backup_dir.mkdir(parents=True, exist_ok=True)
            stamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
            shutil.copy2(self._path, backup_dir / f"plane_registry.{stamp}.yaml")
        self._path.write_text(yaml.safe_dump(data, allow_unicode=True, sort_keys=False), encoding="utf-8")

    def list_planes(self) -> list[dict[str, Any]]:
        data = self.load()
        planes = data.get("planes")
        if not isinstance(planes, list):
            return []
        return [p for p in planes if isinstance(p, dict) and p.get("id")]

    def list_packs(self) -> dict[str, Any]:
        data = self.load()
        packs = data.get("packs")
        return packs if isinstance(packs, dict) else {}

    def get_pack(self, name: str) -> dict[str, Any] | None:
        pack = self.list_packs().get(name)
        return pack if isinstance(pack, dict) else None

    def build_plane_pack(self, regime: Regime, pack_name: str | None = None) -> PlanePack:
        name = pack_name or regime
        pack = self.get_pack(name)
        if pack is None:
            return _legacy_pack(regime)
        active_raw = pack.get("active") or []
        skip_raw = pack.get("skip") or []
        active = frozenset(str(x) for x in active_raw if isinstance(x, str))
        skip = frozenset(str(x) for x in skip_raw if isinstance(x, str))
        if not active:
            return _legacy_pack(regime)
        return PlanePack(regime=regime, active=active, skip=skip)

    def update_pack(self, name: str, *, active: list[str], skip: list[str]) -> dict[str, Any]:
        data = self.load()
        packs = data.setdefault("packs", {})
        if name not in packs or not isinstance(packs.get(name), dict):
            raise KeyError(f"unknown pack: {name}")
        rules = data.get("rules") if isinstance(data.get("rules"), dict) else {}
        required = [str(x) for x in (rules.get("required_planes") or ["P1", "P2", "P3"]) if isinstance(x, str)]
        active_set = set(active)
        for req in required:
            if req not in active_set:
                raise ValueError(f"required plane {req} must stay in active")
        for pid in active:
            if pid not in PLANE_IDS and not str(pid).startswith("P"):
                raise ValueError(f"invalid plane id: {pid}")
        packs[name] = {**packs[name], "active": list(active), "skip": list(skip)}
        self.save(data)
        return packs[name]

    def preview(self, *, text: str, regime: Regime, pack: str | None = None) -> dict[str, Any]:
        pp = self.build_plane_pack(regime, pack or regime)
        active = sorted(pid for pid in pp.active if pp.is_active(pid))
        skip = sorted(pp.skip)
        trace_parts = [f"{pid}✓" if pp.is_active(pid) else f"{pid}✗" for pid in sorted(pp.active | pp.skip | frozenset(PLANE_IDS[:8]))]
        trace = f"[Pack:{pack or regime}] " + " ".join(trace_parts[:12])
        return {
            "regime": regime,
            "pack": pack or regime,
            "sample_text": text[:120],
            "active_planes": active,
            "skip_planes": skip,
            "trace_preview": trace,
        }

    def to_api_dict(self) -> dict[str, Any]:
        data = self.load()
        return {
            "planes": self.list_planes(),
            "packs": self.list_packs(),
            "rules": data.get("rules") if isinstance(data.get("rules"), dict) else {},
            "registry_path": str(self._path),
        }


def resolve_plane_pack(regime: Regime, config: dict[str, Any] | None, *, pack_name: str | None = None, home: Path | None = None) -> PlanePack:
    if not config:
        return _legacy_pack(regime)
    reg = PlaneRegistry(config, home=home)
    if reg.exists():
        return reg.build_plane_pack(regime, pack_name or regime)
    return _legacy_pack(regime)
