#include "PluginProcessor.h"
#include "PluginEditor.h"
#include <BinaryData.h>
#include <SampleLibrary.h>

using namespace juce;

TrommelkisteProcessor::TrommelkisteProcessor()
    : AudioProcessor(BusesProperties()
          .withOutput("Output", juce::AudioChannelSet::stereo(), true)),
      apvts(*this, nullptr, "Parameters", createParameterLayout())
{
    formatManager.registerBasicFormats();
    scanSamples();
    loadKit(0);
}

TrommelkisteProcessor::~TrommelkisteProcessor() {}

std::optional<juce::String> TrommelkisteProcessor::getNameForMidiNoteNumber(int note, int)
{
    static const char* names[] = {"BD", "RS", "SN", "CL", "CH", "PH", "OH", "RD", "LT", "MT"};
    int t = note - BASE_NOTE;
    if (t >= 0 && t < NUM_TRACKS)
        return juce::String(names[t]);
    return std::nullopt;
}

void TrommelkisteProcessor::prepareToPlay(double sr, int)
{
    currentSampleRate = sr;

    juce::dsp::ProcessSpec spec;
    spec.sampleRate = sr;
    spec.maximumBlockSize = 512;
    spec.numChannels = 2;
    reverb.prepare(spec);
    reverbParams.roomSize = 0.5f;
    reverbParams.damping = 0.5f;
    reverbParams.wetLevel = 0.33f;
    reverbParams.dryLevel = 0.8f;
    reverbParams.width = 1.0f;
    reverbParams.freezeMode = 0.0f;
    reverb.setParameters(reverbParams);

    const int delaySamples = DELAY_BUFFER_SECONDS * (int)sr;
    delayBuffer.setSize(2, delaySamples);
    delayBuffer.clear();
    delayWritePos = 0;
}

void TrommelkisteProcessor::releaseResources() {}

void TrommelkisteProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midi)
{
    juce::ScopedNoDenormals nd;
    const int numSamples = buffer.getNumSamples();
    buffer.clear();

    // Shuffle: add ghost notes (bar-quantized)
    const float globalShuffleAmt = apvts.getRawParameterValue("global_shuffleAmount")->load();
    const float globalGroove = apvts.getRawParameterValue("global_groove")->load();
    const int shuffleDivIdx = (int)apvts.getRawParameterValue("global_shuffleDiv")->load();
    // 0=OFF, 1=every 4 bars, 2=every 8, 3=every 16, 4=every 32
    static constexpr int shuffleDivBars[] = { 0, 4, 8, 16, 32 };
    const int divBars = shuffleDivBars[juce::jlimit(0, 4, shuffleDivIdx)];

    // Try to get transport info from play head
    bool hasTransport = false;
    int64_t currentBar = 0;
    double ppq = 0.0;
    double bpm = 120.0;
    if (auto* ph = getPlayHead())
    {
        auto info = ph->getPosition();
        if (info.hasValue() && info->getPpqPosition().hasValue())
        {
            hasTransport = true;
            ppq = *info->getPpqPosition();
            if (info->getBpm().hasValue())
                bpm = *info->getBpm();
            double beatsPerBar = 4.0;
            if (info->getTimeSignature().hasValue())
                beatsPerBar = (double)(*info->getTimeSignature()).numerator;
            currentBar = (int64_t)(ppq / beatsPerBar);
        }
    }

    const bool barAllowed = (divBars == 0) || !hasTransport || (currentBar % divBars == 0);

    // 16th note grid quantization (DAW shuffle is reflected in PPQ)
    const double ppqPerSample = bpm / (60.0 * currentSampleRate);
    const double sixteenthPpq = 0.25;
    auto nextGridPpq = [&](double fromPpq) -> double {
        return std::ceil((fromPpq + 0.0001) / sixteenthPpq) * sixteenthPpq;
    };

    // Process pending shuffle events
    for (int i = numPendingShuffle - 1; i >= 0; --i)
    {
        pendingShuffle[i].delaySamples -= numSamples;
        if (pendingShuffle[i].delaySamples <= 0)
        {
            handleNoteOn(pendingShuffle[i].trackIndex, pendingShuffle[i].velocity);
            pendingShuffle[i] = pendingShuffle[--numPendingShuffle];
        }
    }

    std::uniform_real_distribution<float> dist(0.0f, 1.0f);

    for (const auto metadata : midi)
    {
        auto msg = metadata.getMessage();
        if (msg.isNoteOn())
        {
            int t = msg.getNoteNumber() - BASE_NOTE;
            if (t >= 0 && t < NUM_TRACKS)
            {
                handleNoteOn(t, msg.getFloatVelocity());
                lastTriggeredTrack = t;

                const float shuffleAmt = apvts.getRawParameterValue(
                    "t" + juce::String(t) + "_shuffle")->load();
                const float effShuffle = shuffleAmt * globalShuffleAmt;

                if (effShuffle > 0.0f && barAllowed)
                {
                    int numEvents = juce::jmax(1, (int)(effShuffle * 3.0f + 0.5f));
                    numEvents = std::uniform_int_distribution<int>(0, numEvents)(rng);

                    for (int e = 0; e < numEvents; ++e)
                    {
                        float vel = msg.getFloatVelocity() * (0.3f + dist(rng) * 0.7f) * effShuffle;
                        int stepsAhead = std::uniform_int_distribution<int>(1, 4)(rng);
                        double gridPpq = nextGridPpq(ppq) + stepsAhead * sixteenthPpq;
                        int delay = juce::jmax(1, (int)((gridPpq - ppq) / ppqPerSample));
                        if (numPendingShuffle < MAX_PENDING_SHUFFLE)
                            pendingShuffle[numPendingShuffle++] = { t, vel, delay };
                    }
                }
            }
        }
        else if (msg.isNoteOff())
        {
            int t = msg.getNoteNumber() - BASE_NOTE;
            if (t >= 0 && t < NUM_TRACKS)
                handleNoteOff(t);
        }
    }

    auto* outL = buffer.getWritePointer(0);
    auto* outR = buffer.getNumChannels() > 1 ? buffer.getWritePointer(1) : nullptr;

    // Temporary send buffers
    juce::AudioBuffer<float> delaySend(2, numSamples);
    juce::AudioBuffer<float> reverbSend(2, numSamples);
    delaySend.clear();
    reverbSend.clear();

    for (auto& v : voices)
    {
        if (!v.active)
            continue;

        const int ti = v.trackIndex;
        trackActive[ti] = true;
        auto& track = tracks[ti];
        const juce::SpinLock::ScopedLockType sl(track.lock);

        const int bufLen = track.buffer.getNumSamples();
        if (bufLen == 0)
        {
            v.active = false;
            continue;
        }

        const juce::String p = "t" + juce::String(ti) + "_";
        const float level = apvts.getRawParameterValue(p + "level")->load();
        const float length = apvts.getRawParameterValue(p + "length")->load();
        const float velAmt = apvts.getRawParameterValue(p + "velocity")->load();
        const float pitchSemitones = apvts.getRawParameterValue(p + "pitch")->load();
        const float toneHz = apvts.getRawParameterValue(p + "tone")->load();
        const float pan = apvts.getRawParameterValue(p + "pan")->load();
        const bool decayGate = apvts.getRawParameterValue(p + "decay")->load() > 0.5f;
        const float delaySendAmt = apvts.getRawParameterValue(p + "delaySend")->load();
        const float reverbSendAmt = apvts.getRawParameterValue(p + "reverbSend")->load();
        const float ringModAmt = apvts.getRawParameterValue(p + "ringMod")->load();
        const float destructionAmt = apvts.getRawParameterValue(p + "destruction")->load();

        float pitchRatio = std::pow(2.0f, pitchSemitones / 12.0f)
                         * (float)track.fileSampleRate / (float)currentSampleRate;

        const float maxSamples = length * (float)currentSampleRate;
        const float toneA = (toneHz < 19000.0f)
            ? std::exp(-2.0f * juce::MathConstants<float>::pi * toneHz / (float)currentSampleRate)
            : 0.0f;

        const float effVel = 1.0f - velAmt + velAmt * v.noteVelocity;
        const float gain = level * effVel;

        const float panAngle = (pan + 1.0f) * 0.25f * juce::MathConstants<float>::pi;
        const float panL = std::cos(panAngle) * juce::Decibels::decibelsToGain(-3.0f);
        const float panR = std::sin(panAngle) * juce::Decibels::decibelsToGain(-3.0f);

        const float* sData = track.buffer.getReadPointer(0);
        const bool hasR = track.buffer.getNumChannels() > 1;
        const float* sDataR = hasR ? track.buffer.getReadPointer(1) : nullptr;

        const float globalRingFreq = apvts.getRawParameterValue("global_ringFreq")->load();
        const float globalDestr = apvts.getRawParameterValue("global_destruction")->load();
        const float ringModInc = 2.0f * juce::MathConstants<float>::pi * globalRingFreq / (float)currentSampleRate;

        for (int s = 0; s < numSamples; ++s)
        {
            if (!v.active)
                break;

            float env = 1.0f;
            if (!decayGate)
            {
                env = 1.0f - (float)v.samplesPlayed / maxSamples;
                if (env <= 0.0f) { v.active = false; break; }
            }
            else
            {
                if (v.samplesPlayed >= (int)maxSamples) { v.active = false; break; }
            }

            int pos0 = (int)v.position;
            if (pos0 >= bufLen) { v.active = false; break; }
            const float frac = v.position - (float)pos0;
            int pos1 = pos0 + 1;
            if (pos1 >= bufLen) pos1 = bufLen - 1;

            float smplL = sData[pos0] + (sData[pos1] - sData[pos0]) * frac;
            float smplR = hasR ? (sDataR[pos0] + (sDataR[pos1] - sDataR[pos0]) * frac) : smplL;

            if (toneA > 0.0f)
            {
                v.filterStateL = (1.0f - toneA) * smplL + toneA * v.filterStateL;
                smplL = v.filterStateL;
                v.filterStateR = (1.0f - toneA) * smplR + toneA * v.filterStateR;
                smplR = v.filterStateR;
            }

            // Ring modulation
            if (ringModAmt > 0.0f)
            {
                float mod = std::sin(v.ringModPhase);
                smplL = smplL * (1.0f - ringModAmt + ringModAmt * mod);
                smplR = smplR * (1.0f - ringModAmt + ringModAmt * mod);
                v.ringModPhase += ringModInc;
                if (v.ringModPhase >= 2.0f * juce::MathConstants<float>::pi)
                    v.ringModPhase -= 2.0f * juce::MathConstants<float>::pi;
            }

            // Destruction (soft clip + bitcrush)
            const float effDestr = juce::jmin(1.0f, destructionAmt + globalDestr);
            if (effDestr > 0.0f)
            {
                float drive = 1.0f + effDestr * 19.0f;
                smplL = std::tanh(smplL * drive);
                smplR = std::tanh(smplR * drive);
                float bits = juce::jmax(2.0f, 16.0f - effDestr * 14.0f);
                float levels = std::pow(2.0f, bits);
                smplL = std::round(smplL * levels) / levels;
                smplR = std::round(smplR * levels) / levels;
            }

            smplL *= env * gain;
            smplR *= env * gain;

            outL[s] += smplL * panL;
            if (outR)
                outR[s] += smplR * panR;

            // Send to effects
            delaySend.addSample(0, s, smplL * delaySendAmt);
            delaySend.addSample(1, s, smplR * delaySendAmt);
            reverbSend.addSample(0, s, smplL * reverbSendAmt);
            reverbSend.addSample(1, s, smplR * reverbSendAmt);

            v.position += pitchRatio;
            ++v.samplesPlayed;

            if (v.position >= (float)bufLen)
                v.active = false;
        }
    }

    // Read master effect levels
    const float masterDelayMix = apvts.getRawParameterValue("master_delay")->load();
    const float masterReverbMix = apvts.getRawParameterValue("master_reverb")->load();

    // Delay sync
    const int delaySyncDiv = (int)apvts.getRawParameterValue("delay_syncDiv")->load();
    float delayTimeSec = 0.45f; // default free-running delay time
    if (delaySyncDiv >= 1 && delaySyncDiv <= 8)
    {
        double bpm = 120.0;
        if (auto* ph = getPlayHead())
        {
            auto info = ph->getPosition();
            if (info.hasValue() && info->getBpm().hasValue())
                bpm = *info->getBpm();
        }
        double beats = delaySyncDiv * 0.25;
        delayTimeSec = (float)(beats * 60.0 / bpm);
    }

    // Process delay
    const int delaySamples = delayBuffer.getNumSamples();
    auto* delL = delayBuffer.getWritePointer(0);
    auto* delR = delayBuffer.getNumChannels() > 1
                 ? delayBuffer.getWritePointer(1) : nullptr;
    const float feedback = 0.4f;
    const float delayMix = 0.3f;
    const int delayTimeSamples = (int)(delayTimeSec * currentSampleRate);

    for (int s = 0; s < numSamples; ++s)
    {
        const int readPos = (delayWritePos - delayTimeSamples + delaySamples)
                           % delaySamples;
        const float delOutL = delL[readPos];
        const float delOutR = delR ? delR[readPos] : delOutL;

        delL[delayWritePos] = delaySend.getSample(0, s) + delOutL * feedback;
        if (delR)
            delR[delayWritePos] = delaySend.getSample(1, s) + delOutR * feedback;

        outL[s] += delOutL * delayMix * masterDelayMix;
        if (outR)
            outR[s] += delOutR * delayMix * masterDelayMix;

        delayWritePos = (delayWritePos + 1) % delaySamples;
    }

    // Process reverb
    juce::dsp::AudioBlock<float> reverbBlock(reverbSend);
    juce::dsp::ProcessContextReplacing<float> reverbCtx(reverbBlock);
    reverb.process(reverbCtx);

    for (int s = 0; s < numSamples; ++s)
    {
        outL[s] += reverbSend.getSample(0, s) * masterReverbMix;
        if (outR)
            outR[s] += reverbSend.getSample(1, s) * masterReverbMix;
    }

    // Master volume & pan
    const float masterVol = apvts.getRawParameterValue("master_volume")->load();
    const float masterPan = apvts.getRawParameterValue("master_pan")->load();
    const float mPanAngle = (masterPan + 1.0f) * 0.25f * MathConstants<float>::pi;
    const float mPanL = std::cos(mPanAngle) * Decibels::decibelsToGain(-3.0f);
    const float mPanR = std::sin(mPanAngle) * Decibels::decibelsToGain(-3.0f);

    for (int s = 0; s < numSamples; ++s)
    {
        float tmpL = outL[s] * mPanL * masterVol;
        float tmpR = outR ? outR[s] * mPanR * masterVol : tmpL;
        outL[s] = tmpL;
        if (outR)
            outR[s] = tmpR;
    }

    float peak = 0.0f;
    for (int s = 0; s < numSamples; ++s)
    {
        float a = std::abs(outL[s]);
        if (outR)
            a = jmax(a, std::abs(outR[s]));
        if (a > peak)
            peak = a;
    }
    outputLevel.store(peak, std::memory_order_relaxed);
}

