"""HSL shift postprocessing filter — hue rotation and lightness adjustment."""

from typing import Any, Dict, List, Optional

import numpy as np

from wled_controller.core.filters.base import FilterOptionDef, PostprocessingFilter
from wled_controller.core.filters.image_pool import ImagePool
from wled_controller.core.filters.registry import FilterRegistry


@FilterRegistry.register
class HslShiftFilter(PostprocessingFilter):
    """Shifts hue and lightness of all pixels via integer math.

    Hue is rotated by a fixed offset (0-360 degrees).
    Lightness is scaled by a multiplier (0.0 = black, 1.0 = unchanged, 2.0 = bright).
    """

    filter_id = "hsl_shift"
    filter_name = "HSL Shift"

    def __init__(self, options: Dict[str, Any]):
        super().__init__(options)
        self._f32_buf: Optional[np.ndarray] = None

    @classmethod
    def get_options_schema(cls) -> List[FilterOptionDef]:
        return [
            FilterOptionDef(
                key="hue",
                label="Hue Shift",
                option_type="int",
                default=0,
                min_value=0,
                max_value=359,
                step=1,
            ),
            FilterOptionDef(
                key="lightness",
                label="Lightness",
                option_type="float",
                default=1.0,
                min_value=0.0,
                max_value=2.0,
                step=0.05,
            ),
        ]

    def process_image(self, image: np.ndarray, image_pool: ImagePool) -> Optional[np.ndarray]:
        hue_shift = self.options["hue"]
        lightness = self.options["lightness"]
        if hue_shift == 0 and lightness == 1.0:
            return None

        h, w, c = image.shape
        n = h * w

        # Flatten to (N, 3) float32
        flat = image.reshape(n, c)
        if self._f32_buf is None or self._f32_buf.shape[0] != n:
            self._f32_buf = np.empty((n, 3), dtype=np.float32)
        buf = self._f32_buf
        np.copyto(buf, flat, casting="unsafe")
        buf *= (1.0 / 255.0)

        r = buf[:, 0]
        g = buf[:, 1]
        b = buf[:, 2]

        # RGB -> HSL (vectorized)
        cmax = np.maximum(np.maximum(r, g), b)
        cmin = np.minimum(np.minimum(r, g), b)
        delta = cmax - cmin
        light = (cmax + cmin) * 0.5

        # Hue calculation
        hue = np.zeros(n, dtype=np.float32)
        mask_nonzero = delta > 1e-6
        if np.any(mask_nonzero):
            d = delta[mask_nonzero]
            rm = r[mask_nonzero]
            gm = g[mask_nonzero]
            bm = b[mask_nonzero]
            cm = cmax[mask_nonzero]

            h_val = np.zeros_like(d)
            mr = cm == rm
            mg = (~mr) & (cm == gm)
            mb = (~mr) & (~mg)

            h_val[mr] = ((gm[mr] - bm[mr]) / d[mr]) % 6.0
            h_val[mg] = (bm[mg] - rm[mg]) / d[mg] + 2.0
            h_val[mb] = (rm[mb] - gm[mb]) / d[mb] + 4.0
            h_val *= 60.0
            h_val[h_val < 0] += 360.0
            hue[mask_nonzero] = h_val

        # Saturation
        sat = np.zeros(n, dtype=np.float32)
        mask_sat = mask_nonzero & (light > 1e-6) & (light < 1.0 - 1e-6)
        if np.any(mask_sat):
            sat[mask_sat] = delta[mask_sat] / (1.0 - np.abs(2.0 * light[mask_sat] - 1.0))

        # Apply shifts
        if hue_shift != 0:
            hue = (hue + hue_shift) % 360.0
        if lightness != 1.0:
            light = np.clip(light * lightness, 0.0, 1.0)

        # HSL -> RGB (vectorized)
        c_val = (1.0 - np.abs(2.0 * light - 1.0)) * sat
        x_val = c_val * (1.0 - np.abs((hue / 60.0) % 2.0 - 1.0))
        m_val = light - c_val * 0.5

        sector = (hue / 60.0).astype(np.int32) % 6

        ro = np.empty(n, dtype=np.float32)
        go = np.empty(n, dtype=np.float32)
        bo = np.empty(n, dtype=np.float32)

        for s, rv, gv, bv in (
            (0, c_val, x_val, 0.0),
            (1, x_val, c_val, 0.0),
            (2, 0.0, c_val, x_val),
            (3, 0.0, x_val, c_val),
            (4, x_val, 0.0, c_val),
            (5, c_val, 0.0, x_val),
        ):
            mask_s = sector == s
            if not np.any(mask_s):
                continue
            rv_arr = rv[mask_s] if not isinstance(rv, float) else rv
            gv_arr = gv[mask_s] if not isinstance(gv, float) else gv
            bv_arr = bv[mask_s] if not isinstance(bv, float) else bv
            ro[mask_s] = rv_arr + m_val[mask_s]
            go[mask_s] = gv_arr + m_val[mask_s]
            bo[mask_s] = bv_arr + m_val[mask_s]

        buf[:, 0] = ro
        buf[:, 1] = go
        buf[:, 2] = bo
        np.clip(buf, 0.0, 1.0, out=buf)
        buf *= 255.0
        np.copyto(flat, buf, casting="unsafe")
        return None
