#include "PluginEditor.h"

static juce::String formatTime(float seconds) {
    if (seconds >= 1.0f) return juce::String(seconds, 2) + "s";
    if (seconds >= 0.001f) return juce::String(seconds * 1000.0f, 1) + "ms";
    return juce::String(seconds * 1000.0f, 1) + "ms";
}

const std::vector<SaftpumpeEditor::Preset>& SaftpumpeEditor::getPresets() {
    static const std::vector<Preset> presets = {
        {"-- None --",         0.0f,   1.0f, 0.030f, 0.30f,  6.0f, 0.0f, 1.0f, 1.0f},
        {"Mastering 0dB",     -2.0f,   2.0f, 0.020f, 0.20f,  6.0f, 0.0f, 1.0f, 1.0f},
        {"Mastering -1dB",     -6.0f,   2.5f, 0.015f, 0.18f,  6.0f, 1.0f, 1.0f, 1.0f},
        {"Mastering -2dB",    -10.0f,   3.0f, 0.010f, 0.15f,  8.0f, 1.5f, 1.0f, 1.0f},
        {"Mastering -3dB",    -14.0f,   4.0f, 0.010f, 0.12f,  8.0f, 2.0f, 1.0f, 1.0f},
        {"Kick Punch",        -18.0f,   6.0f, 0.003f, 0.10f, 10.0f, 3.0f, 0.8f, 0.0f},
    };
    return presets;
}

void SaftpumpeEditor::applyPreset(int index) {
    auto& presets = getPresets();
    if (index < 0 || index >= (int)presets.size()) return;
    auto& p = presets[index];
    thresholdSlider.setValue(p.threshold, juce::sendNotificationSync);
    ratioSlider.setValue(p.ratio, juce::sendNotificationSync);
    attackSlider.setValue(p.attack, juce::sendNotificationSync);
    releaseSlider.setValue(p.release, juce::sendNotificationSync);
    kneeSlider.setValue(p.knee, juce::sendNotificationSync);
    makeupSlider.setValue(p.makeup, juce::sendNotificationSync);
    mixSlider.setValue(p.mix, juce::sendNotificationSync);
    autoReleaseSlider.setValue(p.autoRelease, juce::sendNotificationSync);
}

