"""Embedding service — generates vector embeddings for semantic search.

Falls back gracefully when sentence-transformers is not installed.
"""

import logging
import math

logger = logging.getLogger("soagila.tcmg.embedding")

# Global model cache
_model = None
_model_available = None  # None = not checked, True/False = checked
_model_name = "all-MiniLM-L6-v2"
EMBEDDING_DIM = 384


def _get_model():
    """Load sentence-transformers model (cached singleton). Returns None if unavailable."""
    global _model, _model_available
    if _model_available is False:
        return None
    if _model is not None:
        return _model
    try:
        from sentence_transformers import SentenceTransformer
        logger.info(f"Loading embedding model: {_model_name}...")
        _model = SentenceTransformer(_model_name)
        _model_available = True
        logger.info(f"Embedding model loaded: {_model_name} (dim={EMBEDDING_DIM})")
        return _model
    except Exception as e:
        _model_available = False
        logger.warning(f"Embedding model unavailable: {e}. Using keyword fallback.")
        return None


def is_available() -> bool:
    """Check if the embedding model is available."""
    _get_model()
    return _model_available is True


def generate_embedding(text: str) -> list[float] | None:
    """Generate a 384-dim embedding vector. Returns None if model unavailable."""
    model = _get_model()
    if model is None:
        return None
    embedding = model.encode(text, normalize_embeddings=True)
    return embedding.tolist()


def generate_embeddings_batch(texts: list[str], batch_size: int = 64) -> list[list[float] | None]:
    """Generate embeddings for a batch of texts."""
    model = _get_model()
    if model is None:
        return [None] * len(texts)
    embeddings = model.encode(texts, batch_size=batch_size, normalize_embeddings=True)
    return embeddings.tolist()


def cosine_similarity(vec_a: list[float] | None, vec_b: list[float] | None) -> float:
    """Compute cosine similarity between two embedding vectors."""
    if not vec_a or not vec_b:
        return 0.0
    # Use pure Python to avoid numpy dependency issues
    dot = sum(a * b for a, b in zip(vec_a, vec_b))
    norm_a = math.sqrt(sum(a * a for a in vec_a))
    norm_b = math.sqrt(sum(b * b for b in vec_b))
    if norm_a == 0 or norm_b == 0:
        return 0.0
    return dot / (norm_a * norm_b)


def preload_model():
    """Preload the model on startup to avoid first-request latency."""
    model = _get_model()
    if model:
        logger.info("Embedding model preloaded and ready")
    else:
        logger.info("Embedding model not available, keyword matching will be used")
