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

#pragma once
#include <JuceHeader.h>
#include "MyMultibandCompressor.h"
#include "MySmoothedValue.h"

namespace ParamIDs
{
    inline auto constexpr fxLoopMix           = "fxLoopMix";
    inline auto constexpr fxLoopSendDB        = "fxLoopSendDB";
    inline auto constexpr fxLoopReturnDB      = "fxLoopReturnDB";
    inline auto constexpr fxLoopPosition      = "fxLoopPosition";

    inline auto constexpr width               = "width";
    inline auto constexpr pan                 = "pan";
    inline auto constexpr stereoOrder         = "stereoOrder";

    inline auto constexpr hpFreq              = "hpFreq";
    inline auto constexpr hpQ                 = "hpQ";
    inline auto constexpr lpFreq              = "lpFreq";
    inline auto constexpr lpQ                 = "lpQ";

    inline auto constexpr eqFreq              = "eqFreq";
    inline auto constexpr eqQ                 = "eqQ";
    inline auto constexpr eqPeakDB            = "eqPeakDB";
    inline auto constexpr eqTiltDB            = "eqTiltDB";
    inline auto constexpr eqPosition          = "eqPosition";

    inline auto constexpr dynBandLink         = "dynBandLink";
    inline auto constexpr dynStereoLink       = "dynStereoLink";
    inline auto constexpr dynDriveDB          = "dynDriveDB";
    inline auto constexpr dynThreshold        = "dynThreshold";
    inline auto constexpr dynThreshold2       = "dynThreshold2";
    inline auto constexpr dynLowerThreshold   = "dynLowerThreshold";
    inline auto constexpr dynTransientTime    = "dynTransientTime";
    inline auto constexpr dynAttackTime       = "dynAttackTime";
    inline auto constexpr dynReleaseTime      = "dynReleaseTime";
    inline auto constexpr dynSlew             = "dynSlew";
    inline auto constexpr dynCompensation     = "dynCompensation";
    inline auto constexpr dynAdaptability     = "dynAdaptability";
    inline auto constexpr dynCrossover1       = "dynCrossover1";
    inline auto constexpr dynCrossover2       = "dynCrossover2";
    inline auto constexpr dynLowCompPower     = "dynLowCompPower";
    inline auto constexpr dynHighCompPower    = "dynHighCompPower";
    inline auto constexpr dynLowCompOffset    = "dynLowCompOffset";
    inline auto constexpr dynHighCompOffset   = "dynHighCompOffset";
    inline auto constexpr dynResponsePower    = "dynResponsePower";
    inline auto constexpr dynNegMix           = "dynNegMix";
    inline auto constexpr dynRatio            = "dynRatio";
    inline auto constexpr dynVariMuMult       = "dynVariMuMult";
    inline auto constexpr dynKneeDB           = "dynKneeDB";
    inline auto constexpr dynSmoothTime       = "dynSmoothTime";
    inline auto constexpr dynSagTime          = "dynSagTime";
    inline auto constexpr dynMakeupDB         = "dynMakeupDB";
    inline auto constexpr dynMix              = "dynMix";
//    inline auto constexpr dynTestValue1       = "dynTestValue1";
//    inline auto constexpr dynTestValue2       = "dynTestValue2";
//    inline auto constexpr dynTestValue3       = "dynTestValue3";

    inline auto constexpr filterAndDynByp     = "filterAndDynByp";
    inline auto constexpr volumeLinear        = "volumeLinear";
    inline auto constexpr outputDB            = "outputDB";
    inline auto constexpr polarity            = "polarity";

    inline auto constexpr liveMode            = "liveMode";
}

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

class ChannelAudioProcessor : public juce::AudioProcessor
{
public:
    //==============================================================================
    ChannelAudioProcessor();
    ~ChannelAudioProcessor() override = default;

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