SaftpumpeEditor::SaftpumpeEditor(SaftpumpeProcessor& p)
    : AudioProcessorEditor(&p), processorRef(p)
{
    setSize(700, 500);
    setResizable(true, this);
    setResizeLimits(650, 480, 1100, 800);
    startTimerHz(30);

    auto setupKnob = [this](juce::Slider& s, juce::Label& l, const juce::String& text,
                            const juce::String& pid, float min, float max, float def) {
        s.setLookAndFeel(&lnf);
        s.setSliderStyle(juce::Slider::RotaryHorizontalVerticalDrag);
        s.setTextBoxStyle(juce::Slider::NoTextBox, true, 0, 0);
        s.setRange(min, max, 0.001);
        s.setValue(def, juce::dontSendNotification);
        s.setPopupDisplayEnabled(true, true, this);
        addAndMakeVisible(s);

        l.setText(text, juce::dontSendNotification);
        l.setJustificationType(juce::Justification::centred);
        l.setColour(juce::Label::textColourId, textColour);
        l.setFont(juce::Font(juce::FontOptions(11.0f)));
        addAndMakeVisible(l);
    };

    setupKnob(thresholdSlider, thresholdLabel, "THRESHOLD", "threshold", -48.0, 0.0, -20.0);
    thresholdAttachment = std::make_unique<SliderAttachment>(processorRef.parameters, "threshold", thresholdSlider);

    setupKnob(ratioSlider, ratioLabel, "RATIO", "ratio", 1.0, 20.0, 4.0);
    ratioAttachment = std::make_unique<SliderAttachment>(processorRef.parameters, "ratio", ratioSlider);

    setupKnob(attackSlider, attackLabel, "ATTACK", "attack", 0.001, 0.1, 0.03);
    attackAttachment = std::make_unique<SliderAttachment>(processorRef.parameters, "attack", attackSlider);

    setupKnob(releaseSlider, releaseLabel, "RELEASE", "release", 0.05, 1.5, 0.3);
    releaseAttachment = std::make_unique<SliderAttachment>(processorRef.parameters, "release", releaseSlider);

    setupKnob(kneeSlider, kneeLabel, "KNEE", "knee", 0.0, 12.0, 6.0);
    kneeAttachment = std::make_unique<SliderAttachment>(processorRef.parameters, "knee", kneeSlider);

    setupKnob(makeupSlider, makeupLabel, "MAKEUP", "makeup", 0.0, 24.0, 0.0);
    makeupAttachment = std::make_unique<SliderAttachment>(processorRef.parameters, "makeup", makeupSlider);

    setupKnob(mixSlider, mixLabel, "MIX", "mix", 0.0, 1.0, 1.0);
    mixAttachment = std::make_unique<SliderAttachment>(processorRef.parameters, "mix", mixSlider);

    setupKnob(autoReleaseSlider, autoReleaseLabel, "AUTO REL", "autoRelease", 0.0, 1.0, 1.0);
    autoReleaseAttachment = std::make_unique<SliderAttachment>(processorRef.parameters, "autoRelease", autoReleaseSlider);

    // LCD displays
    auto setupLCD = [this](juce::Label& lcd) {
        lcd.setJustificationType(juce::Justification::centred);
        lcd.setColour(juce::Label::textColourId, lcdText);
        lcd.setColour(juce::Label::backgroundColourId, lcdBg);
        lcd.setFont(juce::Font(juce::FontOptions(12.0f * uiScale).withStyle("Bold")));
        addAndMakeVisible(lcd);
    };
    setupLCD(thresholdLCD);
    setupLCD(ratioLCD);
    setupLCD(attackLCD);
    setupLCD(releaseLCD);
    setupLCD(kneeLCD);
    setupLCD(makeupLCD);
    setupLCD(mixLCD);
    setupLCD(autoReleaseLCD);

    // Zoom dropdown
    zoomLabel.setText("ZOOM", juce::dontSendNotification);
    zoomLabel.setJustificationType(juce::Justification::centredRight);
    zoomLabel.setColour(juce::Label::textColourId, textDim);
    zoomLabel.setFont(juce::Font(juce::FontOptions(10.0f)));
    addAndMakeVisible(zoomLabel);

    zoomBox.addItem("1.0x", 1);
    zoomBox.addItem("1.5x", 2);
    zoomBox.addItem("2.0x", 3);
    zoomBox.addItem("2.5x", 4);
    zoomBox.addItem("3.0x", 5);
    zoomBox.setSelectedId(2, juce::dontSendNotification);
    zoomBox.setColour(juce::ComboBox::backgroundColourId, grooveDark);
    zoomBox.setColour(juce::ComboBox::textColourId, textColour);
    zoomBox.setColour(juce::ComboBox::arrowColourId, textDim);
    zoomBox.setColour(juce::ComboBox::outlineColourId, juce::Colour(0xff333338));
    zoomBox.addListener(this);
    addAndMakeVisible(zoomBox);

    // Preset dropdown
    presetLabel.setText("PRESET", juce::dontSendNotification);
    presetLabel.setJustificationType(juce::Justification::centredRight);
    presetLabel.setColour(juce::Label::textColourId, textDim);
    presetLabel.setFont(juce::Font(juce::FontOptions(10.0f)));
    addAndMakeVisible(presetLabel);

    auto& presets = getPresets();
    for (int i = 0; i < (int)presets.size(); ++i)
        presetBox.addItem(presets[i].name, i + 1);
    presetBox.setSelectedId(1, juce::dontSendNotification);
    presetBox.setColour(juce::ComboBox::backgroundColourId, grooveDark);
    presetBox.setColour(juce::ComboBox::textColourId, textColour);
    presetBox.setColour(juce::ComboBox::arrowColourId, textDim);
    presetBox.setColour(juce::ComboBox::outlineColourId, juce::Colour(0xff333338));
    presetBox.addListener(this);
    addAndMakeVisible(presetBox);
}

SaftpumpeEditor::~SaftpumpeEditor() {
    thresholdSlider.setLookAndFeel(nullptr);
    ratioSlider.setLookAndFeel(nullptr);
    attackSlider.setLookAndFeel(nullptr);
    releaseSlider.setLookAndFeel(nullptr);
    kneeSlider.setLookAndFeel(nullptr);
    makeupSlider.setLookAndFeel(nullptr);
    mixSlider.setLookAndFeel(nullptr);
    autoReleaseSlider.setLookAndFeel(nullptr);
    zoomBox.removeListener(this);
    presetBox.removeListener(this);
    stopTimer();
}

void SaftpumpeEditor::comboBoxChanged(juce::ComboBox* box) {
    if (box == &zoomBox) {
        static const float scales[] = { 1.0f, 1.5f, 2.0f, 2.5f, 3.0f };
        int id = box->getSelectedId();
        if (id >= 1 && id <= 5) {
            uiScale = scales[id - 1];
            setSize(static_cast<int>(700 * uiScale), static_cast<int>(500 * uiScale));
        }
    } else if (box == &presetBox) {
        int id = box->getSelectedId() - 1;
        applyPreset(id);
    }
}

