"""Abstract base class for LLM providers."""

from abc import ABC, abstractmethod

from pydantic import BaseModel


class GarmentAnalysis(BaseModel):
    """Single garment analysis result from LLM."""

    category_main: str
    category_sub: str | None = None
    description: str | None = None
    tags: dict | None = None


class StyleCategoryScore(BaseModel):
    """Score and comment for a single style category."""

    score: int
    comment: str


class StyleAnalysis(BaseModel):
    """Overall outfit style analysis result from LLM."""

    color_palette: StyleCategoryScore
    silhouette: StyleCategoryScore
    detail: StyleCategoryScore
    overall_score: float
    overall_comment: str
    style_tags: list[str]


class StyleAnalysisTemp(BaseModel):
    """Context-aware outfit style analysis result from LLM."""

    time_context: str
    occasion_context: str
    weather_context: str
    color_harmony: str
    improvement_tips: str


class BaseLLMProvider(ABC):
    """Interface that all LLM providers must implement."""

    @abstractmethod
    async def analyze_garment(self, image: bytes) -> list[GarmentAnalysis]:
        """Analyze image and return detected garments with categories, tags, description.

        Args:
            image: Raw image bytes (JPEG/PNG).

        Returns:
            List of GarmentAnalysis for each detected garment.
        """
        ...

    @abstractmethod
    async def generate_ghost_mannequin(
        self,
        image: bytes,
        item: GarmentAnalysis,
        mode: str,
        image_model: str | None = None,
    ) -> bytes | None:
        """Generate ghost mannequin image for a garment.

        Args:
            image: Original image bytes.
            item: Analysis result for the target garment.
            mode: "edit" (remove person) or "generate" (new image from description).
            image_model: Optional image-generation model override. ``None`` →
                provider default. The Gemini provider ignores this.

        Returns:
            Generated image bytes, or None on failure.
        """
        ...

    @abstractmethod
    async def analyze_style(self, image: bytes) -> StyleAnalysis:
        """Analyze overall outfit style from image.

        Args:
            image: Raw image bytes (JPEG/PNG).

        Returns:
            StyleAnalysis with scores, comments, and style tags.
        """
        ...

    @abstractmethod
    async def analyze_style_temp(self, image: bytes) -> StyleAnalysisTemp:
        """Context-aware outfit style analysis (time/occasion/weather).

        Args:
            image: Raw image bytes (JPEG/PNG).

        Returns:
            StyleAnalysisTemp with 5 contextual sections.
        """
        ...

    @abstractmethod
    async def generate_vton(
        self,
        person_image: bytes,
        ghost_images: list[bytes],
        categories: list[str],
        image_model: str | None = None,
    ) -> tuple[bytes | None, str | None]:
        """Composite ghost garments onto a person via multi-image generation.

        Args:
            person_image: Raw person photo bytes (JPEG).
            ghost_images: List of isolated garment (ghost mannequin) PNG bytes,
                ordered 1..N — the prompt references them as "#N".
            categories: Parallel list of ``category_main`` strings
                (``"top" | "bottom" | "outer" | "dress"``) used by the prompt
                to let the LLM decide which garments are bottoms
                (to be skipped when only the upper body is visible).
            image_model: Optional image-generation model override. ``None`` →
                provider default. The Gemini provider ignores this.

        Returns:
            Tuple ``(image_bytes, llm_note)``.

            - ``image_bytes`` — composited PNG bytes, or ``None`` on failure.
            - ``llm_note`` — a short English sentence the model returned
              alongside the image (e.g. ``"Skipped #2 because ..."``), or
              ``None`` if the model returned no text.
        """
        ...
