import json
from abc import ABC, abstractmethod
from collections.abc import AsyncIterator

_LANG_NAMES: dict[str, str] = {
    "de": "Deutsch",
    "en": "English",
    "fr": "Français",
    "es": "Español",
    "it": "Italiano",
    "nl": "Nederlands",
    "ru": "Русский",
    "zh": "中文",
    "cmn": "中文",
}


def lang_instruction(language: str | None) -> str | None:
    """Sprach-Anweisung für den ISO-Code, oder None.

    Bewusst nachdrücklich formuliert: Eine lange anderssprachige History (z. B. der
    Nutzer hat zwischendurch englisch gesprochen) soll die gewünschte Sprache NICHT
    überstimmen. Wird zusätzlich nah an die Generierung gesetzt (siehe with_lang_reminder).
    """
    if not language:
        return None
    name = _LANG_NAMES.get(language, language)
    return f"Respond ONLY in {name}, regardless of the language used earlier in this conversation."


def with_lang_reminder(text: str, language: str | None) -> str:
    """Hängt eine knappe Sprach-Erinnerung an die letzte Nutzer-Nachricht.

    Direkt vor der Generierung platziert wirkt sie stärker als der (weit zurückliegende)
    System-Prompt, wenn die bisherige Unterhaltung in einer anderen Sprache lief.
    """
    if not language:
        return text
    name = _LANG_NAMES.get(language, language)
    return f"{text}\n\n[Bitte ausschließlich auf {name} antworten.]"


def sse_delta(line: str) -> str | None:
    """Extrahiert das Token-Delta aus einer OpenAI-kompatiblen SSE-Zeile (oder None)."""
    if not line.startswith("data:"):
        return None
    data = line[len("data:"):].strip()
    if not data or data == "[DONE]":
        return None
    try:
        obj = json.loads(data)
        return obj["choices"][0]["delta"].get("content")
    except (ValueError, KeyError, IndexError, TypeError):
        return None


class LLMProvider(ABC):
    @abstractmethod
    async def complete(
        self,
        text: str,
        history: list[dict] | None = None,
        session_id: str | None = None,
        language: str | None = None,
    ) -> str: ...

    async def stream(
        self,
        text: str,
        history: list[dict] | None = None,
        session_id: str | None = None,
        language: str | None = None,
    ) -> AsyncIterator[str]:
        """Token-Stream. Default: kein echtes Streaming -> komplette Antwort als ein Chunk.

        Provider mit SSE-Unterstuetzung ueberschreiben diese Methode.
        """
        yield await self.complete(text, history=history, session_id=session_id, language=language)
