"""Core data models for engram dual-memory system."""

from __future__ import annotations

__all__ = [
    "MemoryType", "Priority", "FeedbackType",
    "EpisodicMemory",
    "AttributeDef", "NodeDef", "EdgeDef", "SchemaDefinition",
    "SemanticNode", "SemanticEdge", "ExtractionResult", "IngestResult",
    "Entity", "ResolvedText", "SearchResult", "MemoryCandidate", "MeetingLedger",
    "resolve_memory_type",
]

from datetime import datetime, timezone
from enum import Enum
from typing import Any

from pydantic import BaseModel, Field, computed_field, field_validator


def _normalize_entity_name(name: str) -> str:
    """Normalize entity name: trim whitespace, preserve original casing.

    Casing is preserved to avoid breaking proper nouns (HAProxy, RabbitMQ, etc.).
    Deduplication happens at the graph store level via case-insensitive lookup.
    """
    return (name or "").strip()


# --- Helpers ---


def resolve_memory_type(memory_type: "MemoryType | str") -> str:
    """Return the string value of a MemoryType enum or pass through a plain string."""
    if isinstance(memory_type, MemoryType):
        return memory_type.value
    return memory_type


# --- Enums ---


class MemoryType(str, Enum):
    """Classification of episodic memories."""

    FACT = "fact"
    DECISION = "decision"
    PREFERENCE = "preference"
    TODO = "todo"
    ERROR = "error"
    CONTEXT = "context"
    WORKFLOW = "workflow"
    LESSON = "lesson"
    AVOIDANCE = "avoidance"
    PERSONALITY_TRAIT = "personality_trait"
    MEETING_LEDGER = "meeting_ledger"


class Priority(int, Enum):
    """Memory priority levels (1=lowest, 10=critical)."""

    LOWEST = 1
    LOW = 3
    NORMAL = 5
    HIGH = 7
    CRITICAL = 10


class FeedbackType(str, Enum):
    """Type of user feedback on a memory."""

    POSITIVE = "positive"
    NEGATIVE = "negative"


# --- Episodic Memory (Vector DB) ---


class EpisodicMemory(BaseModel):
    """A single episodic memory stored in vector DB."""

    id: str = ""
    content: str
    memory_type: MemoryType = MemoryType.FACT
    priority: int = Priority.NORMAL
    metadata: dict[str, Any] = Field(default_factory=dict)
    entities: list[str] = Field(default_factory=list)
    tags: list[str] = Field(default_factory=list)
    timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
    expires_at: datetime | None = None
    # Ebbinghaus decay fields
    access_count: int = 0
    last_accessed: datetime | None = None
    decay_rate: float = 0.1
    # Consolidation fields
    consolidation_group: str | None = None
    consolidated_into: str | None = None
    # Topic key upsert fields
    topic_key: str | None = None
    revision_count: int = 0
    # Feedback loop fields
    confidence: float = 1.0  # 0.0 - 1.0, adjusted by user feedback
    negative_count: int = 0  # count of negative feedbacks received
    # Source tracking (e.g. "OpenClaw", "ClaudeCode", "api", "cli")
    source: str = ""

    @field_validator("priority")
    @classmethod
    def _validate_priority(cls, v: int) -> int:
        if not 1 <= v <= 10:
            raise ValueError(f"Priority must be 1-10, got {v}")
        return v


# --- Semantic Memory (Graph DB) ---


class AttributeDef(BaseModel):
    """Schema definition for a node attribute."""

    name: str
    type: str = "string"
    required: bool = False


class NodeDef(BaseModel):
    """Schema definition for a node type."""

    name: str
    description: str = ""
    attributes: list[AttributeDef] = Field(default_factory=list)


class EdgeDef(BaseModel):
    """Schema definition for an edge type."""

    name: str
    from_types: list[str] = Field(default_factory=list)
    to_types: list[str] = Field(default_factory=list)


class SchemaDefinition(BaseModel):
    """Complete schema for semantic memory."""

    nodes: list[NodeDef] = Field(default_factory=list)
    edges: list[EdgeDef] = Field(default_factory=list)
    extraction_hints: dict[str, Any] = Field(default_factory=dict)


class SemanticNode(BaseModel):
    """A node in the semantic graph."""

    type: str
    name: str
    attributes: dict[str, Any] = Field(default_factory=dict)

    @field_validator("name")
    @classmethod
    def _normalize_name(cls, v: str) -> str:
        return _normalize_entity_name(v)

    @computed_field
    @property
    def key(self) -> str:
        return f"{self.type}:{self.name}"


class SemanticEdge(BaseModel):
    """An edge in the semantic graph."""

    from_node: str  # node key (type:name)
    to_node: str
    relation: str
    weight: float = 1.0
    attributes: dict[str, Any] = Field(default_factory=dict)

    @computed_field
    @property
    def key(self) -> str:
        return f"{self.from_node}--{self.relation}-->{self.to_node}"


class ExtractionResult(BaseModel):
    """Result of LLM entity extraction."""

    nodes: list[SemanticNode] = Field(default_factory=list)
    edges: list[SemanticEdge] = Field(default_factory=list)


class IngestResult(BaseModel):
    """Result of dual ingest operation."""

    episodic_count: int = 0
    semantic_nodes: int = 0
    semantic_edges: int = 0


# --- Recall Pipeline Models ---


class Entity(BaseModel):
    """A resolved entity from text (person, place, technology, etc.)."""

    name: str
    type: str = "unknown"
    aliases: list[str] = Field(default_factory=list)


class ResolvedText(BaseModel):
    """Result of entity/temporal resolution on input text."""

    original: str
    resolved: str
    entities: list[Entity] = Field(default_factory=list)
    temporal_refs: dict[str, str] = Field(default_factory=dict)


class SearchResult(BaseModel):
    """A single search result from any memory source."""

    id: str
    content: str
    score: float
    source: str  # search channel: "semantic", "entity_graph", "keyword", "fts"
    origin: str = ""  # ingestion source: "ClaudeCode", "OpenClaw", "think", "manual", etc.
    memory_type: str = "fact"
    importance: int = 5
    timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
    metadata: dict[str, Any] = Field(default_factory=dict)


class MeetingLedger(BaseModel):
    """Structured record of a meeting or conversation session."""

    title: str = ""
    attendees: list[str] = []
    decisions: list[str] = []
    action_items: list[str] = []
    topics: list[str] = []
    date: str = ""
    summary: str = ""


class MemoryCandidate(BaseModel):
    """A candidate for auto-saving to memory."""

    content: str
    importance: int = 3
    category: str = "auto"  # manual, identity, preference, explicit, pattern
    memory_type: MemoryType = MemoryType.FACT
