/*
==============================================================================

    This file contains the basic framework code for a JUCE plugin editor.

  ==============================================================================
*/

#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;



//region setupSliders
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);
}
//endregion setupSliders

//region setupAttachments
void CrystalizerEQAudioProcessorEditor::setupAttachments() {
    testNoiseAttach = std::make_unique<SliderAttach>(audioProcessor.apvts, "TestNoiseLevel",  testNoiseSlider);

    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);
}
//endregion setupAttachments

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

    testNoiseSlider.setName("TestNoise");

    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");

    testNoiseSlider.setTextValueSuffix(" Gain");

    //lowBandFreqSlider.setTextValueSuffix  ("\nHz");
    //lowBandSlopeSlider.setTextValueSuffix ("\ndB/Oct");
    lowBandGainSlider.setTextValueSuffix("\ndB");
    //lowBandQSlider.setTextValueSuffix  ("\nQ");

    //peak1FreqSlider.setTextValueSuffix("\nHz");
    peak1GainSlider.setTextValueSuffix("\ndB");
    //peak1QSlider.setTextValueSuffix("\nQ");
    peak1BypassButton.setName("peak1Bypass");

    //peak2FreqSlider.setTextValueSuffix("\nHz");
    peak2GainSlider.setTextValueSuffix("\ndB");
    //peak2QSlider.setTextValueSuffix("\nQ");
    peak2BypassButton.setName("peak2Bypass");

    //peak3FreqSlider.setTextValueSuffix("\nHz");
    peak3GainSlider.setTextValueSuffix("\ndB");
    //peak3QSlider.setTextValueSuffix("\nQ");
    peak3BypassButton.setName("peak3Bypass");


    //highBandFreqSlider.setTextValueSuffix  ("\nHz");
    //highBandSlopeSlider.setTextValueSuffix ("\ndB/Oct");
    highBandGainSlider.setTextValueSuffix("\ndB");
   // highBandQSlider.setTextValueSuffix  ("\nQ");

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

    testNoiseButton.setName("TestNoise");
    testNoiseButton.setButtonText("Test Noise");

    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");

}
//endregion setupDisplayNames

//region setupToggleButtons
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 paramID; // nimm juce::String statt std::string
            juce::String mode;
            if (b == &peak1BypassButton) {
                paramID = "Peak1Bypass";
                mode = "Low-Mid";
            }
            else if (b == &peak2BypassButton) {
                paramID = "Peak2Bypass";
                mode = "Mid";
            }
            else if (b == &peak3BypassButton) {
                paramID = "Peak3Bypass";
                mode = "High-Mid";
            }
            else if (b == &crystalizeButton) {
                paramID = "CrystalizeButton";
            }
            else if (b == &masterBypassButton) {
                paramID = "MasterBypass";
                mode = "Master";
            }

            if (auto* param = audioProcessor.apvts.getParameter(paramID))
            {
                const bool isToggled = b->getToggleState();

                const float target = isToggled ? 1.0f : 0.0f;

                // Nur senden, wenn sich wirklich etwas ändert (Schwelle 0.5f)
                if ((param->getValue() >= 0.5f) != isToggled)
                {
                    param->beginChangeGesture();
                    param->setValueNotifyingHost(target);
                    param->endChangeGesture();
                }
                    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);
            }

        };
    }

}
//endregion setupToggleButtons

//region disableLowBand
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);

};
//endregion disableLowBand

//region disableLowMidBand
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);
}
//endregion disableLowMidBand

//region disableMidBand
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);
}
//endregion disableMidBand

//region disableHighMidBand
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);
}
//endregion disableHighMidBand

//region disableHighBand
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);
};
//endregion disableHighBand

//region disableEverything
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();
        }
    }



}
//endregion disableEverything

//region addComponentsToLayout
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);
}
//endregion addComponentsToLayout

//region setupLabels
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
    };

    //SetupLabels
    {
        //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");

        /*setupLabel(inputLabel,   "IN");
        setupLabel(outputLabel, "OUT");*/
    }
}
//endregion setupLabels

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);

    }

}

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

//region handleLowBandModes
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);
                        }
                    }
                    // Aktuellen aktivieren
                    lowBandBools[i] = true;
                    param->setValueNotifyingHost(param->convertTo0to1((float)i));

                    param->endChangeGesture();
                }
            }
            else if (lowBandBools[i]) {
                lowBandModeButtons[i]->setToggleState(true, juce::dontSendNotification);
            }
        };
    }
}
//endregion handleLowBandModes

