#pragma once

#include "../Parameters/DeepPluckParameters.h"
#include "DeepPluckLfo.h"

#include <juce_audio_basics/juce_audio_basics.h>

#include <array>

namespace deeppluck
{
inline constexpr int voiceCount = 16;

class DeepPluckSynthesiser final : public juce::Synthesiser
{
public:
    void setCurrentPlaybackSampleRate (double newRate);
    void setParameterSource (const DeepPluckParameterSnapshot& parametersToRead) noexcept;
    void setHostTempoBpm (double newTempoBpm) noexcept;
    void renderNextBlock (juce::AudioBuffer<float>& outputAudio,
                          const juce::MidiBuffer& midiData,
                          int startSample, int numSamples);

    void noteOn (int midiChannel, int midiNoteNumber, float velocity) override;
    void noteOff (int midiChannel, int midiNoteNumber, float velocity,
                  bool allowTailOff) override;
    void allNotesOff (int midiChannel, bool allowTailOff) override;

    [[nodiscard]] double getLfoPhase() const noexcept { return lfo.getPhase(); }
    [[nodiscard]] float getLfoCurrentOutput() const noexcept
    {
        return lfo.getCurrentOutput();
    }
    [[nodiscard]] double getLfoCurrentRateHz() const noexcept
    {
        return lfo.getCurrentRateHz();
    }
    [[nodiscard]] double getLfoTargetRateHz() const noexcept
    {
        return lfo.getTargetRateHz();
    }
    void restartLfoForTests() noexcept { lfo.restart(); }

protected:
    void handleMidiEvent (const juce::MidiMessage& message) override;
    juce::SynthesiserVoice* findFreeVoice (juce::SynthesiserSound* soundToPlay,
                                           int midiChannel, int midiNoteNumber,
                                           bool stealIfNoneAvailable) const override;
    juce::SynthesiserVoice* findVoiceToSteal (juce::SynthesiserSound* soundToPlay,
                                              int midiChannel,
                                              int midiNoteNumber) const override;

private:
    static constexpr int modulationChunkSize = 64;

    void updateLfoParameters() noexcept;
    void renderVoiceSegment (juce::AudioBuffer<float>& outputAudio,
                             int startSample, int numSamples);
    [[nodiscard]] bool hasActiveVoice() const noexcept;

    const DeepPluckParameterSnapshot* parameterSource = nullptr;
    DeepPluckLfo lfo;
    std::array<float, modulationChunkSize> modulationBuffer {};
    double hostTempoBpm = 120.0;
};

void configureDeepPluckSynthesiser (DeepPluckSynthesiser& synthesiser,
                                    const DeepPluckParameterSnapshot& parameters);
}
