#include "PluginProcessor.h"
#include "PluginEditor.h"
#include "AXIOMDesignSystem.h"
#include "JuceLibraryCode/BinaryData.h"

using AXIOM::DesignSystem;
using Colours = DesignSystem::Colours;
using Typography = DesignSystem::Typography;
using Spacing = DesignSystem::Spacing;
using Shape = DesignSystem::Shape;
using Opacity = DesignSystem::Opacity;
using Layout = DesignSystem::Layout;
using Components = DesignSystem::Components;
using SliderStyles = Components::SliderStyles;


void CrystalizerEQAudioProcessorEditor::setupSliders() {
    auto style = juce::Slider::RotaryHorizontalVerticalDrag;
    for (auto *s: sliders) {
        s->setSliderStyle(style);
    }
    gainLookAndFeel = std::make_unique<DesignSystem::Components::GainSliderLookAndFeel>();
    freqQLookAndFeel = std::make_unique<DesignSystem::Components::FreqQSliderLookAndFeel>();
    slopeLookAndFeel = std::make_unique<DesignSystem::Components::SlopeSliderLookAndFeel>();
    globalLookAndFeel = std::make_unique<DesignSystem::Components::GlobalSliderLookAndFeel>();

    lowBandGainSlider.setLookAndFeel(gainLookAndFeel.get());
    peak1GainSlider.setLookAndFeel(gainLookAndFeel.get());
    peak2GainSlider.setLookAndFeel(gainLookAndFeel.get());
    peak3GainSlider.setLookAndFeel(gainLookAndFeel.get());
    highBandGainSlider.setLookAndFeel(gainLookAndFeel.get());

    lowBandSlopeSlider.setLookAndFeel(slopeLookAndFeel.get());
    highBandSlopeSlider.setLookAndFeel(slopeLookAndFeel.get());

    inputSlider.setLookAndFeel(globalLookAndFeel.get());
    outputSlider.setLookAndFeel(globalLookAndFeel.get());

    for (auto *s: {
             &lowBandFreqSlider, &lowBandQSlider, &peak1FreqSlider,
             &peak1QSlider, &peak2FreqSlider, &peak2QSlider,
             &peak3FreqSlider, &peak3QSlider, &highBandFreqSlider, &highBandQSlider
         }) {
        s->setLookAndFeel(freqQLookAndFeel.get());
    }

    lowBandSlopeSlider.setTextBoxStyle(juce::Slider::NoTextBox, true, 0, 0);
    highBandSlopeSlider.setTextBoxStyle(juce::Slider::NoTextBox, true, 0, 0);
}


void CrystalizerEQAudioProcessorEditor::setupAttachments() {
    lowBandFreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "LowBandFreq", lowBandFreqSlider);
    lowBandSlopeAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "LowBandSlope", lowBandSlopeSlider);
    lowBandGainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "LowBandGain", lowBandGainSlider);
    lowBandQAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "LowBandQ", lowBandQSlider);

    peak1FreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak1Freq", peak1FreqSlider);
    peak1GainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak1Gain", peak1GainSlider);
    peak1QAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak1Q", peak1QSlider);

    peak2FreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak2Freq", peak2FreqSlider);
    peak2GainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak2Gain", peak2GainSlider);
    peak2QAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak2Q", peak2QSlider);

    peak3FreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak3Freq", peak3FreqSlider);
    peak3GainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak3Gain", peak3GainSlider);
    peak3QAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "Peak3Q", peak3QSlider);

    highBandFreqAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "HighBandFreq", highBandFreqSlider);
    highBandSlopeAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "HighBandSlope", highBandSlopeSlider);
    highBandGainAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "HighBandGain", highBandGainSlider);
    highBandQAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "HighBandQ", highBandQSlider);

    inputAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "InputGain", inputSlider);
    outputAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "OutputGain", outputSlider);

    peak1BypassAttach = std::make_unique<ButtonAttach>(audioProcessor.apvts, "Peak1Bypass", peak1BypassButton);
    peak2BypassAttach = std::make_unique<ButtonAttach>(audioProcessor.apvts, "Peak2Bypass", peak2BypassButton);
    peak3BypassAttach = std::make_unique<ButtonAttach>(audioProcessor.apvts, "Peak3Bypass", peak3BypassButton);
    crystalizeAttach = std::make_unique<ButtonAttach>(audioProcessor.apvts, "CrystalizeButton", crystalizeButton);
    masterBypassAttach = std::make_unique<ButtonAttach>(audioProcessor.apvts, "MasterBypass", masterBypassButton);
}


void CrystalizerEQAudioProcessorEditor::setupDisplayNames() {
    titleLabel.setText("Crystalizer", juce::dontSendNotification);

    lowBypass.setName("LowBypass");
    lowBell.setName("LowBell");
    lowCut.setName("LowCut");
    lowShelf.setName("LowShelf");
    lowBandModeBox.setName("LowBandModeBox");
    lowBandFreqSlider.setName("LowBand Freq");
    lowBandSlopeSlider.setName("LowBand Slope");
    lowBandGainSlider.setName("LowBand Gain");
    lowBandQSlider.setName("LowBand Q");

    peak1FreqSlider.setName("Peak1 Freq");
    peak1GainSlider.setName("Peak1 Gain");
    peak1QSlider.setName("Peak1 Q");

    peak2FreqSlider.setName("Peak2 Freq");
    peak2GainSlider.setName("Peak2 Gain");
    peak2QSlider.setName("Peak2 Q");

    peak3FreqSlider.setName("Peak3 Freq");
    peak3GainSlider.setName("Peak3 Gain");
    peak3QSlider.setName("Peak3 Q");

    highBypass.setName("HighBypass");
    highBell.setName("HighBell");
    highCut.setName("HighCut");
    highShelf.setName("HighShelf");
    highBandModeBox.setName("HighBandModeBox");
    highBandFreqSlider.setName("HighBand Freq");
    highBandSlopeSlider.setName("HighBand Slope");
    highBandGainSlider.setName("HighBand Gain");
    highBandQSlider.setName("HighBand Q");

    inputSlider.setName("Input");
    outputSlider.setName("Output");

    lowBandGainSlider.setTextValueSuffix("\ndB");

    peak1GainSlider.setTextValueSuffix("\ndB");
    peak1BypassButton.setName("peak1Bypass");

    peak2GainSlider.setTextValueSuffix("\ndB");
    peak2BypassButton.setName("peak2Bypass");

    peak3GainSlider.setTextValueSuffix("\ndB");
    peak3BypassButton.setName("peak3Bypass");

    highBandGainSlider.setTextValueSuffix("\ndB");

    inputSlider.setTextValueSuffix("\nIN");
    outputSlider.setTextValueSuffix("\nOUT");

    crystalizeButton.setName("CrystalizeButton");

    resetButton.setName("ResetButton");
    resetButton.setButtonText("Reset");

    masterBypassButton.setName("MasterBypass");

    savePresetButton.setName("SavePresetButton");
    savePresetButton.setButtonText("Save Preset");

    deletePresetButton.setName("DeletePresetButton");
    deletePresetButton.setButtonText("Delete Preset");
}


void CrystalizerEQAudioProcessorEditor::setupToggleButtons() {
    bypassButtonLookAndFeel = std::make_unique<Components::BypassButtonLookAndFeel>();
    svgToggleButtonLookAndFeel = std::make_unique<Components::SvgToggleButtonLookAndFeel>();
    presetMenuButtonLookAndFeel = std::make_unique<Components::PresetMenuButtonLookAndFeel>();
    lowBandButtonLookAndFeel = std::make_unique<Components::lowBandButtonLookAndFeel>();
    highBandButtonLookAndFeel = std::make_unique<Components::highBandButtonLookAndFeel>();

    peak1BypassButton.setLookAndFeel(bypassButtonLookAndFeel.get());
    peak2BypassButton.setLookAndFeel(bypassButtonLookAndFeel.get());
    peak3BypassButton.setLookAndFeel(bypassButtonLookAndFeel.get());
    masterBypassButton.setLookAndFeel(bypassButtonLookAndFeel.get());

    crystalizeButton.setLookAndFeel(svgToggleButtonLookAndFeel.get());
    presetMenuButton.setLookAndFeel(presetMenuButtonLookAndFeel.get());


    lowShelf.setToggleState(true, juce::dontSendNotification);
    highShelf.setToggleState(true, juce::dontSendNotification);

    lowBypass.setLookAndFeel(lowBandButtonLookAndFeel.get());
    lowCut.setLookAndFeel(lowBandButtonLookAndFeel.get());
    lowBell.setLookAndFeel(lowBandButtonLookAndFeel.get());
    lowShelf.setLookAndFeel(lowBandButtonLookAndFeel.get());

    highBypass.setLookAndFeel(highBandButtonLookAndFeel.get());
    highCut.setLookAndFeel(highBandButtonLookAndFeel.get());
    highBell.setLookAndFeel(highBandButtonLookAndFeel.get());
    highShelf.setLookAndFeel(highBandButtonLookAndFeel.get());

    for (auto *b: {
             &peak1BypassButton, &peak2BypassButton, &peak3BypassButton, &crystalizeButton, &masterBypassButton
         }) {
        b->onClick = [this, b]() {
            juce::String mode;
            if (b == &peak1BypassButton) {
                mode = "Low-Mid";
            } else if (b == &peak2BypassButton) {
                mode = "Mid";
            } else if (b == &peak3BypassButton) {
                mode = "High-Mid";
            } else if (b == &masterBypassButton) {
                mode = "Master";
            }

            const float target = b->getToggleState() ? 1.0f : 0.0f;

            if (mode == "Low-Mid") disableLowMidBand(target);
            else if (mode == "Mid") disableMidBand(target);
            else if (mode == "High-Mid") disableHighMidBand(target);
            else if (mode == "Master") disableEverything(target);

            if (b == &crystalizeButton) {
                isAnimatingCrystalize = true;
                isFadingToActive = (svgToggleButtonLookAndFeel->activeIconOpacity < 1.0f);
            }
        };
    }
}


void CrystalizerEQAudioProcessorEditor::disableLowBand(const float target) {
    bool isToggled = target <= 0.5f;
    lowBandFreqSlider.setEnabled(isToggled);
    lowBandFreqLabel.setEnabled(isToggled);
    lowBandSlopeSlider.setEnabled(isToggled);
    lowBandSlopeLabel.setEnabled(isToggled);
    lowBandGainSlider.setEnabled(isToggled);
    lowBandGainLabel.setEnabled(isToggled);
    lowBandQSlider.setEnabled(isToggled);
    lowBandQLabel.setEnabled(isToggled);
};