//region handleHighBandModes
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();
                }
            }
            else if (highBandBools[i]) {
                highBandModeButtons[i]->setToggleState(true, juce::dontSendNotification);
            }
        };
    }
}
//endregion handleHighBandModes
    
//region setupEventListeners
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();
            return;
        }
        const auto selectedPresetWithExt = selectedPreset + ".xml";
        audioProcessor.loadPreset(selectedPresetWithExt);
    };

    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);



    };

    handleLowBandModes();

    handleHighBandModes();



}
//endregion setupEventListeners

//region initPresetSystem
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.setTextToShowWhenEmpty("Preset Name", juce::Colours::grey);
    presetNameInput.setJustification(juce::Justification::centred);
    presetNameInput.setColour(juce::TextEditor::backgroundColourId, juce::Colours::black);
    presetNameInput.setColour(juce::TextEditor::textColourId, juce::Colours::white);

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

    savePresetButton.setLookAndFeel(presetButtonLookAndFeel.get());
    deletePresetButton.setLookAndFeel(presetButtonLookAndFeel.get());
    resetButton.setLookAndFeel(presetButtonLookAndFeel.get());
    // TODO: presetBox.setLookAndFeel(presetComboBoxLookAndFeel.get());
}
//endregion initPresetSystem

//==============================================================================
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();



    auto logoIcon = juce::XmlDocument::parse (BinaryData::logo_icon_svg);
    if (logoIcon != nullptr)
        logoDrawable = juce::Drawable::createFromSVG (*logoIcon);

    addAndMakeVisible (testNoiseButton);

    testNoiseButton.onClick = [this]() {
        if (auto* param = audioProcessor.apvts.getParameter("TestNoiseEnabled"))
        {
            if (param->getValue() == false) {
                param->beginChangeGesture();

                param->setValueNotifyingHost (1.0f);

                param->endChangeGesture();
            } else {
                param->beginChangeGesture();

                param->setValueNotifyingHost (0.0f); // -> true

                param->endChangeGesture();
            }
        }
    };


}

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);


    for (auto* b : lowBandModeButtons) {
        b->setLookAndFeel(nullptr);
    }
    for (auto* b : highBandModeButtons) {
        b->setLookAndFeel(nullptr);
    }
};

float CrystalizerEQAudioProcessorEditor::getInterpolatedDb(float exactBin) {
    int bin0 = static_cast<int>(std::floor(exactBin)) - 1;
    int bin1 = static_cast<int>(std::floor(exactBin));
    int bin2 = static_cast<int>(std::ceil(exactBin));
    int bin3 = static_cast<int>(std::ceil(exactBin)) + 1;

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

    float y0 = spectrumAnalyzer.renderValuesDb[bin0];
    float y1 = spectrumAnalyzer.renderValuesDb[bin1];
    float y2 = spectrumAnalyzer.renderValuesDb[bin2];
    float y3 = spectrumAnalyzer.renderValuesDb[bin3];

    float fraction = exactBin - bin1;

    // Catmull-Rom Spline Interpolation
    float a0 = -0.5f * y0 + 1.5f * y1 - 1.5f * y2 + 0.5f * y3;
    float a1 = y0 - 2.5f * y1 + 2.0f * y2 - 0.5f * y3;
    float a2 = -0.5f * y0 + 0.5f * y2;
    float a3 = y1;

    return a0 * fraction * fraction * fraction +
           a1 * fraction * fraction +
           a2 * fraction +
           a3;
}

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

    auto r = analyzerRect.toFloat();

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

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

    // === Spektrum zeichnen ===
    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 zeroDbY = analyzerBottom - (analyzerHeight * (0.0f - minDb) / dbRange); // Position von 0dB

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

    float sampleRate = spectrumAnalyzer.sampleRate;
    float deltaF = spectrumAnalyzer.deltaF;

    // Zeichne für jede X-Position
    juce::Path spectrumPath;
    bool pathStarted = false;

    // Erhöhe die Auflösung - zeichne mehr Punkte als Pixel
    const int resolutionMultiplier = 4; // 4x mehr Punkte
    const int numPoints = static_cast<int>(analyzerWidth) * resolutionMultiplier;

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

        // Berechne Frequenz (logarithmisch)
        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);

        // Finde Bin und interpoliere
        float exactBin = freq / deltaF;

        float dB = getInterpolatedDb(exactBin);

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


        // Berechne Y-Position
        float pixelHeight = normalized * analyzerHeight;
        float y = analyzerBottom - pixelHeight;

        // Füge zum Path hinzu
        if (!pathStarted) {
            spectrumPath.startNewSubPath(x + analyzerRect.getX(), y);
            pathStarted = true;
        } else {
            spectrumPath.lineTo(x + analyzerRect.getX(), y);
        }
    }

    // Zeichne die Linie mit Anti-Aliasing
    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);
    }


    // Optional: Zeichne Frequenz-Grid-Linien
    drawFrequencyGrid(g, dbRange);
}

