#ifndef SELECTABLE_PANEL_HPP_
#define SELECTABLE_PANEL_HPP_

#include <Sound/Sound.hpp>
#include <ToolTip/ToolTip.hpp>
#include <Utilities/StandardLerping.hpp>
#include <Widget/Layout/Layout.hpp>

/**
 * @brief A panel containing a child widget, and which indicates visually that its value is selected.
 */
template<typename TElement>
class SelectablePanel : public Layout
{
public:
    typedef std::function<void(TElement)> ActivatedCallback;
    typedef std::function<void(TElement, ToolTip&)> TooltipCallback;
    typedef std::function<Widget*(TElement)> ContextMenuCallback;

private:
    TElement &selectedElement;
    TElement ourElement;
    glm::vec4 backgroundColor;
    ActivatedCallback onActivated;
    TooltipCallback getTooltip;
    ContextMenuCallback getContextMenu = [](TElement) { return nullptr; };
    Uint32 lastClickTicks;
    StandardLerping<glm::vec4> backgroundColorLerp;
    StandardLerping<glm::vec4> hoverLerp;

    static constexpr int margin = 1;
    static constexpr Uint32 doubleClickTimeout = 1000;

public:
    SelectablePanel(
        TElement &selectedElement,
        TElement ourElement,
        glm::vec4 backgroundColor,
        Widget *child,
        ActivatedCallback onActivated = [](TElement){},
        TooltipCallback getTooltip = [](TElement, ToolTip&){}
    ) : Layout(child),
        selectedElement(selectedElement),
        ourElement(ourElement),
        backgroundColor(backgroundColor),
        onActivated(onActivated),
        getTooltip(getTooltip)
    {}

    /**
     * @brief Sets the context menu callback and returns `this`.
     * 
     * The context menu callback is given the selected element. If it returns `nullptr`, no menu will
     * be opened. Otherwise, it should return a widget (typically a `Column`) containing the body of
     * the menu.
     */
    SelectablePanel<TElement>* withContextMenu(ContextMenuCallback callback)
    {
        getContextMenu = callback;
        return this;
    }

    virtual void layout() override
    {
        ChildNode &childNode = m_children.back();
        childNode.pos = glm::ivec2(margin, margin);
        childNode.widget->onResize(m_size - glm::ivec2(2 * margin));
    }

    virtual glm::ivec2 getMinSize() override
    {
        return m_children.back().widget->getMinSize() + glm::ivec2(2 * margin);
    }

    virtual void draw(GraphicsContext &context) override
    {
        bool selected = (selectedElement == ourElement);
        context.color = backgroundColorLerp.sample(selected ? Color::selection : backgroundColor);
        context.drawRect(0, 0, m_size.x, m_size.y);

        context.color = hoverLerp.sample(isMouseInside() ? Color::hoverColor : glm::vec4(0.0f));
        context.drawRect(0, 0, m_size.x, m_size.y);

        if (isMouseInside())
        {
            ToolTip &tip = context.createToolTip();
            getTooltip(ourElement, tip);
        }
    
        Layout::draw(context);
    }

    virtual void onMouseDown(Uint8 button) override
    {
        if (button == SDL_BUTTON_LEFT && isMouseInside())
        {
            Uint32 timeDelta = SDL_GetTicks() - lastClickTicks;
            lastClickTicks = SDL_GetTicks();

            if (selectedElement != ourElement)
            {
                Sound::select.play();
                selectedElement = ourElement;
            }
            else if (timeDelta < doubleClickTimeout)
            {
                onActivated(ourElement);
            }
        }

        Layout::onMouseDown(button);
    }

    virtual void onMouseUp(Uint8 button) override
    {
        if (button == SDL_BUTTON_RIGHT && isMouseInside())
        {
            selectedElement = ourElement;

            Widget *menuBody = getContextMenu(ourElement);
            if (menuBody != nullptr)
            {
                openPopup(m_mousePos, menuBody);
            }
        }

        Layout::onMouseUp(button);
    }
};

#endif