// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2026 Andrea De Murtas

#pragma once

#include "PluginProcessor.h"
#include "BlackMetalLookAndFeel.h"

// Three sections left to right in signal-chain order: Pedal | Amp | Cab and
// Output. The Section/Row layout system is adapted from Gala's editor
// (https://github.com/andreademurtas/gala, src/PluginEditor.cpp @ 1.1.0),
// itself from Galdr's; the deviation is the file-loading row for the user IR.
class GjallaAudioProcessorEditor : public juce::AudioProcessorEditor,
                                   private juce::Timer
{
public:
    explicit GjallaAudioProcessorEditor(GjallaAudioProcessor&);
    ~GjallaAudioProcessorEditor() override;

    void paint(juce::Graphics&) override;
    void resized() override;
    bool keyPressed(const juce::KeyPress&) override;

private:
    // Slider whose right click opens the parameter menu instead of dragging.
    struct GjallaSlider : juce::Slider
    {
        std::function<void()> onRightClick;

        void mouseDown(const juce::MouseEvent& e) override
        {
            if (e.mods.isPopupMenu())
            {
                if (onRightClick)
                    onRightClick();
                return;
            }
            juce::Slider::mouseDown(e);
        }
    };

    // 30 Hz bar fed by one of the processor's atomics: upward level in dB or
    // downward gain reduction.
    class MeterBar : public juce::Component, private juce::Timer
    {
    public:
        enum class Style { level, reduction };

        MeterBar(std::atomic<float>& sourceIn, Style styleIn)
            : source(sourceIn), style(styleIn)
        {
            setInterceptsMouseClicks(false, false);
            startTimerHz(30);
        }

    private:
        void timerCallback() override
        {
            const float value = style == Style::level
                ? juce::jmap(juce::jlimit(-60.0f, 0.0f,
                                          juce::Decibels::gainToDecibels(source.load(), -60.0f)),
                             -60.0f, 0.0f, 0.0f, 1.0f)
                : juce::jmap(juce::jlimit(0.0f, 24.0f, source.load()), 0.0f, 24.0f, 0.0f, 1.0f);
            display = juce::jmax(value, display - 0.06f);
            repaint();
        }

        void paint(juce::Graphics& g) override
        {
            auto b = getLocalBounds().toFloat().reduced(1.0f);
            g.setColour(juce::Colour(0xcc08080b));
            g.fillRect(b);
            g.setColour(theme::outline.withAlpha(0.6f));
            g.drawRect(b, 1.0f);

            auto bar = b.reduced(2.0f);
            const float h = bar.getHeight() * display;
            g.setColour(style == Style::level ? theme::blood : theme::bloodBright);
            if (style == Style::level)
                g.fillRect(bar.removeFromBottom(h));
            else
                g.fillRect(bar.removeFromTop(h));
        }

        std::atomic<float>& source;
        Style style;
        float display = 0.0f;
    };

    struct Knob
    {
        GjallaSlider slider;
        juce::Label label;
    };

    struct Row
    {
        bool tall = false;                      // knob rows are tall, combo rows are not
        std::vector<juce::Component*> comps;    // sliders, combos, toggles or buttons
        std::vector<juce::Label*> labels;       // parallel; nullptr for anything but knobs
        std::vector<int> weights;               // relative cell widths
    };

    struct Section
    {
        juce::String title;
        juce::Rectangle<int> baseBounds;        // layout at reference size
        juce::Rectangle<int> bounds;            // scaled, set in resized()
        std::vector<Row> rows;
        juce::Component* meter = nullptr;       // optional right-hand strip
    };

    Section& addSection(const juce::String& title, juce::Rectangle<int> baseBounds);
    Row& comboRow(Section&);
    Row& knobRow(Section&);
    void addKnob(Row&, const char* paramID, const juce::String& name);
    void addCombo(Row&, const char* paramID, int weight = 1);
    void addToggle(Row&, const char* paramID, const juce::String& name, int weight = 1);
    void addPlainButton(Row&, juce::Button&, int weight = 1);
    void addMeter(Section&, std::atomic<float>&, MeterBar::Style);
    void layoutSection(Section&, float scale);

    void showParamMenu(GjallaSlider&, const juce::String& paramID);
    void chooseIrFile();
    void refreshIrButton();
    void timerCallback() override;

    void drawRidge(juce::Graphics&, float baseY, float amplitude, int seedStep,
                   juce::Colour colour) const;

    GjallaAudioProcessor& processorRef;
    BlackMetalLookAndFeel lnf;

    juce::ComboBox presetBox;
    juce::TextButton irButton { "Load IR..." }, irClearButton { "x" };
    juce::TooltipWindow tooltipWindow { this };
    std::unique_ptr<juce::FileChooser> fileChooser;

    juce::OwnedArray<Knob> knobs;
    juce::OwnedArray<juce::ComboBox> combos;
    juce::OwnedArray<juce::TextButton> toggles;
    juce::OwnedArray<MeterBar> meterBars;
    std::vector<Section> sections;

    using SliderAttachment   = juce::AudioProcessorValueTreeState::SliderAttachment;
    using ComboBoxAttachment = juce::AudioProcessorValueTreeState::ComboBoxAttachment;
    using ButtonAttachment   = juce::AudioProcessorValueTreeState::ButtonAttachment;
    std::vector<std::unique_ptr<SliderAttachment>> sliderAttachments;
    std::vector<std::unique_ptr<ComboBoxAttachment>> comboAttachments;
    std::vector<std::unique_ptr<ButtonAttachment>> buttonAttachments;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(GjallaAudioProcessorEditor)
};
