#ifndef INVENTORY_CONTROLLER_HPP_
#define INVENTORY_CONTROLLER_HPP_

#include <ItemContainer/ItemContainer.hpp>
#include <string>
#include <Controller/Controller.hpp>
#include <ItemStack/ItemStack.hpp>
#include <ItemRecipe/ItemRecipe.hpp>
#include <Item/IInventory.hpp>
#include <ToolTip/ToolTip.hpp>
#include <unordered_map>

/**
 * @brief A controller for inventories.
 */
class InventoryController : public Controller
{
private:
    struct ItemCheatBookState
    {
        std::string query;
        int scrollPos = 0;
    };

    struct BoundInventory
    {
        IInventory *inv;
        std::vector<ItemStack> lastKnownStacks;
    };

    bool isBusyPriv;
    
    ItemRecipe::Level level;

    Widget* createRecipeView(ItemRecipe* recipe);
    Widget* createItemCheatBook();
    Widget* createCheatItemGrid(const std::string &query);
    Widget* createBagBuilder(int index);

    std::unordered_map<std::string, BoundInventory> boundInventories;
    bool canGiveSelfItems = false;

protected:
    /**
     * @brief Set the machine to which this inventory is bound.
     * 
     * @param machine The machine.
     */
    void bindMachine(std::shared_ptr<Machine> machine);

    /**
     * @brief Bind the specified item container.
     */
    void bindItemContainer(const std::string &id, ItemContainer &container);

    /**
     * @brief Bind an inventory to the specified ID.
     */
    void bindInventory(const std::string &id, IInventory *inv);
    
public:
    InventoryController(ControllerChannel &channel, ItemRecipe::Level level = ItemRecipe::Level::Manual);

    Command<ItemStack, std::string, uint32_t, ItemStack> leftClickWithStack;
    Command<ItemStack, std::string, uint32_t, ItemStack> rightClickWithStack;
    Command<uint32_t, std::string, uint32_t, std::string> shiftClick;
    Command<uint32_t, uint32_t> execRecipe;
    Command<uint32_t, std::string, uint32_t> setBoundInventoryStackCount;
    Command<uint32_t, std::string, uint32_t, ItemStack> updateBoundInventoryStack;
    Command<uint32_t, uint32_t> enableGiveSelfItems;
    Command<uint32_t, uint32_t, std::string, std::vector<uint8_t>> applyBagTransform;

    void onConnect();
    void onConnect(ItemRecipe::Level level);

    template<typename... TArgs>
    void transformBag(uint32_t slotIndex, const std::string &cmd, TArgs... args)
    {
        std::vector<uint8_t> data;
        serializeInto(data, args...);
        applyBagTransform(slotIndex, cmd, data);
    }

    /**
     * @brief Make a column of item slots, starting with index `base`.
     * 
     * @param invId The bound inventory ID.
     * @param base The first index.
     * @return The widget.
     */
    Widget* makeItemSlotColumn(const std::string &invId, int base, int count = 10, std::string emptyToolTip = "");

    /**
     * @brief Get the stack in the specified slot.
     * 
     * @param id The bound inventory ID.
     * @param slot The slot number.
     * @return The item stack in that slot.
     */
    ItemStack getStack(const std::string &id, int slot);

    /**
     * @brief Called when a slot is left-clicked.
     * 
     * @param id The bound inventory ID.
     * @param slot The slot index.
     * @param invIdForShiftClick The inventory ID to use for shift-click transfers.
     */
    void onSlotLeftClick(const std::string &id, int slot, const std::string &invIdForShiftClick);

    /**
     * @brief Called when a slot is right-clicked.
     * 
     * @param id The bound inventory ID.
     * @param slot The slot index.
     */
    void onSlotRightClick(const std::string &id, int slot);

    /**
     * @brief Get the reference to the currently-moving stack.
     */
    ItemStack& getMovingStack();

    /**
     * @brief Get the widget to be displayed in the right panel.
     * 
     * By default returns the manual construction menu.
     * 
     * @return The widget to display.
     */
    virtual Widget* getRightPanel();

    /**
     * @brief Return the highlight color for the specified slot and stack.
     */
    virtual glm::vec4 getSlotHighlight(const std::string &id, int slot, ItemStack &stack);

    /**
     * @brief Add extra lines to the tooltip based on slot and stack.
     */
    virtual void getExtraToolTipLines(const std::string &id, int slot, ItemStack &stack, ToolTip &tip);

    virtual void update() override;
    virtual Widget* getWidget() override;
    virtual bool isBusy() override;
};

#endif