// SPDX-FileCopyrightText: 2026 0064ff
// SPDX-License-Identifier: MIT-0

#pragma once
#include <JuceHeader.h>
#include "MyCompressorChain.h"

template <typename SampleType>
class MyMultibandCompressor
{
public:
    //==============================================================================
    /** Constructor. */
    MyMultibandCompressor()
    {
        // set defaults for variables
        setBandLink(SampleType(0));
        setStereoLink(SampleType(0));
        setDriveDB(SampleType(0));
        setThresholds(SampleType(-100), SampleType(0), SampleType(0));
        setSlew(SampleType(0.5));
        setCompensation(SampleType(0.5));
        setCrossovers(SampleType(100), SampleType(1400));
        setTransientTime(SampleType(0.125));
        setAttackTime(SampleType(10));
        setReleaseTime(SampleType(100));
        setAdaptability(SampleType(2));
        setLowCompPower(SampleType(0.5));
        setHighCompPower(SampleType(4));
        setLowCompOffset(SampleType(1));
        setHighCompOffset(SampleType(0));
        setResponsePower(SampleType(0.5));
        setNegMix(SampleType(0.4));
        setRatio(SampleType(2));
        setVariMuMultiplier(SampleType(1));
        setKneeDB(SampleType(18));
        setSmoothTime(SampleType(3));
        setSagTime(SampleType(512));
        setMakeupDB(SampleType(0));
        setMix(SampleType(1));
    }

    //==============================================================================

    struct BandConfig
    {
        int bandCount;
        SampleType crossoverLower;
        SampleType crossoverHigher;
    };

    //==============================================================================

    /** Sets band link amount. Range: 0 - 1. */
    void setBandLink(SampleType newBandLink) {bandLink=newBandLink;}

    /** Sets stereo link amount. Range: 0 - 1. */
    void setStereoLink(SampleType newStereoLink) {stereoLink=newStereoLink;}

    /** Sets how much to drive the compressor in dB. */
    void setDriveDB(SampleType newDriveDB)
    {
        compLow.setDriveDB(newDriveDB);
        compMid.setDriveDB(newDriveDB);
        compHigh.setDriveDB(newDriveDB);
    }

    /** Sets the compressor's thresholds in dB. */
    void setThresholds(SampleType newLowerThreshold, SampleType newUpperThreshold1, SampleType newUpperThreshold2)
    {
        compLow.setThresholds(newLowerThreshold, newUpperThreshold1, newUpperThreshold2);
        compMid.setThresholds(newLowerThreshold, newUpperThreshold1, newUpperThreshold2);
        compHigh.setThresholds(newLowerThreshold, newUpperThreshold1, newUpperThreshold2);
    }

    /** Calculates a time compensated for cycle-matching when given a base time, frequency, and value for amount of compensation (0 - 1). */
    static SampleType cycleMatchTime(SampleType baseTime, SampleType crossoverFreq, SampleType compensationValue) noexcept
    {
        SampleType baseFrequency = SampleType(20000);

        SampleType divisor = ((baseFrequency - crossoverFreq) * (SampleType(1) - compensationValue) + crossoverFreq);

        jassert(divisor > SampleType(0));
        divisor = std::max(SampleType(20), divisor);

        return baseTime * baseFrequency / divisor;
    }

    /** Sets the envelope's transient time in milliseconds. */
    void setTransientTime(SampleType newTransientTime, bool forceUpdate=false)
    {
        if (baseTransientTime == newTransientTime && !forceUpdate)
            return;

        baseTransientTime = newTransientTime;

        compLow.setTransientTime(cycleMatchTime(baseTransientTime, crossoverLower, compensation));
        compMid.setTransientTime(cycleMatchTime(baseTransientTime, crossoverHigher, compensation));
        compHigh.setTransientTime(baseTransientTime);
    }

    /** Sets the envelope's attack time in milliseconds. */
    void setAttackTime(SampleType newAttackTime, bool forceUpdate=false)
    {
        if (baseAttackTime == newAttackTime && !forceUpdate)
            return;

        baseAttackTime = newAttackTime;

        compLow.setAttackTime(cycleMatchTime(baseAttackTime, crossoverLower, compensation));
        compMid.setAttackTime(cycleMatchTime(baseAttackTime, crossoverHigher, compensation));
        compHigh.setAttackTime(baseAttackTime);
    }

