"""Peak Hold audio filter — retain peak values with configurable decay."""

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

import numpy as np

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


@AudioFilterRegistry.register
class PeakHoldFilter(AudioFilter):
    """Retain peak values and decay them over time.

    For each spectrum bin (if per_bin) or for rms/peak scalars, retains the
    maximum value seen and decays it at the configured rate.  Output is the
    maximum of the current value and the held (decaying) peak.
    """

    filter_id = "peak_hold"
    filter_name = "Peak Hold"

    def __init__(self, options: Dict[str, Any]):
        super().__init__(options)
        self._decay_rate = self.options["decay_rate"]  # dB/s
        self._per_bin = self.options["per_bin"]
        self._held_spectrum = np.zeros(NUM_BANDS, dtype=np.float32)
        self._held_rms = 0.0
        self._held_peak = 0.0
        self._last_time: float | None = None

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

    def reset(self) -> None:
        self._held_spectrum[:] = 0.0
        self._held_rms = 0.0
        self._held_peak = 0.0
        self._last_time = None

    @classmethod
    def get_options_schema(cls) -> List[AudioFilterOptionDef]:
        return [
            AudioFilterOptionDef(
                key="decay_rate",
                label="Decay Rate (dB/s)",
                option_type="float",
                default=10.0,
                min_value=0.1,
                max_value=50.0,
                step=0.1,
            ),
            AudioFilterOptionDef(
                key="per_bin",
                label="Per Spectrum Bin",
                option_type="bool",
                default=True,
                min_value=None,
                max_value=None,
                step=None,
            ),
        ]

    def process(self, analysis: AudioAnalysis) -> AudioAnalysis:
        now = time.perf_counter()
        if self._last_time is not None:
            dt = now - self._last_time
        else:
            dt = 0.0
        self._last_time = now

        # Compute linear decay factor from dB/s
        # decay_rate dB/s means the held value drops by decay_rate dB each second
        # In linear: factor = 10^(-decay_rate * dt / 20)
        decay_factor = 10.0 ** (-self._decay_rate * dt / 20.0) if dt > 0 else 1.0

        # Decay held values
        self._held_rms *= decay_factor
        self._held_peak *= decay_factor

        # Update held values with current maxima
        self._held_rms = max(self._held_rms, analysis.rms)
        self._held_peak = max(self._held_peak, analysis.peak)

        new_rms = self._held_rms
        new_peak = self._held_peak

        if self._per_bin:
            self._held_spectrum *= decay_factor
            np.maximum(self._held_spectrum, analysis.spectrum, out=self._held_spectrum)
            new_spectrum = np.copy(self._held_spectrum)
        else:
            new_spectrum = np.copy(analysis.spectrum)

        return replace(
            analysis,
            rms=new_rms,
            peak=new_peak,
            spectrum=new_spectrum,
        )
