#pragma once
#include <cmath>
#include <algorithm>

class CompressorDSP {
public:
    CompressorDSP() = default;

    void prepare(double sampleRate, int samplesPerBlock) {
        this->sampleRate = sampleRate;
        envelope = 0.0;
        gainReduction = 0.0;
    }

    void reset() {
        envelope = 0.0;
        gainReduction = 0.0;
    }

    struct Params {
        float threshold = -20.0f;  // dB
        float ratio = 4.0f;       // 2:1 to 8:1
        float attack = 0.03f;     // seconds
        float release = 0.3f;     // seconds (auto mode uses program-dependent)
        float knee = 6.0f;        // dB soft knee
        float makeup = 0.0f;      // dB
        bool autoRelease = true;
        float mix = 1.0f;         // dry/wet
    };

    void setParams(const Params& p) {
        params = p;
        // Convert times to coefficients with analog-style smoothing
        attackCoeff = std::exp(-1.0 / (sampleRate * std::max(0.001f, params.attack)));
        
        // Auto release: program-dependent (faster for transients, slower for sustained)
        if (params.autoRelease) {
            releaseCoeff = std::exp(-1.0 / (sampleRate * 0.4));
        } else {
            releaseCoeff = std::exp(-1.0 / (sampleRate * std::max(0.01f, params.release)));
        }
    }

    // Process a single sample (mono)
    float processSample(float input) {
        float absInput = std::abs(input);
        
        // Input level in dB
        float inputDb = (absInput > 1e-10f) ? 20.0f * std::log10(absInput) : -100.0f;
        
        // Calculate desired gain reduction with soft knee
        float gainDb = 0.0f;
        float overThreshold = inputDb - params.threshold;
        
        if (overThreshold > -params.knee / 2.0f) {
            if (overThreshold < params.knee / 2.0f) {
                // Soft knee region - smooth transition
                float x = overThreshold + params.knee / 2.0f;
                gainDb = (1.0f / params.ratio - 1.0f) * (x * x) / (2.0f * params.knee);
            } else {
                // Above knee - full compression
                gainDb = overThreshold * (1.0f / params.ratio - 1.0f);
            }
        }
        
        // Smooth envelope follower (analog-style ballistics)
        float coeff = (gainDb < envelope) ? attackCoeff : releaseCoeff;
        
        // Auto release: faster release for small reductions, slower for big hits
        if (params.autoRelease && gainDb < envelope) {
            float reductionAmount = std::abs(envelope);
            float autoReleaseAdj = std::clamp(reductionAmount / 12.0f, 0.2f, 1.0f);
            coeff = std::exp(-1.0 / (sampleRate * 0.15 * autoReleaseAdj));
        }
        
        envelope = coeff * envelope + (1.0f - coeff) * gainDb;
        
        // Apply makeup gain and convert to linear
        float totalGainDb = envelope + params.makeup;
        float gain = std::pow(10.0f, totalGainDb / 20.0f);
        
        // Store for metering
        gainReduction = envelope;
        
        // Apply compression with analog saturation
        float compressed = input * gain;
        
        // Subtle analog saturation (soft clipping like VCA bus compressor)
        compressed = softClip(compressed);
        
        // Dry/wet mix
        float output = input * (1.0f - params.mix) + compressed * params.mix;
        
        return output;
    }

    float getGainReduction() const { return gainReduction; }

private:
    double sampleRate = 44100.0;
    Params params;
    
    float attackCoeff = 0.0f;
    float releaseCoeff = 0.0f;
    float envelope = 0.0f;
    float gainReduction = 0.0f;
    
    // Analog-style soft clipping (tanh-like)
    float softClip(float x) {
        // Gentle saturation that adds warmth
        float drive = 1.2f;  // Subtle drive
        x *= drive;
        
        // Tanh saturation for musical distortion
        float out = std::tanh(x);
        
        return out;
    }
};
