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

void KnobLookAndFeel::drawRotarySlider(juce::Graphics& g, int x, int y, int width, int height,
                                        float sliderPos, float rotaryStartAngle,
                                        float rotaryEndAngle, juce::Slider& slider) {
    auto bounds = juce::Rectangle<int>(x, y, width, height).toFloat();

    float labelH = 20.0f;
    auto dialBounds = bounds.withTrimmedTop(labelH);

    auto radius = juce::jmin(dialBounds.getWidth(), dialBounds.getHeight()) / 2.0f - 4.0f;
    auto centreX = dialBounds.getCentreX();
    auto centreY = dialBounds.getCentreY();
    auto rx = centreX - radius;
    auto ry = centreY - radius;
    auto rw = radius * 2.0f;
    auto angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle);

    // Drop shadow
    g.setColour(juce::Colour(0x40000000));
    g.fillEllipse(rx + 2.0f, ry + 3.0f, rw, rw);

    // Outer bevel (dark bottom-right, light top-left)
    auto bevelPath = juce::Path();
    bevelPath.addEllipse(rx, ry, rw, rw);

    // Knob body gradient — light top-left to dark bottom-right for 3D bulge
    juce::ColourGradient bodyGrad(juce::Colour(0xff404040), rx, ry,
                                   juce::Colour(0xff1a1a1a), rx + rw, ry + rw, true);
    g.setGradientFill(bodyGrad);
    g.fillEllipse(rx, ry, rw, rw);

    // Soft inner shadow ring (bottom-right dark edge, subtle)
    g.setColour(juce::Colour(0x18000000));
    auto shadowArc = juce::Path();
    float innerR = radius - 2.0f;
    shadowArc.addCentredArc(centreX, centreY, innerR, innerR, 0.0f,
                            0.5f, 2.7f, true);
    g.strokePath(shadowArc, juce::PathStrokeType(1.5f));

    // Outer rim
    g.setColour(juce::Colour(0xff555555));
    g.drawEllipse(rx, ry, rw, rw, 1.5f);

    // Pointer line with glow — green (left) → yellow (mid) → red (right)
    auto indicatorColour = juce::Colour::fromHSV(0.33f * (1.0f - sliderPos), 0.85f, 0.9f, 1.0f);
    auto glowColour = juce::Colour::fromHSV(0.33f * (1.0f - sliderPos), 0.85f, 0.9f, 0.4f);

    g.setColour(glowColour);
    juce::Path pointerGlow;
    pointerGlow.addRoundedRectangle(-2.5f, -radius + 9, 5.0f, radius * 0.5f, 2.0f);
    g.fillPath(pointerGlow, juce::AffineTransform::rotation(angle).translated(centreX, centreY));

    g.setColour(indicatorColour);
    juce::Path pointer;
    pointer.addRoundedRectangle(-1.5f, -radius + 9, 3.0f, radius * 0.5f, 1.5f);
    g.fillPath(pointer, juce::AffineTransform::rotation(angle).translated(centreX, centreY));

    // Label above knob
    auto name = slider.getName();
    if (name.isNotEmpty()) {
        g.setColour(juce::Colour(0xff999999));
        g.setFont(juce::Font(13.0f).boldened());
        g.drawText(name, bounds.getX(), bounds.getY(), bounds.getWidth(), labelH,
                   juce::Justification::centredBottom);
    }
}

void ComboBoxLookAndFeel::drawComboBox(juce::Graphics& g, int width, int height,
                                       bool isButtonDown, int /*buttonX*/, int /*buttonY*/,
                                       int /*buttonW*/, int /*buttonH*/, juce::ComboBox& /*box*/) {
    auto bounds = juce::Rectangle<int>(0, 0, width, height).toFloat();

    // Drop shadow
    g.setColour(juce::Colour(0x40000000));
    g.fillRoundedRectangle(bounds.getX() + 1.5f, bounds.getY() + 2.0f,
                           bounds.getWidth(), bounds.getHeight(), 4.0f);

    // Body gradient — light top to dark bottom for 3D raised look
    juce::ColourGradient bodyGrad(juce::Colour(0xff404040), bounds.getCentreX(), bounds.getY(),
                                   juce::Colour(0xff1a1a1a), bounds.getCentreX(), bounds.getBottom(), true);
    g.setGradientFill(bodyGrad);
    g.fillRoundedRectangle(bounds, 4.0f);

    // Top highlight edge
    g.setColour(juce::Colour(0x30ffffff));
    g.drawRoundedRectangle(bounds.getX() + 0.5f, bounds.getY() + 0.5f,
                           bounds.getWidth() - 1.0f, bounds.getHeight() - 1.0f, 4.0f, 1.0f);

    // Bottom shadow edge
    g.setColour(juce::Colour(0x30000000));
    g.drawLine(bounds.getX() + 4.0f, bounds.getBottom() - 0.5f,
               bounds.getRight() - 4.0f, bounds.getBottom() - 0.5f, 1.0f);

    // Outline
    g.setColour(juce::Colour(0xff555555));
    g.drawRoundedRectangle(bounds, 4.0f, 1.0f);

    // Arrow (gold triangle on right)
    float arrowSize = 6.0f;
    float arrowX = bounds.getRight() - 16.0f;
    float arrowY = bounds.getCentreY();

    juce::Path arrow;
    arrow.addTriangle(arrowX, arrowY - arrowSize / 2,
                      arrowX + arrowSize, arrowY,
                      arrowX, arrowY + arrowSize / 2);
    g.setColour(juce::Colour(0xffccaa44));
    g.fillPath(arrow);
}

void ComboBoxLookAndFeel::drawPopupMenuItem(juce::Graphics& g, const juce::Rectangle<int>& area,
                                            bool isSeparator, bool /*isActive*/,
                                            bool isHighlighted, bool /*isTicked*/,
                                            bool /*hasSubMenu*/, const juce::String& text,
                                            const juce::String& /*shortcutKeyText*/,
                                            const juce::Drawable* /*icon*/,
                                            const juce::Colour* /*textColour*/) {
    if (isSeparator) {
        g.setColour(juce::Colour(0xff444444));
        g.drawLine(area.getX() + 8.0f, area.getCentreY(),
                   area.getRight() - 8.0f, area.getCentreY(), 1.0f);
        return;
    }

    if (isHighlighted) {
        g.setColour(juce::Colour(0xffccaa44));
        g.fillRoundedRectangle(area.reduced(2).toFloat(), 3.0f);
    }

    g.setColour(isHighlighted ? juce::Colour(0xff1a1a1a) : juce::Colour(0xffcccccc));
    g.setFont(juce::Font(13.0f));
    g.drawText(text, area.reduced(8, 0), juce::Justification::centredLeft, true);
}