int TrommelkisteProcessor::findFreeVoice(int trackIndex)
{
    for (int i = 0; i < MAX_VOICES; ++i)
        if (!voices[i].active)
            return i;

    int oldest = 0, bestAge = -1;
    for (int i = 0; i < MAX_VOICES; ++i)
    {
        if (voices[i].trackIndex == trackIndex && voices[i].samplesPlayed > bestAge)
        {
            bestAge = voices[i].samplesPlayed;
            oldest = i;
        }
    }
    if (bestAge >= 0)
        return oldest;

    oldest = 0;
    bestAge = -1;
    for (int i = 0; i < MAX_VOICES; ++i)
    {
        if (voices[i].samplesPlayed > bestAge)
        {
            bestAge = voices[i].samplesPlayed;
            oldest = i;
        }
    }
    return oldest;
}

void TrommelkisteProcessor::handleNoteOn(int trackIndex, float velocity)
{
    int idx = findFreeVoice(trackIndex);
    auto& v = voices[idx];
    v.active = true;
    v.trackIndex = trackIndex;
    v.position = 0.0f;
    v.samplesPlayed = 0;
    v.filterStateL = 0.0f;
    v.filterStateR = 0.0f;
    v.noteVelocity = velocity;
}

void TrommelkisteProcessor::handleNoteOff(int trackIndex)
{
    for (auto& v : voices)
    {
        if (v.active && v.trackIndex == trackIndex)
        {
            const bool decayGate = apvts.getRawParameterValue(
                "t" + juce::String(trackIndex) + "_decay")->load() > 0.5f;
            if (decayGate)
                v.active = false;
        }
    }
}

