#include "RecipeSelectionDialog.h"

#include <cstddef>

#include <QFont>
#include <QFrame>
#include <QLabel>
#include <QPushButton>
#include <QScrollArea>
#include <QVBoxLayout>

#include "Building.h"
#include "BuildingType.h"
#include "DisplayName.h"
#include "GameConfig.h"
#include "OptionButton.h"
#include "RecipesConfig.h"
#include "ShipsConfig.h"
#include "Simulation.h"

namespace
{

std::vector<RecipeLineRow::Amount> toAmounts(
    const std::vector<RecipeIngredient>& ingredients)
{
    std::vector<RecipeLineRow::Amount> amounts;
    amounts.reserve(ingredients.size());
    for (const RecipeIngredient& ingredient : ingredients)
    {
        amounts.push_back(RecipeLineRow::Amount{ ingredient.item, ingredient.amount });
    }
    return amounts;
}


}  // namespace

std::vector<RecipeSelectionOption> buildRecipeSelectionOptions(
    BuildingType type, Simulation& sim, const GameConfig& config)
{
    std::vector<RecipeSelectionOption> options;
    // The clearing option. On a building that picks its own recipe it does not leave the
    // building idle but hands it back to that selection, and says so
    // (REQ-UI-SELECT-OPTIONS, REQ-BLD-AUTO-RECIPE).
    options.push_back({std::string(),
                       isAutoRecipeBuildingType(type) ? QObject::tr("(Auto)")
                                                      : QObject::tr("(None)"),
                       RecipeLineRow::Spec()});

    if (type == BuildingType::Shipyard)
    {
        for (const ShipDef& def : config.ships.ships)
        {
            if (!sim.isSchematicUnlocked(def.id)) { continue; }

            // The base materials and base production time, excluding the modules the
            // player has not placed yet (REQ-UI-SELECT-OPTIONS). No output item: a ship
            // has no icon, so the caption is what the option produces.
            RecipeLineRow::Spec line;
            line.inputs          = toAmounts(def.schematic.materials);
            line.durationSeconds = def.schematic.productionTimeSeconds;

            options.push_back({def.id,
                               QString::fromStdString(toDisplayName(def.id)),
                               line});
        }
    }
    else
    {
        for (const RecipeDef& recipe : config.recipes.recipes)
        {
            if (recipe.building != type) { continue; }
            // Only recipes the player can run are offered (REQ-LOCK-UI-RECIPE): granted
            // to them where the recipe is individually gated, and able to yield
            // something they can use either way.
            if (!sim.isRecipeUnlocked(recipe.id)) { continue; }

            RecipeLineRow::Spec line;
            line.inputs          = toAmounts(recipe.inputs);
            // What choosing it would actually get them (REQ-LOCK-OUTPUT-POOL), so the
            // option states the same alternatives the panel will state afterwards.
            line.outputGroups    = RecipeLineRow::toUnlockedOutputGroups(
                recipe,
                [&sim](const std::string& itemId) { return sim.isItemUnlocked(itemId); });
            line.durationSeconds = recipe.durationSeconds;

            options.push_back({recipe.id,
                               QString::fromStdString(toDisplayName(recipe.id)),
                               line});
        }
    }

    return options;
}

namespace
{
    // Height the option list is allowed to take, as the dialog's parent window's height
    // less what the window margins and the dialog's own frame need. Fully unlocked, an
    // Assembler offers more options than any window can hold, and the modal is never
    // resized to fit (REQ-UI-PANEL-MODAL) -- so the list scrolls instead.
    const int kListHeightReservePx = 80;

    // Fallback cap for a dialog built without a parent window to measure.
    const int kFallbackListHeightPx = 600;
}

RecipeSelectionDialog::RecipeSelectionDialog(
    const std::vector<RecipeSelectionOption>& options,
    const QString& title, const ItemTooltipContext& context,
    QWidget* parent)
    : ModalDialog(parent)
{
    QVBoxLayout* mainLayout = new QVBoxLayout(this);

    // One vertical column of buttons, each stating what it makes (REQ-UI-SELECT-OPTIONS).
    QScrollArea* scrollArea = new QScrollArea(this);
    scrollArea->setWidgetResizable(true);
    scrollArea->setFrameShape(QFrame::NoFrame);
    scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    scrollArea->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
    scrollArea->setMaximumHeight(parent != nullptr
        ? qMax(kListHeightReservePx, parent->height() - kListHeightReservePx)
        : kFallbackListHeightPx);
    mainLayout->addWidget(scrollArea);

    QWidget*     list       = new QWidget(scrollArea);
    QVBoxLayout* listLayout = new QVBoxLayout(list);
    listLayout->setContentsMargins(0, 0, 0, 0);
    listLayout->setSpacing(4);

    for (int i = 0; i < static_cast<int>(options.size()); ++i)
    {
        const RecipeSelectionOption& option = options[static_cast<std::size_t>(i)];

        // An OptionButton so the face of widgets below is what sizes the button
        // (REQ-UI-SELECT-OPTIONS).
        OptionButton* button = new OptionButton(list);
        if (option.line.isEmpty())
        {
            // The "(None)" option makes nothing and is a caption alone
            // (REQ-UI-SELECT-OPTIONS).
            button->setText(option.caption);
        }
        else
        {
            // The name over the recipe it stands for. Both are transparent to the mouse
            // so a click anywhere on the face still reaches the button beneath them.
            QVBoxLayout* face = new QVBoxLayout(button);
            face->setContentsMargins(8, 6, 8, 6);
            face->setSpacing(2);

            QLabel* nameLabel = new QLabel(option.caption, button);
            QFont nameFont = nameLabel->font();
            nameFont.setBold(true);
            nameLabel->setFont(nameFont);
            nameLabel->setAttribute(Qt::WA_TransparentForMouseEvents, true);
            face->addWidget(nameLabel);

            // The button face says what the option makes; what it does not say is where
            // an input comes from, and that its items answer themselves. Hover only: the
            // click here picks the option (REQ-UI-ITEM-VALUE-TOOLTIP).
            //
            // Unlike the name above it, the line is not transparent to the mouse: Qt
            // never looks inside a transparent widget for the cursor, so its items could
            // not be hovered if it were. The press it does not handle propagates to the
            // button as it would from any other child, so the option still picks.
            RecipeLineRow* line =
                new RecipeLineRow(context.itemIcons, context.buildingIcons, button);
            line->setItemTooltips(context, TooltipTrigger::Trigger::HoverOnly);
            face->addWidget(line);
            line->setLine(option.line);
        }
        listLayout->addWidget(button);

        m_optionIds.push_back(option.id);
        const int index = i;
        connect(button, &QPushButton::clicked, this, [this, index]()
        {
            onOptionClicked(index);
        });
    }
    listLayout->addStretch(1);

    scrollArea->setWidget(list);

    // Added last so the header sits above a list whose height is already settled; the
    // modal draws its own title and close button, having no window frame to carry them
    // (REQ-UI-MODAL-CHROME).
    addHeader(mainLayout, title, true);
}

bool RecipeSelectionDialog::isDismissible() const
{
    return true;
}

std::optional<std::string> RecipeSelectionDialog::getChosenId() const
{
    return m_chosenId;
}

void RecipeSelectionDialog::onOptionClicked(int index)
{
    if (index >= 0 && index < static_cast<int>(m_optionIds.size()))
    {
        m_chosenId = m_optionIds[static_cast<std::size_t>(index)];
    }
    accept();
}