float CrystalizerEQAudioProcessorEditor::mapFrequencyToX(float freq, float minFreq, float maxFreq, float width) {
    // Logarithmische Skalierung: log(freq) zwischen log(minFreq) und log(maxFreq)
    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 = {0, 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];

        // 1: dB normalisieren (wie bei der Kurve)
        float normalized = (db - minDB) / dbRange;
        normalized = juce::jlimit(0.0f, 1.0f, normalized);

        // 2: Y-Position wie beim Spektrum berechnen
        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()));

        // Optional: Frequenz-Label zeichnen
        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);
    }

}

//endregion paintAnalyzer

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);

    const auto logoArea = hB.toFloat().reduced(hBPad);
    logoDrawable->drawWithin(g, logoArea,
                                  juce::RectanglePlacement::xRight, // oder xLeft, yTop etc.
                                  1.0f);

    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);





    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.setColour(Colours::FOREGROUNDBYPASS);
    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);


    if constexpr (false) // -> auf false setzen, wenn nicht gebraucht
    {
        for (auto* c : getChildren()) // headerBar, mainPanel, footerBar
        {
            auto r = c->getBounds();
            g.setColour(juce::Colours::magenta.withAlpha(0.9f));
            g.drawRect(r, 1.0f);              // nur Rahmen, kein fillRect
            g.setColour(juce::Colours::white);
            g.drawText(c->getName(), r, juce::Justification::centred, false);
        }
        for (auto* c : headerBar.getChildren())
        {

            auto r = c->getBounds(); // <-- lokal zu headerBar
            r = r.translated(headerBar.getX(), headerBar.getY()); // <-- in Editor-Koordinaten bringen!
            g.setColour(juce::Colours::cyan.withAlpha(0.3f));
            g.fillRect(r);
            g.setColour(juce::Colours::cyan.withAlpha(0.9f));
            g.drawRect(r, 1.0f);
        }
        for (auto* c : presetArea.getChildren())
        {
            auto r = c->getBounds(); // lokal in presetArea
            r = r.translated(headerBar.getX() + presetArea.getX(),
                             headerBar.getY() + presetArea.getY()); // beide Offsets addieren
            g.setColour(juce::Colours::yellow.withAlpha(0.3f));
            g.fillRect(r);
            g.setColour(juce::Colours::yellow.withAlpha(0.9f));
            g.drawRect(r, 1.0f);
        }

        for (auto* c : mainPanel.getChildren())
        {

            auto r = c->getBounds(); // <-- lokal zu headerBar
            r = r.translated(mainPanel.getX(), mainPanel.getY()); // <-- in Editor-Koordinaten bringen!
            g.setColour(juce::Colours::cyan.withAlpha(0.3f));
            g.fillRect(r);
            g.setColour(juce::Colours::cyan.withAlpha(0.9f));
            g.drawRect(r, 1.0f);
        }



        for (auto* c : filterArea.getChildren())
        {
            auto r = c->getBounds(); // lokal in presetArea
            r = r.translated(mainPanel.getX() + filterArea.getX(),
                             mainPanel.getY() + filterArea.getY()); // beide Offsets addieren
            g.setColour(juce::Colours::yellow.withAlpha(0.3f));
            g.fillRect(r);
            g.setColour(juce::Colours::yellow.withAlpha(0.9f));
            g.drawRect(r, 1.0f);
        }
    {
        for (auto* c : lowFilterArea.getChildren())
        {
            auto r = getLocalArea(&lowFilterArea, c->getBounds()); // lokal in presetArea

            g.setColour(juce::Colours::cyan.withAlpha(0.3f));
            g.fillRect(r);
            g.setColour(juce::Colours::cyan.withAlpha(0.9f));
            g.drawRect(r, 1.0f);
        }
            for (auto* c : lowBandModeBox.getChildren())
            {
                auto r = getLocalArea(&lowBandModeBox, c->getBounds()); // lokal in presetArea

                g.setColour(juce::Colours::yellow.withAlpha(0.3f));
                g.fillRect(r);
                g.setColour(juce::Colours::yellow.withAlpha(0.9f));
                g.drawRect(r, 1.0f);
            }
        for (auto* c : lowMidFilterArea.getChildren())
        {
            auto r = getLocalArea(&lowMidFilterArea, c->getBounds()); // lokal in presetArea

            g.setColour(juce::Colours::cyan.withAlpha(0.3f));
            g.fillRect(r);
            g.setColour(juce::Colours::cyan.withAlpha(0.9f));
            g.drawRect(r, 1.0f);
        }
        for (auto* c : midFilterArea.getChildren())
        {
            auto r = getLocalArea(&midFilterArea, c->getBounds()); // lokal in presetArea

            g.setColour(juce::Colours::cyan.withAlpha(0.3f));
            g.fillRect(r);
            g.setColour(juce::Colours::cyan.withAlpha(0.9f));
            g.drawRect(r, 1.0f);
        }
        for (auto* c : highMidFilterArea.getChildren())
        {
            auto r = getLocalArea(&highMidFilterArea, c->getBounds()); // lokal in presetArea

            g.setColour(juce::Colours::cyan.withAlpha(0.3f));
            g.fillRect(r);
            g.setColour(juce::Colours::cyan.withAlpha(0.9f));
            g.drawRect(r, 1.0f);
        }
        for (auto* c : highFilterArea.getChildren())
        {
            auto r = getLocalArea(&highFilterArea, c->getBounds()); // lokal in presetArea

            g.setColour(juce::Colours::cyan.withAlpha(0.3f));
            g.fillRect(r);
            g.setColour(juce::Colours::cyan.withAlpha(0.9f));
            g.drawRect(r, 1.0f);
        }
    }

        for (auto* c : footerBar.getChildren())
        {

            auto r = c->getBounds(); // <-- lokal zu headerBar
            r = r.translated(footerBar.getX(), footerBar.getY()); // <-- in Editor-Koordinaten bringen!
            g.setColour(juce::Colours::cyan.withAlpha(0.3f));
            g.fillRect(r);
            g.setColour(juce::Colours::cyan.withAlpha(0.9f));
            g.drawRect(r, 1.0f);
        }

        for (auto* c : globalControlArea.getChildren())
        {
            auto r = getLocalArea(&globalControlArea, c->getBounds()); // lokal in presetArea

            g.setColour(juce::Colours::yellow.withAlpha(0.3f));
            g.fillRect(r);
            g.setColour(juce::Colours::yellow.withAlpha(0.9f));
            g.drawRect(r, 1.0f);
        }

    }
}