juce::String SaftpumpeEditor::formatValue(int idx, float val) {
    switch (idx) {
        case 0: return juce::String(val, 1) + " dB";
        case 1: return juce::String(val, 1) + ":1";
        case 2: return formatTime(val);
        case 3: return formatTime(val);
        case 4: return juce::String(val, 1) + " dB";
        case 5: return juce::String(val, 1) + " dB";
        case 6: return juce::String(static_cast<int>(val * 100)) + "%";
        case 7: return val > 0.5f ? "ON" : "OFF";
        default: return {};
    }
}

void SaftpumpeEditor::computeLayout() {
    float s = uiScale;
    auto bounds = getLocalBounds().toFloat();
    auto faceBounds = bounds.reduced(8.0f * s);

    // Header
    float headerH = 28.0f * s + 16.0f * s + 8.0f * s;
    float headerY = faceBounds.getY() + 6.0f * s;
    float contentTop = headerY + headerH + 4.0f * s;

    // Meters — right column
    float meterW = 56.0f * s;
    meterArea = juce::Rectangle<float>(faceBounds.getRight() - meterW - 14.0f * s,
                                       contentTop,
                                       meterW,
                                       faceBounds.getBottom() - contentTop - 48.0f * s);

    // Knob grid area — 2 rows x 4 cols
    float gridLeft = faceBounds.getX() + 14.0f * s;
    float gridW = meterArea.getX() - gridLeft - 14.0f * s;
    float gridH = meterArea.getHeight();
    float rowH = gridH / 2.0f;

    rowBounds[0] = juce::Rectangle<float>(gridLeft, contentTop, gridW, rowH);
    rowBounds[1] = juce::Rectangle<float>(gridLeft, contentTop + rowH, gridW, rowH);

    // Compute knob positions within each row
    int knobsPerRow = 4;
    float colW = gridW / static_cast<float>(knobsPerRow);
    knobRadius = juce::jmin(colW * 0.30f, rowH * 0.28f);

    for (int row = 0; row < 2; ++row) {
        for (int col = 0; col < knobsPerRow; ++col) {
            int idx = row * knobsPerRow + col;
            float cx = rowBounds[row].getX() + colW * static_cast<float>(col) + colW / 2.0f;
            float cy = rowBounds[row].getY() + rowH * 0.40f;
            knobCentre[idx] = {cx, cy};
            knobBounds[idx] = juce::Rectangle<float>(cx - knobRadius, cy - knobRadius,
                                                      knobRadius * 2.0f, knobRadius * 2.0f);
        }
    }

    // Bottom: scope + zoom + preset
    float bottomY = meterArea.getBottom() + 8.0f * s;
    float bottomH = faceBounds.getBottom() - bottomY - 10.0f * s;
    float ctrlW = 110.0f * s;
    float ctrlGap = 6.0f * s;
    float rightX = faceBounds.getRight() - ctrlW - 14.0f * s;
    presetArea = juce::Rectangle<float>(rightX, bottomY, ctrlW, bottomH);
    zoomArea = juce::Rectangle<float>(rightX, bottomY + bottomH * 0.48f, ctrlW, bottomH * 0.48f);
    scopeArea = juce::Rectangle<float>(faceBounds.getX() + 14.0f * s, bottomY,
                                       presetArea.getX() - faceBounds.getX() - 24.0f * s, bottomH);
}

