namespace ytLive.Services.Audio;

/// <summary>
/// The always-on voice processing chain for the mic (TASK 9 audio milestone).
/// Pure, per-sample, unit-tested: bass boost → treble lift → noise gate →
/// compressor. Applied BEFORE the meter and the stream mix so what the creator
/// hears on the meter is what viewers hear — and so background hum is gated
/// out before it reaches the encoder.
/// </summary>
public sealed class VoiceFilterChain
{
    private readonly LowShelfFilter _bass;
    private readonly HighShelfFilter _treble;
    private readonly NoiseGate _gate;
    private readonly Compressor _compressor;

    /// <param name="sampleRate">The (post-resample) mic rate — the RBJ shelf
    /// coefficients are rate-dependent, so the chain is built once at 48 kHz.</param>
    public VoiceFilterChain(int sampleRate)
    {
        _bass = new LowShelfFilter(sampleRate, 120f, 4f);
        _treble = new HighShelfFilter(sampleRate, 8000f, 3f);
        _gate = new NoiseGate();
        _compressor = new Compressor();
    }

    public float Process(float sample)
    {
        var bass = _bass.Process(sample);
        var treble = _treble.Process(bass);
        var gated = _gate.Process(treble);
        return _compressor.Process(gated);
    }

    public void Reset()
    {
        _bass.Reset();
        _treble.Reset();
        _gate.Reset();
        _compressor.Reset();
    }
}

/// <summary>
/// RBJ audio EQ cookbook low-shelf biquad (bass boost). Transposed direct
/// form II — stable, cheap, standard.
/// </summary>
public sealed class LowShelfFilter
{
    private readonly float _b0, _b1, _b2, _a1, _a2;
    private float _z1, _z2;

    public LowShelfFilter(int sampleRate, float cutoffHz, float gainDb)
    {
        var a = MathF.Pow(10f, gainDb / 40f);
        var omega = 2f * MathF.PI * cutoffHz / sampleRate;
        var sin = MathF.Sin(omega);
        var cos = MathF.Cos(omega);
        var alpha = sin / 2f * MathF.Sqrt(2f);
        var twoSqrtA = 2f * MathF.Sqrt(a);

        var b0 = a * ((a + 1f) - (a - 1f) * cos + twoSqrtA * alpha);
        var b1 = 2f * a * ((a - 1f) - (a + 1f) * cos);
        var b2 = a * ((a + 1f) - (a - 1f) * cos - twoSqrtA * alpha);
        var a0 = (a + 1f) + (a - 1f) * cos + twoSqrtA * alpha;
        var a1 = -2f * ((a - 1f) + (a + 1f) * cos);
        var a2 = (a + 1f) + (a - 1f) * cos - twoSqrtA * alpha;

        _b0 = b0 / a0;
        _b1 = b1 / a0;
        _b2 = b2 / a0;
        _a1 = a1 / a0;
        _a2 = a2 / a0;
    }

    public float Process(float x)
    {
        var y = _b0 * x + _z1;
        _z1 = _b1 * x - _a1 * y + _z2;
        _z2 = _b2 * x - _a2 * y;
        return y;
    }

    public void Reset()
    {
        _z1 = 0;
        _z2 = 0;
    }
}

/// <summary>RBJ audio EQ cookbook high-shelf biquad (treble lift).</summary>
public sealed class HighShelfFilter
{
    private readonly float _b0, _b1, _b2, _a1, _a2;
    private float _z1, _z2;

    public HighShelfFilter(int sampleRate, float cutoffHz, float gainDb)
    {
        var a = MathF.Pow(10f, gainDb / 40f);
        var omega = 2f * MathF.PI * cutoffHz / sampleRate;
        var sin = MathF.Sin(omega);
        var cos = MathF.Cos(omega);
        var alpha = sin / 2f * MathF.Sqrt(2f);
        var twoSqrtA = 2f * MathF.Sqrt(a);

        var b0 = a * ((a + 1f) + (a - 1f) * cos + twoSqrtA * alpha);
        var b1 = -2f * a * ((a - 1f) + (a + 1f) * cos);
        var b2 = a * ((a + 1f) + (a - 1f) * cos - twoSqrtA * alpha);
        var a0 = (a + 1f) - (a - 1f) * cos + twoSqrtA * alpha;
        var a1 = 2f * ((a - 1f) - (a + 1f) * cos);
        var a2 = (a + 1f) - (a - 1f) * cos - twoSqrtA * alpha;

        _b0 = b0 / a0;
        _b1 = b1 / a0;
        _b2 = b2 / a0;
        _a1 = a1 / a0;
        _a2 = a2 / a0;
    }

    public float Process(float x)
    {
        var y = _b0 * x + _z1;
        _z1 = _b1 * x - _a1 * y + _z2;
        _z2 = _b2 * x - _a2 * y;
        return y;
    }

    public void Reset()
    {
        _z1 = 0;
        _z2 = 0;
    }
}

/// <summary>
/// A simple noise gate with open/release hysteresis (pure C#, no external DSP
/// dependency). The envelope follows the input peak fast and decays slowly;
/// the gate opens above <see cref="Threshold"/> and closes below half of it, so
/// quiet background hum stays silent and borderline signals don't chatter.
/// </summary>
public sealed class NoiseGate
{
    private const float HysteresisRatio = 0.5f;
    private const float Attack = 0.5f;
    private const float Release = 0.0005f;

    private float _envelope;
    private bool _open;

    public NoiseGate(float threshold = DefaultThreshold)
    {
        Threshold = threshold;
    }

    public const float DefaultThreshold = 0.005f;

    public float Threshold { get; }

    public bool IsOpen => _open;

    public float Process(float sample)
    {
        var abs = MathF.Abs(sample);
        _envelope = abs > _envelope
            ? _envelope + (abs - _envelope) * Attack
            : _envelope * (1f - Release);

        if (!_open && _envelope > Threshold)
            _open = true;
        else if (_open && _envelope < Threshold * HysteresisRatio)
            _open = false;

        return _open ? sample : 0f;
    }

    public void Reset()
    {
        _envelope = 0;
        _open = false;
    }
}

/// <summary>
/// A static soft-limit compressor: samples at or under the threshold pass
/// through; louder samples fold down at <c>Ratio:1</c> so hot mic peaks can't
/// slam the stream. State-free.
/// </summary>
public sealed class Compressor
{
    public const float Threshold = 0.5f;
    public const float Ratio = 4f;

    public float Process(float sample)
    {
        var abs = MathF.Abs(sample);
        if (abs <= Threshold)
            return sample;
        return MathF.CopySign(Threshold + (abs - Threshold) / Ratio, sample);
    }

    public void Reset()
    {
    }
}