    enum class FilterType
    {
        HighPass,
        LowPass,
        BandPass,
        Peak,
        LowShelf,
        HighShelf,
        AllPass
    };

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

    void prepareToPlay(double sampleRate, int samplesPerBlock) override;
    void reset() override;
    void releaseResources() override {}

    bool isBusesLayoutSupported(const BusesLayout&) const override;

    void processBlock(juce::AudioBuffer<float>&, juce::MidiBuffer&) override;
    void processBlock(juce::AudioBuffer<double>&, juce::MidiBuffer&) override;

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

    juce::AudioProcessorEditor* createEditor() override;
    bool hasEditor() const override;

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

    juce::String const getName() const override {return JucePlugin_Name;}
    bool acceptsMidi() const override {return false;}
    bool producesMidi() const override {return false;}
    bool isMidiEffect() const override {return false;}
    double getTailLengthSeconds() const override {return 0.0;}
    bool supportsDoublePrecisionProcessing() const override {return true;}

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

    // some hosts don't cope very well if you tell them there are 0 programs, so this should be at least 1
    int getNumPrograms() override {return 1;}
    int getCurrentProgram() override {return 0;}
    void setCurrentProgram (int /*index*/) override {}
    juce::String const getProgramName (int /*index*/) override {return {};}
    void changeProgramName (int /*index*/, juce::String const& /*newName*/) override {}

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

    /** store parameters in memory block */
    void getStateInformation (juce::MemoryBlock& destData) override;

    /** restore parameters from memory block */
    void setStateInformation (void const* data, int sizeInBytes) override;

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

    // host use of the bypass parameter is unpredictable,
    // so just use juce's default bypass parameter
//    juce::AudioProcessorParameter* getBypassParameter() const override {return nullptr;}

    juce::AudioProcessorValueTreeState& getAPVTS() {return parameters;}

    auto getParameterValueByID(juce::String const& parameterID) const
        {return parameters.getRawParameterValue(parameterID)->load();}

    auto getParameterAsText(juce::String const& parameterID) const
        {return parameters.getParameter(parameterID)->getCurrentValueAsText();}

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

    /** Normalizes pan to the range [0, pi/2] for pan calculations. Takes a value from 0 to 1. */
    float normalizePan(float v) const
    {
        return v * piOverTwo;
    }

    /** Normalizes the second dynamics stage threshold to a value that falls on the curve created by the first dynamics stage. Takes a value from 0 to 1. */
    float normalizeDynThreshold2(float v) const
    {
        float threshold = dynThresholdPtr->load();
        float ratio = dynRatioPtr->load();
        return threshold * (1.0f-v/ratio);
    }

    /** Normalizes the lower dynamics threshold to a value between the noise floor and the first dynamics stage threshold. Takes a value from 0 to 1. */
    float normalizeDynLowerThreshold(float v) const
    {
        float threshold = dynThresholdPtr->load();
        float lowestThreshold = MyCompressor<float>::noiseFloorDB;
        return lowestThreshold + v*(threshold-lowestThreshold);
    }

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

private:
    // math constants
    static float constexpr pi = juce::MathConstants<float>::pi;
    static float constexpr piOverFour = pi / 4.0f;
    static float constexpr piOverTwo = pi / 2.0f;
    static float constexpr twoOverPi = 2.0f / pi;

    // ~4.5 db pan rule, unity at center (when pan = pi/4)
    static inline float const unityAtCenterMultiplier = std::sqrt(1.0f / (0.5f*std::cos(piOverFour)));

    // smoothing duration in seconds
    static float constexpr rampDuration = 0.1f;

    //==============================================================================
    // parameters

    juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout();
    juce::AudioProcessorValueTreeState parameters{*this, nullptr, "AsterismParameters", createParameterLayout()};

