"""Compressor audio filter — reduce dynamic range above threshold."""

from dataclasses import replace
from typing import Any, Dict, List

import numpy as np

from wled_controller.core.audio.analysis import AudioAnalysis
from wled_controller.core.audio.filters.base import AudioFilter, AudioFilterOptionDef
from wled_controller.core.audio.filters.registry import AudioFilterRegistry


@AudioFilterRegistry.register
class CompressorFilter(AudioFilter):
    """Reduce dynamic range above a threshold.

    For signals above ``threshold``, output is compressed:
    ``output = threshold + (input - threshold) / ratio``

    Makeup gain is applied after compression to restore overall level.
    Applied to rms, peak, and per-bin spectrum values.
    """

    filter_id = "compressor"
    filter_name = "Compressor"

    def __init__(self, options: Dict[str, Any]):
        super().__init__(options)
        self._threshold = self.options["threshold"]
        self._ratio = self.options["ratio"]
        self._makeup_gain = self.options["makeup_gain"]

    @property
    def is_stateful(self) -> bool:
        return True

    def reset(self) -> None:
        pass  # Stateful for envelope tracking; minimal state for static compression

    @classmethod
    def get_options_schema(cls) -> List[AudioFilterOptionDef]:
        return [
            AudioFilterOptionDef(
                key="threshold",
                label="Threshold",
                option_type="float",
                default=0.5,
                min_value=0.0,
                max_value=1.0,
                step=0.01,
            ),
            AudioFilterOptionDef(
                key="ratio",
                label="Ratio",
                option_type="float",
                default=4.0,
                min_value=1.0,
                max_value=20.0,
                step=0.1,
            ),
            AudioFilterOptionDef(
                key="makeup_gain",
                label="Makeup Gain",
                option_type="float",
                default=1.0,
                min_value=0.0,
                max_value=2.0,
                step=0.05,
            ),
        ]

    def _compress_scalar(self, value: float) -> float:
        """Compress a single scalar value."""
        threshold = self._threshold
        if value <= threshold:
            compressed = value
        else:
            compressed = threshold + (value - threshold) / self._ratio
        return min(1.0, compressed * self._makeup_gain)

    def _compress_spectrum(self, spectrum: np.ndarray) -> np.ndarray:
        """Compress spectrum array element-wise."""
        threshold = self._threshold
        ratio = self._ratio
        makeup = self._makeup_gain

        above_mask = spectrum > threshold
        result = np.copy(spectrum)
        result[above_mask] = threshold + (result[above_mask] - threshold) / ratio
        result *= makeup
        return np.clip(result, 0.0, 1.0).astype(np.float32)

    def process(self, analysis: AudioAnalysis) -> AudioAnalysis:
        return replace(
            analysis,
            rms=self._compress_scalar(analysis.rms),
            peak=self._compress_scalar(analysis.peak),
            spectrum=self._compress_spectrum(analysis.spectrum),
            left_rms=self._compress_scalar(analysis.left_rms),
            left_spectrum=self._compress_spectrum(analysis.left_spectrum),
            right_rms=self._compress_scalar(analysis.right_rms),
            right_spectrum=self._compress_spectrum(analysis.right_spectrum),
        )