// --- VU Meter ---
void VuMeter::paint(juce::Graphics& g) {
    auto bounds = getLocalBounds().toFloat();

    juce::ColourGradient bgGrad(juce::Colour(0xff222222), bounds.getCentreX(), bounds.getY(),
                                 juce::Colour(0xff111111), bounds.getCentreX(), bounds.getBottom(), false);
    g.setGradientFill(bgGrad);
    g.fillRoundedRectangle(bounds, 3.0f);

    float level = processor.getRmsLevel();
    float clamped = juce::jlimit(0.0f, 1.0f, level);
    float barH = bounds.getHeight() * clamped;
    auto barBounds = juce::Rectangle<float>(bounds.getX(), bounds.getBottom() - barH, bounds.getWidth(), barH);
    auto innerBar = barBounds.reduced(1.0f);

    {
        juce::Graphics::ScopedSaveState saved(g);
        g.setOpacity(0.7f);

        juce::ColourGradient grad(juce::Colour(0xff00cc44), 0.0f, bounds.getBottom(),
                                   juce::Colour(0xffcc2200), 0.0f, bounds.getY(), false);
        g.setGradientFill(grad);
        g.fillRoundedRectangle(innerBar, 2.0f);
    }

    g.setColour(juce::Colour(0xff444444));
    g.drawRoundedRectangle(bounds, 3.0f, 1.0f);

    g.setColour(juce::Colour(0xffccaa44));
    g.setFont(juce::Font(13.0f).boldened());
    g.drawText("VU", bounds.withBottom(bounds.getY() + 22.0f).translated(0, 6), juce::Justification::centred);
}

// --- Waveform Display ---
void WaveformDisplay::paint(juce::Graphics& g) {
    auto bounds = getLocalBounds().toFloat();

    juce::ColourGradient bgGrad(juce::Colour(0xff222222), bounds.getCentreX(), bounds.getY(),
                                 juce::Colour(0xff111111), bounds.getCentreX(), bounds.getBottom(), false);
    g.setGradientFill(bgGrad);
    g.fillRoundedRectangle(bounds, 3.0f);

    g.setColour(juce::Colour(0xff555555));
    g.drawRoundedRectangle(bounds, 3.0f, 1.0f);

    g.setColour(juce::Colour(0xff333333));
    g.drawLine(bounds.getX(), bounds.getCentreY(), bounds.getRight(), bounds.getCentreY(), 1.0f);

    int writePos = processor.scopeWritePos.load(std::memory_order_acquire);
    int bufSize = ChromaFlockProcessor::scopeBufferSize;
    float w = bounds.getWidth();
    float h = bounds.getHeight();
    float midY = bounds.getY() + h * 0.5f;

    juce::Path wavePath;
    bool started = false;
    for (int i = 0; i < bufSize; ++i) {
        int idx = (writePos + i) % bufSize;
        float x = bounds.getX() + (static_cast<float>(i) / static_cast<float>(bufSize)) * w;
        float sample = processor.scopeBuffer[idx];
        float y = midY - sample * h * 0.45f;

        if (!started) {
            wavePath.startNewSubPath(x, y);
            started = true;
        } else {
            wavePath.lineTo(x, y);
        }
    }

    juce::Path filledPath(wavePath);
    filledPath.lineTo(bounds.getRight(), midY);
    filledPath.lineTo(bounds.getX(), midY);
    filledPath.closeSubPath();

    {
        juce::Graphics::ScopedSaveState saved(g);
        g.setOpacity(0.7f);

        juce::ColourGradient waveGrad(juce::Colour(0xff00ff88), 0.0f, bounds.getY(),
                                       juce::Colour(0xff005522), 0.0f, bounds.getBottom(), false);
        g.setGradientFill(waveGrad);
        g.fillPath(filledPath);

        g.setColour(juce::Colour(0xff00ff88).withAlpha(0.8f));
        g.strokePath(wavePath, juce::PathStrokeType(1.5f));
    }

    g.setColour(juce::Colour(0xffccaa44));
    g.setFont(juce::Font(13.0f).boldened());
    g.drawText("WAVE", bounds.withBottom(bounds.getY() + 22.0f).translated(0, 6), juce::Justification::centred);
}