    std::atomic<float>* const fxLoopMixPtr {parameters.getRawParameterValue(ParamIDs::fxLoopMix)};
    std::atomic<float>* const fxLoopSendDBPtr {parameters.getRawParameterValue(ParamIDs::fxLoopSendDB)};
    std::atomic<float>* const fxLoopReturnDBPtr {parameters.getRawParameterValue(ParamIDs::fxLoopReturnDB)};
    std::atomic<float>* const fxLoopPositionPtr {parameters.getRawParameterValue(ParamIDs::fxLoopPosition)};

    std::atomic<float>* const widthPtr {parameters.getRawParameterValue(ParamIDs::width)};
    std::atomic<float>* const panPtr {parameters.getRawParameterValue(ParamIDs::pan)};
    std::atomic<float>* const stereoOrderPtr {parameters.getRawParameterValue(ParamIDs::stereoOrder)};

    std::atomic<float>* const hpFreqPtr {parameters.getRawParameterValue(ParamIDs::hpFreq)};
    std::atomic<float>* const hpQPtr {parameters.getRawParameterValue(ParamIDs::hpQ)};
    std::atomic<float>* const lpFreqPtr {parameters.getRawParameterValue(ParamIDs::lpFreq)};
    std::atomic<float>* const lpQPtr {parameters.getRawParameterValue(ParamIDs::lpQ)};

    std::atomic<float>* const eqFreqPtr {parameters.getRawParameterValue(ParamIDs::eqFreq)};
    std::atomic<float>* const eqQPtr {parameters.getRawParameterValue(ParamIDs::eqQ)};
    std::atomic<float>* const eqPeakDBPtr {parameters.getRawParameterValue(ParamIDs::eqPeakDB)};
    std::atomic<float>* const eqTiltDBPtr {parameters.getRawParameterValue(ParamIDs::eqTiltDB)};
    std::atomic<float>* const eqPositionPtr {parameters.getRawParameterValue(ParamIDs::eqPosition)};

    std::atomic<float>* const dynBandLinkPtr {parameters.getRawParameterValue(ParamIDs::dynBandLink)};
    std::atomic<float>* const dynStereoLinkPtr {parameters.getRawParameterValue(ParamIDs::dynStereoLink)};
    std::atomic<float>* const dynDriveDBPtr {parameters.getRawParameterValue(ParamIDs::dynDriveDB)};
    std::atomic<float>* const dynThresholdPtr {parameters.getRawParameterValue(ParamIDs::dynThreshold)};
    std::atomic<float>* const dynThreshold2Ptr {parameters.getRawParameterValue(ParamIDs::dynThreshold2)};
    std::atomic<float>* const dynLowerThresholdPtr {parameters.getRawParameterValue(ParamIDs::dynLowerThreshold)};
    std::atomic<float>* const dynTransientTimePtr {parameters.getRawParameterValue(ParamIDs::dynTransientTime)};
    std::atomic<float>* const dynAttackTimePtr {parameters.getRawParameterValue(ParamIDs::dynAttackTime)};
    std::atomic<float>* const dynReleaseTimePtr {parameters.getRawParameterValue(ParamIDs::dynReleaseTime)};
    std::atomic<float>* const dynSlewPtr {parameters.getRawParameterValue(ParamIDs::dynSlew)};
    std::atomic<float>* const dynCompensationPtr {parameters.getRawParameterValue(ParamIDs::dynCompensation)};
    std::atomic<float>* const dynAdaptabilityPtr {parameters.getRawParameterValue(ParamIDs::dynAdaptability)};
    std::atomic<float>* const dynCrossover1Ptr {parameters.getRawParameterValue(ParamIDs::dynCrossover1)};
    std::atomic<float>* const dynCrossover2Ptr {parameters.getRawParameterValue(ParamIDs::dynCrossover2)};
    std::atomic<float>* const dynLowCompPowerPtr {parameters.getRawParameterValue(ParamIDs::dynLowCompPower)};
    std::atomic<float>* const dynHighCompPowerPtr {parameters.getRawParameterValue(ParamIDs::dynHighCompPower)};
    std::atomic<float>* const dynLowCompOffsetPtr {parameters.getRawParameterValue(ParamIDs::dynLowCompOffset)};
    std::atomic<float>* const dynHighCompOffsetPtr {parameters.getRawParameterValue(ParamIDs::dynHighCompOffset)};
    std::atomic<float>* const dynResponsePowerPtr {parameters.getRawParameterValue(ParamIDs::dynResponsePower)};
    std::atomic<float>* const dynNegMixPtr {parameters.getRawParameterValue(ParamIDs::dynNegMix)};
    std::atomic<float>* const dynRatioPtr {parameters.getRawParameterValue(ParamIDs::dynRatio)};
    std::atomic<float>* const dynVariMuMultPtr {parameters.getRawParameterValue(ParamIDs::dynVariMuMult)};
    std::atomic<float>* const dynKneeDBPtr {parameters.getRawParameterValue(ParamIDs::dynKneeDB)};
    std::atomic<float>* const dynSmoothTimePtr {parameters.getRawParameterValue(ParamIDs::dynSmoothTime)};
    std::atomic<float>* const dynSagTimePtr {parameters.getRawParameterValue(ParamIDs::dynSagTime)};
    std::atomic<float>* const dynMakeupDBPtr {parameters.getRawParameterValue(ParamIDs::dynMakeupDB)};
    std::atomic<float>* const dynMixPtr {parameters.getRawParameterValue(ParamIDs::dynMix)};
//    std::atomic<float>* const dynTestValue1Ptr {parameters.getRawParameterValue(ParamIDs::dynTestValue1)};
//    std::atomic<float>* const dynTestValue2Ptr {parameters.getRawParameterValue(ParamIDs::dynTestValue2)};
//    std::atomic<float>* const dynTestValue3Ptr {parameters.getRawParameterValue(ParamIDs::dynTestValue3)};

