#include "PluginEditor.h"

using namespace juce;

static Font font(float h, float s = 1.0f) { return Font(FontOptions(h * s)); }
static Font fontBold(float h, float s = 1.0f) { return Font(FontOptions(h * s).withStyle("Bold")); }

static const char* trackNames[]  = {"BD", "RS", "SN", "CL", "CH", "PH", "OH", "RD", "LT", "MT"};
static const char* noteNames[]   = {"C4","C#4","D4","D#4","E4","F4","F#4","G4","G#4","A4"};

static constexpr int ROW_H   = 52;
static constexpr int TOP     = 82;
static constexpr int COL_LBL = 8;
static constexpr int COL_KNOB = 44;
static constexpr int KNOB_W   = 44;
static constexpr int KNOB_GAP = 4;
static constexpr int COL_DECAY = COL_KNOB + 6 * (KNOB_W + KNOB_GAP) + 8;
static constexpr int COL_FILE  = COL_DECAY + 78;
static constexpr int SAMPLE_W  = 110;
static constexpr int COL_DELAY = COL_FILE + SAMPLE_W + 8;
static constexpr int COL_REVERB = COL_DELAY + KNOB_W + KNOB_GAP;
static constexpr int COL_RINGMOD = COL_REVERB + KNOB_W + KNOB_GAP;
static constexpr int COL_DESTR = COL_RINGMOD + KNOB_W + KNOB_GAP;
static constexpr int COL_SHUFFLE = COL_DESTR + KNOB_W + KNOB_GAP;
static constexpr int TOTAL_W = COL_SHUFFLE + KNOB_W;

// Header knob x positions (compact layout)
static constexpr int HDR_KNOB  = 36;
static constexpr int HDR_GAP   = 4;
static constexpr int HDR_VOL   = 356;
static constexpr int HDR_PAN   = HDR_VOL   + HDR_KNOB + HDR_GAP;
static constexpr int HDR_RING  = HDR_PAN   + HDR_KNOB + HDR_GAP;
static constexpr int HDR_DEST  = HDR_RING  + HDR_KNOB + HDR_GAP;
static constexpr int HDR_DELAY = HDR_DEST  + HDR_KNOB + HDR_GAP;
static constexpr int HDR_REVERB= HDR_DELAY + HDR_KNOB + HDR_GAP;
static constexpr int HDR_DLYSYNC= HDR_REVERB+ HDR_KNOB + HDR_GAP;
static constexpr int HDR_DIV   = HDR_DLYSYNC+ HDR_KNOB + HDR_GAP + 12;
static constexpr int HDR_SHFL  = HDR_DIV   + HDR_KNOB + HDR_GAP;
static constexpr int HDR_GRV   = HDR_SHFL  + HDR_KNOB + HDR_GAP;

// ── LookAndFeel ──────────────────────────────────────────────────

void TrommelkisteLookAndFeel::drawRotarySlider(Graphics& g, int x, int y, int w, int h,
                                               float sliderPos, float startAngle, float endAngle,
                                               Slider&)
{
    auto bounds = Rectangle<int>(x, y, w, h).toFloat();
    auto radius = jmin(bounds.getWidth(), bounds.getHeight()) / 2.0f - 3.0f;
    auto cx = bounds.getCentreX();
    auto cy = bounds.getCentreY();
    auto angle = startAngle + sliderPos * (endAngle - startAngle);

    // Outer shadow
    {
        ColourGradient grad(Colour(0x40000000), cx, cy + radius * 0.15f,
                            Colour(0x00000000), cx, cy + radius * 0.6f, true);
        g.setGradientFill(grad);
        g.fillEllipse(cx - radius - 1.0f, cy - radius + 2.0f, (radius + 1.0f) * 2.0f, (radius + 1.0f) * 2.0f);
    }

    // Main body — vertical gradient (light top, dark bottom) for 3D cylinder feel
    {
        ColourGradient grad(Colour(0xFF323232), cx, cy - radius,
                            Colour(0xFF1C1C1C), cx, cy + radius, true);
        grad.addColour(0.10, Colour(0x883D3D3D));
        g.setGradientFill(grad);
        g.fillEllipse(cx - radius, cy - radius, radius * 2.0f, radius * 2.0f);
    }

    // Subtle specular highlight blob (top-left)
    {
        ColourGradient spec(Colour(0x18FFFFFF), cx - radius * 0.35f, cy - radius * 0.5f,
                            Colour(0x00FFFFFF), cx - radius * 0.1f, cy - radius * 0.1f, true);
        spec.isRadial = true;
        g.setGradientFill(spec);
        g.fillEllipse(cx - radius * 0.7f, cy - radius * 0.85f, radius * 0.9f, radius * 0.7f);
    }

    // Indicator arc
    {
        Path arc;
        arc.addCentredArc(cx, cy, radius - 3.0f, radius - 3.0f, 0.0f, startAngle, angle, true);
        g.setColour(accent);
        g.strokePath(arc, PathStrokeType(2.5f, PathStrokeType::curved, PathStrokeType::rounded));
    }

    // Pointer / indicator line
    {
        Path ptr;
        ptr.addRectangle(-1.25f, -radius + 2.0f, 2.5f, radius * 0.5f);
        ptr.applyTransform(AffineTransform::rotation(angle).translated(cx, cy));
        g.setColour(Colours::white);
        g.fillPath(ptr);
    }

    // Center cap — small raised circle
    {
        ColourGradient cap(Colour(0xFF414141), cx, cy - radius * 0.22f,
                           Colour(0xFF313131), cx, cy + radius * 0.22f, true);
        g.setGradientFill(cap);
        g.fillEllipse(cx - radius * 0.22f, cy - radius * 0.22f, radius * 0.44f, radius * 0.44f);
    }
}