void SaftpumpeEditor::resized() {
    computeLayout();

    float s = uiScale;
    float labelH = 14.0f * s;
    float lcdH = 18.0f * s;
    float knobDiam = knobRadius * 2.0f;
    float arcMargin = 14.0f * s;

    auto setKnobBounds = [&](juce::Slider& slider, juce::Label& label, juce::Label& lcd, int idx) {
        // Slider gets arc area (larger than knob body for arc meter)
        float arcR = knobRadius + arcMargin;
        auto arcB = juce::Rectangle<float>(knobCentre[idx].getX() - arcR, knobCentre[idx].getY() - arcR,
                                            arcR * 2.0f, arcR * 2.0f);
        slider.setBounds(arcB.toNearestInt());

        // Label above arc
        auto lb = juce::Rectangle<int>(
            static_cast<int>(knobCentre[idx].getX() - knobRadius),
            static_cast<int>(knobCentre[idx].getY() - knobRadius - 10.0f * s - 4.0f * s - labelH),
            static_cast<int>(knobDiam),
            static_cast<int>(labelH));
        label.setBounds(lb);

        // LCD below knob
        auto lcdB = juce::Rectangle<int>(
            static_cast<int>(knobCentre[idx].getX() - knobRadius * 0.85f),
            static_cast<int>(knobCentre[idx].getY() + knobRadius + 3.0f * s),
            static_cast<int>(knobRadius * 1.7f),
            static_cast<int>(lcdH));
        lcd.setBounds(lcdB);
    };

    setKnobBounds(thresholdSlider, thresholdLabel, thresholdLCD, 0);
    setKnobBounds(ratioSlider, ratioLabel, ratioLCD, 1);
    setKnobBounds(attackSlider, attackLabel, attackLCD, 2);
    setKnobBounds(releaseSlider, releaseLabel, releaseLCD, 3);
    setKnobBounds(kneeSlider, kneeLabel, kneeLCD, 4);
    setKnobBounds(makeupSlider, makeupLabel, makeupLCD, 5);
    setKnobBounds(mixSlider, mixLabel, mixLCD, 6);
    setKnobBounds(autoReleaseSlider, autoReleaseLabel, autoReleaseLCD, 7);

    presetLabel.setBounds(static_cast<int>(presetArea.getX()), static_cast<int>(presetArea.getY()),
                          static_cast<int>(presetArea.getWidth()), static_cast<int>(14.0f * s));
    presetBox.setBounds(static_cast<int>(presetArea.getX()), static_cast<int>(presetArea.getY() + 14.0f * s),
                        static_cast<int>(presetArea.getWidth()), static_cast<int>(22.0f * s));

    zoomLabel.setBounds(static_cast<int>(zoomArea.getX()), static_cast<int>(zoomArea.getY()),
                         static_cast<int>(zoomArea.getWidth()), static_cast<int>(14.0f * s));
    zoomBox.setBounds(static_cast<int>(zoomArea.getX()), static_cast<int>(zoomArea.getY() + 14.0f * s),
                       static_cast<int>(zoomArea.getWidth()), static_cast<int>(22.0f * s));
}