// --- Spectrum Analyzer ---
void SpectrumAnalyzer::paint(juce::Graphics& g) {
    auto bounds = getLocalBounds().toFloat();

    juce::ColourGradient bgGrad(juce::Colour(0xff222222), bounds.getCentreX(), bounds.getY(),
                                 juce::Colour(0xff111111), bounds.getCentreX(), bounds.getBottom(), false);
    g.setGradientFill(bgGrad);
    g.fillRoundedRectangle(bounds, 3.0f);

    g.setColour(juce::Colour(0xff555555));
    g.drawRoundedRectangle(bounds, 3.0f, 1.0f);

    std::array<float, ChromaFlockProcessor::fftSize * 2> fftData{};
    int writePos = processor.fftWritePos.load(std::memory_order_acquire);
    int fftSize = ChromaFlockProcessor::fftSize;

    for (int i = 0; i < fftSize; ++i) {
        int idx = (writePos + i) % fftSize;
        fftData[i] = processor.fftInput[idx];
    }

    for (int i = 0; i < fftSize; ++i) {
        float window = 0.5f - 0.5f * std::cos(2.0f * 3.14159265f * static_cast<float>(i) / static_cast<float>(fftSize));
        fftData[i] *= window;
    }

    fft->performFrequencyOnlyForwardTransform(fftData.data());

    int numBars = 48;
    float w = bounds.getWidth();
    float h = bounds.getHeight() - 22.0f;
    float barBottom = bounds.getBottom() - 2.0f;
    float barW = w / static_cast<float>(numBars);
    int maxBin = fftSize / 4;

    for (int bar = 0; bar < numBars; ++bar) {
        float t = static_cast<float>(bar) / static_cast<float>(numBars);
        float tNext = static_cast<float>(bar + 1) / static_cast<float>(numBars);
        int binStart = static_cast<int>(std::pow(t, 2.0f) * static_cast<float>(maxBin));
        int binEnd = static_cast<int>(std::pow(tNext, 2.0f) * static_cast<float>(maxBin));
        if (binEnd <= binStart) binEnd = binStart + 1;
        if (binEnd > maxBin) binEnd = maxBin;

        float mag = 0.0f;
        int count = 0;
        for (int b = binStart; b < binEnd; ++b) {
            mag += fftData[b];
            ++count;
        }
        mag = count > 0 ? mag / static_cast<float>(count) : 0.0f;
        mag = mag / static_cast<float>(fftSize);
        mag = std::sqrt(mag) * 6.0f;
        mag = juce::jlimit(0.0f, 1.0f, mag);

        float barH = mag * h;
        float x = bounds.getX() + static_cast<float>(bar) * barW;
        float bw = barW - 2.0f;
        float bx = x + 1.0f;
        float by = barBottom - barH;

        {
            juce::Graphics::ScopedSaveState saved(g);
            g.setOpacity(0.7f);

            juce::ColourGradient barGrad(juce::Colour(0xff00ff66), 0.0f, by,
                                          juce::Colour(0xff005522), 0.0f, barBottom, false);
            g.setGradientFill(barGrad);
            g.fillRect(bx, by, bw, barH);
        }
    }

    g.setColour(juce::Colour(0xffccaa44));
    g.setFont(juce::Font(13.0f).boldened());
    g.drawText("SPECTRUM", bounds.withBottom(bounds.getY() + 22.0f).translated(0, 6), juce::Justification::centred);
}

MainContentComponent::MainContentComponent(ChromaFlockProcessor& p)
    : processorRef(p), vuMeter(p), waveformDisplay(p), spectrumAnalyzer(p), pianoRoll(p) {

    auto setupParam = [&](juce::Slider& knob, std::unique_ptr<SliderAttachment>& attach,
                          const juce::String& paramId, const juce::String& name) {
        setupKnob(knob, name);
        attach = std::make_unique<SliderAttachment>(processorRef.apvts, paramId, knob);
    };

    auto setupCB = [&](juce::ComboBox& box, std::unique_ptr<ComboBoxAttachment>& attach,
                       const juce::String& paramId, const juce::StringArray& items) {
        setupCombo(box);
        box.addItemList(items, 1);
        attach = std::make_unique<ComboBoxAttachment>(processorRef.apvts, paramId, box);
    };

    setupLabel(osc1Label, "OSC 1");
    setupCB(osc1WaveBox, osc1WaveAttach, "osc1Wave", {"Sine", "Saw", "Square", "Triangle", "Noise"});
    setupParam(osc1OctKnob, osc1OctAttach, "osc1Oct", "OCT");
    setupParam(osc1SemiKnob, osc1SemiAttach, "osc1Semi", "SEMI");
    setupParam(osc1FineKnob, osc1FineAttach, "osc1Fine", "FINE");
    setupParam(osc1LevelKnob, osc1LevelAttach, "osc1Level", "LEVEL");

    setupLabel(osc2Label, "OSC 2");
    setupCB(osc2WaveBox, osc2WaveAttach, "osc2Wave", {"Sine", "Saw", "Square", "Triangle", "Noise"});
    setupParam(osc2OctKnob, osc2OctAttach, "osc2Oct", "OCT");
    setupParam(osc2SemiKnob, osc2SemiAttach, "osc2Semi", "SEMI");
    setupParam(osc2FineKnob, osc2FineAttach, "osc2Fine", "FINE");
    setupParam(osc2LevelKnob, osc2LevelAttach, "osc2Level", "LEVEL");
    setupParam(phaseOffsetKnob, phaseOffsetAttach, "phaseOffset", "PHASE");

    setupLabel(filterLabel, "FILTER");
    setupCB(filterTypeBox, filterTypeAttach, "filterType", {"LP 12dB", "LP 24dB", "Band Pass", "High Pass", "Notch"});
    setupParam(filterCutoffKnob, filterCutoffAttach, "filterCutoff", "CUTOFF");
    setupParam(filterResKnob, filterResAttach, "filterRes", "RES");
    setupParam(filterEnvAmtKnob, filterEnvAmtAttach, "filterEnvAmt", "ENV AMT");
    setupParam(keyTrackKnob, keyTrackAttach, "keyTrack", "KEY TRK");

    setupLabel(envLabel, "AMP ENV");
    setupParam(envAttackKnob, envAttackAttach, "envAttack", "ATTACK");
    setupParam(envDecayKnob, envDecayAttach, "envDecay", "DECAY");
    setupParam(envSustainKnob, envSustainAttach, "envSustain", "SUSTAIN");
    setupParam(envReleaseKnob, envReleaseAttach, "envRelease", "RELEASE");

    setupLabel(fEnvLabel, "FILTER ENV");
    setupParam(fEnvAttackKnob, fEnvAttackAttach, "fEnvAttack", "ATTACK");
    setupParam(fEnvDecayKnob, fEnvDecayAttach, "fEnvDecay", "DECAY");
    setupParam(fEnvSustainKnob, fEnvSustainAttach, "fEnvSustain", "SUSTAIN");
    setupParam(fEnvReleaseKnob, fEnvReleaseAttach, "fEnvRelease", "RELEASE");

    setupLabel(globalLabel, "MASTER");
    setupParam(panKnob, panAttach, "pan", "PAN");
    setupParam(driveKnob, driveAttach, "drive", "DRIVE");
    setupParam(masterKnob, masterAttach, "masterLevel", "LEVEL");
    setupParam(pbRangeKnob, pbRangeAttach, "pitchBendRange", "PB RANGE");

    // FX
    setupCB(distTypeBox, distTypeAttach, "distType", {"Soft Clip", "Hard Clip", "Foldback", "Overdrive"});
    setupParam(distAmountKnob, distAmountAttach, "distAmount", "AMOUNT");
    setupParam(distMixKnob, distMixAttach, "distMix", "MIX");
    setupParam(compThresholdKnob, compThresholdAttach, "compThreshold", "THRESH");
    setupParam(compRatioKnob, compRatioAttach, "compRatio", "RATIO");
    setupParam(compAttackKnob, compAttackAttach, "compAttack", "ATTACK");
    setupParam(compReleaseKnob, compReleaseAttach, "compRelease", "RELEASE");
    setupParam(compMakeupKnob, compMakeupAttach, "compMakeup", "MAKEUP");
    setupParam(compMixKnob, compMixAttach, "compMix", "MIX");
    setupParam(autoPanRateKnob, autoPanRateAttach, "autoPanRate", "RATE");
    setupParam(autoPanDepthKnob, autoPanDepthAttach, "autoPanDepth", "DEPTH");

    // LFO
    setupParam(lfo1RateKnob, lfo1RateAttach, "lfo1Rate", "RATE");
    setupParam(lfo1DepthKnob, lfo1DepthAttach, "lfo1Depth", "DEPTH");
    setupCB(lfo1ShapeBox, lfo1ShapeAttach, "lfo1Shape", {"Sine", "Triangle", "Saw", "Square"});
    setupCB(lfo1DestBox, lfo1DestAttach, "lfo1Dest", {"Filter", "OSC1", "OSC2", "Both OSC"});
    setupParam(lfo2RateKnob, lfo2RateAttach, "lfo2Rate", "RATE");
    setupParam(lfo2DepthKnob, lfo2DepthAttach, "lfo2Depth", "DEPTH");
    setupCB(lfo2ShapeBox, lfo2ShapeAttach, "lfo2Shape", {"Sine", "Triangle", "Saw", "Square"});
    setupCB(lfo2DestBox, lfo2DestAttach, "lfo2Dest", {"Filter", "OSC1", "OSC2", "Both OSC"});

    // FX2
    setupParam(delayTimeKnob, delayTimeAttach, "delayTime", "TIME");
    setupParam(delayFbKnob, delayFbAttach, "delayFeedback", "FBACK");
    setupParam(delayMixKnob, delayMixAttach, "delayMix", "MIX");
    setupCB(delayPingPongBox, delayPingPongAttach, "delayPingPong", {"Off", "On"});
    setupParam(delaySpreadKnob, delaySpreadAttach, "delaySpread", "SPREAD");
    setupParam(reverbSizeKnob, reverbSizeAttach, "reverbSize", "SIZE");
    setupParam(reverbDampKnob, reverbDampAttach, "reverbDamping", "DAMP");
    setupParam(reverbMixKnob, reverbMixAttach, "reverbMix", "MIX");

    // Preset button
    presetButton.setColour(juce::TextButton::buttonColourId, juce::Colour(0xff2a2a2a));
    presetButton.setColour(juce::TextButton::textColourOffId, juce::Colour(0xffccaa44));
    presetButton.setLookAndFeel(&comboLaf);
    presetButton.onClick = [this]() { showPresetMenu(); };
    addAndMakeVisible(presetButton);

    scaleLabel.setText("SCALE", juce::dontSendNotification);
    scaleLabel.setFont(juce::Font(9.0f));
    scaleLabel.setColour(juce::Label::textColourId, juce::Colour(0xff888888));
    scaleLabel.setJustificationType(juce::Justification::centredRight);
    addAndMakeVisible(scaleLabel);

    addAndMakeVisible(vuMeter);
    addAndMakeVisible(waveformDisplay);
    addAndMakeVisible(spectrumAnalyzer);
    addAndMakeVisible(pianoRoll);

    setupCombo(uiScaleBox);
    uiScaleBox.addItem("100%", 1);
    uiScaleBox.addItem("125%", 2);
    uiScaleBox.addItem("150%", 3);
    uiScaleBox.addItem("200%", 4);
    uiScaleBox.setSelectedId(1, juce::dontSendNotification);
    uiScaleBox.onChange = [this]() {
        static const float scales[] = {1.0f, 1.25f, 1.5f, 2.0f};
        int idx = uiScaleBox.getSelectedId() - 1;
        if (idx >= 0 && idx < 4 && onScaleChanged)
            onScaleChanged(scales[idx]);
    };
}

