"""
MNEMOSYNTH — Universal AI Memory Plugin
Persistent, verified, hallucination-resistant memory for ANY AI system.
"""

__version__ = "0.1.0"

from mnemosynth.core.types import MemoryNode, MemoryType, MemoryStatus, MemorySource


class Mnemosynth:
    """High-level API for the Mnemosynth memory system.

    Usage:
        brain = Mnemosynth()
        brain.remember("User prefers dark mode and TypeScript")
        results = brain.recall("What does the user prefer?")
        digest = brain.digest(query="Setting up a new project")
        report = brain.dream()
    """

    def __init__(self, data_dir: str | None = None):
        from mnemosynth.core.config import MnemosynthConfig
        from mnemosynth.utils.db import DatabaseManager
        from mnemosynth.utils.embeddings import EmbeddingModel
        from mnemosynth.stores.episodic import EpisodicStore
        from mnemosynth.stores.semantic import SemanticStore
        from mnemosynth.stores.procedural import ProceduralStore
        from mnemosynth.engine.router import MemoryRouter
        from mnemosynth.engine.decay import DecayEngine
        from mnemosynth.engine.digest import DigestEngine

        self.config = MnemosynthConfig(data_dir=data_dir)
        self.config.ensure_dirs()

        self.db = DatabaseManager(self.config.db_path)
        self.embeddings = EmbeddingModel(self.config.embedding_model)

        self.episodic = EpisodicStore(self.config, self.embeddings)
        self.semantic = SemanticStore(self.config, self.db)
        self.procedural = ProceduralStore(self.config)

        self.router = MemoryRouter()
        self.decay = DecayEngine(self.config)
        self.digest_engine = DigestEngine(
            episodic=self.episodic,
            semantic=self.semantic,
            procedural=self.procedural,
            decay=self.decay,
            config=self.config,
        )

    def remember(self, content: str, memory_type: str = "auto") -> MemoryNode:
        """Store a new memory. Auto-classifies into episodic/semantic/procedural."""
        if memory_type == "auto":
            classified_type = self.router.classify(content)
        else:
            classified_type = MemoryType(memory_type)

        node = MemoryNode.create(content=content, memory_type=classified_type)

        if classified_type == MemoryType.EPISODIC:
            embedding = self.embeddings.embed(content)
            node.embedding = embedding
            self.episodic.add(node)
        elif classified_type == MemoryType.SEMANTIC:
            self.semantic.add(node)
        else:
            self.procedural.add(node)

        self.db.save_memory(node)
        return node

    def recall(self, query: str, limit: int = 5) -> list[MemoryNode]:
        """Search across all memory types using semantic similarity."""
        results: list[MemoryNode] = []

        # Search episodic memories
        episodic_results = self.episodic.search(query, limit=limit)
        results.extend(episodic_results)

        # Search semantic memories
        semantic_results = self.semantic.search(query, limit=limit)
        results.extend(semantic_results)

        # Search procedural memories
        procedural_results = self.procedural.search(query, limit=limit)
        results.extend(procedural_results)

        # Apply decay scoring and sort
        scored = []
        for mem in results:
            decay_score = self.decay.compute_decay(mem)
            mem.confidence = mem.confidence * decay_score
            scored.append(mem)

        scored.sort(key=lambda m: m.confidence, reverse=True)
        return scored[:limit]

    def digest(self, query: str, max_tokens: int = 150) -> str:
        """Get a compressed memory context block for the current query."""
        return self.digest_engine.build(query=query, max_tokens=max_tokens)

    def dream(self) -> dict:
        """Run dream consolidation: cluster, promote, decay, resolve contradictions."""
        from mnemosynth.engine.digest import DreamReport

        # Get recent episodic memories
        recent = self.episodic.get_recent(days=7)
        if not recent:
            return {"promoted": 0, "contradictions_resolved": 0, "decayed": 0}

        promoted = 0
        decayed = 0

        # Cluster episodic memories
        if len(recent) >= 3:
            embeddings = [m.embedding for m in recent if m.embedding is not None]
            if len(embeddings) >= 3:
                from sklearn.cluster import HDBSCAN
                import numpy as np

                X = np.array(embeddings)
                clusterer = HDBSCAN(min_cluster_size=3, min_samples=2)
                labels = clusterer.fit_predict(X)

                # Promote dense clusters to semantic facts
                unique_labels = set(labels)
                for label in unique_labels:
                    if label == -1:
                        # Noise — apply extra decay
                        for i, l in enumerate(labels):
                            if l == -1:
                                recent[i].confidence *= 0.8
                                self.db.save_memory(recent[i])
                                decayed += 1
                        continue

                    cluster_memories = [recent[i] for i, l in enumerate(labels) if l == label]
                    # Create a semantic summary from the cluster
                    combined = " | ".join([m.content for m in cluster_memories[:5]])
                    summary = combined[:200]  # Truncate for now
                    semantic_node = MemoryNode.create(
                        content=f"[Consolidated] {summary}",
                        memory_type=MemoryType.SEMANTIC,
                        confidence=0.85,
                    )
                    self.semantic.add(semantic_node)
                    self.db.save_memory(semantic_node)
                    promoted += 1

        return {"promoted": promoted, "contradictions_resolved": 0, "decayed": decayed}

    def forget(self, memory_id: str) -> bool:
        """Remove a specific memory."""
        memory = self.db.get_memory(memory_id)
        if not memory:
            return False

        if memory.memory_type == MemoryType.EPISODIC:
            self.episodic.delete(memory_id)
        elif memory.memory_type == MemoryType.SEMANTIC:
            self.semantic.delete(memory_id)
        else:
            self.procedural.delete(memory_id)

        self.db.delete_memory(memory_id)
        return True

    def stats(self) -> dict:
        """Get memory statistics."""
        return {
            "total": self.db.count_memories(),
            "episodic": self.db.count_memories(MemoryType.EPISODIC),
            "semantic": self.db.count_memories(MemoryType.SEMANTIC),
            "procedural": self.db.count_memories(MemoryType.PROCEDURAL),
            "active": self.db.count_memories(status=MemoryStatus.ACTIVE),
            "deprecated": self.db.count_memories(status=MemoryStatus.DEPRECATED),
            "quarantined": self.db.count_memories(status=MemoryStatus.QUARANTINED),
        }


__all__ = [
    "Mnemosynth",
    "MemoryNode",
    "MemoryType",
    "MemoryStatus",
    "MemorySource",
    "__version__",
]