void CrystalizerEQAudioProcessorEditor::disableLowMidBand(const float target) {
    bool isToggled = target <= 0.5f;
    peak1GainSlider.setEnabled(isToggled);
    peak1QSlider.setEnabled(isToggled);
    peak1FreqSlider.setEnabled(isToggled);
    peak1GainLabel.setEnabled(isToggled);
    peak1QLabel.setEnabled(isToggled);
    peak1FreqLabel.setEnabled(isToggled);
}


void CrystalizerEQAudioProcessorEditor::disableMidBand(const float target) {
    bool isToggled = target <= 0.5f;
    peak2GainSlider.setEnabled(isToggled);
    peak2QSlider.setEnabled(isToggled);
    peak2FreqSlider.setEnabled(isToggled);
    peak2GainLabel.setEnabled(isToggled);
    peak2QLabel.setEnabled(isToggled);
    peak2FreqLabel.setEnabled(isToggled);
}


void CrystalizerEQAudioProcessorEditor::disableHighMidBand(const float target) {
    bool isToggled = target <= 0.5f;
    peak3GainSlider.setEnabled(isToggled);
    peak3QSlider.setEnabled(isToggled);
    peak3FreqSlider.setEnabled(isToggled);
    peak3GainLabel.setEnabled(isToggled);
    peak3QLabel.setEnabled(isToggled);
    peak3FreqLabel.setEnabled(isToggled);
}


void CrystalizerEQAudioProcessorEditor::disableHighBand(const float target) {
    bool isToggled = target <= 0.5f;
    highBandFreqSlider.setEnabled(isToggled);
    highBandFreqLabel.setEnabled(isToggled);
    highBandSlopeSlider.setEnabled(isToggled);
    highBandSlopeLabel.setEnabled(isToggled);
    highBandGainSlider.setEnabled(isToggled);
    highBandGainLabel.setEnabled(isToggled);
    highBandQSlider.setEnabled(isToggled);
    highBandQLabel.setEnabled(isToggled);
};


void CrystalizerEQAudioProcessorEditor::disableEverything(const float target) {
    bool isToggled = target <= 0.5f;

    for (auto *s: sliders) {
        s->setEnabled(isToggled);
    }
    for (auto *l: sliderLabels) {
        l->setEnabled(isToggled);
    }

    for (auto *b: peakBypassButtons) {
        b->setEnabled(isToggled);
        const auto bypassTarget = b->getToggleState() ? 1.0f : 0.0f;
        if (b == &peak1BypassButton && isToggled) {
            disableLowMidBand(bypassTarget);
        } else if (b == &peak2BypassButton && isToggled) {
            disableMidBand(bypassTarget);
        } else if (b == &peak3BypassButton && isToggled) {
            disableHighMidBand(bypassTarget);
        }
    }

    resetButton.setEnabled(isToggled);

    crystalizeButton.setEnabled(isToggled);
    lowBandModeBox.setEnabled(isToggled);
    highBandModeBox.setEnabled(isToggled);

    if (!isToggled) {
        if (svgToggleButtonLookAndFeel->activeIconOpacity == 1.0f) {
            svgToggleButtonLookAndFeel->activeIconOpacity = 0.7f;
            crystalizeButton.repaint();
        } else if (svgToggleButtonLookAndFeel->passiveIconOpacity == 1.0f) {
            svgToggleButtonLookAndFeel->passiveIconOpacity = 0.7f;
            crystalizeButton.repaint();
        }
    } else {
        if (svgToggleButtonLookAndFeel->activeIconOpacity == 0.7f) {
            svgToggleButtonLookAndFeel->activeIconOpacity = 1.0f;
            crystalizeButton.repaint();
        } else if (svgToggleButtonLookAndFeel->passiveIconOpacity == 0.7f) {
            svgToggleButtonLookAndFeel->passiveIconOpacity = 1.0f;
            crystalizeButton.repaint();
        }
    }
}


void CrystalizerEQAudioProcessorEditor::addComponentsToLayout() {
    addAndMakeVisible(headerBar);
    headerBar.addAndMakeVisible(titleLabel);

    headerBar.addAndMakeVisible(presetArea);
    presetArea.addAndMakeVisible(presetBoxLabel);
    presetArea.addAndMakeVisible(resetButton);
    presetArea.addAndMakeVisible(presetBox);
    presetArea.addAndMakeVisible(presetMenuButton);

    headerBar.addAndMakeVisible(presetMenu);


    addAndMakeVisible(mainPanel);
    mainPanel.addAndMakeVisible(analyzerArea);
    mainPanel.addAndMakeVisible(crystalizeButton);

    //FILTERAREA
    {
        mainPanel.addAndMakeVisible(filterArea); {
            filterArea.addAndMakeVisible(lowFilterArea);

            lowBandModeBox.addAndMakeVisible(lowBandModeLabel);
            lowBandModeBox.addAndMakeVisible(lowBypass);
            lowBandModeBox.addAndMakeVisible(lowCut);
            lowBandModeBox.addAndMakeVisible(lowBell);
            lowBandModeBox.addAndMakeVisible(lowShelf);
            lowFilterArea.addAndMakeVisible(lowBandModeBox);
            lowBandModeButtons.add(&lowBypass, &lowCut, &lowShelf, &lowBell);
            lowBandBools = {false, false, true, false};


            lowFilterArea.addAndMakeVisible(lowBandSlopeLabel);
            lowFilterArea.addAndMakeVisible(lowBandGainLabel);
            lowFilterArea.addAndMakeVisible(lowBandQLabel);
            lowFilterArea.addAndMakeVisible(lowBandFreqLabel);

            lowBandSlopeSlider.addAndMakeVisible(low12);
            lowBandSlopeSlider.addAndMakeVisible(low24);
            lowBandSlopeSlider.addAndMakeVisible(low36);
            lowBandSlopeSlider.addAndMakeVisible(low48);

            lowFilterArea.addAndMakeVisible(lowBandSlopeSlider);
            lowFilterArea.addAndMakeVisible(lowdBOctLabel);

            lowFilterArea.addAndMakeVisible(lowBandGainSlider);
            lowFilterArea.addAndMakeVisible(lowBandQSlider);
            lowFilterArea.addAndMakeVisible(lowBandFreqSlider);
        } {
            filterArea.addAndMakeVisible(lowMidFilterArea);
            lowMidFilterArea.addAndMakeVisible(peak1FreqLabel);
            lowMidFilterArea.addAndMakeVisible(peak1GainLabel);
            lowMidFilterArea.addAndMakeVisible(peak1QLabel);
            lowMidFilterArea.addAndMakeVisible(peak1FreqSlider);
            lowMidFilterArea.addAndMakeVisible(peak1GainSlider);
            lowMidFilterArea.addAndMakeVisible(peak1QSlider);
            lowMidFilterArea.addAndMakeVisible(peak1BypassButton);
        } {
            filterArea.addAndMakeVisible(midFilterArea);
            midFilterArea.addAndMakeVisible(peak2FreqLabel);
            midFilterArea.addAndMakeVisible(peak2GainLabel);
            midFilterArea.addAndMakeVisible(peak2QLabel);
            midFilterArea.addAndMakeVisible(peak2FreqSlider);
            midFilterArea.addAndMakeVisible(peak2GainSlider);
            midFilterArea.addAndMakeVisible(peak2QSlider);
            midFilterArea.addAndMakeVisible(peak2BypassButton);
        } {
            filterArea.addAndMakeVisible(highMidFilterArea);
            highMidFilterArea.addAndMakeVisible(peak3FreqLabel);
            highMidFilterArea.addAndMakeVisible(peak3GainLabel);
            highMidFilterArea.addAndMakeVisible(peak3QLabel);
            highMidFilterArea.addAndMakeVisible(peak3FreqSlider);
            highMidFilterArea.addAndMakeVisible(peak3GainSlider);
            highMidFilterArea.addAndMakeVisible(peak3QSlider);
            highMidFilterArea.addAndMakeVisible(peak3BypassButton);
        } {
            filterArea.addAndMakeVisible(highFilterArea);

            highBandModeBox.addAndMakeVisible(highBandModeLabel);
            highBandModeBox.addAndMakeVisible(highBypass);
            highBandModeBox.addAndMakeVisible(highCut);
            highBandModeBox.addAndMakeVisible(highBell);
            highBandModeBox.addAndMakeVisible(highShelf);
            highFilterArea.addAndMakeVisible(highBandModeBox);
            highBandModeButtons.add(&highBypass, &highCut, &highShelf, &highBell);
            highBandBools = {false, false, true, false};

            highFilterArea.addAndMakeVisible(highBandSlopeLabel);
            highFilterArea.addAndMakeVisible(highBandGainLabel);
            highFilterArea.addAndMakeVisible(highBandQLabel);
            highFilterArea.addAndMakeVisible(highBandFreqLabel);

            highBandSlopeSlider.addAndMakeVisible(high12);
            highBandSlopeSlider.addAndMakeVisible(high24);
            highBandSlopeSlider.addAndMakeVisible(high36);
            highBandSlopeSlider.addAndMakeVisible(high48);
            highFilterArea.addAndMakeVisible(highBandSlopeSlider);
            highFilterArea.addAndMakeVisible(highdBOctLabel);


            highFilterArea.addAndMakeVisible(highBandGainSlider);
            highFilterArea.addAndMakeVisible(highBandQSlider);
            highFilterArea.addAndMakeVisible(highBandFreqSlider);
        }
    }


    globalControlArea.addAndMakeVisible(inputSlider);
    globalControlArea.addAndMakeVisible(outputSlider);
    globalControlArea.addAndMakeVisible(masterBypassButton);
    footerBar.addAndMakeVisible(globalControlArea);
    addAndMakeVisible(footerBar);
}