void MainContentComponent::setupLabel(juce::Label& label, const juce::String& text) {
    label.setText(text, juce::dontSendNotification);
    label.setFont(juce::Font(13.0f).boldened());
    label.setColour(juce::Label::textColourId, juce::Colour(0xffccaa44));
    label.setJustificationType(juce::Justification::centred);
    addAndMakeVisible(label);
}

void MainContentComponent::setupKnob(juce::Slider& knob, const juce::String& name) {
    knob.setSliderStyle(juce::Slider::RotaryVerticalDrag);
    knob.setRotaryParameters(juce::MathConstants<float>::pi * 1.2f,
                             juce::MathConstants<float>::pi * 2.8f,
                             true);
    knob.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 55, 16);
    knob.setLookAndFeel(&knobLaf);
    knob.setName(name);
    knob.setColour(juce::Slider::textBoxTextColourId, juce::Colour(0xffcccccc));
    knob.setColour(juce::Slider::textBoxBackgroundColourId, juce::Colour(0xff1a1a1a));
    knob.setColour(juce::Slider::textBoxOutlineColourId, juce::Colours::transparentBlack);
    addAndMakeVisible(knob);
}

void MainContentComponent::setupCombo(juce::ComboBox& combo) {
    combo.setEditableText(false);
    combo.setLookAndFeel(&comboLaf);
    combo.setColour(juce::ComboBox::backgroundColourId, juce::Colour(0xff2a2a2a));
    combo.setColour(juce::ComboBox::textColourId, juce::Colour(0xffcccccc));
    combo.setColour(juce::ComboBox::outlineColourId, juce::Colour(0xff555555));
    combo.setColour(juce::ComboBox::arrowColourId, juce::Colour(0xffccaa44));
    combo.setColour(juce::PopupMenu::backgroundColourId, juce::Colour(0xff2a2a2a));
    combo.setColour(juce::PopupMenu::textColourId, juce::Colour(0xffcccccc));
    combo.setColour(juce::PopupMenu::highlightedBackgroundColourId, juce::Colour(0xffccaa44));
    combo.setColour(juce::PopupMenu::highlightedTextColourId, juce::Colour(0xff1a1a1a));
    addAndMakeVisible(combo);
}

