"""High-level color normalization helpers."""

from __future__ import annotations

import io
from dataclasses import dataclass
from typing import Iterable, Literal, Sequence

try:  # pragma: no cover - optional
    from PIL import Image
except Exception:  # pragma: no cover
    Image = None  # type: ignore[assignment]

from .analysis import summarize_palette
from .models import Color
from .parser import parse_color
from .spaces import ColorSpaceConverter


ColorNormalization = Literal["rgb", "rgba", "skip"]


@dataclass(slots=True)
class NormalizedImage:
    data: bytes
    mime_type: str
    summary: dict[str, object] | None


class ColorNormalizer:
    """Combine the converter with palette inspection."""

    def __init__(self, converter: ColorSpaceConverter | None = None) -> None:
        self._converter = converter or ColorSpaceConverter()

    def normalize_bytes(
        self,
        payload: bytes,
        *,
        mime_type: str | None = None,
        mode: ColorNormalization = "rgb",
    ) -> NormalizedImage:
        if mode == "skip":
            summary = self._sample_palette(payload)
            return NormalizedImage(
                data=payload,
                mime_type=mime_type or "application/octet-stream",
                summary=summary,
            )

        target = "RGBA" if mode == "rgba" else "RGB"
        result = self._converter.convert_bytes(
            payload, mime_type=mime_type, target_mode=target
        )
        summary = self._sample_palette(result.data if result.converted else payload)
        return NormalizedImage(
            data=result.data if result.converted else payload,
            mime_type=result.mime_type,
            summary=summary,
        )

    def _sample_palette(self, payload: bytes) -> dict[str, object] | None:
        if Image is None:
            return None
        try:
            with Image.open(io.BytesIO(payload)) as image:
                rgba = image.convert("RGBA")
                raw = rgba.tobytes()
        except Exception:  # pragma: no cover - depends on Pillow
            return None
        if not raw:
            return None
        total_pixels = len(raw) // 4
        max_samples = min(total_pixels, 4096)
        colors = [
            Color(
                raw[i * 4] / 255.0,
                raw[i * 4 + 1] / 255.0,
                raw[i * 4 + 2] / 255.0,
                raw[i * 4 + 3] / 255.0,
            )
            for i in range(max_samples)
        ]
        return summarize_palette(colors)


class ColorEngine:
    """Central service for color normalization and analytics."""

    _default: ColorEngine | None = None

    def __init__(self, normalizer: ColorNormalizer | None = None) -> None:
        self._normalizer = normalizer or ColorNormalizer()

    @classmethod
    def default(cls) -> ColorEngine:
        if cls._default is None:
            cls._default = cls()
        return cls._default

    def normalize_image(
        self,
        payload: bytes,
        *,
        mime_type: str | None = None,
        mode: ColorNormalization = "rgb",
    ) -> NormalizedImage:
        return self._normalizer.normalize_bytes(payload, mime_type=mime_type, mode=mode)

    def summarize_colors(self, colors: Sequence[str | Color]) -> dict[str, object]:
        parsed = [
            value if isinstance(value, Color) else parse_color(value)
            for value in colors
        ]
        return summarize_palette([c for c in parsed if c])

    def summarize_tokens(self, tokens: Iterable[str]) -> dict[str, object]:
        return self.summarize_colors(list(tokens))


def palette_from_tokens(*values: str) -> dict[str, object]:
    engine = ColorEngine.default()
    return engine.summarize_colors(list(values))


__all__ = [
    "ColorNormalizer",
    "ColorNormalization",
    "ColorEngine",
    "NormalizedImage",
    "palette_from_tokens",
]
