"""
Knowledge ontology schema loader.

Loads and validates the knowledge_schema.yaml meta-schema that defines
valid relationship types and node kinds for the knowledge graph.
"""

from __future__ import annotations

import logging
from dataclasses import dataclass, field
from functools import lru_cache
from pathlib import Path
from typing import Any

import yaml

from pycharter.wiki.ontology.models import INVERSE_MAP

logger = logging.getLogger(__name__)

_SCHEMA_PATH = (
    Path(__file__).resolve().parents[2] / "data" / "seed" / "knowledge_schema.yaml"
)


@dataclass(frozen=True, slots=True)
class RelationshipTypeInfo:
    """Metadata for a single relationship type."""

    name: str
    meaning: str
    graph_shape: str
    inverse: str
    symmetric: bool = False


@dataclass(frozen=True, slots=True)
class KnowledgeSchema:
    """Validated knowledge ontology meta-schema."""

    version: int
    relationship_types: dict[str, RelationshipTypeInfo] = field(default_factory=dict)
    node_kinds: dict[str, str] = field(default_factory=dict)


def _parse_schema(raw: dict[str, Any]) -> KnowledgeSchema:
    """Parse raw YAML dict into a KnowledgeSchema."""
    version = raw.get("version", 1)

    rel_types: dict[str, RelationshipTypeInfo] = {}
    for name, info in raw.get("relationship_types", {}).items():
        rel_types[name] = RelationshipTypeInfo(
            name=name,
            meaning=info.get("meaning", ""),
            graph_shape=info.get("graph_shape", "generic"),
            inverse=info.get("inverse", name),
            symmetric=info.get("symmetric", False),
        )

    node_kinds: dict[str, str] = raw.get("node_kinds", {})

    return KnowledgeSchema(
        version=version,
        relationship_types=rel_types,
        node_kinds=node_kinds,
    )


@lru_cache(maxsize=1)
def load_knowledge_schema(path: str | None = None) -> KnowledgeSchema:
    """Load knowledge_schema.yaml and return a validated schema object.

    Args:
        path: Override path to the YAML file.  Defaults to the bundled
              ``data/seed/knowledge_schema.yaml``.

    Returns:
        Parsed and validated KnowledgeSchema.

    Raises:
        FileNotFoundError: If the schema file does not exist.
        ValueError: If the YAML is malformed.
    """
    schema_path = Path(path) if path else _SCHEMA_PATH
    if not schema_path.exists():
        msg = f"Knowledge schema not found: {schema_path}"
        raise FileNotFoundError(msg)

    with schema_path.open() as fh:
        raw = yaml.safe_load(fh)

    if not isinstance(raw, dict):
        msg = f"Expected a YAML mapping, got {type(raw).__name__}"
        raise ValueError(msg)

    schema = _parse_schema(raw)
    logger.info(
        "loaded knowledge schema v%d: %d relationship types, %d node kinds",
        schema.version,
        len(schema.relationship_types),
        len(schema.node_kinds),
    )
    return schema


def get_relationship_types() -> dict[str, RelationshipTypeInfo]:
    """Return all defined relationship types with metadata."""
    return load_knowledge_schema().relationship_types


def get_node_kinds() -> dict[str, str]:
    """Return all defined node kinds with descriptions."""
    return load_knowledge_schema().node_kinds


def validate_relationship_type(rel_type: str) -> bool:
    """Check if a relationship type is valid."""
    return rel_type in load_knowledge_schema().relationship_types


def validate_node_kind(kind: str) -> bool:
    """Check if a node kind is valid."""
    return kind in load_knowledge_schema().node_kinds


def get_inverse(rel_type: str) -> str:
    """Return the inverse of a relationship type.

    Uses ``INVERSE_MAP`` from models as the runtime source of truth,
    falling back to the schema YAML for any types defined only there.
    """
    if rel_type in INVERSE_MAP:
        return INVERSE_MAP[rel_type]
    schema = load_knowledge_schema()
    info = schema.relationship_types.get(rel_type)
    if info:
        return info.inverse
    return rel_type