void MainContentComponent::showPresetMenu() {
    juce::PopupMenu menu;

    auto categories = processorRef.presetManager.getCategories();
    int menuId = 1;

    for (auto& cat : categories) {
        juce::PopupMenu subMenu;
        auto presets = processorRef.presetManager.getPresetsInCategory(cat);
        for (auto& preset : presets) {
            subMenu.addItem(menuId++, preset.name);
        }
        menu.addSubMenu(cat, subMenu);
    }

    menu.showMenuAsync(juce::PopupMenu::Options(), [this](int result) {
        if (result > 0) {
            auto& presets = processorRef.presetManager.getPresets();
            int idx = 0;
            for (int i = 0; i < result - 1 && i < static_cast<int>(presets.size()); ++i)
                ++idx;
            if (idx < static_cast<int>(presets.size()))
                processorRef.presetManager.applyPreset(presets[idx].name, processorRef.apvts);
        }
    });
}

void MainContentComponent::paint(juce::Graphics& g) {
    g.fillAll(juce::Colour(0xff1a1a1a));

    // Draw background image tiled
    if (!bgImageLoaded) {
        bgImage = juce::ImageFileFormat::loadFrom(
            BinaryData::chromaflockbg5_png, BinaryData::chromaflockbg5_pngSize);
        bgImageLoaded = true;
    }
    if (bgImage.isValid()) {
        int imgW = bgImage.getWidth();
        int imgH = bgImage.getHeight();
        int compW = getWidth();
        int compH = getHeight();

        g.setOpacity(0.35f);
        for (int x = 0; x < compW; x += imgW)
            for (int y = 0; y < compH; y += imgH)
                g.drawImageAt(bgImage, x, y);
        g.setOpacity(1.0f);
    }

    // Load logo
    if (!logoLoaded) {
        logoImage = juce::ImageFileFormat::loadFrom(
            BinaryData::cfnew_png, BinaryData::cfnew_pngSize);
        logoLoaded = true;
    }
    if (logoImage.isValid()) {
        int logoH = 124;
        int logoW = static_cast<int>(logoH * 805.0 / 119.0);
        int logoX = (getWidth() - logoW) / 2;
        auto logoArea = juce::Rectangle<int>(logoX, 0, logoW, logoH);
        g.drawImage(logoImage, logoArea.toFloat(), juce::RectanglePlacement::centred);
    }

    g.setColour(juce::Colour(0xffccaa44));
    g.drawHorizontalLine(124, 10.0f, 1650.0f);

    auto drawSection = [&](int x, int y, int w, int h) {
        auto rect = juce::Rectangle<float>(static_cast<float>(x), static_cast<float>(y),
                                           static_cast<float>(w), static_cast<float>(h));
        // 3D fill gradient: slight light top, dark bottom
        juce::ColourGradient fillGrad(juce::Colour(0xff222222), rect.getCentreX(), rect.getY(),
                                       juce::Colour(0xff111111), rect.getCentreX(), rect.getBottom(), false);
        g.setGradientFill(fillGrad);
        g.fillRoundedRectangle(rect, 4.0f);
        // Border
        g.setColour(juce::Colour(0xff333333));
        g.drawRoundedRectangle(rect, 4.0f, 1.0f);
    };

    drawSection(10, 128, 498, 200);
    drawSection(520, 128, 620, 200);
    drawSection(1152, 128, 498, 200);
    drawSection(75, 338, 498, 150);
    drawSection(585, 338, 498, 150);
    drawSection(1095, 338, 490, 150);
    drawSection(10, 900, 1640, 128);
    drawSection(10, 1038, 1640, 136);

    // FX sub-sections
    auto drawSubSection = [&](int x, int y, int w, int h, const juce::String& title, int titlePadY = 2) {
        auto rect = juce::Rectangle<float>(static_cast<float>(x), static_cast<float>(y),
                                           static_cast<float>(w), static_cast<float>(h));
        juce::ColourGradient fillGrad(juce::Colour(0xff222222), rect.getCentreX(), rect.getY(),
                                       juce::Colour(0xff111111), rect.getCentreX(), rect.getBottom(), false);
        g.setGradientFill(fillGrad);
        g.fillRoundedRectangle(rect, 3.0f);
        g.setColour(juce::Colour(0xff333333));
        g.drawRoundedRectangle(rect, 3.0f, 1.0f);
        g.setColour(juce::Colour(0xffccaa44));
        g.setFont(juce::Font(11.0f).boldened());
        g.drawText(title, x + 6, y + titlePadY, w - 12, 14, juce::Justification::centred);
    };

    drawSubSection(15, 524, 395, 140, "DISTORTION", 6);
    drawSubSection(420, 524, 660, 140, "COMPRESSOR", 6);
    drawSubSection(1090, 524, 240, 140, "AUTO-PAN", 6);

    // LFO sub-sections
    drawSubSection(15, 690, 530, 150, "LFO 1", 4);
    drawSubSection(560, 690, 530, 150, "LFO 2", 4);

    // FX2 sub-sections
    drawSubSection(1100, 690, 265, 200, "DELAY", 4);
    drawSubSection(1375, 690, 265, 200, "REVERB", 4);
}