void CrystalizerEQAudioProcessorEditor::setupLabels() {
    auto setupLabel = [](juce::Label &label, const juce::String &text) {
        label.setText(text, juce::dontSendNotification);
        label.setJustificationType(juce::Justification::centred);
        label.setColour(juce::Label::textColourId, juce::Colours::black);
        label.setInterceptsMouseClicks(false, false); // Klicks gehen an den Slider
    }; {
        //LOW-BAND
        setupLabel(lowBandFreqLabel, "Low\nHz");
        setupLabel(lowBandSlopeLabel, "Slope");
        setupLabel(lowBandGainLabel, "Low\nGain");
        setupLabel(lowBandQLabel, "Low\nQ");
        setupLabel(lowBandModeLabel, "Band Mode");
        setupLabel(low12, "12");
        setupLabel(low24, "24");
        setupLabel(low36, "36");
        setupLabel(low48, "48");
        setupLabel(lowdBOctLabel, "dB/Oct");

        //PEAK 1
        setupLabel(peak1FreqLabel, "Low-Mid\nHz");
        setupLabel(peak1GainLabel, "Low-Mid\nGain");
        setupLabel(peak1QLabel, "Low-Mid\nQ");

        //PEAK 2
        setupLabel(peak2FreqLabel, "Mid\nHz");
        setupLabel(peak2GainLabel, "Mid\nGain");
        setupLabel(peak2QLabel, "Mid\nQ");

        //PEAK 3
        setupLabel(peak3FreqLabel, "High-Mid\nHz");
        setupLabel(peak3GainLabel, "High-Mid\nGain");
        setupLabel(peak3QLabel, "High-Mid\nQ");

        //HIGH-BAND
        setupLabel(highBandFreqLabel, "High\nHz");
        setupLabel(highBandSlopeLabel, "Slope");
        setupLabel(highBandGainLabel, "High\nGain");
        setupLabel(highBandQLabel, "High\nQ");
        setupLabel(highBandModeLabel, "Band Mode");
        setupLabel(high12, "12");
        setupLabel(high24, "24");
        setupLabel(high36, "36");
        setupLabel(high48, "48");
        setupLabel(highdBOctLabel, "dB/Oct");


        setupLabel(presetBoxLabel, "Presets");
    }
}


void CrystalizerEQAudioProcessorEditor::setupFontsWithColours() {
    Typography::applyToLabel(titleLabel, Typography::Style::Display, 1.f);
    titleLabel.setColour(juce::Label::textColourId, Colours::ACCENTCOLOUR);

    Typography::applyToLabel(presetBoxLabel, Typography::Style::H3, 1.f);
    presetBoxLabel.setColour(juce::Label::textColourId, Colours::FOREGROUNDCOLOUR);

    for (auto *l: sliderLabels) {
        l->setColour(juce::Label::textColourId, Colours::FOREGROUNDCOLOUR);
        Typography::applyToLabel(*l, Typography::Style::Mono, 1.f);
    }
}


void CrystalizerEQAudioProcessorEditor::setupSliderTextBoxes() {
    baseLookAndFeel = std::make_unique<AXIOM::DesignSystem::Components::BaseSliderLookAndFeel>();
    for (auto *s: sliders) {
        s->setLookAndFeel(baseLookAndFeel.get());
    }
}


void CrystalizerEQAudioProcessorEditor::handleLowBandModes() {
    for (int i = 0; i < lowBandModeButtons.size(); ++i) {
        lowBandModeButtons[i]->onClick = [this, i] {
            if (!lowBandBools[i]) {
                if (auto *param = audioProcessor.apvts.getParameter("LowBandModes")) {
                    param->beginChangeGesture();

                    for (int j = 0; j < lowBandBools.size(); ++j) {
                        if (j != i) {
                            lowBandBools[j] = false;
                            param->setValueNotifyingHost(param->convertTo0to1(0.0f));
                            lowBandModeButtons[j]->setToggleState(false, juce::dontSendNotification);
                        }
                    }
                    lowBandBools[i] = true;
                    param->setValueNotifyingHost(param->convertTo0to1((float) i));

                    param->endChangeGesture();

                    updateFrequencyRanges();
                }
            } else if (lowBandBools[i]) {
                lowBandModeButtons[i]->setToggleState(true, juce::dontSendNotification);
            }
        };
    }
}


void CrystalizerEQAudioProcessorEditor::handleHighBandModes() {
    for (int i = 0; i < highBandModeButtons.size(); ++i) {
        highBandModeButtons[i]->onClick = [this, i] {
            if (!highBandBools[i]) {
                if (auto *param = audioProcessor.apvts.getParameter("HighBandModes")) {
                    param->beginChangeGesture();

                    for (int j = 0; j < highBandBools.size(); ++j) {
                        if (j != i) {
                            highBandBools[j] = false;
                            param->setValueNotifyingHost(param->convertTo0to1(0.0f));
                            highBandModeButtons[j]->setToggleState(false, juce::dontSendNotification);
                        }
                    }
                    highBandBools[i] = true;
                    param->setValueNotifyingHost(param->convertTo0to1((float) i));

                    param->endChangeGesture();
                    updateFrequencyRanges();
                }
            } else if (highBandBools[i]) {
                highBandModeButtons[i]->setToggleState(true, juce::dontSendNotification);
            }
        };
    }
}


void CrystalizerEQAudioProcessorEditor::setupEventListeners() {
    presetMenuButton.onClick = [this]() {
        auto *menu = new DesignSystem::PresetMenu(&presetNameInput, &savePresetButton, &deletePresetButton);
        const auto presetMenuBounds = presetMenu.getLocalBounds();
        menu->setBounds(presetMenuBounds);

        presetMenuLookAndFeel = std::make_unique<DesignSystem::PresetMenuLookAndFeel>();

        juce::Rectangle<int> areaToPointTo = presetMenuButton.getScreenBounds();

        auto *menuRaw = menu;
        presetMenuSafePtr = menuRaw;

        auto &box = juce::CallOutBox::launchAsynchronously(
            std::unique_ptr<juce::Component>(menu),
            areaToPointTo,
            nullptr
        );
        box.setLookAndFeel(presetMenuLookAndFeel.get());
    };

    savePresetButton.onClick = [this]() {
        if (presetNameInput.getText() != "")
            audioProcessor.setPresetName(presetNameInput.getText());
        else
            audioProcessor.setPresetName("Preset");

        const auto presetName = audioProcessor.savePresetToFile();


        presetBox.clear();

        const auto updatedPresets = audioProcessor.getPresetNamesArray();
        presetBox.addItemList(updatedPresets, 1);

        for (int i = 0; i < presetBox.getNumItems(); i++) {
            if (presetName != presetBox.getItemText(i)) continue;
            presetBox.setSelectedId(i + 1, juce::dontSendNotification);
        }

        presetNameInput.setText("");
    };

    deletePresetButton.onClick = [this]() {
        auto selectedPreset = presetBox.getText();
        if (selectedPreset == "Init") return;
        const auto selectedPresetWithExt = selectedPreset + ".xml";
        audioProcessor.deletePreset(selectedPresetWithExt);
        audioProcessor.resetAllParameters();

        presetBox.clear();
        const auto updatedPresets = audioProcessor.getPresetNamesArray();
        presetBox.addItemList(updatedPresets, 1);
        presetBox.setSelectedId(1, juce::dontSendNotification);
    };

    presetBox.onChange = [this]() {
        auto selectedPreset = presetBox.getText();
        if (selectedPreset == "Init") {
            audioProcessor.resetAllParameters();
            updateModeButtons();
            return;
        }
        const auto selectedPresetWithExt = selectedPreset + ".xml";
        audioProcessor.loadPreset(selectedPresetWithExt);
        updateModeButtons(); // ← NEU!
        updateFrequencyRanges();
    };

    resetButton.onClick = [this]() {
        audioProcessor.resetAllParameters();
        if (crystalizeButton.getToggleState()) {
            isAnimatingCrystalize = true;
            isFadingToActive = (svgToggleButtonLookAndFeel->activeIconOpacity < 1.0f);
        }
        resetAllCheckboxes();
        for (auto *s: sliders) {
            s->setEnabled(true);
        }
        for (auto *l: sliderLabels) {
            l->setEnabled(true);
        }
        presetBox.setSelectedId(1, juce::dontSendNotification);
        updateFrequencyRanges();
    };

    handleLowBandModes();

    handleHighBandModes();

    presetNameInput.onMouseDown = [this]() {
        inputFocused = true;
    };
}


void CrystalizerEQAudioProcessorEditor::updateModeButtons() {
    int lowMode = static_cast<int>(audioProcessor.apvts.getRawParameterValue("LowBandModes")->load());

    for (int i = 0; i < lowBandModeButtons.size(); ++i) {
        lowBandBools[i] = (i == lowMode);
        lowBandModeButtons[i]->setToggleState(lowBandBools[i], juce::dontSendNotification);
    }

    int highMode = static_cast<int>(audioProcessor.apvts.getRawParameterValue("HighBandModes")->load());

    for (int i = 0; i < highBandModeButtons.size(); ++i) {
        highBandBools[i] = (i == highMode);
        highBandModeButtons[i]->setToggleState(highBandBools[i], juce::dontSendNotification);
    }
}

void CrystalizerEQAudioProcessorEditor::parameterChanged(const juce::String& parameterID, float newValue) {
    if (parameterID == "LowBandModes" || parameterID == "HighBandModes") {
        juce::MessageManager::callAsync([this]() {
            updateModeButtons();
            repaint();
        });
    }
}


void CrystalizerEQAudioProcessorEditor::initPresetSystem() {
    auto presets = audioProcessor.getPresetNamesArray();
    presetBox.addItemList(presets, 1);
    presetBox.setSelectedId(1, juce::dontSendNotification);

    presetMenuButton.setName("PresetMenuButton");
    presetMenuButton.setColour(juce::ToggleButton::textColourId, juce::Colours::black);
    presetMenuButton.setColour(juce::ToggleButton::tickColourId, juce::Colours::black);
    presetMenuButton.setToggleState(false, juce::dontSendNotification);

    presetNameInput.setJustification(juce::Justification::centred);


    presetButtonLookAndFeel = std::make_unique<DesignSystem::PresetButtonsLookAndFeel>();
    presetComboBoxLookAndFeel = std::make_unique<DesignSystem::PresetComboBoxLookAndFeel>();
    presetInputLookAndFeel = std::make_unique<DesignSystem::PresetInputLookAndFeel>();

    savePresetButton.setLookAndFeel(presetButtonLookAndFeel.get());
    deletePresetButton.setLookAndFeel(presetButtonLookAndFeel.get());
    resetButton.setLookAndFeel(presetButtonLookAndFeel.get());
    presetNameInput.setLookAndFeel(presetInputLookAndFeel.get());
    presetBox.setLookAndFeel(presetComboBoxLookAndFeel.get());
}

void CrystalizerEQAudioProcessorEditor::syncPresetBox() {
    const auto currentPresetName = audioProcessor.getPresetName();

    // Durch alle Items in der ComboBox gehen
    for (int i = 0; i < presetBox.getNumItems(); ++i) {
        if (presetBox.getItemText(i) == currentPresetName) {
            presetBox.setSelectedId(i + 1, juce::dontSendNotification);
            return;
        }
    }

    presetBox.setSelectedId(1, juce::dontSendNotification);
}