void SaftpumpeEditor::paint(juce::Graphics& g) {
    if (rowBounds[0].getWidth() <= 0) computeLayout();

    auto bounds = getLocalBounds().toFloat();
    float s = uiScale;
    auto faceBounds = bounds.reduced(8.0f * s);

    // Dark bezel
    g.setColour(bezelColour);
    g.fillRect(bounds);

    // Faceplate
    juce::ColourGradient faceGrad(faceplateTop, faceBounds.getX(), faceBounds.getY(),
                                  faceplateBot, faceBounds.getX(), faceBounds.getBottom(), false);
    g.setGradientFill(faceGrad);
    g.fillRoundedRectangle(faceBounds, 6.0f * s);

    // Brushed texture
    g.setColour(juce::Colours::white.withAlpha(0.02f));
    for (float y = faceBounds.getY(); y < faceBounds.getBottom(); y += 2.0f * s)
        g.drawLine(faceBounds.getX(), y, faceBounds.getRight(), y, 0.4f);

    // Edge
    g.setColour(juce::Colours::white.withAlpha(0.06f));
    g.drawLine(faceBounds.getX() + 4, faceBounds.getY() + 1, faceBounds.getRight() - 4, faceBounds.getY() + 1, 1.0f);
    g.setColour(juce::Colours::black.withAlpha(0.4f));
    g.drawLine(faceBounds.getX() + 4, faceBounds.getBottom() - 1, faceBounds.getRight() - 4, faceBounds.getBottom() - 1, 1.0f);

    // Screws
    float screwR = 3.5f * s;
    float screwInset = 14.0f * s;
    drawScrew(g, faceBounds.getX() + screwInset, faceBounds.getY() + screwInset, screwR);
    drawScrew(g, faceBounds.getRight() - screwInset, faceBounds.getY() + screwInset, screwR);
    drawScrew(g, faceBounds.getX() + screwInset, faceBounds.getBottom() - screwInset, screwR);
    drawScrew(g, faceBounds.getRight() - screwInset, faceBounds.getBottom() - screwInset, screwR);

    // Title
    auto titleArea = juce::Rectangle<float>(faceBounds.getX() + 30.0f * s, faceBounds.getY() + 6.0f * s,
                                            faceBounds.getWidth() - 60.0f * s, 28.0f * s);
    g.setColour(textBright);
    g.setFont(juce::Font(juce::FontOptions(22.0f * s).withStyle("Bold")));
    g.drawText("SAFTPUMPE", titleArea, juce::Justification::centred);
    g.setColour(textDim.withAlpha(0.2f));
    g.drawText("SAFTPUMPE", titleArea.translated(0, 1.0f), juce::Justification::centred);

    // Subtitle
    auto subArea = titleArea.translated(0, 22.0f * s).withHeight(16.0f * s);
    g.setColour(textDim);
    g.setFont(juce::Font(juce::FontOptions(11.0f * s)));
    g.drawText("BUS COMPRESSOR", subArea, juce::Justification::centred);

    // Accent line
    float lineY = subArea.getBottom() + 4.0f * s;
    g.setColour(accentLine.withAlpha(0.25f));
    g.drawLine(faceBounds.getX() + 20.0f * s, lineY, faceBounds.getRight() - 20.0f * s, lineY, 1.0f);

    // Draw knobs
    juce::Slider* sliders[] = { &thresholdSlider, &ratioSlider, &attackSlider, &releaseSlider,
                                  &kneeSlider, &makeupSlider, &mixSlider, &autoReleaseSlider };
    juce::Label* lcds[] = { &thresholdLCD, &ratioLCD, &attackLCD, &releaseLCD,
                             &kneeLCD, &makeupLCD, &mixLCD, &autoReleaseLCD };
    const char* labels[] = { "THRESHOLD", "RATIO", "ATTACK", "RELEASE",
                             "KNEE", "MAKEUP", "MIX", "AUTO REL" };
    const juce::StringArray marks[] = {
        {"0", "-12", "-24", "-36", "-48"},
        {"20:1", "10:1", "6:1", "4:1", "2:1", "1:1"},
        {"100m", "30m", "10m", "3m", "1m"},
        {"1.5s", "500m", "100m", "50m"},
        {"12", "6", "0"},
        {"24", "12", "0"},
        {"100", "50", "0"},
        {"OFF", "ON"}
    };

    for (int i = 0; i < 8; ++i) {
        float norm = static_cast<float>(sliders[i]->valueToProportionOfLength(sliders[i]->getValue()));
        drawKnob(g, knobBounds[i], norm, norm);
    }

    // Meters
    float grH = meterArea.getHeight() * 0.42f;
    auto grBounds = juce::Rectangle<float>(meterArea.getX(), meterArea.getY(), meterArea.getWidth(), grH);
    float gr = processorRef.getGainReductionMeter();
    drawMeter(g, grBounds, gr, 0.0f, 24.0f, true);
    drawPeakHold(g, grBounds, grPeak.peak, true);
    g.setColour(textDim);
    g.setFont(juce::Font(juce::FontOptions(10.0f * s)));
    g.drawText("GR", grBounds.translated(0, grBounds.getHeight() + 2.0f * s), juce::Justification::centredTop);

    float ioY = grBounds.getBottom() + 16.0f * s;
    float ioH = meterArea.getBottom() - ioY - 14.0f * s;
    float ioW = (meterArea.getWidth() - 3.0f * s) / 2.0f;
    auto inB = juce::Rectangle<float>(meterArea.getX(), ioY, ioW, ioH);
    auto outB = juce::Rectangle<float>(meterArea.getX() + ioW + 3.0f * s, ioY, ioW, ioH);

    drawMeter(g, inB, processorRef.getInputLevel(), -60.0f, 0.0f, false);
    drawMeter(g, outB, processorRef.getOutputLevel(), -60.0f, 0.0f, false);
    drawPeakHold(g, inB, inPeak.peak);
    drawPeakHold(g, outB, outPeak.peak);
    g.drawText("IN", inB.translated(0, ioH + 2.0f * s), juce::Justification::centredTop);
    g.drawText("OUT", outB.translated(0, ioH + 2.0f * s), juce::Justification::centredTop);

    // Scope
    drawScope(g, scopeArea);

    // Zoom + Preset area borders
    g.setColour(juce::Colour(0xff333338));
    g.drawRoundedRectangle(zoomArea.reduced(2.0f * s), 2.0f * s, 0.5f);
    g.drawRoundedRectangle(presetArea.reduced(2.0f * s), 2.0f * s, 0.5f);
}