void MainContentComponent::resized() {
    auto sectionY = 132;
    auto knobSize = 112;
    auto knobSpacing = 10;
    auto labelH = 18;
    auto comboH = 48;

    osc1Label.setBounds(10, sectionY, 498, labelH);
    osc1WaveBox.setBounds(20, sectionY + labelH + 4, 200, comboH);
    int knobY1 = sectionY + labelH + comboH + 8;
    osc1OctKnob.setBounds(20, knobY1, knobSize, knobSize);
    osc1SemiKnob.setBounds(20 + knobSize + knobSpacing, knobY1, knobSize, knobSize);
    osc1FineKnob.setBounds(20 + (knobSize + knobSpacing) * 2, knobY1, knobSize, knobSize);
    osc1LevelKnob.setBounds(20 + (knobSize + knobSpacing) * 3, knobY1, knobSize, knobSize);

    osc2Label.setBounds(520, sectionY, 620, labelH);
    osc2WaveBox.setBounds(530, sectionY + labelH + 4, 200, comboH);
    int knobY2 = sectionY + labelH + comboH + 8;
    osc2OctKnob.setBounds(530, knobY2, knobSize, knobSize);
    osc2SemiKnob.setBounds(530 + knobSize + knobSpacing, knobY2, knobSize, knobSize);
    osc2FineKnob.setBounds(530 + (knobSize + knobSpacing) * 2, knobY2, knobSize, knobSize);
    osc2LevelKnob.setBounds(530 + (knobSize + knobSpacing) * 3, knobY2, knobSize, knobSize);
    phaseOffsetKnob.setBounds(530 + (knobSize + knobSpacing) * 4, knobY2, knobSize, knobSize);

    filterLabel.setBounds(1152, sectionY, 498, labelH);
    filterTypeBox.setBounds(1162, sectionY + labelH + 4, 200, comboH);
    int knobYF = sectionY + labelH + comboH + 8;
    filterCutoffKnob.setBounds(1162, knobYF, knobSize, knobSize);
    filterResKnob.setBounds(1162 + knobSize + knobSpacing, knobYF, knobSize, knobSize);
    filterEnvAmtKnob.setBounds(1162 + (knobSize + knobSpacing) * 2, knobYF, knobSize, knobSize);
    keyTrackKnob.setBounds(1162 + (knobSize + knobSpacing) * 3, knobYF, knobSize, knobSize);

    envLabel.setBounds(75, 352, 498, labelH);
    int knobYA = 352 + labelH + 6;
    envAttackKnob.setBounds(85, knobYA, knobSize, knobSize);
    envDecayKnob.setBounds(85 + knobSize + knobSpacing, knobYA, knobSize, knobSize);
    envSustainKnob.setBounds(85 + (knobSize + knobSpacing) * 2, knobYA, knobSize, knobSize);
    envReleaseKnob.setBounds(85 + (knobSize + knobSpacing) * 3, knobYA, knobSize, knobSize);

    fEnvLabel.setBounds(585, 352, 498, labelH);
    int knobYFE = 352 + labelH + 6;
    fEnvAttackKnob.setBounds(595, knobYFE, knobSize, knobSize);
    fEnvDecayKnob.setBounds(595 + knobSize + knobSpacing, knobYFE, knobSize, knobSize);
    fEnvSustainKnob.setBounds(595 + (knobSize + knobSpacing) * 2, knobYFE, knobSize, knobSize);
    fEnvReleaseKnob.setBounds(595 + (knobSize + knobSpacing) * 3, knobYFE, knobSize, knobSize);

    globalLabel.setBounds(1095, 352, 490, labelH);
    int knobYM = 352 + labelH + 6;
    panKnob.setBounds(1105, knobYM, knobSize, knobSize);
    driveKnob.setBounds(1105 + knobSize + knobSpacing, knobYM, knobSize, knobSize);
    masterKnob.setBounds(1105 + (knobSize + knobSpacing) * 2, knobYM, knobSize, knobSize);
    pbRangeKnob.setBounds(1105 + (knobSize + knobSpacing) * 3, knobYM, knobSize, knobSize);

    // --- FX SECTION ---
    int fxY = 496;
    int fxKnobSize = 100;
    int fxLabelH = 16;
    int fxKnobY = fxY + 54; // sub-section y(476) + titlePad(6) + titleH(14) + bottomPad(6) + 4

    fxLabel.setBounds(10, fxY + 8, 1640, fxLabelH);

    // Distortion sub-section (X=15, Y=476, W=395, H=140)
    distTypeBox.setBounds(22, fxKnobY + 2, 140, 36);
    distAmountKnob.setBounds(170, fxKnobY, fxKnobSize, fxKnobSize);
    distMixKnob.setBounds(280, fxKnobY, fxKnobSize, fxKnobSize);

    // Compressor sub-section (X=420, Y=476, W=660, H=140)
    compThresholdKnob.setBounds(428, fxKnobY, fxKnobSize, fxKnobSize);
    compRatioKnob.setBounds(538, fxKnobY, fxKnobSize, fxKnobSize);
    compAttackKnob.setBounds(648, fxKnobY, fxKnobSize, fxKnobSize);
    compReleaseKnob.setBounds(758, fxKnobY, fxKnobSize, fxKnobSize);
    compMakeupKnob.setBounds(868, fxKnobY, fxKnobSize, fxKnobSize);
    compMixKnob.setBounds(978, fxKnobY, fxKnobSize, fxKnobSize);

    // Auto-Pan sub-section (X=1090, Y=476, W=240, H=140)
    autoPanRateKnob.setBounds(1098, fxKnobY, fxKnobSize, fxKnobSize);
    autoPanDepthKnob.setBounds(1098 + fxKnobSize + 8, fxKnobY, fxKnobSize, fxKnobSize);

    // --- LFO SECTION ---
    int lfoY = 670;
    int lfoKnobSize = 80;
    int lfoKnobY = 690 + 4 + 14 + 6; // subSectionY + titlePad + titleH + bottomPad

    lfoLabel.setBounds(10, lfoY, 1080, fxLabelH);
    fx2Label.setBounds(1100, lfoY, 540, fxLabelH);

    // LFO 1 sub-section (X=15, Y=642, W=530, H=110)
    lfo1RateKnob.setBounds(22, lfoKnobY, lfoKnobSize, lfoKnobSize);
    lfo1DepthKnob.setBounds(132, lfoKnobY, lfoKnobSize, lfoKnobSize);
    lfo1ShapeBox.setBounds(242, lfoKnobY + 2, 130, 36);
    lfo1DestBox.setBounds(382, lfoKnobY + 2, 155, 36);

    // LFO 2 sub-section (X=560, Y=642, W=530, H=110)
    lfo2RateKnob.setBounds(568, lfoKnobY, lfoKnobSize, lfoKnobSize);
    lfo2DepthKnob.setBounds(678, lfoKnobY, lfoKnobSize, lfoKnobSize);
    lfo2ShapeBox.setBounds(788, lfoKnobY + 2, 130, 36);
    lfo2DestBox.setBounds(928, lfoKnobY + 2, 155, 36);

    // --- FX2 SECTION ---
    int fx2KnobY = lfoKnobY;

    // Delay sub-section (X=1100, Y=690, W=265, H=130)
    // Row 1: TIME, FBACK, MIX
    delayTimeKnob.setBounds(1110, fx2KnobY, lfoKnobSize, lfoKnobSize);
    delayFbKnob.setBounds(1198, fx2KnobY, lfoKnobSize, lfoKnobSize);
    delayMixKnob.setBounds(1286, fx2KnobY, lfoKnobSize, lfoKnobSize);
    // Row 2: PINGPONG combo, SPREAD knob
    int delayRow2Y = fx2KnobY + lfoKnobSize + 4;
    delayPingPongBox.setBounds(1110, delayRow2Y, 100, 36);
    delaySpreadKnob.setBounds(1220, delayRow2Y, lfoKnobSize, lfoKnobSize);

    // Reverb sub-section (X=1375, Y=690, W=265, H=130)
    reverbSizeKnob.setBounds(1383, fx2KnobY, lfoKnobSize, lfoKnobSize);
    reverbDampKnob.setBounds(1468, fx2KnobY, lfoKnobSize, lfoKnobSize);
    reverbMixKnob.setBounds(1553, fx2KnobY, lfoKnobSize, lfoKnobSize);

    // --- PRESET + SCALE ---
    presetButton.setBounds(20, 8, 120, 28);
    scaleLabel.setBounds(1520, 10, 42, 20);
    uiScaleBox.setBounds(1566, 8, 80, 24);

    // Visualizer area
    int vizY = 900;
    int vizH = 128;
    vuMeter.setBounds(14, vizY, 40, vizH);
    waveformDisplay.setBounds(60, vizY, 780, vizH);
    spectrumAnalyzer.setBounds(850, vizY, 794, vizH);

    // Piano roll
    pianoRoll.setBounds(10, 1038, 1640, 136);
}