    /** Sets the envelope's release time in milliseconds. */
    void setReleaseTime(SampleType newReleaseTime, bool forceUpdate=false)
    {
        if (baseReleaseTime == newReleaseTime && !forceUpdate)
            return;

        baseReleaseTime = newReleaseTime;

        compLow.setReleaseTime(cycleMatchTime(baseReleaseTime, crossoverLower, compensation));
        compMid.setReleaseTime(cycleMatchTime(baseReleaseTime, crossoverHigher, compensation));
        compHigh.setReleaseTime(baseReleaseTime);
    }

    /** Sets slew as a percentage of the attack and release. Takes a value from 0 to 1. */
    void setSlew(SampleType newSlew)
    {
        compLow.setSlew(newSlew);
        compMid.setSlew(newSlew);
        compHigh.setSlew(newSlew);
    }

    /** Sets how much the attack and release should be automatically adjusted per band, with lower frequencies having longer times. Range: 0 - 1. */
    void setCompensation(SampleType newCompensation)
    {
        if (compensation == newCompensation)
            return;

        compensation = newCompensation;

        if (!std::isnan(baseTransientTime)) setTransientTime(baseTransientTime, true);
        if (!std::isnan(baseAttackTime)) setAttackTime(baseAttackTime, true);
        if (!std::isnan(baseReleaseTime)) setReleaseTime(baseReleaseTime, true);
    }

    /** Sets the program-dependent multiplier for attack and release. */
    void setAdaptability(SampleType newAdaptability)
    {
        compLow.setAdaptability(newAdaptability);
        compMid.setAdaptability(newAdaptability);
        compHigh.setAdaptability(newAdaptability);
    }

    /** Calculates the band configuration based on given crossovers. */
    static BandConfig calculateBandConfig(SampleType xover1, SampleType xover2)
    {
        // determine number of bands
        bool const xover1Disabled = (xover1 == SampleType(20) || xover1 == SampleType(20000));
        bool const xover2Disabled = (xover2 == SampleType(20) || xover2 == SampleType(20000));
        int const numOfBands = 3 - xover1Disabled - xover2Disabled;

        // initialize crossovers to defaults
        SampleType xoverLower = SampleType(20);
        SampleType xoverHigher = SampleType(20000);

        // determine crossover frequencies
        if (numOfBands == 3) // 3-band mode uses both crossovers
        {
            xoverLower = std::min(xover1, xover2);
            xoverHigher = std::max(xover1, xover2);
        }
        else if (numOfBands == 2) // 2-band mode only uses the higher crossover
            xoverHigher = xover1Disabled ? xover2 : xover1;
        // 1-band mode uses no crossovers,
        // but still needs the higher crossover to be 20000 for proper compensation

        return {numOfBands, xoverLower, xoverHigher};
    }

    /** Sets crossover frequencies. */
    void setCrossovers(SampleType newCrossover1, SampleType newCrossover2)
    {
        if (crossover1 == newCrossover1 && crossover2 == newCrossover2)
            return;

        crossover1 = newCrossover1;
        crossover2 = newCrossover2;

        auto const bandConfig = calculateBandConfig(crossover1, crossover2);
        int const newBandCount = bandConfig.bandCount;
        crossoverLower = bandConfig.crossoverLower;
        crossoverHigher = bandConfig.crossoverHigher;

        // reset high band before reactivating
        if (newBandCount >= 2 && bandCount < 2)
        {
            midBandHighCrossoverLowPass.reset();
            highBandHighCrossoverHighPass.reset();
            compHigh.reset();
        }

        // reset low band before reactivating
        if (newBandCount >= 3 && bandCount < 3)
        {
            lowBandHighCrossoverLowPass.reset();
            lowBandLowCrossoverLowPass.reset();
            midBandLowCrossoverHighPass.reset();
            highBandLowCrossoverHighPass.reset();
            compLow.reset();
        }

        bandCount = newBandCount;

        lowBandLowCrossoverLowPass.setCutoffFrequency(crossoverLower);
        lowBandHighCrossoverLowPass.setCutoffFrequency(crossoverHigher);

        midBandLowCrossoverHighPass.setCutoffFrequency(crossoverLower);
        midBandHighCrossoverLowPass.setCutoffFrequency(crossoverHigher);

        highBandLowCrossoverHighPass.setCutoffFrequency(crossoverLower);
        highBandHighCrossoverHighPass.setCutoffFrequency(crossoverHigher);

        if (!std::isnan(baseTransientTime)) setTransientTime(baseTransientTime, true);
        if (!std::isnan(baseAttackTime)) setAttackTime(baseAttackTime, true);
        if (!std::isnan(baseReleaseTime)) setReleaseTime(baseReleaseTime, true);
    }