void SaftpumpeEditor::drawKnob(juce::Graphics& g, juce::Rectangle<float> bounds,
                                float normalizedValue, float arcFillFrac) {
    float s = uiScale;
    float cx = bounds.getCentreX();
    float cy = bounds.getCentreY();
    float r = bounds.getWidth() / 2.0f;

    // Arc meter around the knob
    float arcR = r + 10.0f * s;
    float arcThickness = 3.0f * s;

    auto buildArc = [&](float fromFrac, float toFrac) {
        juce::Path p;
        float a0 = startAngle + fromFrac * (endAngle - startAngle);
        float a1 = startAngle + toFrac * (endAngle - startAngle);
        int steps = juce::jmax(2, (int) std::ceil(std::abs(a1 - a0) / 0.15f));
        float da = (a1 - a0) / (float) steps;
        p.startNewSubPath(cx + std::cos(a0) * arcR, cy + std::sin(a0) * arcR);
        for (int i = 1; i <= steps; ++i) {
            float a = a0 + da * (float) i;
            p.lineTo(cx + std::cos(a) * arcR, cy + std::sin(a) * arcR);
        }
        return p;
    };

    // Background arc track
    g.setColour(juce::Colour(0xff3a3a42));
    g.strokePath(buildArc(0.0f, 1.0f), juce::PathStrokeType(arcThickness));

    // Filled arc — colored by parameter zone
    if (arcFillFrac > 0.001f) {
        juce::Colour arcCol;
        if (arcFillFrac < 0.5f)
            arcCol = greenLed.interpolatedWith(amberLed, arcFillFrac * 2.0f);
        else
            arcCol = amberLed.interpolatedWith(redLed, (arcFillFrac - 0.5f) * 2.0f);
        g.setColour(arcCol.withAlpha(0.9f));
        g.strokePath(buildArc(0.0f, arcFillFrac), juce::PathStrokeType(arcThickness));
    }

    // Knob shadow — soft outer + hard inner
    g.setColour(juce::Colours::black.withAlpha(0.3f));
    g.fillEllipse(cx - r - 3.0f * s, cy - r + 3.0f * s, r * 2.0f + 6.0f * s, r * 2.0f + 6.0f * s);
    g.setColour(juce::Colours::black.withAlpha(0.6f));
    g.fillEllipse(cx - r - 1.0f, cy - r + 2.0f * s, r * 2.0f + 2.0f * s, r * 2.0f + 2.0f * s);

    // Outer bevel ring — catches light on top, shadow on bottom
    float bevelR = r + 1.0f * s;
    {
        juce::ColourGradient bevelGrad(juce::Colour(0xff5a5a60), cx - bevelR, cy - bevelR,
                                       juce::Colour(0xff2a2a2e), cx + bevelR, cy + bevelR, true);
        g.setGradientFill(bevelGrad);
        g.fillEllipse(cx - bevelR, cy - bevelR, bevelR * 2.0f, bevelR * 2.0f);
    }

    // Knob body — radial gradient for 3D dome effect
    {
        juce::ColourGradient bodyGrad(juce::Colour(0xff9a9aa0), cx, cy - r * 0.3f,
                                       juce::Colour(0xff3a3a40), cx, cy + r, true);
        bodyGrad.addColour(0.3, juce::Colour(0xff808086));
        bodyGrad.addColour(0.7, juce::Colour(0xff505056));
        g.setGradientFill(bodyGrad);
        g.fillEllipse(bounds);
    }

    // Knurling — fine radial lines around the edge
    {
        float knurlR = r - 1.0f * s;
        float knurlInner = r - 4.0f * s;
        int numTeeth = 40;
        g.setColour(juce::Colours::black.withAlpha(0.12f));
        for (int i = 0; i < numTeeth; ++i) {
            float a = (static_cast<float>(i) / static_cast<float>(numTeeth)) * juce::MathConstants<float>::twoPi;
            float ca = std::cos(a);
            float sa = std::sin(a);
            g.drawLine(cx + ca * knurlInner, cy + sa * knurlInner,
                       cx + ca * knurlR, cy + sa * knurlR, 0.6f);
        }
    }

    // Inner chamfer ring
    g.setColour(juce::Colours::white.withAlpha(0.06f));
    g.drawEllipse(bounds.reduced(3.0f * s), 0.8f);
    g.setColour(juce::Colours::black.withAlpha(0.15f));
    g.drawEllipse(bounds.reduced(1.5f * s), 0.8f);

    // Specular highlight — top-left crescent
    {
        juce::ColourGradient specGrad(juce::Colours::white.withAlpha(0.18f), cx - r * 0.2f, cy - r * 0.5f,
                                       juce::Colours::white.withAlpha(0.0f), cx + r * 0.3f, cy + r * 0.1f, true);
        g.setGradientFill(specGrad);
        g.fillEllipse(cx - r * 0.6f, cy - r * 0.8f, r * 1.0f, r * 0.55f);
    }

    // Bottom rim shadow inside the body
    g.setColour(juce::Colours::black.withAlpha(0.12f));
    g.fillEllipse(cx - r * 0.7f, cy + r * 0.3f, r * 1.4f, r * 0.6f);

    // Pointer line — wider with subtle glow
    float angle = startAngle + normalizedValue * (endAngle - startAngle);
    float pIn = r * 0.22f;
    float pOut = r * 0.72f;
    float px1 = cx + std::cos(angle) * pIn;
    float py1 = cy + std::sin(angle) * pIn;
    float px2 = cx + std::cos(angle) * pOut;
    float py2 = cy + std::sin(angle) * pOut;

    // Glow behind pointer
    g.setColour(juce::Colours::white.withAlpha(0.12f));
    g.drawLine(px1, py1, px2, py2, 4.0f * s);
    // Pointer itself
    g.setColour(juce::Colours::white.withAlpha(0.85f));
    g.drawLine(px1, py1, px2, py2, 1.8f * s);
    // Dark edge for definition
    g.setColour(juce::Colours::black.withAlpha(0.2f));
    g.drawLine(px1, py1, px2, py2, 0.4f);
}