void CrystalizerEQAudioProcessorEditor::syncAllButtonStates() {
    // Peak Bypass Buttons
    const bool peak1Bypassed = audioProcessor.apvts.getRawParameterValue("Peak1Bypass")->load() > 0.5f;
    if (peak1Bypassed) {
        disableLowMidBand(1.0f);
    }

    const bool peak2Bypassed = audioProcessor.apvts.getRawParameterValue("Peak2Bypass")->load() > 0.5f;
    if (peak2Bypassed) {
        disableMidBand(1.0f);
    }

    const bool peak3Bypassed = audioProcessor.apvts.getRawParameterValue("Peak3Bypass")->load() > 0.5f;
    if (peak3Bypassed) {
        disableHighMidBand(1.0f);
    }

    // Master Bypass
    const bool masterBypassed = audioProcessor.apvts.getRawParameterValue("MasterBypass")->load() > 0.5f;
    if (masterBypassed) {
        disableEverything(1.0f);
    }

    // Crystalize Button - Icon-Opacity setzen
    const bool crystalized = audioProcessor.apvts.getRawParameterValue("CrystalizeButton")->load() > 0.5f;
    if (crystalized) {
        svgToggleButtonLookAndFeel->activeIconOpacity = 1.0f;
        svgToggleButtonLookAndFeel->passiveIconOpacity = 0.0f;
    } else {
        svgToggleButtonLookAndFeel->activeIconOpacity = 0.0f;
        svgToggleButtonLookAndFeel->passiveIconOpacity = 1.0f;
    }
    crystalizeButton.repaint();

    syncPresetBox();
}


//==============================================================================
CrystalizerEQAudioProcessorEditor::CrystalizerEQAudioProcessorEditor(CrystalizerEQAudioProcessor &p)
    : AudioProcessorEditor(&p), audioProcessor(p), spectrumAnalyzer(p.audioFIFO, p) {
    setSize(1280, 720);
    startTimerHz(60);
    addComponentsToLayout();
    setupLabels();

    setupFontsWithColours();
    setupAttachments();
    setupDisplayNames();
    setupToggleButtons();
    setupEventListeners();
    setupSliderTextBoxes();
    setupSliders();
    initPresetSystem();
    updateFrequencyRanges();

    auto logoIcon = juce::XmlDocument::parse(BinaryData::logo_icon_svg);
    if (logoIcon != nullptr)
        logoDrawable = juce::Drawable::createFromSVG(*logoIcon);
    audioProcessor.apvts.addParameterListener("LowBandModes", this);
    audioProcessor.apvts.addParameterListener("HighBandModes", this);

    updateModeButtons();
    syncAllButtonStates();
}

CrystalizerEQAudioProcessorEditor::~CrystalizerEQAudioProcessorEditor() {
    stopTimer();
    for (auto *s: sliders) {
        s->setLookAndFeel(nullptr);
    }
    peak1BypassButton.setLookAndFeel(nullptr);
    peak2BypassButton.setLookAndFeel(nullptr);
    peak3BypassButton.setLookAndFeel(nullptr);
    masterBypassButton.setLookAndFeel(nullptr);
    crystalizeButton.setLookAndFeel(nullptr);
    presetMenuButton.setLookAndFeel(nullptr);
    presetNameInput.setLookAndFeel(nullptr);
    presetBox.setLookAndFeel(nullptr);

    audioProcessor.apvts.removeParameterListener("LowBandModes", this);
    audioProcessor.apvts.removeParameterListener("HighBandModes", this);
    for (auto *b: lowBandModeButtons) {
        b->setLookAndFeel(nullptr);
    }
    for (auto *b: highBandModeButtons) {
        b->setLookAndFeel(nullptr);
    }
};

float CrystalizerEQAudioProcessorEditor::getInterpolatedDb(float exactBin) {
    int bin1 = static_cast<int>(std::floor(exactBin));
    int bin2 = static_cast<int>(std::ceil(exactBin));

    const int maxBin = static_cast<int>(spectrumAnalyzer.renderValuesDb.size()) - 1;
    bin1 = juce::jlimit(0, maxBin, bin1);
    bin2 = juce::jlimit(0, maxBin, bin2);

    if (bin1 == bin2) return spectrumAnalyzer.renderValuesDb[bin1];

    float y1 = juce::jlimit(spectrumAnalyzer.MINDB, spectrumAnalyzer.MAXDB, spectrumAnalyzer.renderValuesDb[bin1]);
    float y2 = juce::jlimit(spectrumAnalyzer.MINDB, spectrumAnalyzer.MAXDB, spectrumAnalyzer.renderValuesDb[bin2]);

    float fraction = exactBin - bin1;
    return y1 + fraction * (y2 - y1);
}

void CrystalizerEQAudioProcessorEditor::paintAnalyzer(juce::Graphics &g) {
    analyzerRect = getLocalArea(&analyzerArea, analyzerArea.getLocalBounds());
    juce::Graphics::ScopedSaveState guard(g);
    g.reduceClipRegion(analyzerRect);

    auto r = analyzerRect.toFloat();

    // BACKGROUND
    g.setColour(juce::Colours::black);
    g.fillRect(r);

    // BORDER
    g.setColour(juce::Colours::grey);
    g.drawRect(analyzerRect);

    // SPECTRUM
    g.setColour(Colours::ACCENTCOLOUR);

    const float analyzerWidth = analyzerRect.getWidth();
    const float analyzerHeight = analyzerRect.getHeight();
    const float analyzerBottom = analyzerRect.getBottom();

    const float minDb = spectrumAnalyzer.MINDB;
    const float maxDb = spectrumAnalyzer.MAXDB;
    const float dbRange = maxDb - minDb; // = 96 dB

    const float minFreq = spectrumAnalyzer.MINFREQ; // 20 Hz
    const float maxFreq = spectrumAnalyzer.MAXFREQ; // 20 kHz

    float deltaF = spectrumAnalyzer.deltaF;

    juce::Path spectrumPath;
    bool pathStarted = false;

    const int resolutionMultiplier = 4;
    const int numPoints = static_cast<int>(analyzerWidth) * resolutionMultiplier;

    for (int i = 0; i < numPoints; ++i) {
        float x = (i / static_cast<float>(numPoints)) * analyzerWidth;

        float normalizedX = i / static_cast<float>(numPoints);
        float logMin = std::log10(minFreq);
        float logMax = std::log10(maxFreq);
        float logFreq = logMin + normalizedX * (logMax - logMin);
        float freq = std::pow(10.0f, logFreq);

        float exactBin = freq / deltaF;

        float dB = getInterpolatedDb(exactBin);

        float normalized = (dB - minDb) / dbRange;
        normalized = juce::jlimit(0.0f, 1.0f, normalized);

        float pixelHeight = normalized * analyzerHeight;
        float y = analyzerBottom - pixelHeight;

        if (!pathStarted) {
            spectrumPath.startNewSubPath(x + analyzerRect.getX(), y);
            pathStarted = true;
        } else {
            spectrumPath.lineTo(x + analyzerRect.getX(), y);
        }
    }

    if (pathStarted) {
        juce::PathStrokeType stroke(2.0f);
        stroke.setJointStyle(juce::PathStrokeType::curved); // Runde Ecken
        g.strokePath(spectrumPath, stroke);

        spectrumPath.lineTo(analyzerRect.getRight(), analyzerBottom);
        spectrumPath.lineTo(analyzerRect.getX(), analyzerBottom);
        spectrumPath.closeSubPath();

        g.setColour(Colours::ACCENTCOLOUR.withAlpha(0.3f));
        g.fillPath(spectrumPath);
    }

    drawFrequencyGrid(g, dbRange);
}


float CrystalizerEQAudioProcessorEditor::mapFrequencyToX(float freq, float minFreq, float maxFreq, float width) {
    float logMin = std::log10(minFreq);
    float logMax = std::log10(maxFreq);
    float logFreq = std::log10(freq);

    float normalized = (logFreq - logMin) / (logMax - logMin);
    return normalized * width;
}


void CrystalizerEQAudioProcessorEditor::drawFrequencyGrid(juce::Graphics &g, const float dbRange) {
    g.setColour(Colours::FOREGROUNDCOLOUR.withAlpha(0.3f));

    const float minFreq = spectrumAnalyzer.MINFREQ;
    const float maxFreq = spectrumAnalyzer.MAXFREQ;
    const float analyzerWidth = analyzerRect.getWidth();

    const float minDB = spectrumAnalyzer.MINDB;
    const float maxDB = spectrumAnalyzer.MAXDB;
    const float analyzerHeight = analyzerRect.getHeight();
    const float analyzerBottom = analyzerRect.getBottom();

    std::vector<float> gridFreqs = {50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000};
    std::vector<float> gridDB = {0, -12, -24, -36, -48, -60, -72};


    for (int i = 0; i < gridDB.size(); ++i) {
        float db = gridDB[i];

        float normalized = (db - minDB) / dbRange;
        normalized = juce::jlimit(0.0f, 1.0f, normalized);

        float pixelHeight = normalized * analyzerHeight;
        float y = analyzerBottom - pixelHeight;

        g.drawHorizontalLine(static_cast<int>(y), static_cast<float>(analyzerRect.getX()),
                             static_cast<float>(analyzerRect.getRight()));
        g.setFont(Typography::getFont(Typography::Style::Mono, 1.0f));
        juce::String label = juce::String(gridDB[i]);
        g.drawText(label, analyzerRect.getX(), y, 30, 15,
                   juce::Justification::centred);
    }

    for (float freq: gridFreqs) {
        float x = mapFrequencyToX(freq, minFreq, maxFreq, analyzerWidth);
        x += analyzerRect.getX();

        g.drawVerticalLine(static_cast<int>(x),
                           static_cast<float>(analyzerRect.getY()),
                           static_cast<float>(analyzerRect.getBottom()));

        if (freq == 20) continue;
        g.setFont(Typography::getFont(Typography::Style::Mono, 1.0f));
        juce::String label = (freq >= 1000) ? juce::String(freq / 1000) + "k" : juce::String(freq);
        g.drawText(label, static_cast<int>(x) - 15, analyzerRect.getY(), 30, 15,
                   juce::Justification::centred);
    }
}


void CrystalizerEQAudioProcessorEditor::paintModeBoxBorders(juce::Graphics &g) {
    for (auto *b: lowBandModeButtons) {
        if (b->getToggleState()) {
            auto r = getLocalArea(&lowBandModeBox, b->getBounds());
            int side = juce::jmin(r.getWidth(), r.getHeight());
            r = r.withSizeKeepingCentre(side, side);

            auto rf = r.toFloat();
            auto rfFilled = rf.toNearestInt().toFloat();
            const float stroke = 1.0f;
            auto rfStroke = rfFilled.reduced(stroke * 0.5f);

            g.setColour(juce::Colours::white.withAlpha(0.3f));
            g.fillRoundedRectangle(r.toFloat(), 5);
            g.setColour(juce::Colours::white.withAlpha(0.9f));
            g.drawRoundedRectangle(rfStroke, 5, stroke);
        }
    }
}