void TrommelkisteProcessor::loadSample(int trackIndex, const juce::File& file)
{
    if (trackIndex < 0 || trackIndex >= NUM_TRACKS)
        return;

    std::unique_ptr<juce::AudioFormatReader> reader(formatManager.createReaderFor(file));
    if (reader == nullptr)
        return;

    juce::AudioBuffer<float> newBuf(reader->numChannels, (int)reader->lengthInSamples);
    reader->read(&newBuf, 0, (int)reader->lengthInSamples, 0, true, true);

    {
        const juce::SpinLock::ScopedLockType sl(tracks[trackIndex].lock);
        tracks[trackIndex].buffer = std::move(newBuf);
        tracks[trackIndex].fileSampleRate = (int)reader->sampleRate;
        tracks[trackIndex].filePath = file.getFullPathName();
    }
}

void TrommelkisteProcessor::clearSample(int trackIndex)
{
    if (trackIndex < 0 || trackIndex >= NUM_TRACKS)
        return;
    const juce::SpinLock::ScopedLockType sl(tracks[trackIndex].lock);
    tracks[trackIndex].buffer.setSize(1, 0);
    tracks[trackIndex].filePath = "";
}

static void readAudioFromBuffer(juce::AudioFormatManager& fmtMgr,
                                juce::AudioBuffer<float>& dest,
                                int& destSampleRate,
                                const char* data, int size)
{
    auto memStream = std::make_unique<juce::MemoryInputStream>(data, size, false);
    std::unique_ptr<juce::AudioFormatReader> reader(fmtMgr.createReaderFor(std::move(memStream)));
    if (reader == nullptr)
        return;
    dest.setSize((int)reader->numChannels, (int)reader->lengthInSamples);
    reader->read(&dest, 0, (int)reader->lengthInSamples, 0, true, true);
    destSampleRate = (int)reader->sampleRate;
}

