"""Envelope Follower audio filter — smooth amplitude with asymmetric attack/release."""

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


def _time_constant_coeff(time_ms: float, dt: float) -> float:
    """Compute exponential smoothing coefficient from time constant and delta-time.

    Returns a value in [0, 1] where 0 = no change, 1 = instant follow.
    """
    if time_ms <= 0.0 or dt <= 0.0:
        return 1.0
    # Time constant: the coefficient such that we reach ~63.2% in time_ms
    tau = time_ms / 1000.0
    return min(1.0, 1.0 - np.exp(-dt / tau))


@AudioFilterRegistry.register
class EnvelopeFollowerFilter(AudioFilter):
    """Smooth RMS and peak with asymmetric attack/release time constants.

    Fast attack + slow release produces punchy transients that fade smoothly.
    Applied to rms, peak, and per-bin spectrum values.
    """

    filter_id = "envelope_follower"
    filter_name = "Envelope Follower"

    def __init__(self, options: Dict[str, Any]):
        super().__init__(options)
        self._attack_ms = self.options["attack_ms"]
        self._release_ms = self.options["release_ms"]
        self._env_rms = 0.0
        self._env_peak = 0.0
        self._env_spectrum = np.zeros(NUM_BANDS, dtype=np.float32)
        self._env_left_rms = 0.0
        self._env_right_rms = 0.0
        self._last_time: float | None = None

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

    def reset(self) -> None:
        self._env_rms = 0.0
        self._env_peak = 0.0
        self._env_spectrum[:] = 0.0
        self._env_left_rms = 0.0
        self._env_right_rms = 0.0
        self._last_time = None

    @classmethod
    def get_options_schema(cls) -> List[AudioFilterOptionDef]:
        return [
            AudioFilterOptionDef(
                key="attack_ms",
                label="Attack (ms)",
                option_type="float",
                default=10.0,
                min_value=1.0,
                max_value=500.0,
                step=1.0,
            ),
            AudioFilterOptionDef(
                key="release_ms",
                label="Release (ms)",
                option_type="float",
                default=200.0,
                min_value=10.0,
                max_value=2000.0,
                step=1.0,
            ),
        ]

    def _smooth_scalar(self, current: float, env: float, dt: float) -> float:
        """Apply asymmetric smoothing to a single scalar value."""
        if current > env:
            coeff = _time_constant_coeff(self._attack_ms, dt)
        else:
            coeff = _time_constant_coeff(self._release_ms, dt)
        return env + coeff * (current - env)

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

        # Smooth scalars
        self._env_rms = self._smooth_scalar(analysis.rms, self._env_rms, dt)
        self._env_peak = self._smooth_scalar(analysis.peak, self._env_peak, dt)
        self._env_left_rms = self._smooth_scalar(analysis.left_rms, self._env_left_rms, dt)
        self._env_right_rms = self._smooth_scalar(analysis.right_rms, self._env_right_rms, dt)

        # Smooth spectrum per-bin
        attack_coeff = _time_constant_coeff(self._attack_ms, dt)
        release_coeff = _time_constant_coeff(self._release_ms, dt)
        rising = analysis.spectrum > self._env_spectrum
        coeff = np.where(rising, attack_coeff, release_coeff).astype(np.float32)
        self._env_spectrum = self._env_spectrum + coeff * (analysis.spectrum - self._env_spectrum)

        return replace(
            analysis,
            rms=self._env_rms,
            peak=self._env_peak,
            spectrum=np.copy(self._env_spectrum),
            left_rms=self._env_left_rms,
            right_rms=self._env_right_rms,
        )