void CrystalizerEQAudioProcessorEditor::paint(juce::Graphics &g) {
    g.fillAll(Colours::BACKGROUNDCOLOUR);
    g.setColour(AXIOM::DesignSystem::Colours::FOREGROUNDCOLOUR);


    const auto mP = getLocalArea(&mainPanel, mainPanel.getLocalBounds());
    auto mPX = mP.getX();
    auto mPY = mP.getY();
    auto mPW = mP.getWidth();
    auto mPH = mP.getHeight();

    paintAnalyzer(g);
    g.setColour(Colours::SURFACEBYPASS);
    const auto hB = getLocalArea(&headerBar, headerBar.getLocalBounds());
    auto hBX = hB.getX();
    auto hBY = hB.getY();
    auto hBW = hB.getWidth();
    auto hBH = hB.getHeight();
    auto hBPad = ((hBY + mPY) - hBH) / 2;
    g.fillRect(hBX, hBY, hBW, mPY - hBPad);


    g.setColour(Colours::SURFACECOLOUR);
    const auto pA = getLocalArea(&presetArea, presetArea.getLocalBounds());
    auto pAX = pA.getX();
    auto pAY = pA.getY();
    auto pAWidth = pA.getWidth();
    auto pAHeight = pA.getHeight();
    g.fillRoundedRectangle(pAX, pAY - 10.f, pAWidth, pAHeight + 10.f, 10.0f);

    /*auto logoArea = hB.toFloat().reduced(hBPad);
    logoArea = logoArea.withLeft(pAWidth + pAWidth / 2 + pAWidth / 4 + pAWidth / 8);


    logoDrawable->drawWithin(g, logoArea, juce::RectanglePlacement::centred, 1.0f);*/


    const auto fA = getLocalArea(&filterArea, filterArea.getLocalBounds());
    const int fABorderWidth = 3;
    auto fABorder = fA.withSizeKeepingCentre(fA.getWidth() + fABorderWidth, fA.getHeight() + fABorderWidth);
    fABorder = fABorder.withX(fABorder.getX() + fABorderWidth).withY(fABorder.getY() + fABorderWidth);


    g.setColour(Colours::SURFACECOLOUR);
    g.fillRect(fABorder);

    fABorder = fABorder.withX(fABorder.getX() - 2 * fABorderWidth).withY(fABorder.getY() - 2 * fABorderWidth);
    g.fillRect(fABorder);

    g.setColour(Colours::BACKGROUNDBYPASS);
    g.fillRect(fA);

    //paintModeBoxBorders(g);

    g.setColour(Colours::SURFACEBYPASS);
    const auto fB = getLocalArea(&footerBar, footerBar.getLocalBounds());
    g.fillRect(fB);
    paintBorderLines(g);

    /*
    auto logoArea = fB.toFloat().reduced(hBPad);

    logoDrawable->drawWithin(g, logoArea, juce::RectanglePlacement::xLeft, 1.0f);
    */
}


void CrystalizerEQAudioProcessorEditor::paintBorderLines(juce::Graphics &g) {
    g.setColour(DesignSystem::Colours::BACKGROUNDCOLOUR);
    auto prevRight = (float) lowFilterArea.getRight();

    for (auto *c: filterAreas) {
        if (c == &lowFilterArea) continue;

        const auto area = getLocalArea(c, c->getLocalBounds());
        const float xAvg = ((float) area.getX() - prevRight) / 2;
        const int x = area.getX() - xAvg + (filterAreaMargin / 2);
        prevRight = (float) c->getRight();

        const auto top = (float) area.getY();
        const auto bot = (float) area.getBottom();


        const float center = (top + bot) * 0.5f;
        const float halfLen = (bot - top) * 0.375f;
        g.drawVerticalLine(x, center - halfLen, center + halfLen);
    }
    g.setColour(DesignSystem::Colours::SURFACECOLOUR);
    auto prevRightGlobal = (float) globalControlArea.getRight();
    for (auto *c: globalControlArea.getChildren()) {
        if (c == &inputSlider) continue;
        if (c == &masterBypassButton) continue;

        const auto area = getLocalArea(c, c->getLocalBounds());
        const float xAvg = ((float) area.getX() - prevRightGlobal) / 2;
        const int x = area.getX() - xAvg;
        prevRightGlobal = (float) c->getRight();

        const auto top = (float) area.getY();
        const auto bot = (float) area.getBottom();


        const float center = (top + bot) * 0.5f;
        const float halfLen = (bot - top) * 0.375f;
        g.drawVerticalLine(x, center - halfLen, center + halfLen);
    }
}


void CrystalizerEQAudioProcessorEditor::setKnobVisibility() {
    int lowMode = (int) audioProcessor.apvts
            .getRawParameterValue("LowBandModes")->load();
    const bool masterIsToggled = masterBypassButton.getToggleState();

    const float target = masterIsToggled ? 1.0f : 0.0f;
    if (masterIsToggled) {
        lowMode = 0;
        disableLowBand(target);
    }

    lowBandFreqSlider.setEnabled(lowMode >= 1);
    lowBandFreqLabel.setEnabled(lowMode >= 1);
    lowBandSlopeSlider.setEnabled(lowMode == 1);
    lowBandSlopeLabel.setEnabled(lowMode == 1);
    lowdBOctLabel.setEnabled(lowMode == 1);
    lowBandGainSlider.setEnabled(lowMode >= 2);
    lowBandGainLabel.setEnabled(lowMode >= 2);
    lowBandQSlider.setEnabled(lowMode >= 1);
    lowBandQLabel.setEnabled(lowMode >= 1);
    lowBandModeLabel.setEnabled(lowMode >= 1);

    int highMode = (int) audioProcessor.apvts
            .getRawParameterValue("HighBandModes")->load();

    if (masterIsToggled) {
        highMode = 0;
        disableHighBand(target);
    }
    highBandFreqSlider.setEnabled(highMode >= 1);
    highBandFreqLabel.setEnabled(highMode >= 1);
    highBandSlopeSlider.setEnabled(highMode == 1);
    highBandSlopeLabel.setEnabled(highMode == 1);
    highdBOctLabel.setEnabled(highMode == 1);
    highBandGainSlider.setEnabled(highMode >= 2);
    highBandGainLabel.setEnabled(highMode >= 2);
    highBandQSlider.setEnabled(highMode >= 1);
    highBandQLabel.setEnabled(highMode >= 1);
    highBandModeLabel.setEnabled(highMode >= 1);
}


void CrystalizerEQAudioProcessorEditor::updateFrequencyRanges() {
    int lowMode = (int) audioProcessor.apvts.getRawParameterValue("LowBandModes")->load();

    if (lowMode == 3) {
        lowBandFreqSlider.setRange(20.0, 500.0, 1.0);

        float currentFreq = audioProcessor.apvts.getRawParameterValue("LowBandFreq")->load();
        if (currentFreq > 500.0f) {
            lowBandFreqSlider.setValue(500.0, juce::sendNotificationSync);
        }
    } else if (lowMode >= 1) {
        lowBandFreqSlider.setRange(20.0, 20000.0, 1.0);
    }

    lowBandFreqSlider.repaint();
    int highMode = (int) audioProcessor.apvts.getRawParameterValue("HighBandModes")->load();

    if (highMode == 3) {
        highBandFreqSlider.setRange(8000.0, 20000.0, 1.0);

        float currentFreq = audioProcessor.apvts.getRawParameterValue("HighBandFreq")->load();
        if (currentFreq < 8000.0f) {
            highBandFreqSlider.setValue(8000.0, juce::sendNotificationSync);
        }
    } else if (highMode >= 1) {
        highBandFreqSlider.setRange(20.0, 20000.0, 1.0);
    }
    highBandFreqSlider.repaint();
}


void CrystalizerEQAudioProcessorEditor::animateCrystalizeButton() {
    if (isAnimatingCrystalize) {
        const float step = 0.1f;
        if (isFadingToActive) {
            svgToggleButtonLookAndFeel->activeIconOpacity += step;
            svgToggleButtonLookAndFeel->passiveIconOpacity -= step;

            if (svgToggleButtonLookAndFeel->activeIconOpacity >= 1.0f) {
                svgToggleButtonLookAndFeel->activeIconOpacity = 1.0f;
                svgToggleButtonLookAndFeel->passiveIconOpacity = 0.0f;
                isAnimatingCrystalize = false;
            }
        } else {
            svgToggleButtonLookAndFeel->activeIconOpacity -= step;
            svgToggleButtonLookAndFeel->passiveIconOpacity += step;

            if (svgToggleButtonLookAndFeel->passiveIconOpacity >= 1.0f) {
                svgToggleButtonLookAndFeel->activeIconOpacity = 0.0f;
                svgToggleButtonLookAndFeel->passiveIconOpacity = 1.0f;
                isAnimatingCrystalize = false;
            }
        }
        crystalizeButton.repaint();
    }
}


void CrystalizerEQAudioProcessorEditor::timerCallback() {
    setKnobVisibility();
    spectrumAnalyzer.processSamples();
    repaint(analyzerRect);
    resized();
    animateCrystalizeButton();
    updateFrequencyRanges();

    if (presetMenuSafePtr == nullptr) {
        presetMenuButton.setToggleState(false, juce::dontSendNotification);
        inputFocused = false;
    } else if (!inputFocused) {
        presetNameInput.giveAwayKeyboardFocus();
    }
}


void CrystalizerEQAudioProcessorEditor::resized() {
    auto pluginArea = getLocalBounds();
    scalePluginWindow(pluginArea);
    setupMainGrid(pluginArea);
    setupHeader();
    setupBody();
    setupFooter();
}

void CrystalizerEQAudioProcessorEditor::resetAllCheckboxes() {
    const auto notify = juce::dontSendNotification;
    masterBypassButton.setToggleState(false, notify);
    crystalizeButton.setToggleState(false, notify);
    peak1BypassButton.setToggleState(false, notify);
    peak2BypassButton.setToggleState(false, notify);
    peak3BypassButton.setToggleState(false, notify);

    lowBypass.setToggleState(false, notify);
    lowCut.setToggleState(false, notify);
    lowBell.setToggleState(false, notify);
    lowShelf.setToggleState(true, notify);

    highBypass.setToggleState(false, notify);
    highCut.setToggleState(false, notify);
    highBell.setToggleState(false, notify);
    highShelf.setToggleState(true, notify);
}


void CrystalizerEQAudioProcessorEditor::scalePluginWindow(juce::Rectangle<int> area) {
    if (area.getWidth() < 520) Spacing::setUiScale(Spacing::uiScaleMode::XS);
    else if (area.getWidth() < 720) Spacing::setUiScale(Spacing::uiScaleMode::S);
    else if (area.getWidth() < 1024) Spacing::setUiScale(Spacing::uiScaleMode::M);
    else if (area.getWidth() < 1366) Spacing::setUiScale(Spacing::uiScaleMode::L);
    else Spacing::setUiScale(Spacing::uiScaleMode::XL);
}


