"""GenericOpenAIProvider — unified OpenAI-compatible provider for all 8 compat vendors.

Covers: Groq, xAI/Grok, DeepSeek, OpenRouter, Gemini-compat, Mistral-compat,
        Ollama, LMStudio (D-16/D-17/D-18).

Key design decisions:
  - Subclasses OpenAIProvider to inherit singleton _client and _truncate (D-07/D-13).
  - Conditionally passes response_format={"type":"json_object"} based on
    supports_json_mode flag (D-01) — Gemini preset sets this to False (D-02).
  - Always parses the raw response with parse_classification / parse_suggestions
    imported from ai.utils (D-02 last-resort fallback contract — NEVER redefine
    locally; CLAUDE.md shared module map rule).
"""
from __future__ import annotations

from ai.openai_provider import OpenAIProvider
from ai.utils import parse_classification, parse_suggestions  # D-02 contract


class GenericOpenAIProvider(OpenAIProvider):
    """OpenAI-compatible provider that enforces JSON mode on every call.

    Named presets (Groq, xAI, DeepSeek, OpenRouter, Gemini-compat, Mistral-compat,
    Ollama, LMStudio) are factory shortcuts in ai/__init__.py that pass the known
    base_url default for each vendor.

    supports_json_mode=False routes the provider through the parse_classification()
    fallback path without sending response_format — used for Gemini (D-02).
    """

    supports_json_mode: bool = True  # class-level default; overridden per instance

    def __init__(
        self,
        api_key: str,
        model: str,
        base_url: str | None,
        context_chars: int = 8000,
        supports_json_mode: bool = True,
    ):
        super().__init__(
            api_key=api_key,
            model=model,
            base_url=base_url,
            context_chars=context_chars,
        )
        # Instance-level flag (may differ from class-level default)
        self.supports_json_mode = supports_json_mode

    async def classify(
        self,
        document_text: str,
        existing_topics: list[str],
        system_prompt: str,
    ):
        topics_str = ", ".join(existing_topics) if existing_topics else "(none yet)"
        user_msg = (
            f"Existing topics: [{topics_str}]\n\n"
            f"Document text:\n{self._truncate(document_text)}"
        )
        create_kwargs = dict(
            model=self._model,
            max_tokens=4096,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": user_msg},
            ],
        )
        if self.supports_json_mode:
            # D-01: enforce structured JSON output on all supporting providers
            create_kwargs["response_format"] = {"type": "json_object"}
        # else: Gemini preset — omit response_format, fall back to parse_classification()
        response = await self._client.chat.completions.create(**create_kwargs)
        raw = response.choices[0].message.content or ""
        # D-02: parse_classification is the last-resort fallback — always called
        return parse_classification(raw)

    async def suggest_topics(
        self,
        document_text: str,
        system_prompt: str,
    ) -> list[str]:
        user_msg = (
            "Suggest 3-5 topic names for this document. "
            "Return ONLY valid JSON: {\"suggested_topics\": [\"topic1\", \"topic2\"]}\n\n"
            f"Document text:\n{self._truncate(document_text)}"
        )
        create_kwargs = dict(
            model=self._model,
            max_tokens=1024,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": user_msg},
            ],
        )
        if self.supports_json_mode:
            create_kwargs["response_format"] = {"type": "json_object"}
        response = await self._client.chat.completions.create(**create_kwargs)
        raw = response.choices[0].message.content or ""
        # D-02: parse_suggestions is the last-resort fallback — always called
        return parse_suggestions(raw)

    # health_check is inherited from OpenAIProvider — no override needed