//region paintBorderLines
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);
    }
}
//endregion paintBorderLines

//region setKnobVisibility
void CrystalizerEQAudioProcessorEditor::setKnobVisibility() {
    int lowMode = (int) audioProcessor.apvts
            .getRawParameterValue("LowBandModes")->load(); // 0..3
    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(); // 0..3

    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);
}
//endregion setKnobVisibility

//region animateCrystalizeButton
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();
    }
}
//endregion animateCrystalizeButton

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

    if (presetMenuSafePtr == nullptr)
    {
        presetMenuButton.setToggleState(false, juce::dontSendNotification);
    }
}
//endregion timerCallback

void CrystalizerEQAudioProcessorEditor::resized()
{


    auto pluginArea = getLocalBounds();
    scalePluginWindow(pluginArea);
    setupMainGrid(pluginArea);
    setupHeader();
    setupBody();
    setupFooter();
    //setSliderSizes();




    const auto testBounds = mainPanel.getLocalBounds();
    const auto testWidth = testBounds.getWidth();
    const auto testHeight = testBounds.getHeight();
    testNoiseButton.setBounds(50, testHeight / 2, testWidth / 8, testHeight / 8);

}

//region resetAllCheckboxes
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);
}
//endregion resetAllCheckboxes

//region scalePluginWindow
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);
}
//endregion scalePluginWindow

