#pragma once
#include <cmath>
#include <algorithm>

enum class ADSRStage { Idle = 0, Attack, Decay, Sustain, Release };

class ADSREnvelope {
public:
    void prepare(double sr) {
        sampleRate = sr;
        stage = ADSRStage::Idle;
        level = 0.0f;
    }

    void setAttack(float a)  { attackTime = std::max(a, 0.001f); }
    void setDecay(float d)   { decayTime = std::max(d, 0.001f); }
    void setSustain(float s) { sustainLevel = std::clamp(s, 0.0f, 1.0f); }
    void setRelease(float r) { releaseTime = std::max(r, 0.001f); }

    void noteOn() {
        stage = ADSRStage::Attack;
        level = 0.0f;
    }

    void noteOff() {
        if (stage != ADSRStage::Idle)
            stage = ADSRStage::Release;
    }

    bool isActive() const { return stage != ADSRStage::Idle; }

    float getNextSample() {
        switch (stage) {
            case ADSRStage::Idle:
                level = 0.0f;
                break;

            case ADSRStage::Attack: {
                float attackInc = 1.0f / (attackTime * static_cast<float>(sampleRate));
                level += attackInc;
                if (level >= 1.0f) {
                    level = 1.0f;
                    stage = ADSRStage::Decay;
                }
                break;
            }

            case ADSRStage::Decay: {
                float decayInc = (1.0f - sustainLevel) / (decayTime * static_cast<float>(sampleRate));
                level -= decayInc;
                if (level <= sustainLevel) {
                    level = sustainLevel;
                    stage = ADSRStage::Sustain;
                }
                break;
            }

            case ADSRStage::Sustain:
                level = sustainLevel;
                break;

            case ADSRStage::Release: {
                float releaseInc = level / (releaseTime * static_cast<float>(sampleRate));
                level -= releaseInc;
                if (level <= 0.0f) {
                    level = 0.0f;
                    stage = ADSRStage::Idle;
                }
                break;
            }
        }

        return level;
    }

    float getCurrentLevel() const { return level; }
    ADSRStage getCurrentStage() const { return stage; }

private:
    double sampleRate = 44100.0;
    float attackTime = 0.01f;
    float decayTime = 0.3f;
    float sustainLevel = 0.7f;
    float releaseTime = 0.5f;
    float level = 0.0f;
    ADSRStage stage = ADSRStage::Idle;
};
