"""Protocols for dependency injection — LLM and Embedding providers.

Consumers implement these protocols to plug any LLM/embedding backend
into the memory system. Uses typing.Protocol for structural subtyping
(duck typing) — no inheritance required.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Protocol, runtime_checkable


@dataclass
class TokenUsage:
    """Token usage from an LLM completion call."""

    input_tokens: int = 0
    output_tokens: int = 0
    total_tokens: int = 0

    def __add__(self, other: TokenUsage) -> TokenUsage:
        return TokenUsage(
            input_tokens=self.input_tokens + other.input_tokens,
            output_tokens=self.output_tokens + other.output_tokens,
            total_tokens=self.total_tokens + other.total_tokens,
        )


@dataclass
class LLMResult:
    """Result of an LLM completion call.

    Attributes:
        text: The assistant's response text.
        usage: Token usage for this call, or None if the provider
            does not report usage.
    """

    text: str
    usage: TokenUsage | None = None


@runtime_checkable
class LLMProvider(Protocol):
    """Protocol for LLM completion calls."""

    async def complete(
        self,
        messages: list[dict],
        temperature: float = 0,
        response_format: dict | None = None,
        max_tokens: int | None = None,
    ) -> LLMResult:
        """Send a chat completion request and return the result.

        Args:
            messages: List of message dicts with 'role' and 'content' keys.
            temperature: Sampling temperature (0 = deterministic).
            response_format: Optional format spec (e.g. {"type": "json_object"}).
            max_tokens: Optional max tokens for the response.

        Returns:
            LLMResult with the response text and optional token usage.
        """
        ...


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

    async def embed(self, texts: list[str]) -> list[list[float]]:
        """Generate embeddings for a batch of texts.

        Args:
            texts: List of text strings to embed.

        Returns:
            List of embedding vectors (one per input text).
        """
        ...

    async def embed_one(self, text: str) -> list[float] | None:
        """Generate an embedding for a single text.

        Args:
            text: Text string to embed.

        Returns:
            Embedding vector, or None if the text is empty/invalid.
        """
        ...
