// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2026 Andrea De Murtas

#pragma once

#include <juce_audio_utils/juce_audio_utils.h>
#include <juce_dsp/juce_dsp.h>
#include "Params.h"
#include "Meters.h"
#include "GjallaDSP.h"
#include "NoiseGate.h"
#include "Pedal.h"
#include "Preamp.h"
#include "ToneStack.h"
#include "PowerAmp.h"
#include "Cabinet.h"

class GjallaAudioProcessor : public juce::AudioProcessor,
                             private juce::AudioProcessorValueTreeState::Listener
{
public:
    GjallaAudioProcessor();
    ~GjallaAudioProcessor() override;

    void prepareToPlay(double sampleRate, int samplesPerBlock) override;
    void releaseResources() override {}
    bool isBusesLayoutSupported(const BusesLayout& layouts) const override;
    void processBlock(juce::AudioBuffer<float>&, juce::MidiBuffer&) override;

    juce::AudioProcessorEditor* createEditor() override;
    bool hasEditor() const override { return true; }

    const juce::String 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 gjalla::Cabinet::maxIrSeconds; }

    int getNumPrograms() override { return 1; }
    int getCurrentProgram() override { return 0; }
    void setCurrentProgram(int) override {}
    const juce::String getProgramName(int) override { return {}; }
    void changeProgramName(int, const juce::String&) override {}

    void getStateInformation(juce::MemoryBlock& destData) override;
    void setStateInformation(const void* data, int sizeInBytes) override;

    // Versioned state. The user IR path rides on the same tree as a property
    // rather than as a parameter: a file path is not something a host can
    // automate or interpolate.
    static constexpr int stateVersion = 1;
    juce::ValueTree capturePresetState();
    void applyStateTree(juce::ValueTree tree);

    // Message thread. Loading goes through juce::dsp::Convolution's background
    // loader, so neither of these blocks or allocates on the audio thread.
    void loadIrFile(const juce::File& file);
    void clearIr();
    juce::File getIrFile() const;

    void applyPreset(int index);

    juce::UndoManager undoManager;
    juce::AudioProcessorValueTreeState apvts;
    gjalla::Meters meters;
    std::atomic<bool> presetDirty { false };

private:
    void parameterChanged(const juce::String&, float) override { presetDirty.store(true); }
    static void migrateState(juce::ValueTree& state, int fromVersion);

    void updateLatency();
    void resetChain(bool includeCabinet) noexcept;
    juce::dsp::Oversampling<float>& activeOversampling() noexcept;
    double oversampledRate() const noexcept;

    // Cached raw parameter pointers. The brief forbids anything but atomics on
    // the audio thread, and getRawParameterValue() is a hashed lookup by string.
    struct RawParams
    {
        std::atomic<float>* inTrim = nullptr;
        std::atomic<float>* gateOn = nullptr;
        std::atomic<float>* gateThresh = nullptr;
        std::atomic<float>* pedalOn = nullptr;
        std::atomic<float>* pedalMode = nullptr;
        std::atomic<float>* pedalDrive = nullptr;
        std::atomic<float>* pedalTone = nullptr;
        std::atomic<float>* pedalLevel = nullptr;
        std::atomic<float>* ampGain = nullptr;
        std::atomic<float>* tight = nullptr;
        std::atomic<float>* bass = nullptr;
        std::atomic<float>* mid = nullptr;
        std::atomic<float>* treble = nullptr;
        std::atomic<float>* presence = nullptr;
        std::atomic<float>* master = nullptr;
        std::atomic<float>* cabMode = nullptr;
        std::atomic<float>* frost = nullptr;
        std::atomic<float>* outTrim = nullptr;
        std::atomic<float>* quality = nullptr;
    };

    RawParams raw;

    gjalla::NoiseGate gate;
    gjalla::Pedal pedal;
    gjalla::Preamp preamp;
    gjalla::ToneStack toneStack;
    gjalla::PowerAmp powerAmp;
    gjalla::Presence presenceStage;
    gjalla::Cabinet cabinet;
    gjalla::TiltEq frostTilt;
    gjalla::ClickGuard clickGuard;

    // Last thing before the output trim. The preamp deliberately runs an
    // offset into its shapers, and although its own blocker takes that back
    // out, the cabinet's high-pass cascade has a DC rejection of only about
    // -120 dB in single precision, so a fraction of a bit of offset survives
    // to the output, and with the cabinet off, nothing removes it at all.
    // A one-pole blocker cancels a constant exactly, by construction.
    gjalla::DcBlocker outputDcBlocker;

    // Both oversamplers exist for the whole session: allocating one on a
    // quality change would be an allocation on the audio thread, and deferring
    // the change to the message thread would make the switch unpredictable.
    std::unique_ptr<juce::dsp::Oversampling<float>> osEco, osFull;

    // Eco and Full do not have the same latency. Rather than tell the host the
    // latency changed mid-playback (which most of them handle by rebuilding
    // the graph, if at all), both are padded out to the larger of the two, so
    // the reported figure is constant for the life of the instance.
    juce::dsp::DelayLine<float, juce::dsp::DelayLineInterpolationTypes::None> latencyPad { 64 };
    int reportedLatency = 0;

    juce::SmoothedValue<float> inTrimSm, outTrimSm, frostLoSm, frostHiSm;

    // Block-rate smoothing for the parameters that drive filter coefficients
    // or waveshaper gains inside the oversampled stages. Their values only
    // take effect once per block anyway (the coefficients are recomputed per
    // block), so the smoother is stepped a whole block at a time; the gains
    // that multiply audio directly are smoothed per sample elsewhere.
    juce::SmoothedValue<float> tightSm, bassSm, midSm, trebleSm, presenceSm,
                               pedalDriveSm, pedalToneSm, ampGainSm;

    // Values the chain is currently running with. They only follow the
    // parameters at a click-guard switch point, never mid-block.
    int activeQuality = (int) choices::Quality::full;
    int activePedalMode = (int) choices::PedalMode::dist;
    int activeCabMode = (int) choices::CabMode::w412;

    double srHz = 44100.0;
    int preparedChannels = 2;
    int preparedBlockSize = 512;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(GjallaAudioProcessor)
};