    /** Sets the power to raise the signal to for detection when minimal compression is happening. 1 = Peak. 2 = RMS.
        Expects a power that's 2^k, where k is an integer. */
    void setLowCompPower(SampleType newLowCompPower)
    {
        compLow.setLowCompPower(newLowCompPower);
        compMid.setLowCompPower(newLowCompPower);
        compHigh.setLowCompPower(newLowCompPower);
    }

    /** Sets the power to raise the signal to for detection when maximal compression is happening. 1 = Peak. 2 = RMS.
        Expects a power that's 2^k, where k is an integer. */
    void setHighCompPower(SampleType newHighCompPower)
    {
        compLow.setHighCompPower(newHighCompPower);
        compMid.setHighCompPower(newHighCompPower);
        compHigh.setHighCompPower(newHighCompPower);
    }

    /** Sets the offset for the signal when minimal compression is occurring. 0 = symmetric (odd harmonics). 1 = asymmetric (even harmonics). */
    void setLowCompOffset(SampleType newLowCompOffset)
    {
        compLow.setLowCompOffset(newLowCompOffset);
        compMid.setLowCompOffset(newLowCompOffset);
        compHigh.setLowCompOffset(newLowCompOffset);
    }

    /** Sets the offset for the signal when maximal compression is occurring. 0 = symmetric (odd harmonics). 1 = asymmetric (even harmonics). */
    void setHighCompOffset(SampleType newHighCompOffset)
    {
        compLow.setHighCompOffset(newHighCompOffset);
        compMid.setHighCompOffset(newHighCompOffset);
        compHigh.setHighCompOffset(newHighCompOffset);
    }

    /** Sets the power the compression amount multiplier is raised to. Expects a power that's 2^k, where k is an integer. */
    void setResponsePower(SampleType newResponsePower)
    {
        compLow.setResponsePower(newResponsePower);
        compMid.setResponsePower(newResponsePower);
        compHigh.setResponsePower(newResponsePower);
    }

    /** Sets amount of negator to mix in. Range: 0 - 1. */
    void setNegMix(SampleType newNegMix)
    {
        compLow.setNegMix(newNegMix);
        compMid.setNegMix(newNegMix);
        compHigh.setNegMix(newNegMix);
    }

    /** Sets the compressor's ratio. */
    void setRatio(SampleType newRatio)
    {
        compLow.setRatio(newRatio);
        compMid.setRatio(newRatio);
        compHigh.setRatio(newRatio);
    }

    /** Sets the max compression-amount-scaled multiplier for ratio. */
    void setVariMuMultiplier(SampleType newVariMuMultiplier)
    {
        compLow.setVariMuMultiplier(newVariMuMultiplier);
        compMid.setVariMuMultiplier(newVariMuMultiplier);
        compHigh.setVariMuMultiplier(newVariMuMultiplier);
    }

    /** Sets the compressor's knee width in dB. */
    void setKneeDB(SampleType newKneeDB)
    {
        compLow.setKneeDB(newKneeDB);
        compMid.setKneeDB(newKneeDB);
        compHigh.setKneeDB(newKneeDB);
    }

    /** Sets how quickly the compressor smooths gain changes in milliseconds. */
    void setSmoothTime(SampleType newSmoothTime)
    {
        compLow.setSmoothTime(newSmoothTime);
        compMid.setSmoothTime(newSmoothTime);
        compHigh.setSmoothTime(newSmoothTime);
    }