    std::atomic<float>* const filterAndDynBypPtr {parameters.getRawParameterValue(ParamIDs::filterAndDynByp)};
    std::atomic<float>* const volumeLinearPtr {parameters.getRawParameterValue(ParamIDs::volumeLinear)};
    std::atomic<float>* const outputDBPtr {parameters.getRawParameterValue(ParamIDs::outputDB)};
    std::atomic<float>* const polarityPtr {parameters.getRawParameterValue(ParamIDs::polarity)};

    std::atomic<float>* const liveModePtr {parameters.getRawParameterValue(ParamIDs::liveMode)};

    //==============================================================================
    // smoothers

    using Multiplicative = juce::ValueSmoothingTypes::Multiplicative;

    MySmoothedValue<float> smoothedWidth, smoothedPan;

    MySmoothedValue<float, Multiplicative> smoothedHPFreq, smoothedHPQ;
    MySmoothedValue<float, Multiplicative> smoothedLPFreq, smoothedLPQ;

    MySmoothedValue<float, Multiplicative> smoothedEQFreq, smoothedEQQ;
    MySmoothedValue<float>                 smoothedEQPeakDB, smoothedEQTiltDB;
    MySmoothedValue<float>                 smoothedEQPosition;

    MySmoothedValue<float>                 smoothedDynBandLink, smoothedDynStereoLink;
    MySmoothedValue<float>                 smoothedDynDriveDB;
    MySmoothedValue<float>                 smoothedDynThreshold, smoothedDynThreshold2, smoothedDynLowerThreshold;
    MySmoothedValue<float, Multiplicative> smoothedDynTransientTime;
    MySmoothedValue<float, Multiplicative> smoothedDynAttackTime;
    MySmoothedValue<float, Multiplicative> smoothedDynReleaseTime;
    MySmoothedValue<float, Multiplicative> smoothedDynSlew;
    MySmoothedValue<float>                 smoothedDynCompensation;
    MySmoothedValue<float, Multiplicative> smoothedDynAdaptability;
    MySmoothedValue<float, Multiplicative> smoothedDynCrossover1, smoothedDynCrossover2;
    MySmoothedValue<float>                 smoothedDynLowCompOffset, smoothedDynHighCompOffset;
    MySmoothedValue<float>                 smoothedDynNegMix;
    MySmoothedValue<float, Multiplicative> smoothedDynRatio;
    MySmoothedValue<float, Multiplicative> smoothedDynVariMuMult;
    MySmoothedValue<float>                 smoothedDynKneeDB;
    MySmoothedValue<float, Multiplicative> smoothedDynSmoothTime, smoothedDynSagTime;
    MySmoothedValue<float>                 smoothedDynMakeupDB;
    MySmoothedValue<float>                 smoothedDynMix;