void TrommelkisteLookAndFeel::drawButtonBackground(Graphics& g, Button& btn, const Colour&,
                                                   bool over, bool down)
{
    auto bounds = btn.getLocalBounds().toFloat();
    Colour fill;
    if (btn.getButtonText() == "X")
        fill = down ? Colour(0xFFCC3333) : (over ? Colour(0xFFAA3333) : Colour(0xFF663333));
    else
        fill = down ? accent.darker(0.3f) : (over ? accent.brighter(0.2f) : accent);

    g.setColour(fill);
    g.fillRoundedRectangle(bounds, 3.0f);
}

void TrommelkisteLookAndFeel::drawComboBox(Graphics& g, int w, int h, bool,
                                           int, int, int, int, ComboBox& box)
{
    auto bounds = Rectangle<float>(0, 0, (float)w, (float)h);
    g.setColour(Colour(0xFF333333));
    g.fillRoundedRectangle(bounds, 3.0f);
    g.setColour(Colour(0xFF555555));
    g.drawRoundedRectangle(bounds, 3.0f, 1.0f);

    Path arrow;
    arrow.addTriangle((float)w - 13.0f, (float)h * 0.5f - 2.5f,
                      (float)w - 13.0f, (float)h * 0.5f + 2.5f,
                      (float)w - 7.0f,  (float)h * 0.5f);
    g.setColour(Colours::white);
    g.fillPath(arrow);
}

void TrommelkisteLookAndFeel::positionComboBoxText(ComboBox& box, Label& label)
{
    label.setBounds(6, 0, box.getWidth() - 22, box.getHeight());
    label.setFont(font(11.0f, fontScale));
}

void TrommelkisteLookAndFeel::drawToggleButton(Graphics& g, ToggleButton& btn,
                                               bool /*isMouseOver*/, bool /*isButtonDown*/)
{
    auto bounds = btn.getLocalBounds().toFloat();
    const bool on = btn.getToggleState();
    const float corner = 4.0f;

    // Track background
    g.setColour(Colour(0xFF2A2A2A));
    g.fillRoundedRectangle(bounds, corner);

    // Active half highlight
    auto half = bounds;
    half.setWidth(half.getWidth() * 0.5f);
    if (on)
        half.setX(half.getX() + half.getWidth());

    g.setColour(Colour(0xFF444444));
    g.fillRoundedRectangle(half.expanded(-1.0f, -1.0f), corner - 1.0f);

    // Thumb / knob
    auto thumb = bounds;
    thumb.setWidth(thumb.getWidth() * 0.5f);
    if (on)
        thumb.setX(thumb.getX() + thumb.getWidth());
    thumb = thumb.reduced(2.0f);

    ColourGradient thumbGrad(Colour(0xFF555555), thumb.getCentreX(), thumb.getY(),
                             Colour(0xFF333333), thumb.getCentreX(), thumb.getBottom(), true);
    g.setGradientFill(thumbGrad);
    g.fillRoundedRectangle(thumb, 3.0f);

    // Labels
    g.setColour(on ? Colours::white.withAlpha(0.4f) : Colours::white.withAlpha(0.9f));
    g.setFont(fontBold(8.0f, fontScale));
    g.drawText("SAW", bounds.getX(), bounds.getY(),
               bounds.getWidth() * 0.5f, bounds.getHeight(), Justification::centred);

    g.setColour(on ? Colours::white.withAlpha(0.9f) : Colours::white.withAlpha(0.4f));
    g.drawText("GATE", bounds.getX() + bounds.getWidth() * 0.5f, bounds.getY(),
               bounds.getWidth() * 0.5f, bounds.getHeight(), Justification::centred);
}

