#pragma once
#include "Oscillator.h"
#include "Filter.h"
#include "Envelope.h"
#include "LFO.h"
#include <cmath>
#include <algorithm>

struct SubtractiveSound : public juce::SynthesiserSound {
    bool appliesToNote(int /*midiNoteNumber*/) override { return true; }
    bool appliesToChannel(int /*midiChannel*/) override { return true; }
};

class SubtractiveVoice : public juce::SynthesiserVoice {
public:
    SubtractiveVoice() = default;

    bool canPlaySound(juce::SynthesiserSound* sound) override {
        return dynamic_cast<SubtractiveSound*>(sound) != nullptr;
    }

    void startNote(int midiNoteNumber, float velocity,
                   juce::SynthesiserSound* sound, int currentPitchWheelPosition) override {
        currentMidiNote = midiNoteNumber;
        noteVelocity = velocity;

        double sr = getSampleRate();
        if (sr <= 0.0) sr = 44100.0;
        osc1.prepare(sr);
        osc2.prepare(sr);
        env.prepare(sr);
        filterEnv.prepare(sr);
        filter.prepare(sr);
        filterR.prepare(sr);
        lfo1.prepare(sr);
        lfo2.prepare(sr);
        lfo1.reset();
        lfo2.reset();

        baseFreq1 = 440.0f * std::pow(2.0f, (static_cast<float>(midiNoteNumber) - 69.0f) / 12.0f)
                     * std::pow(2.0f, semitone1 / 12.0f + fineTune1 / 1200.0f) * std::pow(2.0f, octave1);
        baseFreq2 = 440.0f * std::pow(2.0f, (static_cast<float>(midiNoteNumber) - 69.0f) / 12.0f)
                     * std::pow(2.0f, semitone2 / 12.0f + fineTune2 / 1200.0f) * std::pow(2.0f, octave2);

        osc1.setFrequency(baseFreq1);
        osc2.setFrequency(baseFreq2);
        osc1.setWaveform(waveform1);
        osc2.setWaveform(waveform2);
        osc1.setPan(pan);
        osc2.setPan(pan);

        osc1.setPhase(0.0f);
        osc2.setPhase(phaseOffset2);

        filter.setCoefficients(filterCutoff, filterResonance, filterType);
        filterR.setCoefficients(filterCutoff, filterResonance, filterType);

        env.noteOn();
        filterEnv.noteOn();
    }

    void stopNote(float velocity, bool allowTailOff) override {
        env.noteOff();
        filterEnv.noteOff();

        if (!allowTailOff || !env.isActive())
            clearCurrentNote();
    }

    void renderNextBlock(juce::AudioBuffer<float>& outputBuffer,
                         int startSample, int numSamples) override {
        if (!isVoiceActive()) return;

        for (int i = startSample; i < startSample + numSamples; ++i) {
            float envSample = env.getNextSample();
            float fEnvSample = filterEnv.getNextSample();

            if (!env.isActive()) {
                clearCurrentNote();
                return;
            }

            // LFO outputs
            float lfo1Out = lfo1.getNextSample();
            float lfo2Out = lfo2.getNextSample();

            // Apply pitch bend + LFO to oscillator frequencies
            float pitchBendMult = std::pow(2.0f, pitchBendSemitones / 12.0f);
            float freq1 = baseFreq1 * pitchBendMult;
            float freq2 = baseFreq2 * pitchBendMult;

            auto applyPitchLfo = [&](float lfoOut, LFODest dest) {
                float semitoneShift = lfoOut * 12.0f; // +/- 12 semitones range
                float pitchMult = std::pow(2.0f, semitoneShift / 12.0f);
                if (dest == LFODest::OSC1Freq || dest == LFODest::BothOsc)
                    freq1 *= pitchMult;
                if (dest == LFODest::OSC2Freq || dest == LFODest::BothOsc)
                    freq2 *= pitchMult;
            };

            if (lfo1Depth > 0.001f)
                applyPitchLfo(lfo1Out, lfo1Dest);
            if (lfo2Depth > 0.001f)
                applyPitchLfo(lfo2Out, lfo2Dest);

            osc1.setFrequency(freq1);
            osc2.setFrequency(freq2);

            float left = 0.0f, right = 0.0f;
            osc1.process(&left, &right);
            osc2.process(&left, &right);

            left *= noteVelocity * envSample;
            right *= noteVelocity * envSample;

            // Filter cutoff with env + keytrack
            float modCutoff = filterCutoff * std::pow(2.0f, fEnvSample * filterEnvAmount * 4.0f);
            float keyTrackFactor = std::pow(2.0f, (static_cast<float>(currentMidiNote) - 60.0f) / 12.0f * keyTrack);
            modCutoff *= keyTrackFactor;

            // Apply LFO to filter cutoff
            if (lfo1Depth > 0.001f && lfo1Dest == LFODest::FilterCutoff)
                modCutoff *= std::pow(2.0f, lfo1Out * 4.0f);
            if (lfo2Depth > 0.001f && lfo2Dest == LFODest::FilterCutoff)
                modCutoff *= std::pow(2.0f, lfo2Out * 4.0f);

            modCutoff = std::clamp(modCutoff, 50.0f, static_cast<float>(getSampleRate() * 0.49));

            filter.setCoefficients(modCutoff, filterResonance, filterType);
            filterR.setCoefficients(modCutoff, filterResonance, filterType);

            left = filter.process(left);
            right = filterR.process(right);

            left = std::tanh(left * drive) / std::tanh(drive);
            right = std::tanh(right * drive) / std::tanh(drive);

            outputBuffer.addSample(0, i, left * outputLevel);
            outputBuffer.addSample(1, i, right * outputLevel);
        }
    }

