"""增量 compile — atoms → pages v2 + graph edges。"""

from __future__ import annotations

from datetime import datetime, timezone
from typing import Any
from uuid import uuid4

from niannian.brain.cerebellum.wiki_migrate import (
    graph_node_from_page,
    normalize_entity_id,
)
from niannian.brain.cerebellum.wiki_store import WikiStore
from niannian.schemas.models import Atom

_DEFAULT_EDGE_SIGNALS = {
    "wikilink": 0.7,
    "shared_vault_id": 0.9,
    "shared_tag": 0.5,
    "page_type_affinity": 0.15,
}


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


def _load_edge_signals() -> dict[str, float]:
    from niannian.core.config import load_config

    wiki = (load_config().get("cerebellum") or {}).get("wiki") or {}
    graph = wiki.get("graph") or {}
    raw = graph.get("edge_signals") or {}
    out = dict(_DEFAULT_EDGE_SIGNALS)
    if isinstance(raw, dict):
        for key in out:
            if key in raw:
                out[key] = float(raw[key])
    return out


def _atom_vault_id(atom: Atom) -> str | None:
    meta = atom.meta if isinstance(atom.meta, dict) else {}
    vault = meta.get("vault")
    if isinstance(vault, dict) and vault.get("vault_id"):
        return str(vault["vault_id"])
    return None


def _atom_vault_path(atom: Atom) -> str | None:
    meta = atom.meta if isinstance(atom.meta, dict) else {}
    vault = meta.get("vault")
    if isinstance(vault, dict) and vault.get("path"):
        return str(vault["path"])
    return None


def _page_entity_type(page: dict[str, Any]) -> str:
    meta = page.get("meta") if isinstance(page.get("meta"), dict) else {}
    return str(meta.get("entity_type") or meta.get("page_type") or "concept")


class WikiCompiler:
    def __init__(self, store) -> None:
        self._wiki = WikiStore(store)
        self._edge_signals = _load_edge_signals()

    def _edge_weight(
        self,
        from_page: dict[str, Any],
        to_page: dict[str, Any] | None,
        *,
        from_atoms: list[Atom],
        to_atoms: list[Atom],
    ) -> tuple[float, list[str]]:
        """四信号边权：wikilink · 共 vault_id · 共 tag · page_type 亲和。"""
        sig = self._edge_signals
        applied: list[str] = []
        weight = float(sig["shared_tag"])
        applied.append("shared_tag")

        from_vaults = {_atom_vault_id(a) for a in from_atoms if _atom_vault_id(a)}
        to_vaults = {_atom_vault_id(a) for a in to_atoms if _atom_vault_id(a)}
        if from_vaults & to_vaults:
            weight = max(weight, float(sig["shared_vault_id"]))
            applied.append("shared_vault_id")

        if to_page is not None:
            target_id = str(to_page.get("id") or "")
            links = [normalize_entity_id(x) for x in (from_page.get("links") or [])]
            if target_id and target_id in links:
                weight = max(weight, float(sig["wikilink"]))
                applied.append("wikilink")
            if _page_entity_type(from_page) == _page_entity_type(to_page):
                weight += float(sig["page_type_affinity"])
                applied.append("page_type_affinity")

        return round(min(1.0, weight), 3), applied

    def compile_atoms(self, atoms: list[Atom], *, namespace: str = "subject") -> dict[str, Any]:
        if not atoms:
            return {"touched_page_ids": [], "edges_added": 0}

        pages_doc = self._wiki.load_pages_document(namespace=namespace)
        pages: dict[str, Any] = dict(pages_doc.get("pages") or {})
        graph = self._wiki.load_graph()
        nodes: dict[str, Any] = dict(graph.get("nodes") or {})
        edges: list[dict[str, Any]] = list(graph.get("edges") or [])
        edge_keys = {(e.get("from"), e.get("to")) for e in edges if isinstance(e, dict)}

        touched: list[str] = []
        edges_added = 0
        by_tag: dict[str, list[Atom]] = {}
        for atom in atoms:
            for tag in atom.tags or ["未分类"]:
                by_tag.setdefault(tag, []).append(atom)

        for tag, group in by_tag.items():
            page_id = normalize_entity_id(tag)
            existing = pages.get(page_id) if isinstance(pages.get(page_id), dict) else {}
            atom_ids = list(dict.fromkeys(list(existing.get("atom_ids") or []) + [a.id for a in group]))[-20:]
            snippets = [a.raw_text[:120] for a in group[-8:]]
            summary = "；".join(snippets)[:400]
            vault_paths = [_atom_vault_path(a) for a in group if _atom_vault_path(a)]
            link_ids = sorted(
                {
                    normalize_entity_id(t)
                    for a in group
                    for t in (a.tags or [])
                    if normalize_entity_id(t) != page_id
                }
            )
            page = {
                "id": page_id,
                "title": tag,
                "namespace": namespace,
                "summary": summary or str(existing.get("summary") or ""),
                "body_md": None if vault_paths else existing.get("body_md"),
                "atom_ids": atom_ids,
                "links": link_ids,
                "meta": {
                    **(existing.get("meta") if isinstance(existing.get("meta"), dict) else {}),
                    "entity_type": "concept",
                    "tier": "semantic",
                    "confidence": min(0.95, float((existing.get("meta") or {}).get("confidence") or 0.5) + 0.05),
                    "stale": False,
                    "updated_at": _now_iso(),
                    "provenance": "compile_v2",
                },
            }
            if vault_paths:
                page["meta"]["vault_path"] = vault_paths[0]
            pages[page_id] = page
            nodes[page_id] = graph_node_from_page(page)
            touched.append(page_id)

            for target in link_ids:
                key = (page_id, target)
                if key in edge_keys:
                    continue
                target_tag = target.removeprefix("ent:")
                to_atoms = by_tag.get(target_tag, [])
                to_page = pages.get(target) if isinstance(pages.get(target), dict) else None
                weight, signals = self._edge_weight(
                    page,
                    to_page,
                    from_atoms=group,
                    to_atoms=to_atoms,
                )
                edges.append(
                    {
                        "id": f"e_{uuid4().hex[:8]}",
                        "from": page_id,
                        "to": target,
                        "type": "related-to",
                        "weight": weight,
                        "signals": signals,
                        "source_atom_ids": atom_ids[:5],
                    }
                )
                edge_keys.add(key)
                edges_added += 1
                if target not in nodes:
                    nodes[target] = {
                        "page_id": target,
                        "label": target.removeprefix("ent:"),
                        "namespace": namespace,
                        "entity_type": "concept",
                        "degree": 0,
                        "confidence": 0.5,
                    }

        pages_doc["pages"] = pages
        self._wiki.save_pages_document(pages_doc)
        graph["nodes"] = nodes
        graph["edges"] = edges
        self._wiki.save_graph(graph)

        state = self._wiki.load_compile_state()
        pending = set(state.get("pending_atom_ids") or [])
        pending.difference_update(a.id for a in atoms)
        state["last_compile_at"] = _now_iso()
        state["pending_atom_ids"] = sorted(pending)
        state["last_touched"] = touched[-10:]
        self._wiki.save_compile_state(state)

        return {"touched_page_ids": touched, "edges_added": edges_added}