// ── Sample library ─────────────────────────────────────────────

static bool matchesPrefix(const juce::String& name, const juce::String& prefix)
{
    return name.toUpperCase().startsWith(prefix.toUpperCase());
}

void TrommelkisteProcessor::scanSamples()
{
    static const char* prefixes[][8] = {
        { "BT", nullptr },                                    // 0 BD
        { "RIM", nullptr },                                   // 1 RS
        { "ST", "STAT", nullptr },                            // 2 SN
        { "HANDCLP", nullptr },                               // 3 CL
        { "HHCD", nullptr },                                  // 4 CH
        { "CLOP", nullptr },                                  // 5 PH
        { "HHOD", nullptr },                                  // 6 OH
        { "RIDE", nullptr },                                  // 7 RD
        { "LT", nullptr },                                    // 8 LT
        { "MT", nullptr },                                    // 9 MT
    };

    for (int t = 0; t < NUM_TRACKS; ++t)
    {
        trackSamples[t].clear();
        currentSampleIndex[t] = -1;
    }

    for (int i = 0; i < numEmbeddedSamples; ++i)
    {
        const auto& es = embeddedSamples[i];
        juce::String name(es.name);

        for (int t = 0; t < NUM_TRACKS; ++t)
        {
            for (int p = 0; prefixes[t][p] != nullptr; ++p)
            {
                if (matchesPrefix(name, prefixes[t][p]))
                {
                    SampleRef ref;
                    ref.name = name;
                    ref.data = es.data;
                    ref.dataSize = es.size;
                    trackSamples[t].add(ref);
                    break;
                }
            }
        }
    }

    struct SampleRefSorter
    {
        int compareElements(const SampleRef& a, const SampleRef& b) const { return a.name.compare(b.name); }
    };
    SampleRefSorter sorter;
    for (int t = 0; t < NUM_TRACKS; ++t)
        trackSamples[t].sort(sorter);
}

