#ifndef LIST_BOX_HPP_
#define LIST_BOX_HPP_

#include <ToolTip/ToolTip.hpp>
#include <Widget/Builder/Builder.hpp>
#include <Widget/Label/Label.hpp>
#include <Widget/Layout/Column/Column.hpp>
#include <Widget/Layout/ScrollPanel/ScrollPanel.hpp>
#include <Widget/Panel/Panel.hpp>
#include <Widget/Layout/CompoundWidget/CompoundWidget.hpp>
#include <Widget/Layout/SelectablePanel/SelectablePanel.hpp>
#include <vector>

/**
 * @brief A widget allowing the user to select and activate entries in a list.
 */
template<typename TElement>
class ListBox : public CompoundWidget
{
public:
    typedef std::function<void(TElement)> ActivatedCallback;
    typedef std::function<Widget*(TElement)> BuildCallback;
    typedef std::function<void(TElement, ToolTip&)> TooltipCallback;
    typedef std::function<Widget*(TElement)> ContextMenuCallback;

private:
    static constexpr int naturalHeight = 110;
    static constexpr int minWidth = 120;

    ContextMenuCallback getContextMenu = [](TElement) { return nullptr; };

    static Widget* makeBody(
        std::vector<TElement> &elements,
        TElement &selectedElement,
        BuildCallback buildElement,
        ActivatedCallback onActivated,
        TooltipCallback getTooltip,
        ContextMenuCallback getContextMenu
    )
    {
        return new Builder<size_t>(
            [&elements](){ return elements.size(); },
            [&elements, &selectedElement, buildElement, onActivated, getTooltip, getContextMenu](size_t)
            {
                std::vector<Widget*> children;

                for (size_t i = 0; i < elements.size(); i++)
                {
                    children.push_back(
                        new Builder<TElement>(
                            [&elements, i]() { return elements[i]; },
                            [&elements, &selectedElement, buildElement, onActivated, getTooltip, i, getContextMenu](TElement element)
                            {
                                return (new SelectablePanel<TElement>(
                                    selectedElement,
                                    element,
                                    (i & 1) ? Color::tableRowOdd : Color::tableRowEven,
                                    buildElement(element),
                                    onActivated,
                                    getTooltip
                                ))->withContextMenu(getContextMenu);
                            }
                        )
                    );
                }

                return new Column(children);
            }
        );
    }

public:
    ListBox(
        std::vector<TElement> &elements,
        TElement &selectedElement,
        BuildCallback buildElement,
        ActivatedCallback onActivated = [](TElement){},
        TooltipCallback getTooltip = [](TElement, ToolTip&){}
    ) : CompoundWidget(
            (new ScrollPanel(
                naturalHeight,
                new Column(
                    new Panel(glm::ivec2(minWidth, 0)),
                    Layout::WithProportion<1>(),
                    makeBody(
                        elements,
                        selectedElement,
                        buildElement,
                        onActivated,
                        getTooltip,
                        [this](TElement element)
                        {
                            return this->getContextMenu(element);
                        }
                    )
                )
            ))
            ->withBackgroundColor(Color::widgetBackground)
        )
    {}

    /**
     * @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.
     */
    ListBox<TElement>* withContextMenu(ContextMenuCallback callback)
    {
        getContextMenu = callback;
        return this;
    }
};

#endif