// ===== PianoRollComponent =====

int PianoRollComponent::midiNoteForKey(int index) const {
    // Map key index to MIDI note, skipping black keys in the index
    // White keys: C D E F G A B (indices 0,2,4,5,7,9,11 in chromatic scale)
    static const int whiteToChromatic[] = {0, 2, 4, 5, 7, 9, 11};
    int octave = index / 7;
    int noteInOctave = index % 7;
    return firstMidiNote + octave * 12 + whiteToChromatic[noteInOctave];
}

bool PianoRollComponent::isBlackKey(int midiNote) const {
    int noteInOctave = midiNote % 12;
    return noteInOctave == 1 || noteInOctave == 3 || noteInOctave == 6 ||
           noteInOctave == 8 || noteInOctave == 10;
}

int PianoRollComponent::keyAtPosition(juce::Point<int> pos) const {
    int pbRight = pitchBendWidth;
    int keysX = pbRight + 8;
    int keysWidth = getWidth() - keysX - 6;
    int numWhiteKeys = (numKeys * 7) / 12 + 1;
    int localWhiteKeyWidth = keysWidth / numWhiteKeys;

    // Check black keys first (they're on top)
    for (int i = 0; i < numKeys; ++i) {
        int note = firstMidiNote + i;
        if (!isBlackKey(note)) continue;

        int chroma = note % 12;
        int octave = (note - firstMidiNote) / 12;
        static const int blackToLeftWhite[] = { 0,0,1,1,2,3,3,4,4,5,5,6 };
        int leftWhite = blackToLeftWhite[chroma];
        int whiteIndexBefore = octave * 7 + leftWhite;

        float bx = static_cast<float>(keysX + (whiteIndexBefore + 1) * localWhiteKeyWidth) - static_cast<float>(blackKeyWidth) * 0.5f;
        auto blackRect = juce::Rectangle<int>(static_cast<int>(bx), 0, blackKeyWidth, blackKeyHeight);
        if (blackRect.contains(pos))
            return note;
    }

    // Check white keys
    for (int i = 0; i < numKeys; ++i) {
        int note = firstMidiNote + i;
        if (isBlackKey(note)) continue;

        int chroma = note % 12;
        int octave = (note - firstMidiNote) / 12;
        int whiteIndex = octave * 7;
        if (chroma == 2) whiteIndex += 1;
        else if (chroma == 4) whiteIndex += 2;
        else if (chroma == 5) whiteIndex += 3;
        else if (chroma == 7) whiteIndex += 4;
        else if (chroma == 9) whiteIndex += 5;
        else if (chroma == 11) whiteIndex += 6;

        auto whiteRect = juce::Rectangle<int>(keysX + whiteIndex * localWhiteKeyWidth, 0, localWhiteKeyWidth, whiteKeyHeight);
        if (whiteRect.contains(pos))
            return note;
    }

    return -1;
}

void PianoRollComponent::triggerNote(int note, bool on) {
    if (on)
        processor.noteOn(note, 0.8f);
    else
        processor.noteOff(note);
}