// ── Editor ───────────────────────────────────────────────────────

TrommelkisteEditor::TrommelkisteEditor(TrommelkisteProcessor& p)
    : AudioProcessorEditor(p), proc(p)
{
    setLookAndFeel(&lnf);
    lnf.fontScale = 1.5f;
    uiScale = 1.5f;
    setSize(scaled(TOTAL_W), scaled(TOP + NUM_TRACKS * ROW_H));
    setResizable(false, false);
    setWantsKeyboardFocus(false);

    for (int i = 0; i < NUM_TRACKS; ++i)
    {
        auto& t = ui[i];
        const String pfx = "t" + String(i) + "_";

        auto setupKnob = [&](Slider& s, const String& /*id*/)
        {
            s.setSliderStyle(Slider::RotaryHorizontalVerticalDrag);
            s.setTextBoxStyle(Slider::TextBoxBelow, false, 44, 15);
            s.setColour(Slider::textBoxTextColourId, Colours::white);
            s.setColour(Slider::textBoxOutlineColourId, Colours::transparentBlack);
            s.setColour(Slider::textBoxBackgroundColourId, Colours::transparentBlack);
            addAndMakeVisible(s);
        };

        setupKnob(t.level,    "level");
        setupKnob(t.length,   "length");
        setupKnob(t.velocity, "velocity");
        setupKnob(t.pitch,    "pitch");
        setupKnob(t.tone,     "tone");
        setupKnob(t.pan,      "pan");
        setupKnob(t.delaySend,  "delaySend");
        setupKnob(t.reverbSend, "reverbSend");
        setupKnob(t.ringMod,    "ringMod");
        setupKnob(t.destruction,"destruction");
        setupKnob(t.shuffle,    "shuffle");

        t.level.formatFn    = [](double v) { return String(v, 1); };
        t.length.formatFn   = [](double v) { return String(v, 1) + "s"; };
        t.velocity.formatFn = [](double v) { return String(v, 1); };
        t.pitch.formatFn    = [](double v) { return String(v, 1); };
        t.tone.formatFn     = [](double v) -> String {
            return v >= 1000.0 ? String(v / 1000.0, 1) + "k" : String((int) v);
        };
        t.pan.formatFn      = [](double v) -> String {
            if (std::abs(v) < 0.05) return "C";
            int pct = (int) std::round(std::abs(v) * 100.0);
            return (v < 0 ? "L" : "R") + String(pct);
        };
        t.delaySend.formatFn  = [](double v) { return String(v, 1); };
        t.reverbSend.formatFn = [](double v) { return String(v, 1); };
        t.ringMod.formatFn    = [](double v) { return String(v, 1); };
        t.destruction.formatFn = [](double v) { return String(v, 1); };
        t.shuffle.formatFn    = [](double v) { return String(v, 1); };

        t.levelAtt    = std::make_unique<TrackUI::SliderAtt>(proc.apvts, pfx + "level",    t.level);
        t.lengthAtt   = std::make_unique<TrackUI::SliderAtt>(proc.apvts, pfx + "length",   t.length);
        t.velAtt      = std::make_unique<TrackUI::SliderAtt>(proc.apvts, pfx + "velocity", t.velocity);
        t.pitchAtt    = std::make_unique<TrackUI::SliderAtt>(proc.apvts, pfx + "pitch",    t.pitch);
        t.toneAtt     = std::make_unique<TrackUI::SliderAtt>(proc.apvts, pfx + "tone",     t.tone);
        t.panAtt      = std::make_unique<TrackUI::SliderAtt>(proc.apvts, pfx + "pan",      t.pan);
        t.delaySendAtt  = std::make_unique<TrackUI::SliderAtt>(proc.apvts, pfx + "delaySend",  t.delaySend);
        t.reverbSendAtt = std::make_unique<TrackUI::SliderAtt>(proc.apvts, pfx + "reverbSend", t.reverbSend);
        t.ringModAtt    = std::make_unique<TrackUI::SliderAtt>(proc.apvts, pfx + "ringMod",    t.ringMod);
        t.destructionAtt = std::make_unique<TrackUI::SliderAtt>(proc.apvts, pfx + "destruction", t.destruction);
        t.shuffleAtt    = std::make_unique<TrackUI::SliderAtt>(proc.apvts, pfx + "shuffle",    t.shuffle);

        t.decay.setClickingTogglesState(true);
        t.decay.setButtonText("");
        t.decay.setColour(ToggleButton::textColourId, Colours::white);
        t.decay.setColour(ToggleButton::tickColourId, lnf.accent);
        addAndMakeVisible(t.decay);
        t.decayAtt = std::make_unique<TrackUI::ButtonAtt>(proc.apvts, pfx + "decay", t.decay);

        t.prevBtn.onClick = [this, i] {
            proc.prevSample(i);
            updateFileLabel(i);
        };
        addAndMakeVisible(t.prevBtn);

        t.nextBtn.onClick = [this, i] {
            proc.nextSample(i);
            updateFileLabel(i);
        };
        addAndMakeVisible(t.nextBtn);

        t.fileLabel.setFont(font(10.5f, uiScale));
        t.fileLabel.setColour(Label::textColourId, Colour(0xFF888888));
        t.fileLabel.setJustificationType(Justification::centredLeft);
        t.fileLabel.setEditable(false);
        t.fileLabel.addMouseListener(this, true);
        addAndMakeVisible(t.fileLabel);

        updateFileLabel(i);
    }

    // Preset selector
    refreshPresetCombo();
    presetCombo.setColour(ComboBox::backgroundColourId, Colour(0xFF333333));
    presetCombo.setColour(ComboBox::textColourId, Colours::white);
    presetCombo.setColour(ComboBox::outlineColourId, Colour(0xFF555555));
    presetCombo.onChange = [this] {
        int id = presetCombo.getSelectedId();
        if (id >= 1)
        {
            proc.loadKit(id - 1);
            for (int i = 0; i < NUM_TRACKS; ++i)
                updateFileLabel(i);
        }
    };
    addAndMakeVisible(presetCombo);

    presetPrevBtn.onClick = [this] {
        proc.prevKit();
        refreshPresetCombo();
        for (int i = 0; i < NUM_TRACKS; ++i)
            updateFileLabel(i);
    };
    addAndMakeVisible(presetPrevBtn);

    presetNextBtn.onClick = [this] {
        proc.nextKit();
        refreshPresetCombo();
        for (int i = 0; i < NUM_TRACKS; ++i)
            updateFileLabel(i);
    };
    addAndMakeVisible(presetNextBtn);

    scaleCombo.addItemList({"100%", "125%", "150%", "175%", "200%"}, 1);
    scaleCombo.setSelectedId(3, dontSendNotification);
    scaleCombo.setColour(ComboBox::backgroundColourId, Colour(0xFF333333));
    scaleCombo.setColour(ComboBox::textColourId, Colours::white);
    scaleCombo.setColour(ComboBox::outlineColourId, Colour(0xFF555555));
    scaleCombo.onChange = [this] { scaleChanged(); };
    addAndMakeVisible(scaleCombo);

    // Global effect knobs in header
    auto setupHeaderKnob = [&](KnobSlider& s, const String& id, const String& paramId)
    {
        s.setSliderStyle(Slider::RotaryHorizontalVerticalDrag);
        s.setTextBoxStyle(Slider::TextBoxBelow, false, 40, 14);
        s.setColour(Slider::textBoxTextColourId, Colours::white);
        s.setColour(Slider::textBoxOutlineColourId, Colours::transparentBlack);
        s.setColour(Slider::textBoxBackgroundColourId, Colours::transparentBlack);
        addAndMakeVisible(s);
    };

    setupHeaderKnob(globalRingFreq, "RF", "global_ringFreq");
    setupHeaderKnob(globalDestruction, "DEST", "global_destruction");
    globalRingFreq.setTextBoxStyle(Slider::NoTextBox, false, 0, 0);
    globalDestruction.setTextBoxStyle(Slider::NoTextBox, false, 0, 0);
    globalRingFreq.formatFn = [](double v) { return String((int)v) + "Hz"; };
    globalDestruction.formatFn = [](double v) { return String(v, 1); };
    globalRingFreqAtt = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(proc.apvts, "global_ringFreq", globalRingFreq);
    globalDestructionAtt = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(proc.apvts, "global_destruction", globalDestruction);

    setupHeaderKnob(masterVolume, "VOL", "master_volume");
    setupHeaderKnob(masterPan, "PAN", "master_pan");
    masterVolume.setTextBoxStyle(Slider::NoTextBox, false, 0, 0);
    masterPan.setTextBoxStyle(Slider::NoTextBox, false, 0, 0);
    masterVolume.formatFn = [](double v) { return String(v, 1); };
    masterPan.formatFn = [](double v) -> String {
        if (std::abs(v) < 0.05) return "C";
        int pct = (int) std::round(std::abs(v) * 100.0);
        return (v < 0 ? "L" : "R") + String(pct);
    };
    masterVolumeAtt = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(proc.apvts, "master_volume", masterVolume);
    masterPanAtt = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(proc.apvts, "master_pan", masterPan);

    setupHeaderKnob(masterDelay, "DELAY", "master_delay");
    setupHeaderKnob(masterReverb, "REVERB", "master_reverb");
    masterDelay.setTextBoxStyle(Slider::NoTextBox, false, 0, 0);
    masterReverb.setTextBoxStyle(Slider::NoTextBox, false, 0, 0);
    masterDelay.formatFn = [](double v) { return String(v, 1); };
    masterReverb.formatFn = [](double v) { return String(v, 1); };
    masterDelayAtt = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(proc.apvts, "master_delay", masterDelay);
    masterReverbAtt = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(proc.apvts, "master_reverb", masterReverb);

    // Shuffle section knobs
    globalShuffleDiv.setSliderStyle(Slider::RotaryHorizontalVerticalDrag);
    globalShuffleDiv.setTextBoxStyle(Slider::NoTextBox, false, 0, 0);
    globalShuffleDiv.setColour(Slider::textBoxTextColourId, Colours::white);
    globalShuffleDiv.setColour(Slider::textBoxOutlineColourId, Colours::transparentBlack);
    globalShuffleDiv.setColour(Slider::textBoxBackgroundColourId, Colours::transparentBlack);
    globalShuffleDiv.setRange(0, 4, 1);
    globalShuffleDiv.setScrollWheelEnabled(false);
    globalShuffleDiv.formatFn = [](double v) {
        static const char* labels[] = { "OFF", "4", "8", "16", "32" };
        return labels[juce::jlimit(0, 4, (int)v)];
    };
    addAndMakeVisible(globalShuffleDiv);

    setupHeaderKnob(globalShuffleAmt, "SHFL", "global_shuffleAmount");
    setupHeaderKnob(globalGroove, "GRV", "global_groove");
    globalShuffleAmt.setTextBoxStyle(Slider::NoTextBox, false, 0, 0);
    globalGroove.setTextBoxStyle(Slider::NoTextBox, false, 0, 0);
    globalShuffleAmt.formatFn = [](double v) { return String(v, 1); };
    globalGroove.formatFn = [](double v) { return String(v, 1); };

    globalShuffleDivAtt = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(proc.apvts, "global_shuffleDiv", globalShuffleDiv);
    globalShuffleAmtAtt = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(proc.apvts, "global_shuffleAmount", globalShuffleAmt);
    globalGrooveAtt = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(proc.apvts, "global_groove", globalGroove);

    // Delay sync knob
    delaySyncDiv.setSliderStyle(Slider::RotaryHorizontalVerticalDrag);
    delaySyncDiv.setTextBoxStyle(Slider::NoTextBox, false, 0, 0);
    delaySyncDiv.setColour(Slider::textBoxTextColourId, Colours::white);
    delaySyncDiv.setColour(Slider::textBoxOutlineColourId, Colours::transparentBlack);
    delaySyncDiv.setColour(Slider::textBoxBackgroundColourId, Colours::transparentBlack);
    delaySyncDiv.setRange(0, 8, 1);
    delaySyncDiv.setScrollWheelEnabled(false);
    delaySyncDiv.formatFn = [](double v) {
        static const char* labels[] = { "OFF", "1/4", "1/2", "3/4", "1/1", "5/4", "6/4", "7/4", "2/1" };
        return labels[juce::jlimit(0, 8, (int)v)];
    };
    addAndMakeVisible(delaySyncDiv);
    delaySyncDivAtt = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(proc.apvts, "delay_syncDiv", delaySyncDiv);

    startTimerHz(30);
    resized();
}