void CrystalizerEQAudioProcessorEditor::setupMainGrid(juce::Rectangle<int> area) {
    const float headerHeight = static_cast<float>(getHeight()) * 0.1f;
    const float footerHeight = static_cast<float>(getHeight()) * 0.1f;

    Layout::GridSpec spec{
        /* cols */ {Layout::fr(1)},
        /* rows */ {Layout::pxTrack(headerHeight), Layout::fr(1), Layout::pxTrack(footerHeight)},
        // Header / Body / Footer
        /* colGap */ Spacing::SizeMode::S,
        /* rowGap */ Spacing::SizeMode::S,
        /* pad    */ Layout::padding(Spacing::SizeMode::XS)
    };
    Layout::grid(area, spec, {&headerBar, &mainPanel, &footerBar});
}


void CrystalizerEQAudioProcessorEditor::setupHeader() {
    const auto bounds = headerBar.getLocalBounds();

    const float presetAreaWidth = static_cast<float>(bounds.getWidth()) * 0.5f;

    const auto titleWidthCentre = titleLabel.getFont().getStringWidth("Crystalizer") / 4;

    Layout::GridSpec headerSpec{
        /* cols */ {Layout::fr(1), Layout::pxTrack(presetAreaWidth), Layout::fr(1)},
        /* rows */ {Layout::fr(1)},
        /* colGap */ Spacing::SizeMode::XS,
        /* rowGap */ Spacing::SizeMode::XS,
        /* pad    */ Layout::padding(Spacing::SizeMode::XS)
    };
    Layout::grid(bounds, headerSpec, {
                     Layout::area(presetArea, 1, 2, 2, 3),
                     Layout::area(titleLabel, 1, 1, 2, 2)
                     .withMargin(juce::GridItem::Margin(0, 0, 0, titleWidthCentre)),
                     Layout::area(presetMenu, 1, 3, 2, 4),
                 });

    const auto presetMenuBounds = presetMenu.getLocalBounds();

    Layout::GridSpec presetMenuSpec{
        /* cols */ {Layout::fr(1), Layout::fr(1)},
        /* rows */ {Layout::fr(1), Layout::fr(1)},
        /* colGap */ Spacing::SizeMode::XS,
        /* rowGap */ Spacing::SizeMode::XS,
        /* pad    */ Layout::padding(Spacing::SizeMode::XS)
    };
    Layout::grid(presetMenuBounds, presetMenuSpec, {
                     Layout::area(presetNameInput, 1, 1, 2, 3),
                     Layout::area(savePresetButton, 2, 1, 3, 2),
                     Layout::area(deletePresetButton, 2, 2, 3, 3),
                 });

    const auto presetAreaBounds = presetArea.getLocalBounds();
    const auto presetBoxWidth = static_cast<float>(presetArea.getWidth());
    const auto presetBoxHeight = static_cast<float>(presetArea.getHeight());
    const float rowHeight = presetBoxHeight * 0.25f;
    const float iconSize = rowHeight;

    Layout::GridSpec presetSpec{
        /* cols */ {Layout::fr(1), Layout::pxTrack(presetBoxWidth * 0.5f), Layout::fr(1)},
        /* rows */ {Layout::pxTrack(rowHeight), Layout::pxTrack(rowHeight)},
        /* colGap */ Spacing::SizeMode::XS,
        /* rowGap */ Spacing::SizeMode::XS,
        /* pad    */ Layout::padding(Spacing::SizeMode::S)
    };
    Layout::grid(presetAreaBounds, presetSpec, {
                     Layout::area(presetBoxLabel, 1, 2, 2, 3),
                     Layout::area(presetBox, 2, 2, 3, 3),
                     Layout::area(resetButton, 2, 1, 2, 2),
                     Layout::area(presetMenuButton, 2, 3, 3, 4)
                     .withWidth(iconSize)
                     .withHeight(iconSize)

                 });
}


void CrystalizerEQAudioProcessorEditor::setupBody() {
    const auto bounds = mainPanel.getLocalBounds();

    const auto bodyHeight = static_cast<float>(bounds.getHeight());
    const auto bodyWidth = static_cast<float>(bounds.getWidth());
    const auto bodyColWidth = bodyWidth / 5.0f;
    const auto crystalizeIconSize = 140.0f;

    Layout::GridSpec bodySpec{
        /* cols */
        {
            Layout::fr(1), Layout::pxTrack(bodyColWidth), Layout::pxTrack(bodyColWidth), Layout::pxTrack(bodyColWidth),
            Layout::fr(1)
        },
        /* rows */ {Layout::fr(1), Layout::fr(1)},
        /* colGap */ Spacing::SizeMode::XS,
        /* rowGap */ Spacing::SizeMode::XS,
        /* pad    */ Layout::padding(Spacing::SizeMode::XS)
    };
    Layout::grid(bounds, bodySpec, {
                     Layout::area(analyzerArea, 1, 2, 2, 5)
                     .withMargin(juce::GridItem::Margin(filterAreaMargin, 0, filterAreaMargin, 0)),
                     Layout::area(crystalizeButton, 1, 5, 2, 6)
                     .withWidth(crystalizeIconSize)
                     .withHeight(crystalizeIconSize * 0.5f)
                     .withAlignSelf(juce::GridItem::AlignSelf::center)
                     .withJustifySelf(juce::GridItem::JustifySelf::center),
                     Layout::area(filterArea, 2, 1, 3, 6)
                     .withMargin(juce::GridItem::Margin(0, filterAreaMargin, 0, filterAreaMargin))

                 });

    const auto filterAreaBounds = filterArea.getLocalBounds();
    const auto filterAreaWidth = static_cast<float>(filterArea.getWidth());
    const auto filterColWidth = filterAreaWidth * 0.2f;


    Layout::GridSpec filterSpec{
        /* cols */
        {
            Layout::fr(1), Layout::pxTrack(filterColWidth), Layout::pxTrack(filterColWidth),
            Layout::pxTrack(filterColWidth), Layout::fr(1)
        },
        /* rows */ {Layout::fr(1)},
        /* colGap */ Spacing::SizeMode::XS,
        /* rowGap */ Spacing::SizeMode::XS,
        /* pad    */ Layout::padding(Spacing::SizeMode::S)
    };
    Layout::grid(filterAreaBounds, filterSpec, {
                     Layout::area(lowFilterArea, 1, 1, 2, 2),
                     Layout::area(lowMidFilterArea, 1, 2, 2, 3),
                     Layout::area(midFilterArea, 1, 3, 2, 4),
                     Layout::area(highMidFilterArea, 1, 4, 2, 5),
                     Layout::area(highFilterArea, 1, 5, 2, 6),

                 });

    setupLowBandLayout();
    setupLowMidBandLayout();
    setupMidBandLayout();
    setupHighMidBandLayout();
    setupHighBandLayout();
}


void CrystalizerEQAudioProcessorEditor::setupLowBandLayout() {
    const auto bounds = lowFilterArea.getLocalBounds();
    const auto areaWidth = static_cast<float>(bounds.getWidth());
    const auto areaHeight = static_cast<float>(bounds.getHeight());
    const auto knobColWidth = areaWidth / 3.0f;
    const auto knobRowHeight = areaHeight / 3.0f;

    const auto refW = getReferenceCell()[0];
    const auto refH = getReferenceCell()[1];
    const auto gainSize = refH * gainMod;
    const auto offSetToGainTop = gainSize;
    const float labelPadding = Components::SlopeSliderLookAndFeel::labelPadding * 2;

    Layout::GridSpec lowBandSpec{
        /* cols */ {Layout::fr(1), Layout::pxTrack(knobColWidth), Layout::fr(1)},
        /* rows */ {Layout::fr(1), Layout::pxTrack(knobRowHeight * 1.25f), Layout::fr(1)},
        /* colGap */ Spacing::SizeMode::XS,
        /* rowGap */ Spacing::SizeMode::XS,
        /* pad    */ Layout::padding(Spacing::SizeMode::XS)
    };
    Layout::grid(bounds, lowBandSpec, {

                     Layout::area(lowBandFreqSlider, 2, 1, 3, 2)
                     .withWidth(refW * freqMod * 0.8f)
                     .withHeight(refH * freqMod * 0.8f)
                     .withAlignSelf(juce::GridItem::AlignSelf::center)
                     .withJustifySelf(juce::GridItem::JustifySelf::center),

                     Layout::area(lowBandGainSlider, 2, 2, 3, 3)
                     .withWidth(refW * gainMod * 0.8f)
                     .withHeight(refH * gainMod * 0.8f)
                     .withAlignSelf(juce::GridItem::AlignSelf::center)
                     .withJustifySelf(juce::GridItem::JustifySelf::center),

                     Layout::area(lowBandQSlider, 2, 3, 3, 4)
                     .withWidth(refW * qMod * 0.8f)
                     .withHeight(refH * qMod * 0.8f)
                     .withAlignSelf(juce::GridItem::AlignSelf::center)
                     .withJustifySelf(juce::GridItem::JustifySelf::center),

                     Layout::area(lowBandSlopeSlider, 3, 1, 4, 2)
                     .withWidth(refW * slopeMod + labelPadding)
                     .withHeight(refH * slopeMod + labelPadding)
                     .withAlignSelf(juce::GridItem::AlignSelf::center)
                     .withJustifySelf(juce::GridItem::JustifySelf::center),

                     Layout::area(lowBandModeBox, 3, 2, 4, 4),


                     Layout::area(lowBandFreqLabel, 2, 1, 3, 2)
                     .withMargin(juce::GridItem::Margin(-offSetToGainTop, 0, 0, 0)),
                     Layout::area(lowBandGainLabel, 1, 2, 2, 3),
                     Layout::area(lowBandQLabel, 2, 3, 3, 4)
                     .withMargin(juce::GridItem::Margin(-offSetToGainTop, 0, 0, 0)),
                     Layout::area(lowBandSlopeLabel, 3, 1, 4, 2)
                     .withMargin(juce::GridItem::Margin(-offSetToGainTop / 1.5f, 0, 0, 0)),

                     Layout::area(lowdBOctLabel, 3, 1, 4, 2)
                     .withAlignSelf(juce::GridItem::AlignSelf::center)
                     .withJustifySelf(juce::GridItem::JustifySelf::center),


                 });

    const auto modeBoxBounds = lowBandModeBox.getLocalBounds();
    const auto slopeH = lowBandSlopeSlider.getY() - lowBandModeBox.getY() + lowBandModeLabel.getFont().getHeight() / 2;

    Layout::GridSpec lowBandModeBoxSpec{
        /* cols */ {Layout::fr(1), Layout::fr(1), Layout::fr(1), Layout::fr(1)},
        /* rows */ {Layout::fr(1), Layout::fr(1)},
        /* colGap */ Spacing::SizeMode::XS,
        /* rowGap */ Spacing::SizeMode::XS,
        /* pad    */ Layout::padding(Spacing::SizeMode::XS)
    };

    Layout::grid(modeBoxBounds, lowBandModeBoxSpec, {
                     Layout::area(lowBandModeLabel, 1, 2, 2, 4)
                     .withMargin(juce::GridItem::Margin(slopeH, 0, 0, 0)),
                     Layout::area(lowBypass, 1, 1, 3, 2),
                     Layout::area(lowCut, 1, 2, 3, 3),
                     Layout::area(lowBell, 1, 3, 3, 4),
                     Layout::area(lowShelf, 1, 4, 3, 5)
                 });
}


