"""Auto-gain audio filter — automatic level normalization.

Tracks a rolling peak of the audio signal and scales all levels so the
output uses the full 0-1 range regardless of input volume.  Zero-config
by default; optional parameters for tuning response speed and target level.
"""

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

import numpy as np

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


@AudioFilterRegistry.register
class AutoGainFilter(AudioFilter):
    """Normalize audio levels against a rolling observed peak.

    Tracks the maximum signal level over a configurable time window and
    scales all values so the loudest recent signal maps to ``target_level``.
    The rolling peak decays slowly so the gain adapts to changing volumes.
    """

    filter_id = "auto_gain"
    filter_name = "Auto Gain"

    def __init__(self, options: Dict[str, Any]):
        super().__init__(options)
        self._target = self.options["target_level"]
        # Decay factor per frame (~30 fps): controls how fast the peak forgets.
        # response_time seconds → peak halves in that time.
        response_time = self.options["response_time"]
        # decay = 0.5^(1 / (fps * response_time))  ≈ e^(-ln2 / (fps * t))
        self._decay = 0.5 ** (1.0 / max(1.0, 30.0 * response_time))
        self._rolling_peak = 0.0

    @classmethod
    def get_options_schema(cls) -> List[AudioFilterOptionDef]:
        return [
            AudioFilterOptionDef(
                key="target_level",
                label="Target Level",
                option_type="float",
                default=0.8,
                min_value=0.1,
                max_value=1.0,
                step=0.05,
            ),
            AudioFilterOptionDef(
                key="response_time",
                label="Response Time (s)",
                option_type="float",
                default=3.0,
                min_value=0.5,
                max_value=15.0,
                step=0.5,
            ),
        ]

    def process(self, analysis: AudioAnalysis) -> AudioAnalysis:
        # Track the rolling peak from the loudest signal component
        current_peak = max(analysis.rms, analysis.peak)
        self._rolling_peak = max(current_peak, self._rolling_peak * self._decay)

        if self._rolling_peak < 0.001:
            return analysis  # signal too quiet, don't amplify noise

        factor = self._target / self._rolling_peak
        if 0.95 <= factor <= 1.05:
            return analysis  # close enough, avoid jitter

        return replace(
            analysis,
            rms=min(1.0, analysis.rms * factor),
            peak=min(1.0, analysis.peak * factor),
            spectrum=np.clip(analysis.spectrum * factor, 0.0, 1.0).astype(np.float32),
            left_rms=min(1.0, analysis.left_rms * factor),
            left_spectrum=np.clip(analysis.left_spectrum * factor, 0.0, 1.0).astype(np.float32),
            right_rms=min(1.0, analysis.right_rms * factor),
            right_spectrum=np.clip(analysis.right_spectrum * factor, 0.0, 1.0).astype(np.float32),
        )