void TrommelkisteProcessor::loadSampleByIndex(int trackIndex, int sampleIndex)
{
    if (trackIndex < 0 || trackIndex >= NUM_TRACKS)
        return;
    if (sampleIndex < 0 || sampleIndex >= trackSamples[trackIndex].size())
        return;

    currentSampleIndex[trackIndex] = sampleIndex;
    const auto& ref = trackSamples[trackIndex][sampleIndex];

    if (ref.data != nullptr && ref.dataSize > 0)
    {
        const juce::SpinLock::ScopedLockType sl(tracks[trackIndex].lock);
        readAudioFromBuffer(formatManager, tracks[trackIndex].buffer,
                            tracks[trackIndex].fileSampleRate,
                            ref.data, ref.dataSize);
        tracks[trackIndex].filePath = "";
    }
    else if (ref.file.existsAsFile())
    {
        loadSample(trackIndex, ref.file);
    }
}

void TrommelkisteProcessor::nextSample(int trackIndex)
{
    if (trackIndex < 0 || trackIndex >= NUM_TRACKS)
        return;
    const int n = trackSamples[trackIndex].size();
    if (n == 0) return;

    int idx = currentSampleIndex[trackIndex] + 1;
    if (idx >= n) idx = 0;
    loadSampleByIndex(trackIndex, idx);
}

void TrommelkisteProcessor::prevSample(int trackIndex)
{
    if (trackIndex < 0 || trackIndex >= NUM_TRACKS)
        return;
    const int n = trackSamples[trackIndex].size();
    if (n == 0) return;

    int idx = currentSampleIndex[trackIndex] - 1;
    if (idx < 0) idx = n - 1;
    loadSampleByIndex(trackIndex, idx);
}

// ── Kit / Preset system ───────────────────────────────────────

static const TrommelkisteProcessor::Kit kits[] = {
    { "Classic", {
        "BT0AADA", "RIM63", "ST0T0SA", "HANDCLP2", "HHCD6",
        "CLOP2", "HHOD6", "RIDED6", "LT0DA", "MT0DA" } },
    { "Punchy", {
        "BT7A0D3", "RIM127", "ST7T7S3", "HANDCLP1", "HHCD2",
        "CLOP1", "HHOD2", "RIDED2", "LT7D3", "MT7D3" } },
    { "Soft", {
        "BT0A0DA", "RIM63", "ST0T0S7", "HANDCLP2", "HHCD8",
        "CLOP4", "HHOD8", "RIDED8", "LT0D7", "MT0D7" } },
    { "Raw", {
        "BTAAADA", "RIM127", "STATASA", "HANDCLP1", "HHCDA",
        "CLOP3", "HHODA", "RIDEDA", "LTADA", "MTADA" } },
};

const TrommelkisteProcessor::Kit* TrommelkisteProcessor::getKitList() { return kits; }
int TrommelkisteProcessor::getNumKits() const { return 4; }
juce::String TrommelkisteProcessor::getCurrentKitName() const
{
    if (currentKitIndex >= 0 && currentKitIndex < getNumKits())
        return kits[currentKitIndex].name;
    return {};
}