void CrystalizerEQAudioProcessorEditor::setupLowMidBandLayout() {
    const auto bounds = lowMidFilterArea.getLocalBounds();
    const auto areaWidth = static_cast<float>(bounds.getWidth());
    const auto areaHeight = static_cast<float>(bounds.getHeight());
    const auto knobColWidth = areaWidth / 3.0f;
    const auto knobRowHeight = areaHeight / 3.0f;

    const auto refW = getReferenceCell()[0];
    const auto refH = getReferenceCell()[1];
    const auto gainSize = refH * gainMod;
    const auto offSetToGainTop = gainSize;

    Layout::GridSpec lowMidBandSpec{
        /* cols */ {Layout::fr(1), Layout::pxTrack(knobColWidth), Layout::fr(1)},
        /* rows */ {Layout::fr(1), Layout::pxTrack(knobRowHeight * 1.25f), Layout::fr(1)},
        /* colGap */ Spacing::SizeMode::XS,
        /* rowGap */ Spacing::SizeMode::XS,
        /* pad    */ Layout::padding(Spacing::SizeMode::XS)
    };
    Layout::grid(bounds, lowMidBandSpec, {

                     Layout::area(peak1FreqSlider, 2, 1, 3, 2)
                     .withWidth(refW * freqMod)
                     .withHeight(refH * freqMod)
                     .withAlignSelf(juce::GridItem::AlignSelf::center)
                     .withJustifySelf(juce::GridItem::JustifySelf::center),
                     Layout::area(peak1GainSlider, 2, 2, 3, 3)
                     .withWidth(refW * gainMod)
                     .withHeight(refH * gainMod)
                     .withAlignSelf(juce::GridItem::AlignSelf::center)
                     .withJustifySelf(juce::GridItem::JustifySelf::center),
                     Layout::area(peak1QSlider, 2, 3, 3, 4)
                     .withWidth(refW * qMod)
                     .withHeight(refH * qMod)
                     .withAlignSelf(juce::GridItem::AlignSelf::center)
                     .withJustifySelf(juce::GridItem::JustifySelf::center),

                     Layout::area(peak1FreqLabel, 2, 1, 3, 2)
                     .withMargin(juce::GridItem::Margin(-offSetToGainTop, 0, 0, 0)),
                     Layout::area(peak1GainLabel, 1, 2, 2, 3),
                     Layout::area(peak1QLabel, 2, 3, 3, 4)
                     .withMargin(juce::GridItem::Margin(-offSetToGainTop, 0, 0, 0)),

                     Layout::area(peak1BypassButton, 3, 2, 4, 3)
                 });
}

//endregion setupLowMidBandLayout

//region setupMidBandLayout
void CrystalizerEQAudioProcessorEditor::setupMidBandLayout() {
    const auto bounds = midFilterArea.getLocalBounds();
    const auto areaWidth = static_cast<float>(bounds.getWidth());
    const auto areaHeight = static_cast<float>(bounds.getHeight());
    const auto knobColWidth = areaWidth / 3.0f;
    const auto knobRowHeight = areaHeight / 3.0f;

    const auto refW = getReferenceCell()[0];
    const auto refH = getReferenceCell()[1];
    const auto gainSize = refH * gainMod;
    const auto offSetToGainTop = gainSize;

    Layout::GridSpec midBandSpec{
        /* cols */ {Layout::fr(1), Layout::pxTrack(knobColWidth), Layout::fr(1)},
        /* rows */ {Layout::fr(1), Layout::pxTrack(knobRowHeight * 1.25f), Layout::fr(1)},
        /* colGap */ Spacing::SizeMode::XS,
        /* rowGap */ Spacing::SizeMode::XS,
        /* pad    */ Layout::padding(Spacing::SizeMode::XS)
    };
    Layout::grid(bounds, midBandSpec, {


                     Layout::area(peak2FreqSlider, 2, 1, 3, 2)
                     .withWidth(refW * freqMod)
                     .withHeight(refH * freqMod)
                     .withAlignSelf(juce::GridItem::AlignSelf::center)
                     .withJustifySelf(juce::GridItem::JustifySelf::center),
                     Layout::area(peak2GainSlider, 2, 2, 3, 3)
                     .withWidth(refW * gainMod)
                     .withHeight(refH * gainMod)
                     .withAlignSelf(juce::GridItem::AlignSelf::center)
                     .withJustifySelf(juce::GridItem::JustifySelf::center),
                     Layout::area(peak2QSlider, 2, 3, 3, 4)
                     .withWidth(refW * qMod)
                     .withHeight(refH * qMod)
                     .withAlignSelf(juce::GridItem::AlignSelf::center)
                     .withJustifySelf(juce::GridItem::JustifySelf::center),

                     Layout::area(peak2FreqLabel, 2, 1, 3, 2)
                     .withMargin(juce::GridItem::Margin(-offSetToGainTop, 0, 0, 0)),
                     Layout::area(peak2GainLabel, 1, 2, 2, 3),

                     Layout::area(peak2QLabel, 2, 3, 3, 4)
                     .withMargin(juce::GridItem::Margin(-offSetToGainTop, 0, 0, 0)),

                     Layout::area(peak2BypassButton, 3, 2, 4, 3)
                 });
}


void CrystalizerEQAudioProcessorEditor::setupHighMidBandLayout() {
    const auto bounds = highMidFilterArea.getLocalBounds();
    const auto areaWidth = static_cast<float>(bounds.getWidth());
    const auto areaHeight = static_cast<float>(bounds.getHeight());
    const auto knobColWidth = areaWidth / 3.0f;
    const auto knobRowHeight = areaHeight / 3.0f;

    const auto refW = getReferenceCell()[0];
    const auto refH = getReferenceCell()[1];
    const auto gainSize = refH * gainMod;
    const auto offSetToGainTop = gainSize;

    Layout::GridSpec highMidBandSpec{
        /* cols */ {Layout::fr(1), Layout::pxTrack(knobColWidth), Layout::fr(1)},
        /* rows */ {Layout::fr(1), Layout::pxTrack(knobRowHeight * 1.25f), Layout::fr(1)},
        /* colGap */ Spacing::SizeMode::XS,
        /* rowGap */ Spacing::SizeMode::XS,
        /* pad    */ Layout::padding(Spacing::SizeMode::XS)
    };
    Layout::grid(bounds, highMidBandSpec, {

                     Layout::area(peak3FreqSlider, 2, 1, 3, 2)
                     .withWidth(refW * freqMod)
                     .withHeight(refH * freqMod)
                     .withAlignSelf(juce::GridItem::AlignSelf::center)
                     .withJustifySelf(juce::GridItem::JustifySelf::center),
                     Layout::area(peak3GainSlider, 2, 2, 3, 3)
                     .withWidth(refW * gainMod)
                     .withHeight(refH * gainMod)
                     .withAlignSelf(juce::GridItem::AlignSelf::center)
                     .withJustifySelf(juce::GridItem::JustifySelf::center),
                     Layout::area(peak3QSlider, 2, 3, 3, 4)
                     .withWidth(refW * qMod)
                     .withHeight(refH * qMod)
                     .withAlignSelf(juce::GridItem::AlignSelf::center)
                     .withJustifySelf(juce::GridItem::JustifySelf::center),

                     Layout::area(peak3FreqLabel, 2, 1, 3, 2)
                     .withMargin(juce::GridItem::Margin(-offSetToGainTop, 0, 0, 0)),
                     Layout::area(peak3GainLabel, 1, 2, 2, 3),
                     Layout::area(peak3QLabel, 2, 3, 3, 4)
                     .withMargin(juce::GridItem::Margin(-offSetToGainTop, 0, 0, 0)),

                     Layout::area(peak3BypassButton, 3, 2, 4, 3)
                 });
}