TrommelkisteEditor::~TrommelkisteEditor()
{
    setLookAndFeel(nullptr);
}

void TrommelkisteEditor::timerCallback()
{
    const double now = juce::Time::getMillisecondCounterHiRes() / 1000.0;
    for (int i = 0; i < NUM_TRACKS; ++i)
    {
        if (proc.trackActive[i].exchange(false))
            trackActivatedTime[i] = now;
    }
    repaint();
}

void TrommelkisteEditor::scaleChanged()
{
    static const float scales[] = { 1.0f, 1.25f, 1.5f, 1.75f, 2.0f };
    int selId = scaleCombo.getSelectedId();
    if (selId >= 1 && selId <= 5)
    {
        uiScale = scales[selId - 1];
        lnf.fontScale = uiScale;
    setSize(scaled(TOTAL_W), scaled(TOP + NUM_TRACKS * ROW_H));
    }
}

void TrommelkisteEditor::updateFileLabel(int idx)
{
    if (idx < 0 || idx >= NUM_TRACKS) return;
    const int ci = proc.currentSampleIndex[idx];
    if (ci >= 0 && ci < proc.trackSamples[idx].size())
        ui[idx].fileLabel.setText(proc.trackSamples[idx][ci].name,
                                  dontSendNotification);
    else
        ui[idx].fileLabel.setText("--", dontSendNotification);
}