void TrommelkisteProcessor::loadKit(int index)
{
    if (index < 0 || index >= getNumKits())
        return;

    currentKitIndex = index;
    const auto& kit = kits[index];

    for (int t = 0; t < NUM_TRACKS; ++t)
    {
        if (kit.samples[t] == nullptr || kit.samples[t][0] == '\0')
        {
            currentSampleIndex[t] = -1;
            clearSample(t);
            continue;
        }

        juce::String target(kit.samples[t]);
        int found = -1;
        for (int s = 0; s < trackSamples[t].size(); ++s)
        {
            if (trackSamples[t][s].name.equalsIgnoreCase(target))
            {
                found = s;
                break;
            }
        }

        if (found >= 0)
            loadSampleByIndex(t, found);
        else
            clearSample(t);
    }
}

void TrommelkisteProcessor::nextKit()
{
    int idx = currentKitIndex + 1;
    if (idx >= getNumKits()) idx = 0;
    loadKit(idx);
}

void TrommelkisteProcessor::prevKit()
{
    int idx = currentKitIndex - 1;
    if (idx < 0) idx = getNumKits() - 1;
    loadKit(idx);
}

juce::AudioProcessorEditor* TrommelkisteProcessor::createEditor()
{
    return new TrommelkisteEditor(*this);
}

void TrommelkisteProcessor::getStateInformation(juce::MemoryBlock& destData)
{
    auto state = apvts.copyState();

    state.setProperty("kitIndex", currentKitIndex, nullptr);

    juce::ValueTree samples("Samples");
    for (int i = 0; i < NUM_TRACKS; ++i)
    {
        juce::ValueTree tr("Track" + juce::String(i));
        tr.setProperty("path", tracks[i].filePath, nullptr);
        tr.setProperty("sampleIndex", currentSampleIndex[i], nullptr);
        if (currentSampleIndex[i] >= 0 && currentSampleIndex[i] < trackSamples[i].size())
            tr.setProperty("sampleName", trackSamples[i][currentSampleIndex[i]].name, nullptr);
        samples.appendChild(tr, nullptr);
    }
    state.appendChild(samples, nullptr);

    std::unique_ptr<juce::XmlElement> xml(state.createXml());
    copyXmlToBinary(*xml, destData);
}

void TrommelkisteProcessor::setStateInformation(const void* data, int sizeInBytes)
{
    std::unique_ptr<juce::XmlElement> xml(getXmlFromBinary(data, sizeInBytes));
    if (xml == nullptr)
        return;

    auto state = juce::ValueTree::fromXml(*xml);
    apvts.replaceState(state);

    currentKitIndex = (int)state.getProperty("kitIndex", 0);

    auto samples = state.getChildWithName("Samples");
    if (samples.isValid())
    {
        for (int i = 0; i < NUM_TRACKS; ++i)
        {
            auto tr = samples.getChildWithName("Track" + juce::String(i));
            if (!tr.isValid())
                continue;

            auto path = tr.getProperty("path", "").toString();
            auto sampleName = tr.getProperty("sampleName", "").toString();

            if (path.isNotEmpty())
            {
                loadSample(i, juce::File(path));
                currentSampleIndex[i] = (int)tr.getProperty("sampleIndex", -1);
            }
            else if (sampleName.isNotEmpty())
            {
                int found = -1;
                for (int s = 0; s < trackSamples[i].size(); ++s)
                {
                    if (trackSamples[i][s].name.equalsIgnoreCase(sampleName))
                    {
                        found = s;
                        break;
                    }
                }
                if (found >= 0)
                    loadSampleByIndex(i, found);
            }
        }
    }
}

