from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import Any, Optional, Sequence, TypedDict, Union

ImageInput = Union[str, Path]
EmbeddingVector = list[float]


class Message(TypedDict):
    role: str
    content: str


MessageSequence = Sequence[Message]


class RequestOptions(TypedDict, total=False):
    request_id: str
    timeout_s: float
    max_retries: int
    retry_backoff_s: float
    reasoning_effort: str
    temperature: float
    top_p: float
    max_tokens: int
    images: Sequence[ImageInput]
    messages: MessageSequence


@dataclass(frozen=True)
class TokenUsage:
    input_tokens: int | None = None
    output_tokens: int | None = None
    total_tokens: int | None = None


@dataclass(frozen=True)
class LLMResponse:
    text: str
    usage: TokenUsage | None = None
    provider: str | None = None
    model: str | None = None
    raw: Any | None = None


def normalize_messages(
    *,
    prompt: Optional[str],
    messages: Optional[MessageSequence],
) -> list[Message]:
    result: list[Message] = []
    if messages:
        for message in messages:
            role = message.get("role")
            content = message.get("content")
            if not isinstance(role, str) or not role:
                raise ValueError("message role must be a non-empty string")
            if content is None:
                content = ""
            if not isinstance(content, str):
                raise ValueError("message content must be a string")
            result.append({"role": role, "content": content})

    if prompt:
        result.append({"role": "user", "content": prompt})

    return result
