"""
ChunkHive Semantic Scorer
=========================

Computes semantic similarity between query and chunk embeddings.

Uses cosine similarity (via dot product because embeddings are normalized).
Does NOT re-embed chunks.
Embeds query only once per search session.
"""

import numpy as np
from typing import Optional
from .base import BaseScorer, ScoreResult
from chunkhive.embeddings import SemanticEmbeddingEngine


# =============================================================================
# SEMANTIC SCORER
# =============================================================================

class SemanticScorer(BaseScorer):
    """
    Scores chunks using cosine similarity between query embedding
    and precomputed chunk embeddings.
    """

    def __init__(
        self,
        embedding_engine: SemanticEmbeddingEngine,
        weight: float = 1.0
    ):
        super().__init__(weight)
        self.engine = embedding_engine
        self._query_embedding: Optional[np.ndarray] = None

    def prepare_query(self, query: str) -> None:
        """
        Generate and cache query embedding.
        Call this once before scoring multiple chunks.
        """
        embedding = self.engine.generate_embedding_for_query(query)

        # Ensure it's a proper numpy float32 array
        self._query_embedding = np.asarray(embedding, dtype=np.float32)

    def score(self, query: str, chunk) -> ScoreResult:
        """
        Compute semantic similarity score.

        IMPORTANT:
        - Chunk must already contain embedding.
        - Embeddings must be normalized (they already are in your engine).
        """

        if chunk.embedding is None:
            return ScoreResult(score=0.0, details={"error": "no_embedding"})

        # Ensure query embedding exists
        if self._query_embedding is None:
            self.prepare_query(query)

        # After this point it is guaranteed non-None
        assert self._query_embedding is not None

        chunk_embedding = np.asarray(chunk.embedding, dtype=np.float32)

        # Cosine similarity (dot product because normalized)
        similarity = float(np.dot(self._query_embedding, chunk_embedding))

        weighted_score = similarity * self.weight

        return ScoreResult(
            score=weighted_score,
            details={
                "semantic_similarity": similarity,
                "weight": self.weight
            }
        )
