"""pages.json v2 · graph.json v1 · compile_state 读写。"""

from __future__ import annotations

from typing import Any

from niannian.brain.cerebellum.wiki_migrate import (
    GRAPH_VERSION,
    PAGES_VERSION,
    migrate_graph_from_pages,
    migrate_pages_document,
    normalize_entity_id,
)

if False:  # TYPE_CHECKING
    from niannian.storage.base import MemoryStore


class WikiStore:
    def __init__(self, store) -> None:
        self._store = store

    def load_pages_document(self, *, namespace: str = "subject") -> dict[str, Any]:
        raw = self._store.read_wiki_pages_raw()
        doc = migrate_pages_document(raw, namespace=namespace)
        if doc != raw:
            self._store.write_wiki_pages_document(doc)
        return doc

    def save_pages_document(self, doc: dict[str, Any]) -> None:
        if doc.get("version") != PAGES_VERSION:
            doc = migrate_pages_document(doc)
        self._store.write_wiki_pages_document(doc)

    def pages_map(self, *, namespace: str = "subject") -> dict[str, Any]:
        doc = self.load_pages_document(namespace=namespace)
        pages = doc.get("pages") or {}
        return {k: v for k, v in pages.items() if isinstance(v, dict)}

    def patch_pages(self, patch: dict[str, Any], *, namespace: str = "subject") -> None:
        doc = self.load_pages_document(namespace=namespace)
        pages = dict(doc.get("pages") or {})
        for key, page in patch.items():
            if not isinstance(page, dict):
                continue
            pid = normalize_entity_id(str(key))
            existing = pages.get(pid) if isinstance(pages.get(pid), dict) else {}
            merged = {**existing, **page, "id": pid, "namespace": page.get("namespace") or namespace}
            pages[pid] = merged
        doc["pages"] = pages
        self.save_pages_document(doc)

    def load_graph(self) -> dict[str, Any]:
        raw = self._store.read_wiki_graph_raw()
        if raw.get("version") == GRAPH_VERSION:
            return raw
        pages_doc = self.load_pages_document()
        graph = migrate_graph_from_pages(pages_doc)
        self._store.write_wiki_graph(graph)
        return graph

    def save_graph(self, graph: dict[str, Any]) -> None:
        graph.setdefault("version", GRAPH_VERSION)
        self._store.write_wiki_graph(graph)

    def load_compile_state(self) -> dict[str, Any]:
        return self._store.read_wiki_compile_state()

    def save_compile_state(self, state: dict[str, Any]) -> None:
        self._store.write_wiki_compile_state(state)

    def neighbors(self, page_id: str) -> list[str]:
        """graph 邻接 page_id（出边 + 入边，去重保序）。"""
        graph = self.load_graph()
        out: list[str] = []
        seen: set[str] = set()
        for edge in graph.get("edges") or []:
            if not isinstance(edge, dict):
                continue
            src = edge.get("from")
            dst = edge.get("to")
            if src == page_id and dst and dst not in seen:
                seen.add(str(dst))
                out.append(str(dst))
            elif dst == page_id and src and src not in seen:
                seen.add(str(src))
                out.append(str(src))
        return out

    @staticmethod
    def page_visible(
        page: dict[str, Any],
        *,
        namespace: str = "subject",
        object_id: str | None = None,
    ) -> bool:
        """disclosure / namespace 过滤 hook（与 wiki API 一致）。"""
        ns = str(page.get("namespace") or "subject")
        if object_id:
            return ns == f"object:{object_id}"
        if namespace == "subject":
            return ns == "subject"
        if namespace.startswith("object:"):
            return ns == namespace
        return ns == namespace

    def filter_pages(
        self,
        pages: dict[str, Any],
        *,
        namespace: str = "subject",
        object_id: str | None = None,
    ) -> dict[str, Any]:
        return {
            pid: page
            for pid, page in pages.items()
            if isinstance(page, dict) and self.page_visible(page, namespace=namespace, object_id=object_id)
        }

    def find_page_id_by_vault(
        self,
        *,
        vault_id: str,
        vault_rel_path: str | None = None,
        wiki_page_id_hint: str | None = None,
    ) -> str | None:
        """vault_id / rel_path / frontmatter wiki_page_id → wiki page_id。"""
        pages = self.pages_map()
        hint = str(wiki_page_id_hint or "").strip()
        if hint and hint in pages:
            return hint
        vid = str(vault_id or "").strip()
        rel = str(vault_rel_path or "").strip()
        for pid, page in pages.items():
            if not isinstance(page, dict):
                continue
            meta = page.get("meta") if isinstance(page.get("meta"), dict) else {}
            if vid and str(meta.get("vault_id") or "") == vid:
                return pid
            if rel and str(meta.get("vault_path") or "") == rel:
                return pid
        return None

    def search(
        self,
        query: str,
        *,
        namespace: str = "subject",
        object_id: str | None = None,
        limit: int = 20,
    ) -> list[Any]:
        from niannian.brain.cerebellum.wiki_search import WikiSearch

        return WikiSearch(self._store).search(
            query,
            namespace=namespace,
            object_id=object_id,
            limit=limit,
        )