void TrommelkisteEditor::refreshPresetCombo()
{
    presetCombo.clear(dontSendNotification);
    const int n = proc.getNumKits();
    for (int i = 0; i < n; ++i)
        presetCombo.addItem(proc.getKitList()[i].name, i + 1);
    presetCombo.setSelectedId(proc.currentKitIndex + 1, dontSendNotification);
}

void TrommelkisteEditor::paint(Graphics& g)
{
    g.fillAll(lnf.bg);

    g.setColour(lnf.accent);
    g.setFont(fontBold(16.0f, uiScale));
    g.drawText("TROMMELKISTE", scaled(10), scaled(4), scaled(400), scaled(20), Justification::centredLeft);

    // VU meter
    {
        const float level = proc.outputLevel.load(std::memory_order_relaxed);
        const float meterX = 10.0f;
        const float meterY = 30.0f;
        const float meterW = 134.0f;
        const float meterH = 6.0f;

        // Background
        g.setColour(Colour(0xFF111111));
        g.fillRect(scaled((int) meterX), scaled((int) meterY), scaled((int) meterW), scaled((int) meterH));

        // Level bar (dB normalized: -40 dB..0 dB maps to 0..1)
        float norm = 0.0f;
        if (level > 0.001f)
        {
            float db = 20.0f * std::log10(level);
            norm = jlimit(0.0f, 1.0f, (db + 40.0f) / 40.0f);
        }
        float barW = meterW * norm;
        g.setColour(lnf.accent);
        g.fillRect(scaled((int) meterX), scaled((int) meterY), scaled((int) barW), scaled((int) meterH));
    }

    g.setColour(lnf.accent.withAlpha(0.3f));
    g.fillRect(scaled(10), scaled(60), getWidth() - scaled(15), scaled(1));

    g.setColour(Colours::white.withAlpha(0.9f));
    g.setFont(font(8.0f, uiScale));
    g.drawText("VOL", scaled(HDR_VOL), scaled(46), scaled(HDR_KNOB), scaled(10), Justification::centred);
    g.drawText("PAN", scaled(HDR_PAN), scaled(46), scaled(HDR_KNOB), scaled(10), Justification::centred);
    g.drawText("RING", scaled(HDR_RING), scaled(46), scaled(HDR_KNOB), scaled(10), Justification::centred);
    g.drawText("DEST", scaled(HDR_DEST), scaled(46), scaled(HDR_KNOB), scaled(10), Justification::centred);
    g.drawText("DLY", scaled(HDR_DELAY), scaled(46), scaled(HDR_KNOB), scaled(10), Justification::centred);
    g.drawText("REV", scaled(HDR_REVERB), scaled(46), scaled(HDR_KNOB), scaled(10), Justification::centred);
    g.drawText("SYNC", scaled(HDR_DLYSYNC), scaled(46), scaled(HDR_KNOB), scaled(10), Justification::centred);

    // Shuffle section separator
    g.setColour(lnf.accent.withAlpha(0.5f));
    g.fillRect(scaled(HDR_DIV - 8), scaled(6), scaled(1), scaled(46));

    g.setColour(Colour(0xFF3A3A1E).withAlpha(0.4f));
    g.fillRect(scaled(HDR_RING), scaled(8), scaled(HDR_KNOB), scaled(HDR_KNOB));

    g.setColour(Colours::white.withAlpha(0.9f));
    g.setFont(font(8.0f, uiScale));
    g.drawText("DIV", scaled(HDR_DIV), scaled(46), scaled(HDR_KNOB), scaled(10), Justification::centred);
    g.drawText("SHFL", scaled(HDR_SHFL), scaled(46), scaled(HDR_KNOB), scaled(10), Justification::centred);
    g.drawText("GRV", scaled(HDR_GRV), scaled(46), scaled(HDR_KNOB), scaled(10), Justification::centred);

    g.setColour(Colours::white.withAlpha(0.9f));
    g.setFont(font(9.0f, uiScale));
    g.drawText("LVL",  scaled(COL_KNOB),                          scaled(65), scaled(KNOB_W), scaled(12), Justification::centred);
    g.drawText("LEN",  scaled(COL_KNOB + 1*(KNOB_W + KNOB_GAP)), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred);
    g.drawText("VEL",  scaled(COL_KNOB + 2*(KNOB_W + KNOB_GAP)), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred);
    g.drawText("PIT",  scaled(COL_KNOB + 3*(KNOB_W + KNOB_GAP)), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred);
    g.drawText("TONE", scaled(COL_KNOB + 4*(KNOB_W + KNOB_GAP)), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred);
    g.drawText("PAN",  scaled(COL_KNOB + 5*(KNOB_W + KNOB_GAP)), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred);
    g.drawText("DECAY", scaled(COL_DECAY), scaled(65), scaled(68), scaled(12), Justification::centred);
    g.drawText("SAMPLE", scaled(COL_FILE), scaled(65), scaled(SAMPLE_W), scaled(12), Justification::centred);
    g.drawText("DELAY", scaled(COL_DELAY), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred);
    g.drawText("REVERB", scaled(COL_REVERB), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred);
    g.drawText("RING", scaled(COL_RINGMOD), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred);
    g.drawText("DESTR", scaled(COL_DESTR), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred);
    g.drawText("SHFL", scaled(COL_SHUFFLE), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred);

    const double now = juce::Time::getMillisecondCounterHiRes() / 1000.0;
    for (int i = 0; i < NUM_TRACKS; ++i)
    {
        const int ry = TOP + i * ROW_H;
        if (i % 2 == 1)
        {
            g.setColour(lnf.rowAlt);
            g.fillRect(0, scaled(ry), getWidth(), scaled(ROW_H));
        }

        g.setColour(Colour(0xFF3A3A2E).withAlpha(0.4f));
        g.fillRect(scaled(COL_KNOB + 3 * (KNOB_W + KNOB_GAP)), scaled(ry), scaled(KNOB_W), scaled(ROW_H));

        g.setColour(Colour(0xFF2E3A3A).withAlpha(0.4f));
        g.fillRect(scaled(COL_DELAY), scaled(ry), scaled(KNOB_W), scaled(ROW_H));

        g.setColour(Colour(0xFF3A2E3A).withAlpha(0.4f));
        g.fillRect(scaled(COL_REVERB), scaled(ry), scaled(KNOB_W), scaled(ROW_H));

        g.setColour(Colour(0xFF3A3A1E).withAlpha(0.4f));
        g.fillRect(scaled(COL_RINGMOD), scaled(ry), scaled(KNOB_W), scaled(ROW_H));

        g.setColour(Colour(0xFF3A1E1E).withAlpha(0.4f));
        g.fillRect(scaled(COL_DESTR), scaled(ry), scaled(KNOB_W), scaled(ROW_H));

        g.setColour(Colour(0xFF1E3A3A).withAlpha(0.4f));
        g.fillRect(scaled(COL_SHUFFLE), scaled(ry), scaled(KNOB_W), scaled(ROW_H));

        g.setColour(Colour(0xFF2E3A2E).withAlpha(0.4f));
        g.fillRect(scaled(COL_KNOB), scaled(ry), scaled(KNOB_W), scaled(ROW_H));

        g.setColour(Colours::white);
        g.setFont(fontBold(12.0f, uiScale));
        g.drawText(trackNames[i], scaled(COL_LBL), scaled(ry + 14), scaled(32), scaled(16), Justification::centredLeft);

        g.setColour(Colour(0xFF666666));
        g.setFont(font(9.0f, uiScale));
        g.drawText(noteNames[i], scaled(COL_LBL), scaled(ry + 28), scaled(32), scaled(14), Justification::centredLeft);

        const double elapsed = now - trackActivatedTime[i];
        if (elapsed < 0.4)
        {
            float alpha = elapsed < 0.2 ? 1.0f : (float) (1.0 - (elapsed - 0.2) / 0.2);
            g.setColour(lnf.accent.withAlpha(alpha));
            g.fillRect(scaled(0), scaled(ry + 4), scaled(3), scaled(ROW_H - 8));
        }
    }
}

