#pragma once

#include <functional>
#include <optional>
#include <string>
#include <vector>

#include <QString>
#include <QWidget>

#include "BuildingType.h"
#include "ItemTooltipContext.h"
#include "RecipesConfig.h"
#include "TooltipTrigger.h"

class BuildingIconCache;
class ItemIconCache;
class QHBoxLayout;
class QVBoxLayout;

// One recipe drawn: what it consumes, an arrow, what it produces, and how long a cycle
// takes (REQ-UI-RECIPE-SUMMARY), under a header naming the building that runs it and the
// recipe itself where the recipe has to identify itself. Every place the UI states what
// something makes draws this same line, so a recipe reads alike wherever it is shown:
//
//  * beneath the selection button, for the recipe a building is running
//    (REQ-UI-RECIPE-SUMMARY);
//  * on a selection dialog's option buttons, under the option's name
//    (REQ-UI-SELECT-OPTIONS);
//  * in an item's production tooltip and in the unlock-choice dialog, there led by the
//    icon of the building that runs the recipe and its name
//    (REQ-UI-ITEM-TOOLTIP, REQ-DEF-SCHEMATIC-DROP);
//  * on a module button and in the layout dialog's build cost, where there is a price
//    and a time but nothing produced (REQ-MOD-UI-DIALOG, REQ-MOD-UI-STATS-PANEL).
class RecipeLineRow : public QWidget
{
    Q_OBJECT

public:
    // One item and how many of it the line states.
    struct Amount
    {
        std::string itemId;
        int         amount = 0;

        bool operator==(const Amount& other) const
        {
            return itemId == other.itemId && amount == other.amount;
        }
    };

    // What the line says. Everything but the inputs is optional, which is what lets the
    // one widget serve the summary, the option buttons, the tooltip lines and the cost
    // lines.
    struct Spec
    {
        // The chip of the building running the recipe, on the header line, where the
        // recipe has to say which building that is -- an item may have several producers
        // (REQ-UI-ITEM-TOOLTIP). Unset wherever the surrounding widget already
        // establishes the building, and the header line is then left out entirely.
        std::optional<BuildingType> building;
        // The recipe's name, beside the building chip on the header line. Empty where the
        // name is the caption of the widget around this one instead.
        QString                     name;
        std::vector<Amount>         inputs;
        // What the recipe produces, one entry per output group (REQ-MAT-OUTPUT-GROUP).
        // The items of a group are drawn joined by `+` because they come together, and the
        // groups joined by `/` because only one of them happens. Empty for a line that
        // produces no item of its own: a ship schematic, or a module's price. No arrow is
        // drawn then.
        std::vector<std::vector<Amount>> outputGroups;
        std::optional<double>       durationSeconds;
        // True where the time is added to something else rather than being a cycle of
        // its own, and so reads "+3.0 s" (REQ-MOD-UI-DIALOG).
        bool                        durationIsAddition = false;

        bool operator==(const Spec& other) const
        {
            return building == other.building && name == other.name
                && inputs == other.inputs && outputGroups == other.outputGroups
                && durationSeconds == other.durationSeconds
                && durationIsAddition == other.durationIsAddition;
        }

        bool isEmpty() const { return inputs.empty() && outputGroups.empty(); }
    };

    // The named output groups of a recipe as this row states them
    // (REQ-MAT-OUTPUT-GROUP), by index into the recipe's own `outputGroups`. For a caller
    // that has already been told which groups to draw -- the unlock preview, whose
    // groups were chosen against the state an award would bring about rather than the
    // current one (REQ-DEF-SCHEMATIC-DROP).
    static std::vector<std::vector<Amount>> toOutputGroups(
        const RecipeDef& recipe, const std::vector<int>& groupIndices);

    // Every group the player can be handed, for a caller stating what a recipe does
    // now. A group is dropped whole
    // where any of its items is locked, matching what the pool would pick
    // (REQ-LOCK-OUTPUT-POOL). This is what keeps a Reprocessing Plant from advertising a
    // yield it will never produce (REQ-UI-RECIPE-SUMMARY).
    //
    // Unlike the pool, this asks of every group, single-group recipes included: the pool
    // exempts them so that a building whose one output nothing yet demands still
    // produces, which is a rule about running, not about what to state. A recipe left
    // with no group at all is therefore possible in principle -- but such a recipe is
    // never offered, adopted, or applied from a blueprint (REQ-LOCK-UI-RECIPE,
    // REQ-BLD-AUTO-RECIPE, REQ-LOCK-UI-BLUEPRINT), so no building holds one. Were it
    // ever to happen, setLine hides the row rather than drawing an arrow to nothing.
    static std::vector<std::vector<Amount>> toUnlockedOutputGroups(
        const RecipeDef& recipe,
        const std::function<bool(const std::string&)>& isItemUnlocked);

    // Both caches may be null, which leaves the icons off: an item with no square and
    // no icon falls back to its id, and a building with no chip to its name alone.
    // Neither is owned.
    RecipeLineRow(ItemIconCache* itemIcons, BuildingIconCache* buildingIcons,
                  QWidget* parent = nullptr);

    // Hides the row when the spec has nothing to state, which is how a building with no
    // recipe shows no summary at all (REQ-UI-RECIPE-SUMMARY).
    void setLine(const Spec& spec);

    // Boxes the line in a card of its own, for the two places that stack several of them
    // (REQ-UI-ITEM-TOOLTIP, REQ-DEF-SCHEMATIC-DROP): a run of bare lines reads as one
    // field of icons and numbers, and the box is what separates one recipe from the next.
    // Off by default -- a line drawn inside a button or a panel section is already framed
    // by what holds it.
    void setCardChrome(bool enabled);

    // Lets every amount on the line explain the item it names, the number and the icon
    // together as one target (REQ-UI-ITEM-VALUE-TOOLTIP). Off by default, because the
    // line is silent wherever what holds it carries a tooltip of its own -- a module
    // button (REQ-MOD-UI-MODULE-TOOLTIP) -- or is itself a tooltip (REQ-UI-ITEM-TOOLTIP).
    // HoverOnly where the line sits on something the player clicks, whose click is not
    // free to explain (REQ-UI-TOOLTIP-TRIGGER).
    //
    // Call before the first setLine(): the tooltips are attached as the line is built,
    // and an unchanged spec is not rebuilt.
    void setItemTooltips(const ItemTooltipContext& context,
                         TooltipTrigger::Trigger trigger);

private:
    void rebuild(const Spec& spec);
    void addAmounts(const std::vector<Amount>& amounts);
    // The icon and the number of one item, side by side in a widget of their own so the
    // pair can be pointed at as one thing (REQ-UI-ITEM-VALUE-TOOLTIP).
    void addAmount(const Amount& entry);

    ItemIconCache*     m_itemIcons;
    BuildingIconCache* m_buildingIcons;
    QVBoxLayout*       m_outerLayout;
    // The two lines, each a row widget of its own so a rebuild only has to empty their
    // layouts -- no nested layout to take apart. The header is hidden where the spec
    // names neither a building nor a recipe.
    QWidget*           m_headerRow;
    QHBoxLayout*       m_headerLayout;
    QWidget*           m_amountsRow;
    QHBoxLayout*       m_amountsLayout;
    // What the row currently shows, so a refresh at tick rate rebuilds it only when the
    // recipe actually changed.
    Spec               m_spec;
    // Unset on a line whose items say nothing, which is most of them.
    std::optional<ItemTooltipContext> m_tooltipContext;
    TooltipTrigger::Trigger           m_tooltipTrigger =
        TooltipTrigger::Trigger::HoverOnly;
};
