"""
Formal Memory Type Ontology for Knowledge Graph

Provides controlled vocabulary and type hierarchy for semantic memory classification.
Part of Phase 0: Ontology Foundation (Knowledge Graph Evolution).

Usage:
    from mcp_memory_service.models.ontology import MemoryTypeOntology

    # Validate memory type
    is_valid = MemoryTypeOntology.validate_memory_type("observation")

    # Get parent type
    parent = MemoryTypeOntology.get_parent_type("code_edit")  # Returns "observation"
"""

from enum import Enum
from typing import Dict, List, Optional, Final


# Module-level caches for performance optimization
_ALL_TYPES_CACHE: Optional[List[str]] = None
_PARENT_TYPE_MAP_CACHE: Optional[Dict[str, str]] = None
_BASE_TYPES_CACHE: Optional[set] = None


class BaseMemoryType(str, Enum):
    """
    Base memory types forming the top-level ontology.

    These are the fundamental categories that all memories belong to.
    Each base type can have multiple subtypes for finer-grained classification.
    """
    OBSERVATION = "observation"
    DECISION = "decision"
    LEARNING = "learning"
    ERROR = "error"
    PATTERN = "pattern"


# Taxonomy hierarchy: base types → subtypes
TAXONOMY: Final[Dict[str, List[str]]] = {
    "observation": [
        "code_edit",
        "file_access",
        "search",
        "command",
        "conversation",
        "document",
        "note",
        "reference"
    ],
    "decision": [
        "architecture",
        "tool_choice",
        "approach",
        "configuration"
    ],
    "learning": [
        "insight",
        "best_practice",
        "anti_pattern",
        "gotcha"
    ],
    "error": [
        "bug",
        "failure",
        "exception",
        "timeout"
    ],
    "pattern": [
        "recurring_issue",
        "code_smell",
        "design_pattern",
        "workflow"
    ]
}


# Relationship types with valid source → target patterns
RELATIONSHIPS: Final[Dict[str, Dict[str, List[str]]]] = {
    "causes": {
        "description": "A causes B (causal relationship)",
        "valid_patterns": ["observation → error", "decision → observation", "error → error"]
    },
    "fixes": {
        "description": "A fixes B (remediation relationship)",
        "valid_patterns": ["decision → error", "learning → error", "pattern → error"]
    },
    "contradicts": {
        "description": "A contradicts B (conflict relationship)",
        "valid_patterns": ["decision → decision", "learning → learning", "observation → observation"]
    },
    "supports": {
        "description": "A supports B (reinforcement relationship)",
        "valid_patterns": ["learning → decision", "observation → learning", "pattern → learning"]
    },
    "follows": {
        "description": "A follows B (temporal/sequential relationship)",
        "valid_patterns": ["observation → observation", "decision → decision", "any → any"]
    },
    "related": {
        "description": "A is related to B (generic association)",
        "valid_patterns": ["any → any"]
    }
}

# Symmetric relationships (bidirectional semantics)
SYMMETRIC_RELATIONSHIPS: Final[set] = {"related", "contradicts"}


def validate_memory_type(memory_type: str) -> bool:
    """
    Validate if a memory type is in the ontology (base type or subtype).

    Args:
        memory_type: The type string to validate

    Returns:
        True if the type is valid (exists in base types or subtypes), False otherwise

    Examples:
        >>> validate_memory_type("observation")  # Base type
        True
        >>> validate_memory_type("code_edit")    # Subtype
        True
        >>> validate_memory_type("invalid")
        False
    """
    global _BASE_TYPES_CACHE

    # Initialize base types cache on first access
    if _BASE_TYPES_CACHE is None:
        _BASE_TYPES_CACHE = {member.value for member in BaseMemoryType}

    # Check if it's a base type
    if memory_type in _BASE_TYPES_CACHE:
        return True

    # Check if it's a subtype - leverage get_all_types cache
    all_types = get_all_types()
    return memory_type in all_types