//region setupMainGrid
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) },                     // eine Spalte, füllt Breite
        /* 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 });

}
//endregion setupMainGrid

//region setupHeader
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, {
       // Label über beide Spalten (row1, col1..2)
       Layout::area(presetBoxLabel,   1, 2, 2, 3),
       // Box unten links (row2, col1)
       Layout::area(presetBox,        2, 2, 3, 3),
        Layout::area(resetButton,      2, 1, 2, 2),
       // Menütaste unten rechts (row2, col2)
       Layout::area(presetMenuButton, 2, 3, 3, 4)
        .withWidth(iconSize)
        .withHeight(iconSize)

   });


}
//endregion setupHeader

//region setupBody
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;

    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),
        Layout::area(filterArea, 2, 1, 3, 6)
        .withMargin(juce::GridItem::Margin(0, filterAreaMargin, 0, filterAreaMargin))

    });

    const auto analyzerAreaBounds = analyzerArea.getLocalBounds();
    const auto analyzerAreaWidth = static_cast<float>(analyzerArea.getWidth());
    const auto analyzerAreaHeight = static_cast<float>(analyzerArea.getHeight());

    const auto filterAreaBounds = filterArea.getLocalBounds();
    const auto filterAreaWidth = static_cast<float>(filterArea.getWidth());
    const auto filterAreaHeight = static_cast<float>(filterArea.getHeight());
    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();

}
//endregion setupBody

//region setupLowBandLayout
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 modeBoxAreaWidth = static_cast<float>(modeBoxBounds.getWidth());
    const auto modeBoxAreaHeight = static_cast<float>(modeBoxBounds.getHeight());
    const auto modeBoxButtonColWidth = modeBoxAreaWidth / 4.0f;
    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)
        });
}
//endregion setupLowBandLayout

//region setupLowMidBandLayout
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)
   });
}
//endregion setupMidBandLayout

//region setupHighMidBandLayout
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)
   });
}
//endregion setupHighMidBandLayout

//region setupHighBandLayout
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 modeBoxAreaWidth = static_cast<float>(modeBoxBounds.getWidth());
    const auto modeBoxAreaHeight = static_cast<float>(modeBoxBounds.getHeight());
    const auto modeBoxButtonColWidth = modeBoxAreaWidth / 4.0f;
    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)
        });
}
//endregion setupHighBandLayout

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

    const auto footerWidth = static_cast<float>(bounds.getWidth());
    const auto footerHeight = static_cast<float>(bounds.getHeight());

    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 globalControlAreaWidth = static_cast<float>(globalControlArea.getWidth());
    const auto globalControlAreaHeight = static_cast<float>(globalControlArea.getHeight());
    const auto globalControlColWidth = globalControlAreaWidth / 3.0f;
    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, {

        //TODO: Bring components closer together

        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)),
   });

}
//endregion setupFooter

//region getReferenceCell
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;
}
//endregion getReferenceCell

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

    getFftFrame(samples);
}
//endregion processSamples

//region getFftFrame
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);
        }
    }
//endregion getFftFrame

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

//region processWindowedFrame
void SpectrumAnalyzer::processWindowedFrame(std::vector<float> &windowedFrame) {
    if (windowedFrame.size() != FFTSIZE || fftData.size() != FFTSIZE * 2) return;
    fillFftDataFromFrame(windowedFrame);
    //    fft.performRealOnlyForwardTransform(fftData.data());

    fft.performFrequencyOnlyForwardTransform(fftData.data(), true);
    buildMagnitudeSpectrum();
    convertToDb();
    applySmoothing();
    renderValuesDb = getRenderValues();

}
//endregion processWindowedFrame

//region fillFftDataFromFrame
void SpectrumAnalyzer::fillFftDataFromFrame(std::vector<float> &windowedFrame) {
    for (int n = 0; n < FFTSIZE; ++n) {
        /*fftData[2*n] = windowedFrame[n];
        fftData[2*n + 1] = 0.0f;*/
        fftData[n] = windowedFrame[n];
    }
}
//endregion fillFftDataFromFrame

//region buildMagnitudeSpectrum
void SpectrumAnalyzer::buildMagnitudeSpectrum() {
    for (int k = 0; k < BINS; ++k) {
        float mag = fftData[k];
        mag /= (FFTSIZE * 0.5f);
        mag = std::max(mag, 1e-24f);
        magnitudes[k] = mag;
    }
}
//endregion buildMagnitudeSpectrum

//region convertToDb
void SpectrumAnalyzer::convertToDb() {


    for (int k = 0; k < magnitudes.size(); ++k) {
        float mag = magnitudes[k];
        float dB  = juce::Decibels::gainToDecibels(mag);



        dB        = juce::jlimit(MINDB, MAXDB, dB);
        magnitudesDb[k] = dB;
    }
}
//endregion convertToDb

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

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

}
//endregion applyEMA

//region applyFreqSmoothing
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;


    }
}
//endregion applyFreqSmoothing

//region applyPeakHoldAndFalloff
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);

    }
}
//endregion applyPeakHoldAndFalloff

//region getRenderValues
std::vector<float> SpectrumAnalyzer::getRenderValues() {
        std::vector<float> renderValues = peakHoldMagnitudesDb;
    return renderValues;
}
//endregion getRenderValues








