"""Typed LED flash configuration for firmware LED activate command (0x0073)."""

from __future__ import annotations

from dataclasses import dataclass, field


def _check_u8(name: str, value: int) -> None:
    if not 0 <= value <= 0xFF:
        raise ValueError(f"{name} out of range: {value} (must be 0-255)")


def _check_nibble(name: str, value: int) -> None:
    if not 0 <= value <= 0x0F:
        raise ValueError(f"{name} out of range: {value} (must be 0-15)")


@dataclass(frozen=True, slots=True)
class LedFlashStep:
    """One LED flash step used by firmware LED mode 1."""

    color: int = 0
    flash_count: int = 0
    loop_delay_units: int = 0
    inter_delay_units: int = 0

    def __post_init__(self) -> None:
        _check_u8("color", self.color)
        _check_nibble("flash_count", self.flash_count)
        _check_nibble("loop_delay_units", self.loop_delay_units)
        _check_u8("inter_delay_units", self.inter_delay_units)


@dataclass(frozen=True, slots=True)
class LedFlashConfig:
    """Typed 12-byte payload for firmware LED activate command (0x0073)."""

    mode: int = 1
    brightness: int = 8
    step1: LedFlashStep = field(default_factory=LedFlashStep)
    step2: LedFlashStep = field(default_factory=LedFlashStep)
    step3: LedFlashStep = field(default_factory=LedFlashStep)
    group_repeats: int | None = 1
    reserved: int = 0

    def __post_init__(self) -> None:
        _check_nibble("mode", self.mode)
        if not 1 <= self.brightness <= 16:
            raise ValueError(f"brightness out of range: {self.brightness} (must be 1-16)")
        if self.group_repeats is not None and not 1 <= self.group_repeats <= 255:
            raise ValueError(f"group_repeats out of range: {self.group_repeats} (must be 1-255 or None for infinite)")
        _check_u8("reserved", self.reserved)

    @classmethod
    def single(
        cls,
        *,
        color: int,
        flash_count: int = 1,
        loop_delay_units: int = 0,
        inter_delay_units: int = 0,
        brightness: int = 8,
        group_repeats: int | None = 1,
    ) -> LedFlashConfig:
        """Build a simple one-step flash pattern."""
        return cls(
            mode=1,
            brightness=brightness,
            step1=LedFlashStep(
                color=color,
                flash_count=flash_count,
                loop_delay_units=loop_delay_units,
                inter_delay_units=inter_delay_units,
            ),
            group_repeats=group_repeats,
        )

    @staticmethod
    def _encode_step(step: LedFlashStep) -> tuple[int, int, int]:
        packed = ((step.loop_delay_units & 0x0F) << 4) | (step.flash_count & 0x0F)
        return step.color & 0xFF, packed, step.inter_delay_units & 0xFF

    @staticmethod
    def _decode_step(color: int, packed: int, inter_delay: int) -> LedFlashStep:
        return LedFlashStep(
            color=color,
            flash_count=packed & 0x0F,
            loop_delay_units=(packed >> 4) & 0x0F,
            inter_delay_units=inter_delay,
        )

    def to_bytes(self) -> bytes:
        """Serialize to 12-byte firmware payload."""
        mode_and_brightness = (((self.brightness - 1) & 0x0F) << 4) | (self.mode & 0x0F)

        c1, p1, i1 = self._encode_step(self.step1)
        c2, p2, i2 = self._encode_step(self.step2)
        c3, p3, i3 = self._encode_step(self.step3)

        group_repeats_raw = 0xFE if self.group_repeats is None else (self.group_repeats - 1) & 0xFF

        return bytes(
            [
                mode_and_brightness,
                c1,
                p1,
                i1,
                c2,
                p2,
                i2,
                c3,
                p3,
                i3,
                group_repeats_raw,
                self.reserved & 0xFF,
            ]
        )

    @classmethod
    def from_bytes(cls, data: bytes) -> LedFlashConfig:
        """Parse a 12-byte firmware payload."""
        if len(data) != 12:
            raise ValueError(f"LED flash config must be exactly 12 bytes, got {len(data)}")

        mode = data[0] & 0x0F
        brightness = ((data[0] >> 4) & 0x0F) + 1
        group_raw = data[10]
        group_repeats = None if group_raw == 0xFE else (group_raw + 1)

        return cls(
            mode=mode,
            brightness=brightness,
            step1=cls._decode_step(data[1], data[2], data[3]),
            step2=cls._decode_step(data[4], data[5], data[6]),
            step3=cls._decode_step(data[7], data[8], data[9]),
            group_repeats=group_repeats,
            reserved=data[11],
        )