def get_parent_type(subtype: str) -> Optional[str]:
    """
    Get the parent base type for a subtype. Returns itself if already a base type.

    Args:
        subtype: The subtype (or base type) to look up

    Returns:
        Parent base type string, or None if subtype is invalid

    Examples:
        >>> get_parent_type("code_edit")  # Subtype
        'observation'
        >>> get_parent_type("observation")  # Base type returns itself
        'observation'
        >>> get_parent_type("invalid")
        None
    """
    global _PARENT_TYPE_MAP_CACHE, _BASE_TYPES_CACHE

    # Initialize caches on first access
    if _BASE_TYPES_CACHE is None:
        _BASE_TYPES_CACHE = {member.value for member in BaseMemoryType}

    if _PARENT_TYPE_MAP_CACHE is None:
        # Build reverse lookup map: subtype → parent
        _PARENT_TYPE_MAP_CACHE = {}

        # Base types map to themselves
        for base_type in _BASE_TYPES_CACHE:
            _PARENT_TYPE_MAP_CACHE[base_type] = base_type

        # Subtypes map to their parent
        for base_type, subtypes in TAXONOMY.items():
            for st in subtypes:
                _PARENT_TYPE_MAP_CACHE[st] = base_type

    # Return cached lookup result
    return _PARENT_TYPE_MAP_CACHE.get(subtype)


def get_all_types() -> List[str]:
    """
    Get flattened list of all valid memory types (base + subtypes).

    Returns:
        List of all type strings in the ontology

    Examples:
        >>> types = get_all_types()
        >>> "observation" in types
        True
        >>> "code_edit" in types
        True
        >>> len(types)  # 5 base + 21 subtypes
        26
    """
    global _ALL_TYPES_CACHE

    # Initialize cache on first access
    if _ALL_TYPES_CACHE is None:
        # Get all base types
        all_types = [member.value for member in BaseMemoryType]

        # Add all subtypes
        for subtypes in TAXONOMY.values():
            all_types.extend(subtypes)

        _ALL_TYPES_CACHE = all_types

    # Return copy to prevent mutation of cached list
    return _ALL_TYPES_CACHE.copy()


def validate_relationship(rel_type: str) -> bool:
    """
    Validate if a relationship type is in the allowed list.

    Args:
        rel_type: The relationship type string to validate

    Returns:
        True if relationship type is valid, False otherwise

    Examples:
        >>> validate_relationship("causes")
        True
        >>> validate_relationship("fixes")
        True
        >>> validate_relationship("invalid_rel")
        False
    """
    return rel_type in RELATIONSHIPS


def is_symmetric_relationship(rel_type: str) -> bool:
    """
    Determine if a relationship type is symmetric (bidirectional).

    Symmetric relationships work the same in both directions:
    - related: A related to B implies B related to A
    - contradicts: A contradicts B implies B contradicts A

    Asymmetric relationships have directionality:
    - causes: A causes B does NOT imply B causes A
    - fixes: A fixes B does NOT imply B fixes A
    - supports: A supports B does NOT imply B supports A
    - follows: A follows B does NOT imply B follows A

    Args:
        rel_type: The relationship type string to check

    Returns:
        True if symmetric (bidirectional storage correct), False if asymmetric

    Examples:
        >>> is_symmetric_relationship("related")
        True
        >>> is_symmetric_relationship("causes")
        False
        >>> is_symmetric_relationship("contradicts")
        True

    Raises:
        ValueError: If rel_type is not a valid relationship type
    """
    # Validate input first
    if not validate_relationship(rel_type):
        raise ValueError(f"Invalid relationship type: '{rel_type}'")

    return rel_type in SYMMETRIC_RELATIONSHIPS


class MemoryTypeOntology:
    """
    Memory Type Ontology - Formal type system for knowledge graph classification.

    Provides controlled vocabulary, validation, and type hierarchy management
    for semantic memory classification. All methods are class methods for
    stateless, pure functional access to the ontology.

    Usage:
        # Validate memory type
        if MemoryTypeOntology.validate_memory_type("observation"):
            print("Valid type")

        # Get parent type
        parent = MemoryTypeOntology.get_parent_type("code_edit")  # → "observation"

        # Get all types
        all_types = MemoryTypeOntology.get_all_types()

        # Validate relationship
        if MemoryTypeOntology.validate_relationship("causes"):
            print("Valid relationship")
    """

    @classmethod
    def validate_memory_type(cls, memory_type: str) -> bool:
        """Validate if memory type exists in ontology."""
        return validate_memory_type(memory_type)

    @classmethod
    def get_parent_type(cls, subtype: str) -> Optional[str]:
        """Get parent base type for a subtype."""
        return get_parent_type(subtype)

    @classmethod
    def get_all_types(cls) -> List[str]:
        """Get flattened list of all types."""
        return get_all_types()

    @classmethod
    def validate_relationship(cls, rel_type: str) -> bool:
        """Validate if relationship type is valid."""
        return validate_relationship(rel_type)

    @classmethod
    def is_symmetric_relationship(cls, rel_type: str) -> bool:
        """Check if relationship type is symmetric (bidirectional)."""
        return is_symmetric_relationship(rel_type)
