"""Embedding provider protocol and base types.

This module defines the EmbeddingProvider protocol that abstracts text embedding
operations. Implementations can be local (GPU inference) or remote (HTTP client).

Design Principles:
    - Protocol-first: Implementations are swappable without code changes
    - Batch-oriented: Efficient bulk embedding for high throughput
    - Dimension-aware: Embeddings can be truncated to target dimension
    - Instruction-conditioned: Support for task-specific embedding instructions

Example:
    class LocalQwen3Embedding(EmbeddingProvider):
        def embed(self, texts: list[str]) -> np.ndarray:
            # Local GPU inference
            ...

    class RemoteEmbeddingClient(EmbeddingProvider):
        def embed(self, texts: list[str]) -> np.ndarray:
            # HTTP call to embedding service
            ...
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING, Protocol, runtime_checkable

if TYPE_CHECKING:
    import numpy as np


@dataclass(frozen=True, slots=True)
class EmbeddingResult:
    """Result of an embedding operation.

    Attributes:
        embeddings: Array of shape (num_texts, embedding_dim).
        model_name: Name of the model used for embedding.
        dimension: Embedding dimension (may be truncated from model's native dim).
        latency_ms: Time taken for the embedding operation.
    """

    embeddings: "np.ndarray"
    model_name: str
    dimension: int
    latency_ms: float


@runtime_checkable
class EmbeddingProvider(Protocol):
    """Protocol for text embedding providers.

    This protocol defines the interface for embedding text into dense vectors.
    Implementations can be local (loading a model on GPU) or remote (calling
    an embedding service over HTTP).

    The protocol is designed for:
        - Batch efficiency: Process multiple texts in one call
        - Dimension control: Support Matryoshka-style dimension truncation
        - Instruction conditioning: Support task-specific prefixes

    Attributes:
        model_name: Identifier for the embedding model.
        dimension: Output embedding dimension.
        device: Compute device (e.g., "cuda", "cpu", "remote").
    """

    @property
    def model_name(self) -> str:
        """Return the model identifier."""
        ...

    @property
    def dimension(self) -> int:
        """Return the embedding dimension."""
        ...

    @property
    def device(self) -> str:
        """Return the compute device."""
        ...

    def embed(self, texts: list[str]) -> "np.ndarray":
        """Embed a batch of texts into dense vectors.

        Args:
            texts: List of text strings to embed.

        Returns:
            Array of shape (len(texts), self.dimension) containing embeddings.

        Raises:
            RuntimeError: If the embedding operation fails.
        """
        ...

    def embed_with_result(self, texts: list[str]) -> EmbeddingResult:
        """Embed texts and return detailed result with metadata.

        Args:
            texts: List of text strings to embed.

        Returns:
            EmbeddingResult with embeddings, model info, and timing.
        """
        ...