    /** Sets the time period for measuring sag amount in milliseconds. */
    void setSagTime(SampleType newSagTime)
    {
        compLow.setSagTime(newSagTime);
        compMid.setSagTime(newSagTime);
        compHigh.setSagTime(newSagTime);
    }

    /** Sets how much to boost the post-compression signal in dB, for matching levels before the mix knob. */
    void setMakeupDB(SampleType newMakeupDB)
    {
        compLow.setMakeupDB(newMakeupDB);
        compMid.setMakeupDB(newMakeupDB);
        compHigh.setMakeupDB(newMakeupDB);
    }

    /** Sets how much of the compressor to mix in. Range: 0  - 1. */
    void setMix(SampleType newMix)
    {
        compLow.setMix(newMix);
        compMid.setMix(newMix);
        compHigh.setMix(newMix);
    }

    /** Sets values for testing. */
    void setTestValues(SampleType newTestValue1, SampleType newTestValue2, SampleType newTestValue3)
    {
        testValue1 = newTestValue1;
        testValue2 = newTestValue2;
        testValue3 = newTestValue3;
        compLow.setTestValues(newTestValue1, newTestValue2, newTestValue3);
        compMid.setTestValues(newTestValue1, newTestValue2, newTestValue3);
        compHigh.setTestValues(newTestValue1, newTestValue2, newTestValue3);
    }

    //==============================================================================
    /** Initialises the processor. */
    void prepare(juce::dsp::ProcessSpec const& spec)
    {
        // prepare compressors
        compLow.prepare(spec);
        compMid.prepare(spec);
        compHigh.prepare(spec);

        // set filter types
        lowBandLowCrossoverLowPass.setType(juce::dsp::LinkwitzRileyFilterType::lowpass);
        lowBandHighCrossoverLowPass.setType(juce::dsp::LinkwitzRileyFilterType::lowpass);
        midBandLowCrossoverHighPass.setType(juce::dsp::LinkwitzRileyFilterType::highpass);
        midBandHighCrossoverLowPass.setType(juce::dsp::LinkwitzRileyFilterType::lowpass);
        highBandLowCrossoverHighPass.setType(juce::dsp::LinkwitzRileyFilterType::highpass);
        highBandHighCrossoverHighPass.setType(juce::dsp::LinkwitzRileyFilterType::highpass);

        // prepare filters
        lowBandLowCrossoverLowPass.prepare(spec);
        lowBandHighCrossoverLowPass.prepare(spec);
        midBandLowCrossoverHighPass.prepare(spec);
        midBandHighCrossoverLowPass.prepare(spec);
        highBandLowCrossoverHighPass.prepare(spec);
        highBandHighCrossoverHighPass.prepare(spec);

        // reset compressors and filters
        reset();
    }

    //==============================================================================
    /** Resets the internal state variables of the processor. */
    void reset()
    {
        compLow.reset();
        compMid.reset();
        compHigh.reset();

        lowBandLowCrossoverLowPass.reset();
        lowBandHighCrossoverLowPass.reset();
        midBandLowCrossoverHighPass.reset();
        midBandHighCrossoverLowPass.reset();
        highBandLowCrossoverHighPass.reset();
        highBandHighCrossoverHighPass.reset();
    }

