"""Semantic deduplication of memories."""

from __future__ import annotations

from typing import Any

import numpy as np


class SemanticDeduplicator:
    """Detect and merge semantically duplicate memories."""

    DUPLICATE_THRESHOLD = 0.95
    SIMILAR_THRESHOLD = 0.85

    def __init__(self, storage, embedding_engine):
        self._storage = storage
        self._embedding = embedding_engine

    def check_duplicate(self, content: str, embedding: list[float], limit: int = 20) -> dict[str, Any] | None:
        """Check if content is semantically duplicate of existing memory."""
        del content
        candidates = self._storage.get_recent_embeddings(limit=limit)
        if not candidates:
            return None

        query_vec = np.array(embedding, dtype=np.float32)
        query_norm = np.linalg.norm(query_vec)
        if query_norm < 1e-8:
            return None

        for candidate in candidates:
            cand_vec = np.array(candidate["embedding"], dtype=np.float32)
            cand_norm = np.linalg.norm(cand_vec)
            if cand_norm < 1e-8:
                continue

            similarity = float(np.dot(query_vec, cand_vec) / (query_norm * cand_norm))
            if similarity >= self.DUPLICATE_THRESHOLD:
                return {
                    "memory_id": candidate["id"],
                    "similarity": similarity,
                    "action": "skip",
                }
            if similarity >= self.SIMILAR_THRESHOLD:
                return {
                    "memory_id": candidate["id"],
                    "similarity": similarity,
                    "action": "merge_metadata",
                }

        return None

    def deduplicate_batch(self, embeddings: list[list[float]], memory_ids: list[bytes]) -> list[tuple[bytes, bytes]]:
        """Find duplicate pairs in a batch of memories."""
        if len(embeddings) < 2:
            return []

        matrix = np.array(embeddings, dtype=np.float32)
        norms = np.linalg.norm(matrix, axis=1, keepdims=True)
        normalized = matrix / (norms + 1e-8)
        sim_matrix = normalized @ normalized.T

        duplicates: list[tuple[bytes, bytes]] = []
        merged = set()

        for i in range(len(embeddings)):
            if i in merged:
                continue
            for j in range(i + 1, len(embeddings)):
                if j in merged:
                    continue
                if sim_matrix[i][j] >= self.DUPLICATE_THRESHOLD:
                    duplicates.append((memory_ids[j], memory_ids[i]))
                    merged.add(j)

        return duplicates
