"""Embedding providers for GreyMatter semantic search.

Provider-agnostic abstraction with Ollama implementation.
Same Protocol pattern as Database and LLMGateway.
"""
import struct
from typing import Protocol, runtime_checkable

import httpx


def serialize_f32(vector: list[float]) -> bytes:
    """Serialize a list of floats to bytes for sqlite-vec."""
    return struct.pack(f"{len(vector)}f", *vector)


def deserialize_f32(blob: bytes) -> list[float]:
    """Deserialize bytes back to a list of floats."""
    return list(struct.unpack(f"{len(blob) // 4}f", blob))


@runtime_checkable
class EmbeddingProvider(Protocol):
    """Abstract embedding interface.

    Implementations must support single and batch embedding.
    """

    def embed(self, text: str) -> list[float]: ...
    def embed_batch(self, texts: list[str]) -> list[list[float]]: ...
    @property
    def dimensions(self) -> int: ...


class OllamaEmbedding:
    """Local embeddings via Ollama nomic-embed-text.

    Free, private, offline. ~100ms per embedding, 768 dimensions.
    """

    def __init__(
        self,
        model: str = "nomic-embed-text",
        base_url: str = "http://localhost:11434",
    ):
        self.model = model
        self.base_url = base_url

    @property
    def dimensions(self) -> int:
        return 768

    def embed(self, text: str) -> list[float]:
        """Embed a single text string."""
        resp = httpx.post(
            f"{self.base_url}/api/embed",
            json={"model": self.model, "input": text},
            timeout=30.0,
        )
        resp.raise_for_status()
        return resp.json()["embeddings"][0]

    def embed_batch(self, texts: list[str]) -> list[list[float]]:
        """Embed multiple texts in one call."""
        resp = httpx.post(
            f"{self.base_url}/api/embed",
            json={"model": self.model, "input": texts},
            timeout=120.0,
        )
        resp.raise_for_status()
        return resp.json()["embeddings"]