void SaftpumpeEditor::drawLCD(juce::Graphics& g, juce::Rectangle<float> bounds, const juce::String& text) {
    float s = uiScale;
    g.setColour(lcdBg);
    g.fillRoundedRectangle(bounds, 2.0f * s);
    g.setColour(juce::Colour(0xff1a2a1a));
    g.drawRoundedRectangle(bounds.expanded(0.5f), 2.0f * s, 1.0f);

    g.setColour(lcdText);
    g.setFont(juce::Font(juce::FontOptions(12.0f * s).withStyle("Bold")));
    g.drawText(text, bounds, juce::Justification::centred);
}

void SaftpumpeEditor::drawMeter(juce::Graphics& g, juce::Rectangle<float> bounds,
                                 float level, float minDb, float maxDb, bool isGainReduction) {
    float s = uiScale;
    g.setColour(grooveDark);
    g.fillRoundedRectangle(bounds, 2.0f * s);
    g.setColour(juce::Colours::black.withAlpha(0.5f));
    g.drawRoundedRectangle(bounds.expanded(0.5f), 2.0f * s, 1.0f);

    float norm = juce::jlimit(0.0f, 1.0f, (level - minDb) / (maxDb - minDb));
    if (norm > 0.005f) {
        float h = bounds.getHeight() * norm;
        float y = isGainReduction ? bounds.getY() : bounds.getBottom() - h;
        juce::ColourGradient grad;
        if (isGainReduction) {
            grad = juce::ColourGradient(greenLed, 0.0f, y, redLed, 0.0f, y + h, false);
        } else {
            grad = juce::ColourGradient(greenLed, 0.0f, bounds.getBottom(), amberLed, 0.0f, y, false);
            if (norm > 0.8f) grad.addColour(1.0, redLed);
        }
        g.setGradientFill(grad);
        g.fillRect(bounds.getX() + 1.0f, y, bounds.getWidth() - 2.0f, h);
    }
}

void SaftpumpeEditor::drawPeakHold(juce::Graphics& g, juce::Rectangle<float> bounds,
                                    float peakNorm, bool fromTop) {
    float s = uiScale;
    if (peakNorm < 0.005f) return;
    float y = fromTop ? bounds.getY() + bounds.getHeight() * peakNorm
                      : bounds.getBottom() - bounds.getHeight() * peakNorm;
    g.setColour(juce::Colours::white.withAlpha(0.85f));
    g.fillRect(bounds.getX() + 1.0f, y - 1.0f, bounds.getWidth() - 2.0f, 2.0f);
}