    //==============================================================================
    /** Processes the input and output samples supplied in the processing context. */
    void process(SampleType& left, SampleType& right) noexcept
    {
        // hardcode stereo because the send and return needs to be in stereo
        int const numChannels = 2;
        SampleType samples[2] = {left, right};
        SampleType low[2];
        SampleType mid[2];
        SampleType high[2];

        // apply crossovers
        for (int channel = 0; channel < numChannels; ++channel)
        {
            SampleType l=samples[channel], m=samples[channel], h=samples[channel];

            if (bandCount > 1)
            {
                m = midBandHighCrossoverLowPass.processSample(channel, m);
                h = highBandHighCrossoverHighPass.processSample(channel, h);

                if (bandCount > 2)
                {
                    l = lowBandHighCrossoverLowPass.processSample(channel, l);
                    l = lowBandLowCrossoverLowPass.processSample(channel, l);
                    m = midBandLowCrossoverHighPass.processSample(channel, m);
                    h = highBandLowCrossoverHighPass.processSample(channel, h);
                }
            }

            low[channel] = l;
            mid[channel] = m;
            high[channel] = h;
        }

        // stereo link signal is largest rectified signal between the channels
        SampleType maxLow = std::abs(low[0]);
        SampleType maxMid = std::abs(mid[0]);
        SampleType maxHigh = std::abs(high[0]);
        for (int channel = 1; channel < numChannels; ++channel)
        {
            maxMid = std::max(maxMid, std::abs(mid[channel]));
            if (bandCount > 1)
            {
                maxHigh = std::max(maxHigh, std::abs(high[channel]));
                if (bandCount > 2)
                    maxLow = std::max(maxLow, std::abs(low[channel]));
            }
        }

        // detection linking and processing
        for (int channel = 0; channel < numChannels; ++channel)
        {
            // apply stereo link
            SampleType lowDetection = SampleType(0);
            SampleType midDetection = SampleType(0);
            SampleType highDetection = SampleType(0);
            midDetection =  std::abs(mid[channel])  * (SampleType(1)-stereoLink) +
                            maxMid                  *                stereoLink;
            if (bandCount > 1)
            {
                highDetection = std::abs(high[channel]) * (SampleType(1)-stereoLink) +
                                maxHigh                 *                stereoLink;
                if (bandCount > 2)
                    lowDetection =  std::abs(low[channel])  * (SampleType(1)-stereoLink) +
                                    maxLow                  *                stereoLink;
            }

            // band link signal is largest individual band signal
            SampleType bandLinkDetection = midDetection;
            if (bandCount > 1)
            {
                bandLinkDetection = std::max(bandLinkDetection, highDetection);
                if (bandCount > 2)
                    bandLinkDetection = std::max(bandLinkDetection, lowDetection);
            }

            // apply band link
            midDetection = midDetection * (SampleType(1)-bandLink) + bandLinkDetection * bandLink;
            if (bandCount > 1)
            {
                highDetection = highDetection * (SampleType(1)-bandLink) + bandLinkDetection * bandLink;
                if (bandCount > 2)
                    lowDetection = lowDetection * (SampleType(1)-bandLink) + bandLinkDetection * bandLink;
            }

            // reapply sign to rectified signal
            // necessary for the offset feature of the ballistics filter
            midDetection = std::copysign(midDetection, mid[channel]);
            if (bandCount > 1)
            {
                highDetection = std::copysign(highDetection, high[channel]);
                if (bandCount > 2)
                    lowDetection = std::copysign(lowDetection, low[channel]);
            }

            // process dynamics
            samples[channel] = compMid.processSample(channel, mid[channel], midDetection);
            if (bandCount > 1)
            {
                samples[channel] += compHigh.processSample(channel, high[channel], highDetection);
                if (bandCount > 2)
                    samples[channel] += compLow.processSample(channel, low[channel], lowDetection);
            }
        } // end of channel loop

        left = samples[0];
        right = samples[1];
    }

    //==============================================================================
private:
    MyCompressorChain<SampleType> compLow, compMid, compHigh;

    // compensate with additional low- and high-pass filters
    // rather than all-pass filters because they seem to measure better,
    // though when crossovers are in close proximity, there's a slight dip
    juce::dsp::LinkwitzRileyFilter<SampleType> lowBandLowCrossoverLowPass, lowBandHighCrossoverLowPass,
                                               midBandLowCrossoverHighPass, midBandHighCrossoverLowPass,
                                               highBandLowCrossoverHighPass, highBandHighCrossoverHighPass;

    static constexpr SampleType qNAN = std::numeric_limits<SampleType>::quiet_NaN();

    int bandCount = -1;
    SampleType bandLink=qNAN, stereoLink=qNAN;
    SampleType baseTransientTime=qNAN, baseAttackTime=qNAN, baseReleaseTime=qNAN, compensation=qNAN;
    SampleType crossover1=qNAN, crossover2=qNAN;
    SampleType crossoverLower=qNAN, crossoverHigher=qNAN;

    SampleType testValue1=qNAN, testValue2=qNAN, testValue3=qNAN;
};
