#pragma once

#include <juce_audio_basics/juce_audio_basics.h>

#include <cstdint>

namespace deeppluck
{
struct DeepPluckTransientNoiseParameters
{
    float transientEnabled = 1.0f;
    float transientLevelDb = -18.0f;
    float transientDecaySeconds = 0.012f;
    float transientTone = 0.55f;
    float noiseEnabled = 1.0f;
    float noiseLevelDb = -27.0f;
    float noiseDecaySeconds = 0.120f;
    float noiseTone = 0.35f;
};

class DeepPluckTransientNoise final
{
public:
    explicit DeepPluckTransientNoise (std::uint32_t voiceIndexToUse = 0) noexcept;

    void prepare (double newSampleRate) noexcept;
    void reset() noexcept;
    void setParameters (DeepPluckTransientNoiseParameters newParameters) noexcept;
    void noteOn (int midiNoteNumber) noexcept;
    [[nodiscard]] float processSample() noexcept;

    [[nodiscard]] bool isTransientActive() const noexcept;
    [[nodiscard]] bool isNoiseActive() const noexcept;
    [[nodiscard]] float getTransientEnvelopeLevel() const noexcept;
    [[nodiscard]] float getNoiseEnvelopeLevel() const noexcept;
    [[nodiscard]] float getTransientMix() const noexcept { return transientMix.getCurrentValue(); }
    [[nodiscard]] float getNoiseMix() const noexcept { return noiseMix.getCurrentValue(); }

private:
    class OneShotEnvelope final
    {
    public:
        void prepare (double newSampleRate, float microAttackSeconds) noexcept;
        void reset() noexcept;
        void setDecaySeconds (float newDecaySeconds, float minimum, float maximum,
                              float fallback) noexcept;
        void trigger() noexcept;
        [[nodiscard]] float getNextSample() noexcept;
        [[nodiscard]] bool isActive() const noexcept { return stage != Stage::idle; }
        [[nodiscard]] float getCurrentLevel() const noexcept { return currentLevel; }

    private:
        enum class Stage : std::uint8_t { idle, attack, decay };
        void beginDecay() noexcept;
        void updateDecayCoefficient() noexcept;

        double sampleRate = 44100.0;
        float decaySeconds = 0.012f;
        float currentLevel = 0.0f;
        float decayCoefficient = 0.0f;
        int attackSamples = 1;
        int attackSamplesRemaining = 0;
        int decaySamplesRemaining = 0;
        Stage stage = Stage::idle;
    };

    [[nodiscard]] static float sanitise (float value, float fallback,
                                         float minimum, float maximum) noexcept;
    [[nodiscard]] static float levelGain (float decibels, float calibration) noexcept;
    [[nodiscard]] static float toneFrequency (float tone, float minimumHz,
                                              float maximumHz) noexcept;
    [[nodiscard]] static std::uint32_t mixSeed (std::uint32_t value) noexcept;
    [[nodiscard]] float nextNoiseSample() noexcept;
    void synchroniseSmoothers() noexcept;

    DeepPluckTransientNoiseParameters parameters;
    OneShotEnvelope transientEnvelope;
    OneShotEnvelope noiseEnvelope;
    juce::SmoothedValue<float, juce::ValueSmoothingTypes::Linear> transientGain;
    juce::SmoothedValue<float, juce::ValueSmoothingTypes::Linear> transientFrequency;
    juce::SmoothedValue<float, juce::ValueSmoothingTypes::Linear> transientMix;
    juce::SmoothedValue<float, juce::ValueSmoothingTypes::Linear> noiseGain;
    juce::SmoothedValue<float, juce::ValueSmoothingTypes::Linear> noiseFilterCoefficient;
    juce::SmoothedValue<float, juce::ValueSmoothingTypes::Linear> noiseMix;
    double sampleRate = 44100.0;
    double transientPhase = 0.0;
    float noiseInputHistory = 0.0f;
    float noiseHighPassHistory = 0.0f;
    float noiseLowPassState1 = 0.0f;
    float noiseLowPassState2 = 0.0f;
    float dcBlockCoefficient = 0.99f;
    std::uint32_t baseSeed = 1;
    std::uint32_t randomState = 1;
    std::uint32_t noteCounter = 0;
};
}
