use super::BandConfig;

/// Compresor linked-stereo feed-forward con detección RMS.
///
/// "Linked stereo": el envelope se calcula con L²+R² (potencia total),
/// y ambos canales reciben la misma ganancia. Preserva la imagen estéreo.
///
/// "Feed-forward": el detector mide la entrada, no la salida.
pub struct Compressor {
    envelope: f32,     // acumulador de potencia (dominio lineal cuadrado)
    gain_db:  f32,     // ganancia actual (dB, negativo = reducción)
    atk_coef: f32,     // coef. filtro de 1er orden para ataque
    rel_coef: f32,     // coef. filtro de 1er orden para release
}

impl Compressor {
    pub fn new(cfg: &BandConfig, sample_rate: f32) -> Self {
        // gain_db arranca en makeup_db (no en 0) para evitar el pop inicial:
        // si empezara en 0 el audio saldría sin makeup hasta que el compresor converge.
        let mut c = Self { envelope: 0.0, gain_db: cfg.makeup_db, atk_coef: 0.0, rel_coef: 0.0 };
        c.update_params(cfg, sample_rate);
        c
    }

    pub fn update_params(&mut self, cfg: &BandConfig, sample_rate: f32) {
        self.atk_coef = Self::time_coef(cfg.attack_ms, sample_rate);
        self.rel_coef = Self::time_coef(cfg.release_ms, sample_rate);
    }

    fn time_coef(time_ms: f32, sample_rate: f32) -> f32 {
        (-1.0 / (time_ms * 0.001 * sample_rate)).exp()
    }

    /// Procesa una muestra estéreo (linked). Devuelve la reducción de
    /// ganancia en dB (valor negativo o cero).
    #[inline]
    pub fn process_sample(
        &mut self,
        l: &mut f32,
        r: &mut f32,
        cfg: &BandConfig,
    ) -> f32 {
        if !cfg.enabled { return 0.0; }

        // Detección de potencia (linked stereo)
        let power = *l * *l + *r * *r;

        // Filtro de sobre/bajada con tiempos diferenciados
        let coef = if power > self.envelope { self.atk_coef } else { self.rel_coef };
        self.envelope = coef * self.envelope + (1.0 - coef) * power;

        // Nivel RMS en dBFS
        let rms_db = 10.0 * (self.envelope + 1e-12).log10();

        // Curva de compresión (sin knee por ahora)
        let gr_db = if rms_db > cfg.threshold_db {
            (cfg.threshold_db - rms_db) * (1.0 - 1.0 / cfg.ratio)
        } else {
            0.0_f32
        };

        // Suavizado de la ganancia con los mismos coeficientes
        let gain_target = gr_db + cfg.makeup_db;
        let g_coef = if gain_target < self.gain_db { self.atk_coef } else { self.rel_coef };
        self.gain_db = g_coef * self.gain_db + (1.0 - g_coef) * gain_target;

        let gain_lin = 10f32.powf(self.gain_db / 20.0);
        *l *= gain_lin;
        *r *= gain_lin;

        gr_db  // retornamos solo la reducción (sin makeup) para el meter
    }

    pub fn reset(&mut self) {
        self.envelope = 0.0;
        self.gain_db  = 0.0;
    }
}