void PianoRollComponent::paint(juce::Graphics& g) {
    int pbRight = pitchBendWidth;
    int keysX = pbRight + 8;
    int keysWidth = getWidth() - keysX - 6;
    int numWhiteKeys = (numKeys * 7) / 12 + 1;
    whiteKeyWidth = keysWidth / numWhiteKeys;

    // Draw pitch bend strip background
    g.setColour(juce::Colour(0xff222222));
    g.fillRect(10, 0, pbRight - 10, whiteKeyHeight);

    // Right edge border to separate from keys
    g.setColour(juce::Colour(0xff444444));
    g.drawVerticalLine(pbRight - 8, 0.0f, static_cast<float>(whiteKeyHeight));

    // Active bend area indicator (shaded region from center to current position)
    int pbCenter = whiteKeyHeight / 2;
    float pbNorm = static_cast<float>(currentPitchBend - 64) / 64.0f; // -1..+1
    int pbY = pbCenter - static_cast<int>(pbNorm * static_cast<float>(pbCenter - 8));

    g.setColour(juce::Colour(0x40ccaa44));
    if (pbY < pbCenter)
        g.fillRect(14, pbY, 18, pbCenter - pbY);
    else
        g.fillRect(14, pbCenter, 18, pbY - pbCenter);

    // Pitch bend center line
    g.setColour(juce::Colour(0xffccaa44));
    g.drawHorizontalLine(pbCenter, 12.0f, static_cast<float>(pbRight - 14));

    // Pitch bend indicator dot
    g.setColour(juce::Colour(0xffccaa44));
    g.fillEllipse(14, pbY - 5, 18, 10);

    // PB labels
    g.setColour(juce::Colour(0xff888888));
    g.setFont(9.0f);
    g.drawText("+", 14, 2, 18, 12, juce::Justification::centred);
    g.drawText("-", 14, whiteKeyHeight - 14, 18, 12, juce::Justification::centred);

    // Draw white keys first
    for (int i = 0; i < numKeys; ++i) {
        int note = firstMidiNote + i;
        if (isBlackKey(note)) continue;

        int chroma = note % 12;
        int octave = (note - firstMidiNote) / 12;
        int whiteIndex = octave * 7;
        if (chroma == 2) whiteIndex += 1;
        else if (chroma == 4) whiteIndex += 2;
        else if (chroma == 5) whiteIndex += 3;
        else if (chroma == 7) whiteIndex += 4;
        else if (chroma == 9) whiteIndex += 5;
        else if (chroma == 11) whiteIndex += 6;

        int kx = keysX + whiteIndex * whiteKeyWidth;
        auto rect = juce::Rectangle<int>(kx, 0, whiteKeyWidth - 1, whiteKeyHeight);

        bool pressed = processor.isNoteActive(note);
        if (pressed)
            g.setColour(juce::Colour(0xffccaa44));
        else
            g.setColour(juce::Colour(0xffdddddd));
        g.fillRect(rect);

        g.setColour(juce::Colour(0xff555555));
        g.drawRect(rect, 1);

        // Draw note name on C keys
        if (chroma == 0) {
            g.setColour(juce::Colour(0xff666666));
            g.setFont(9.0f);
            g.drawText("C" + juce::String(octave + 3), kx + 2, whiteKeyHeight - 14, whiteKeyWidth - 4, 12,
                       juce::Justification::centred);
        }
    }

    // Draw black keys on top
    for (int i = 0; i < numKeys; ++i) {
        int note = firstMidiNote + i;
        if (!isBlackKey(note)) continue;

        int chroma = note % 12;
        int octave = (note - firstMidiNote) / 12;
        // chroma → white key index to the LEFT of this black key
        static const int blackToLeftWhite[] = { 0,0,1,1,2,3,3,4,4,5,5,6 };
        int leftWhite = blackToLeftWhite[chroma];
        int whiteIndexBefore = octave * 7 + leftWhite;

        float bx = static_cast<float>(keysX + (whiteIndexBefore + 1) * whiteKeyWidth) - static_cast<float>(blackKeyWidth) * 0.5f;
        auto rect = juce::Rectangle<int>(static_cast<int>(bx), 0, blackKeyWidth, blackKeyHeight);

        bool pressed = processor.isNoteActive(note);
        if (pressed)
            g.setColour(juce::Colour(0xffccaa44));
        else
            g.setColour(juce::Colour(0xff333333));
        g.fillRect(rect);

        g.setColour(juce::Colour(0xff555555));
        g.drawRect(rect, 1);
    }
}

void PianoRollComponent::mouseDown(const juce::MouseEvent& e) {
    auto pos = e.getPosition();

    // Pitch bend strip
    if (pos.x < pitchBendWidth) {
        float pbNorm = 1.0f - static_cast<float>(pos.y) / static_cast<float>(whiteKeyHeight);
        pbNorm = std::clamp(pbNorm, -1.0f, 1.0f);
        currentPitchBend = 64 + static_cast<int>(pbNorm * 64.0f);
        processor.pitchBendValue.store(pbNorm);
        return;
    }

    int note = keyAtPosition(pos);
    if (note >= 0) {
        if (lastTriggeredNote >= 0 && lastTriggeredNote != note)
            triggerNote(lastTriggeredNote, false);
        lastTriggeredNote = note;
        triggerNote(note, true);
        repaint();
    }
}

void PianoRollComponent::mouseUp(const juce::MouseEvent& e) {
    // Reset pitch bend to center on release
    if (e.getPosition().x < pitchBendWidth || lastTriggeredNote < 0) {
        if (e.getPosition().x < pitchBendWidth) {
            currentPitchBend = 64;
            processor.pitchBendValue.store(0.0f);
            repaint();
        }
        if (lastTriggeredNote < 0) return;
    }

    if (lastTriggeredNote >= 0) {
        triggerNote(lastTriggeredNote, false);
        lastTriggeredNote = -1;
        repaint();
    }
}

void PianoRollComponent::mouseDrag(const juce::MouseEvent& e) {
    auto pos = e.getPosition();

    // Pitch bend drag
    if (pos.x < pitchBendWidth) {
        float pbNorm = 1.0f - static_cast<float>(pos.y) / static_cast<float>(whiteKeyHeight);
        pbNorm = std::clamp(pbNorm, -1.0f, 1.0f);
        currentPitchBend = 64 + static_cast<int>(pbNorm * 64.0f);
        processor.pitchBendValue.store(pbNorm);
        repaint();
        return;
    }

    int note = keyAtPosition(pos);
    if (note >= 0 && note != lastTriggeredNote) {
        if (lastTriggeredNote >= 0)
            triggerNote(lastTriggeredNote, false);
        lastTriggeredNote = note;
        triggerNote(note, true);
        repaint();
    }
}

ChromaFlockEditor::ChromaFlockEditor(ChromaFlockProcessor& p)
    : AudioProcessorEditor(&p), content(p) {
    addAndMakeVisible(content);
    content.onScaleChanged = [this](float s) { setUIScale(s); };
    setSize(baseWidth, baseHeight);
    setResizable(false, false);
}

ChromaFlockEditor::~ChromaFlockEditor() {}

void ChromaFlockEditor::paint(juce::Graphics& g) {
    g.fillAll(juce::Colour(0xff1a1a1a));
}

void ChromaFlockEditor::resized() {
    content.setBounds(0, 0, baseWidth, baseHeight);
    content.setTransform(juce::AffineTransform::scale(currentScale, currentScale));
}

void ChromaFlockEditor::setUIScale(float newScale) {
    currentScale = newScale;
    setSize(static_cast<int>(baseWidth * newScale), static_cast<int>(baseHeight * newScale));
}