void CrystalizerEQAudioProcessorEditor::setupHighBandLayout() {
    const auto bounds = highFilterArea.getLocalBounds();
    const auto areaWidth = static_cast<float>(bounds.getWidth());
    const auto areaHeight = static_cast<float>(bounds.getHeight());
    const auto knobColWidth = areaWidth / 3.0f;
    const auto knobRowHeight = areaHeight / 3.0f;

    const auto refW = getReferenceCell()[0];
    const auto refH = getReferenceCell()[1];
    const auto gainSize = refH * gainMod;
    const auto offSetToGainTop = gainSize;
    const float labelPadding = Components::SlopeSliderLookAndFeel::labelPadding * 2;


    Layout::GridSpec highBandSpec{
        /* cols */ {Layout::fr(1), Layout::pxTrack(knobColWidth), Layout::fr(1)},
        /* rows */ {Layout::fr(1), Layout::pxTrack(knobRowHeight * 1.25f), Layout::fr(1)},
        /* colGap */ Spacing::SizeMode::XS,
        /* rowGap */ Spacing::SizeMode::XS,
        /* pad    */ Layout::padding(Spacing::SizeMode::XS)
    };
    Layout::grid(bounds, highBandSpec, {


                     Layout::area(highBandFreqSlider, 2, 1, 3, 2)
                     .withWidth(refW * freqMod * 0.8f)
                     .withHeight(refH * freqMod * 0.8f)
                     .withAlignSelf(juce::GridItem::AlignSelf::center)
                     .withJustifySelf(juce::GridItem::JustifySelf::center),
                     Layout::area(highBandGainSlider, 2, 2, 3, 3)
                     .withWidth(refW * gainMod * 0.8f)
                     .withHeight(refH * gainMod * 0.8f)
                     .withAlignSelf(juce::GridItem::AlignSelf::center)
                     .withJustifySelf(juce::GridItem::JustifySelf::center),
                     Layout::area(highBandQSlider, 2, 3, 3, 4)
                     .withWidth(refW * qMod * 0.8f)
                     .withHeight(refH * qMod * 0.8f)
                     .withAlignSelf(juce::GridItem::AlignSelf::center)
                     .withJustifySelf(juce::GridItem::JustifySelf::center),

                     Layout::area(highBandModeBox, 3, 1, 4, 3),


                     Layout::area(highBandSlopeSlider, 3, 3, 4, 4)
                     .withWidth(refW * slopeMod + labelPadding)
                     .withHeight(refH * slopeMod + labelPadding)
                     .withAlignSelf(juce::GridItem::AlignSelf::center)
                     .withJustifySelf(juce::GridItem::JustifySelf::center),

                     Layout::area(highBandFreqLabel, 2, 1, 3, 2)
                     .withMargin(juce::GridItem::Margin(-offSetToGainTop, 0, 0, 0)),
                     Layout::area(highBandGainLabel, 1, 2, 2, 3),
                     Layout::area(highBandQLabel, 2, 3, 3, 4)
                     .withMargin(juce::GridItem::Margin(-offSetToGainTop, 0, 0, 0)),
                     Layout::area(highBandSlopeLabel, 3, 3, 4, 4)
                     .withMargin(juce::GridItem::Margin(-offSetToGainTop / 1.5f, 0, 0, 0)),

                     Layout::area(highdBOctLabel, 3, 3, 4, 4)
                     .withAlignSelf(juce::GridItem::AlignSelf::center)
                     .withJustifySelf(juce::GridItem::JustifySelf::center),

                 });

    const auto modeBoxBounds = lowBandModeBox.getLocalBounds();
    const auto slopeH = highBandSlopeSlider.getY() - highBandModeBox.getY() + highBandModeLabel.getFont().getHeight() /
                        2;

    Layout::GridSpec highBandModeBoxSpec{
        /* cols */ {Layout::fr(1), Layout::fr(1), Layout::fr(1), Layout::fr(1)},
        /* rows */ {Layout::fr(1), Layout::fr(1)},
        /* colGap */ Spacing::SizeMode::XS,
        /* rowGap */ Spacing::SizeMode::XS,
        /* pad    */ Layout::padding(Spacing::SizeMode::XS)
    };

    Layout::grid(modeBoxBounds, highBandModeBoxSpec, {
                     Layout::area(highBandModeLabel, 1, 2, 2, 4)
                     .withMargin(juce::GridItem::Margin(slopeH, 0, 0, 0)),
                     Layout::area(highBypass, 1, 4, 3, 5),
                     Layout::area(highCut, 1, 3, 3, 4),
                     Layout::area(highBell, 1, 2, 3, 3),
                     Layout::area(highShelf, 1, 1, 3, 2)
                 });
}


void CrystalizerEQAudioProcessorEditor::setupFooter() {
    const auto bounds = footerBar.getLocalBounds();

    const auto refW = getReferenceCell()[0];
    const auto refH = getReferenceCell()[1];

    Layout::GridSpec footerSpec{
        /* cols */ {Layout::fr(1), Layout::fr(1), Layout::fr(1)},
        /* rows */ {Layout::fr(1)},
        /* colGap */ Spacing::SizeMode::XS,
        /* rowGap */ Spacing::SizeMode::XS,
        /* pad    */ Layout::padding(Spacing::SizeMode::XS)
    };
    Layout::grid(bounds, footerSpec, {
                     Layout::area(globalControlArea, 1, 3, 2, 4)
                 });

    const auto globalControlAreaBounds = globalControlArea.getLocalBounds();
    const auto sliderRadius = outputSlider.getWidth() / 2.0f;
    const auto sliderWidth = outputSlider.getWidth();


    Layout::GridSpec globalControlAreaSpec{
        /* cols */ {Layout::fr(1), Layout::fr(1), Layout::fr(1)},
        /* rows */ {Layout::fr(1)},
        /* colGap */ Spacing::SizeMode::XS,
        /* rowGap */ Spacing::SizeMode::XS,
        /* pad    */ Layout::padding(Spacing::SizeMode::XS)
    };
    Layout::grid(globalControlAreaBounds, globalControlAreaSpec, {

                     Layout::area(outputSlider, 1, 2, 3, 3)
                     .withWidth(refW * globalMod)
                     .withHeight(refH * globalMod)
                     .withMargin(juce::GridItem::Margin(0, 0, 0, sliderRadius * 6))
                     .withAlignSelf(juce::GridItem::AlignSelf::center)
                     .withJustifySelf(juce::GridItem::JustifySelf::center),

                     Layout::area(inputSlider, 1, 1, 3, 2)
                     .withWidth(refW * globalMod)
                     .withHeight(refH * globalMod)
                     .withMargin(juce::GridItem::Margin(0, 0, 0, sliderRadius * 10))
                     .withAlignSelf(juce::GridItem::AlignSelf::center)
                     .withJustifySelf(juce::GridItem::JustifySelf::center),


                     Layout::area(masterBypassButton, 1, 3, 3, 4)
                     .withWidth(sliderWidth)

                     .withMargin(juce::GridItem::Margin(0, 0, 0, sliderRadius * 2)),
                 });
}


juce::Array<float> CrystalizerEQAudioProcessorEditor::getReferenceCell() {
    const auto bounds = midFilterArea.getLocalBounds();
    const auto areaWidth = static_cast<float>(bounds.getWidth());
    const auto areaHeight = static_cast<float>(bounds.getHeight());
    const auto knobColWidth = areaWidth / 3.0f;
    const auto knobRowHeight = areaHeight / 3.0f;

    const auto gainCellW = knobColWidth;
    const auto gainCellH = knobRowHeight * 1.25f;
    const juce::Array<float> refCell = {gainCellW, gainCellH};
    return refCell;
}


void SpectrumAnalyzer::processSamples() {
    auto samples = audioFIFO.sendSamplesToEditor();

    getFftFrame(samples);
}


void SpectrumAnalyzer::getFftFrame(juce::Array<float> &samples) {
    const int needed = FFTSIZE - fftFrame.size();
    if (needed <= 0)
        return;

    const int available = samples.size();
    if (available <= 0)
        return;

    const int take = std::min(needed, available);

    for (int i = 0; i < take; ++i)
        fftFrame.add(samples.getUnchecked(i));

    samples.removeRange(0, take);

    if (fftFrame.size() == FFTSIZE) {
        std::vector<float> fullFrame(fftFrame.begin(), fftFrame.end());
        fftFrame.removeRange(0, HOPSIZE);
        applyWindowOnFftFrame(fullFrame);
    }
}


void SpectrumAnalyzer::applyWindowOnFftFrame(std::vector<float> &fullFrame) {
    if (fullFrame.size() != FFTSIZE) return;
    window.multiplyWithWindowingTable(fullFrame.data(), FFTSIZE);
    processWindowedFrame(fullFrame);
}


void SpectrumAnalyzer::processWindowedFrame(std::vector<float> &windowedFrame) {
    if (windowedFrame.size() != FFTSIZE || fftData.size() != FFTSIZE * 2) return;
    fillFftDataFromFrame(windowedFrame);
    fft.performFrequencyOnlyForwardTransform(fftData.data(), true);
    buildMagnitudeSpectrum();
    convertToDb();
    applySmoothing();
    renderValuesDb = getRenderValues();
}


void SpectrumAnalyzer::fillFftDataFromFrame(std::vector<float> &windowedFrame) {
    for (int n = 0; n < FFTSIZE; ++n) {
        fftData[n] = windowedFrame[n];
    }
    for (int n = FFTSIZE; n < fftData.size(); ++n) {
        fftData[n] = 0.0f;
    }
}


void SpectrumAnalyzer::buildMagnitudeSpectrum() {
    for (int k = 0; k < BINS; ++k) {
        float mag = fftData[k];
        mag /= FFTSIZE;

        mag = std::max(mag, 1e-24f);
        magnitudes[k] = mag;
    }
}

float getFrequencyWeighting(float freq) {
    // Pre-Emphasis: +3dB pro Oktave (kompensiert Pink Noise Charakteristik)
    // Basis: 1kHz = 0dB
    const float referenceFreq = 1000.0f;
    float octaves = std::log2(freq / referenceFreq);
    float weightingDb = octaves * 3.0f; // 3dB pro Oktave

    // Begrenzen auf sinnvolle Werte
    weightingDb = juce::jlimit(-12.0f, 12.0f, weightingDb);

    return weightingDb;
}

void SpectrumAnalyzer::convertToDb() {
    for (int k = 0; k < magnitudes.size(); ++k) {
        float mag = std::max(magnitudes[k], 1e-9f);
        float dB = juce::Decibels::gainToDecibels(mag);

        float freq = k * deltaF;
        float weighting = getFrequencyWeighting(freq);
        dB += weighting;
        if (dB < -60.0f) {
            dB = MINDB;
        }

        magnitudesDb[k] = dB;
    }
}


void SpectrumAnalyzer::applySmoothing() {
    applyEMA();
    applyFreqSmoothing();
    applyPeakHoldAndFalloff();
}


void SpectrumAnalyzer::applyEMA() {
    for (int k = 0; k < magnitudesDb.size(); ++k) {
        float smoothedVal = smoothingFactor * magnitudesDb[k] + (1 - smoothingFactor) * emaSmoothedMagnitudesDb[k];
        emaSmoothedMagnitudesDb[k] = smoothedVal;
    }
}


void SpectrumAnalyzer::applyFreqSmoothing() {
    for (int k = 0; k < BINS; ++k) {
        double freq = k * deltaF;

        double lowestFreq = freq * pow(2.0, -OCTAVERADIUS);
        double highestFreq = freq * pow(2.0, OCTAVERADIUS);

        int lowestBin = std::max(0, static_cast<int>(floor(lowestFreq / deltaF)));
        int highestBin = std::min(BINS - 1, static_cast<int>(ceil(highestFreq / deltaF)));

        if (lowestBin > highestBin) {
            lowestBin = k;
            highestBin = k;
        };

        if (k == 0) {
            lowestBin = 0;
            highestBin = std::min(1, BINS - 1);
        }

        float sum = 0.f;

        for (int bin = lowestBin; bin <= highestBin; ++bin) {
            sum += emaSmoothedMagnitudesDb[bin];
        }
        float avg = sum / static_cast<float>(highestBin - lowestBin + 1);
        freqSmoothedMagnitudesDb[k] = avg;
    }
}


void SpectrumAnalyzer::applyPeakHoldAndFalloff() {
    std::vector<float> prevPeak = peakHoldMagnitudesDb;
    for (int k = 0; k < BINS; ++k) {
        float current = freqSmoothedMagnitudesDb[k];
        float prev = prevPeak[k];

        peakHoldMagnitudesDb[k] = std::max(current, prev - FALLOFFRATE * DELTAT);
    }
}


std::vector<float> SpectrumAnalyzer::getRenderValues() {
    std::vector<float> renderValues = peakHoldMagnitudesDb;

    return renderValues;
}