void TrommelkisteEditor::resized()
{
    // Header: title is painted; position controls in top row
    presetPrevBtn.setBounds(scaled(150), scaled(14), scaled(20), scaled(20));
    presetCombo.setBounds(scaled(172), scaled(14), scaled(100), scaled(20));
    presetNextBtn.setBounds(scaled(274), scaled(14), scaled(20), scaled(20));
    scaleCombo.setBounds(scaled(298), scaled(14), scaled(52), scaled(20));

    // Header knobs
    const int hk = HDR_KNOB;
    masterVolume.setBounds(scaled(HDR_VOL), scaled(8), scaled(hk), scaled(hk));
    masterPan.setBounds(scaled(HDR_PAN), scaled(8), scaled(hk), scaled(hk));
    globalRingFreq.setBounds(scaled(HDR_RING), scaled(8), scaled(hk), scaled(hk));
    globalDestruction.setBounds(scaled(HDR_DEST), scaled(8), scaled(hk), scaled(hk));
    masterDelay.setBounds(scaled(HDR_DELAY), scaled(8), scaled(hk), scaled(hk));
    masterReverb.setBounds(scaled(HDR_REVERB), scaled(8), scaled(hk), scaled(hk));
    delaySyncDiv.setBounds(scaled(HDR_DLYSYNC), scaled(8), scaled(hk), scaled(hk));
    globalShuffleDiv.setBounds(scaled(HDR_DIV), scaled(8), scaled(hk), scaled(hk));
    globalShuffleAmt.setBounds(scaled(HDR_SHFL), scaled(8), scaled(hk), scaled(hk));
    globalGroove.setBounds(scaled(HDR_GRV), scaled(8), scaled(hk), scaled(hk));

    for (int i = 0; i < NUM_TRACKS; ++i)
    {
        const int ry = TOP + i * ROW_H;
        auto& t = ui[i];

        Slider* knobs[] = {&t.level, &t.length, &t.velocity, &t.pitch, &t.tone, &t.pan};
        for (int k = 0; k < 6; ++k)
        {
            int kx = COL_KNOB + k * (KNOB_W + KNOB_GAP);
            knobs[k]->setBounds(scaled(kx), scaled(ry + 3), scaled(KNOB_W), scaled(ROW_H - 4));
        }

        t.delaySend.setBounds(scaled(COL_DELAY), scaled(ry + 3), scaled(KNOB_W), scaled(ROW_H - 4));
        t.reverbSend.setBounds(scaled(COL_REVERB), scaled(ry + 3), scaled(KNOB_W), scaled(ROW_H - 4));
        t.ringMod.setBounds(scaled(COL_RINGMOD), scaled(ry + 3), scaled(KNOB_W), scaled(ROW_H - 4));
        t.destruction.setBounds(scaled(COL_DESTR), scaled(ry + 3), scaled(KNOB_W), scaled(ROW_H - 4));
        t.shuffle.setBounds(scaled(COL_SHUFFLE), scaled(ry + 3), scaled(KNOB_W), scaled(ROW_H - 4));

        t.decay.setBounds(scaled(COL_DECAY), scaled(ry + 16), scaled(68), scaled(22));

        const int fileX = COL_FILE;
        const int fileEnd = COL_DELAY - 8;
        t.prevBtn.setBounds(scaled(fileX), scaled(ry + 16), scaled(18), scaled(22));
        t.nextBtn.setBounds(scaled(fileEnd - 18), scaled(ry + 16), scaled(18), scaled(22));
        t.fileLabel.setBounds(scaled(fileX + 20), scaled(ry + 16),
                              scaled(fileEnd - fileX - 38), scaled(22));
    }
}

void TrommelkisteEditor::loadForTrack(int idx)
{
    chooser.launchAsync(FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles,
        [this, idx](const FileChooser& fc)
        {
            auto file = fc.getResult();
            if (file.existsAsFile())
            {
                proc.loadSample(idx, file);
                proc.currentSampleIndex[idx] = -1;
                ui[idx].fileLabel.setText(file.getFileNameWithoutExtension(), dontSendNotification);
            }
        });
}

void TrommelkisteEditor::mouseDown(const MouseEvent& event)
{
    for (int i = 0; i < NUM_TRACKS; ++i)
    {
        if (event.eventComponent == &ui[i].fileLabel)
        {
            loadForTrack(i);
            return;
        }
    }
}