    void pitchWheelMoved(int newPitchWheelValue) override {}
    void controllerMoved(int controllerNumber, int newControllerValue) override {}

    void setParameters(Waveform wf1, Waveform wf2,
                       float oct1, float oct2,
                       float semi1, float semi2,
                       float fine1, float fine2,
                       float lvl1, float lvl2,
                       float phOff2,
                       float cut, float res, FilterType ft,
                       float fEnvAmt, float kTrack,
                       float att, float dec, float sus, float rel,
                       float fAtt, float fDec, float fSus, float fRel,
                       float p, float drv, float outLvl,
                       float l1Rate, float l1Depth, int l1Shape, int l1Dest,
                       float l2Rate, float l2Depth, int l2Shape, int l2Dest) {
        waveform1 = wf1;
        waveform2 = wf2;
        octave1 = oct1;
        octave2 = oct2;
        semitone1 = semi1;
        semitone2 = semi2;
        fineTune1 = fine1;
        fineTune2 = fine2;
        osc1.setLevel(lvl1);
        osc2.setLevel(lvl2);
        phaseOffset2 = phOff2;
        filterCutoff = cut;
        filterResonance = res;
        filterType = ft;
        filterEnvAmount = fEnvAmt;
        keyTrack = kTrack;
        pan = p;
        drive = std::max(drv, 1.001f);
        outputLevel = outLvl;

        lfo1Depth = l1Depth;
        lfo1Dest = static_cast<LFODest>(l1Dest);
        lfo1.setParameters(l1Rate, l1Depth, static_cast<LFOShape>(l1Shape), lfo1Dest);

        lfo2Depth = l2Depth;
        lfo2Dest = static_cast<LFODest>(l2Dest);
        lfo2.setParameters(l2Rate, l2Depth, static_cast<LFOShape>(l2Shape), lfo2Dest);

        env.setAttack(att);
        env.setDecay(dec);
        env.setSustain(sus);
        env.setRelease(rel);

        filterEnv.setAttack(fAtt);
        filterEnv.setDecay(fDec);
        filterEnv.setSustain(fSus);
        filterEnv.setRelease(fRel);
    }

    void setPitchBend(float semitones) { pitchBendSemitones = semitones; }

private:
    Oscillator osc1, osc2;
    Filter filter;
    Filter filterR;
    ADSREnvelope env;
    ADSREnvelope filterEnv;
    LFO lfo1, lfo2;

    Waveform waveform1 = Waveform::Saw;
    Waveform waveform2 = Waveform::Square;
    float octave1 = 0.0f, octave2 = -1.0f;
    float semitone1 = 0.0f, semitone2 = 0.0f;
    float fineTune1 = 0.0f, fineTune2 = 7.0f;
    float phaseOffset2 = 0.5f;
    float filterCutoff = 8000.0f;
    float filterResonance = 0.7f;
    FilterType filterType = FilterType::LowPass12;
    float filterEnvAmount = 0.0f;
    float keyTrack = 0.5f;
    float pan = 0.0f;
    float drive = 1.5f;
    float outputLevel = 0.8f;
    float baseFreq1 = 440.0f;
    float baseFreq2 = 440.0f;
    float lfo1Depth = 0.0f;
    float lfo2Depth = 0.0f;
    LFODest lfo1Dest = LFODest::FilterCutoff;
    LFODest lfo2Dest = LFODest::FilterCutoff;
    float pitchBendSemitones = 0.0f;
    int currentMidiNote = 60;
    float noteVelocity = 1.0f;
};