juce::AudioProcessorValueTreeState::ParameterLayout TrommelkisteProcessor::createParameterLayout()
{
    AudioProcessorValueTreeState::ParameterLayout layout;

    for (int i = 0; i < NUM_TRACKS; ++i)
    {
        const String p = "t" + String(i) + "_";
        const String tn = "T" + String(i + 1) + " ";

        layout.add(std::make_unique<AudioParameterFloat>(
            p + "level", tn + "Level",
            NormalisableRange<float>(0.0f, 1.0f), 0.8f));

        layout.add(std::make_unique<AudioParameterFloat>(
            p + "length", tn + "Length",
            NormalisableRange<float>(0.01f, 2.0f, 0.001f, 0.45f), 0.5f));

        layout.add(std::make_unique<AudioParameterFloat>(
            p + "velocity", tn + "Velocity",
            NormalisableRange<float>(0.0f, 1.0f), 1.0f));

        layout.add(std::make_unique<AudioParameterFloat>(
            p + "pitch", tn + "Pitch",
            NormalisableRange<float>(-12.0f, 12.0f, 0.1f), 0.0f));

        layout.add(std::make_unique<AudioParameterFloat>(
            p + "tone", tn + "Tone",
            NormalisableRange<float>(20.0f, 20000.0f, 1.0f, 0.25f), 20000.0f));

        layout.add(std::make_unique<AudioParameterFloat>(
            p + "pan", tn + "Pan",
            NormalisableRange<float>(-1.0f, 1.0f), 0.0f));

        layout.add(std::make_unique<AudioParameterBool>(
            p + "decay", tn + "Decay", false));

        layout.add(std::make_unique<AudioParameterFloat>(
            p + "delaySend", tn + "Delay",
            NormalisableRange<float>(0.0f, 1.0f), 0.0f));

        layout.add(std::make_unique<AudioParameterFloat>(
            p + "reverbSend", tn + "Reverb",
            NormalisableRange<float>(0.0f, 1.0f), 0.0f));

        layout.add(std::make_unique<AudioParameterFloat>(
            p + "ringMod", tn + "RingMod",
            NormalisableRange<float>(0.0f, 1.0f), 0.0f));

        layout.add(std::make_unique<AudioParameterFloat>(
            p + "destruction", tn + "Destr",
            NormalisableRange<float>(0.0f, 1.0f), 0.0f));

        layout.add(std::make_unique<AudioParameterFloat>(
            p + "shuffle", tn + "Shuffle",
            NormalisableRange<float>(0.0f, 1.0f), 0.0f));
    }

    // Global controls
    layout.add(std::make_unique<AudioParameterFloat>(
        "master_volume", "Master Volume",
        NormalisableRange<float>(0.0f, 1.2f, 0.01f), 1.0f));

    layout.add(std::make_unique<AudioParameterFloat>(
        "master_pan", "Master Pan",
        NormalisableRange<float>(-1.0f, 1.0f, 0.01f), 0.0f));

    layout.add(std::make_unique<AudioParameterFloat>(
        "master_delay", "Master Delay",
        NormalisableRange<float>(0.0f, 1.0f, 0.01f), 1.0f));

    layout.add(std::make_unique<AudioParameterFloat>(
        "master_reverb", "Master Reverb",
        NormalisableRange<float>(0.0f, 1.0f, 0.01f), 1.0f));

    layout.add(std::make_unique<AudioParameterFloat>(
        "delay_syncDiv", "Delay Sync",
        NormalisableRange<float>(0.0f, 8.0f, 1.0f), 0.0f));

    layout.add(std::make_unique<AudioParameterFloat>(
        "global_shuffleAmount", "Shuffle Amt",
        NormalisableRange<float>(0.0f, 1.0f, 0.01f), 0.0f));

    layout.add(std::make_unique<AudioParameterFloat>(
        "global_groove", "Groove",
        NormalisableRange<float>(0.0f, 1.0f, 0.01f), 0.0f));

    layout.add(std::make_unique<AudioParameterFloat>(
        "global_shuffleDiv", "Shuffle Div",
        NormalisableRange<float>(0.0f, 4.0f, 1.0f), 0.0f));

    layout.add(std::make_unique<AudioParameterFloat>(
        "global_ringFreq", "Ring Freq",
        NormalisableRange<float>(20.0f, 2000.0f, 1.0f, 0.3f), 80.0f));

    layout.add(std::make_unique<AudioParameterFloat>(
        "global_destruction", "Destruction",
        NormalisableRange<float>(0.0f, 1.0f), 0.0f));

    return layout;
}

AudioProcessor* JUCE_CALLTYPE createPluginFilter()
{
    return new TrommelkisteProcessor();
}