    MySmoothedValue<float>                 smoothedPolarity;

    //==============================================================================
    // buffers and processors

    // oversampler
    juce::dsp::Oversampling<double> oversampler{2, // 2 channels
        1, // 2^1 (2x) oversampling
        juce::dsp::Oversampling<double>::FilterType::filterHalfBandFIREquiripple, // filter type
        true, // max quality
        true}; // use integer latency
    bool oversampling = false;

    // effects loop
    juce::AudioBuffer<double> bufferDouble, returnBuffer;

    // setting maximum latency to 0 bypasses the mixer's delay-line processing
    juce::dsp::DryWetMixer<double> fxLoopMixer{0};
    juce::dsp::Gain<double> fxLoopSendGain, fxLoopReturnGain;

    // filters
    juce::dsp::StateVariableTPTFilter<double> hp, lp;

    using Filter = juce::dsp::ProcessorDuplicator<juce::dsp::IIR::Filter<double>, juce::dsp::IIR::Coefficients<double>>;
    Filter peak, tiltShelf, tiltPeak;
    Filter peak2, tiltShelf2, tiltPeak2;

    static constexpr float qNAN = std::numeric_limits<float>::quiet_NaN();
    struct FilterCache // used to bypass coefficient recalculation when nothing has changed
    {
        float sampleRate = 0.0f;
        FilterType filterType = FilterType::Peak;
        float freq = qNAN;
        float q = qNAN;
        float db = qNAN;
    };
    FilterCache peakCache, tiltShelfCache, tiltPeakCache;
    FilterCache peak2Cache, tiltShelf2Cache, tiltPeak2Cache;

    // compressor
    MyMultibandCompressor<double> comp;

    // filter and dynamics bypass and output
    juce::dsp::DryWetMixer<double> filterAndDynBypMixer{static_cast<int>(oversampler.getLatencyInSamples())};
    juce::dsp::Gain<double> volumeGain, outputGain;

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

    void prepareProcessors(double sampleRate, int samplesPerBlock);
    void prepareOversampledProcessors(double sampleRate, int maxSamplesPerBlock);

    void prepareSmoothers(double sampleRate);
    void prepareOversampledSmoothers(double sampleRate);
    void initializeSmoothers();

    void processWidth(juce::dsp::AudioBlock<double> block);
    void processPan(juce::dsp::AudioBlock<double> block);
    void processStereo(juce::dsp::AudioBlock<double> block);
    void processPolarity(juce::dsp::AudioBlock<double> block);
    void processFXLoop(juce::dsp::AudioBlock<double> block,
                       juce::dsp::AudioBlock<double> sendBlock,
                       juce::dsp::AudioBlock<double> returnBlock);
    void processHPLPFilters(juce::dsp::AudioBlock<double> block);
    void processEQBand(juce::dsp::ProcessContextReplacing<double> context,
        Filter* filter, FilterCache* filterCache,
        ChannelAudioProcessor::FilterType filterType,
        float freq, float q=1.0f, float db=0.0f);
    void processComp(juce::dsp::AudioBlock<double> block);

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

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ChannelAudioProcessor);
};