void SaftpumpeEditor::drawScope(juce::Graphics& g, juce::Rectangle<float> bounds) {
    float s = uiScale;
    g.setColour(grooveDark);
    g.fillRoundedRectangle(bounds, 3.0f * s);
    g.setColour(juce::Colours::black.withAlpha(0.4f));
    g.drawRoundedRectangle(bounds.expanded(0.5f), 3.0f * s, 1.0f);

    g.setColour(accentLine.withAlpha(0.06f));
    float centerY = bounds.getCentreY();
    g.drawLine(bounds.getX() + 2.0f, centerY, bounds.getRight() - 2.0f, centerY, 0.5f);
    for (int i = 1; i <= 4; ++i) {
        float gx = bounds.getX() + bounds.getWidth() * (static_cast<float>(i) / 4.0f);
        g.drawLine(gx, bounds.getY() + 2.0f, gx, bounds.getBottom() - 2.0f, 0.3f);
    }

    auto& inData = processorRef.inputScope;
    auto& outData = processorRef.outputScope;
    int numPoints = processorRef.scopeSize;
    if (numPoints < 2) return;

    float width = bounds.getWidth() - 4.0f;
    float halfH = bounds.getHeight() / 2.0f - 2.0f;
    float xStep = width / static_cast<float>(numPoints - 1);
    float ox = bounds.getX() + 2.0f;

    {
        juce::Path path;
        float x = ox;
        path.startNewSubPath(x, centerY - inData[0] * halfH);
        for (int i = 1; i < numPoints; ++i) { x += xStep; path.lineTo(x, centerY - inData[i] * halfH); }
        g.setColour(redLed.withAlpha(0.15f));
        g.strokePath(path, juce::PathStrokeType(4.0f));
        g.setColour(redLed.withAlpha(0.8f));
        g.strokePath(path, juce::PathStrokeType(1.5f));
    }
    {
        juce::Path path;
        float x = ox;
        path.startNewSubPath(x, centerY - outData[0] * halfH);
        for (int i = 1; i < numPoints; ++i) { x += xStep; path.lineTo(x, centerY - outData[i] * halfH); }
        g.setColour(greenLed.withAlpha(0.12f));
        g.strokePath(path, juce::PathStrokeType(3.0f));
        g.setColour(greenLed.withAlpha(0.65f));
        g.strokePath(path, juce::PathStrokeType(1.0f));
    }

    float legendY = bounds.getBottom() - 12.0f * s;
    float legendX = bounds.getX() + 6.0f * s;
    g.setFont(juce::Font(juce::FontOptions(9.0f * s)));
    g.setColour(redLed.withAlpha(0.8f));
    g.drawText("IN", legendX, legendY, 20.0f * s, 10.0f * s, juce::Justification::centredLeft);
    g.setColour(greenLed.withAlpha(0.65f));
    g.drawText("OUT", legendX + 24.0f * s, legendY, 26.0f * s, 10.0f * s, juce::Justification::centredLeft);
}

void SaftpumpeEditor::drawScrew(juce::Graphics& g, float cx, float cy, float radius) {
    juce::ColourGradient grad(juce::Colour(0xff3a3a40), cx - radius, cy - radius,
                               juce::Colour(0xff1a1a1e), cx + radius, cy + radius, true);
    g.setGradientFill(grad);
    g.fillEllipse(cx - radius, cy - radius, radius * 2.0f, radius * 2.0f);
    g.setColour(juce::Colour(0xff555558));
    float cs = radius * 0.6f;
    g.drawLine(cx - cs, cy, cx + cs, cy, 0.8f);
    g.drawLine(cx, cy - cs, cx, cy + cs, 0.8f);
    g.setColour(juce::Colours::white.withAlpha(0.15f));
    g.fillEllipse(cx - radius * 0.3f, cy - radius * 0.5f, radius * 0.4f, radius * 0.25f);
}

void SaftpumpeEditor::timerCallback() {
    double now = juce::Time::getMillisecondCounterHiRes();

    auto updatePeak = [&](PeakHold& pk, float level) {
        if (level > pk.peak || (now - pk.peakTime) > PeakHold::holdMs) {
            pk.peak = level;
            pk.peakTime = now;
        }
    };

    float grNorm = juce::jlimit(0.0f, 1.0f,
        processorRef.getGainReductionMeter() / 24.0f);
    float inNorm = juce::jlimit(0.0f, 1.0f,
        (processorRef.getInputLevel() - (-60.0f)) / (0.0f - (-60.0f)));
    float outNorm = juce::jlimit(0.0f, 1.0f,
        (processorRef.getOutputLevel() - (-60.0f)) / (0.0f - (-60.0f)));

    updatePeak(grPeak, grNorm);
    updatePeak(inPeak, inNorm);
    updatePeak(outPeak, outNorm);

    // Update LCD displays
    auto updateLCD = [](juce::Label& lcd, const juce::String& text) {
        if (lcd.getText() != text) lcd.setText(text, juce::dontSendNotification);
    };
    updateLCD(thresholdLCD, formatValue(0, static_cast<float>(thresholdSlider.getValue())));
    updateLCD(ratioLCD, formatValue(1, static_cast<float>(ratioSlider.getValue())));
    updateLCD(attackLCD, formatValue(2, static_cast<float>(attackSlider.getValue())));
    updateLCD(releaseLCD, formatValue(3, static_cast<float>(releaseSlider.getValue())));
    updateLCD(kneeLCD, formatValue(4, static_cast<float>(kneeSlider.getValue())));
    updateLCD(makeupLCD, formatValue(5, static_cast<float>(makeupSlider.getValue())));
    updateLCD(mixLCD, formatValue(6, static_cast<float>(mixSlider.getValue())));
    updateLCD(autoReleaseLCD, formatValue(7, static_cast<float>(autoReleaseSlider.getValue())));

    repaint();
}
