#include "SelectedBuildingPanel.h"
#include "FactoryQueries.h"

#include <algorithm>
#include <cctype>
#include <map>
#include <set>
#include <string>

#include <QLabel>
#include <QListWidget>
#include <QPushButton>
#include <QVBoxLayout>

#include "BeltSystem.h"
#include "Command.h"
#include "CommandRequestedEvent.h"
#include "EntitySelectionChangedEvent.h"
#include "EventManager.h"
#include "FieldSelectionPanel.h"
#include "TickAdvancedEvent.h"
#include "Building.h"
#include "BuildingSystem.h"
#include "BuildingType.h"
#include "ItemType.h"
#include "LayoutDialogRequestedEvent.h"
#include "ModulesConfig.h"
#include "PlayerCommandsAppliedEvent.h"
#include "RecipeSelectionDialog.h"
#include "RecipeSelectionRequestedEvent.h"
#include "Rotation.h"
#include "ShipLayoutPreview.h"
#include "Simulation.h"

namespace
{

QString buildingTypeName(BuildingType type)
{
    if (type == BuildingType::Hq)
    {
        return QObject::tr("Player HQ");
    }

    const std::string id = buildingTypeId(type);
    QString result;
    bool nextUpper = true;
    for (char c : id)
    {
        if (c == '_')
        {
            result += ' ';
            nextUpper = true;
        }
        else if (nextUpper)
        {
            result += static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
            nextUpper = false;
        }
        else
        {
            result += c;
        }
    }
    return result;
}

bool isProductionBuilding(BuildingType type)
{
    return type == BuildingType::Miner
        || type == BuildingType::Smelter
        || type == BuildingType::Assembler
        || type == BuildingType::ReprocessingPlant
        || type == BuildingType::Shipyard;
}

// Buildings that expose a player recipe/schematic selection control
// (REQ-UI-SELECT-BUTTON): Miner ore type, Assembler recipe, Shipyard schematic.
// The Smelter and Reprocessing Plant auto-process and offer no selection
// (REQ-BLD-SMELTER, REQ-BLD-REPROCESSING).
bool hasRecipeSelection(BuildingType type)
{
    return type == BuildingType::Miner
        || type == BuildingType::Assembler
        || type == BuildingType::Shipyard;
}

QString rotationLabel(Rotation r)
{
    switch (r)
    {
        case Rotation::North: return QObject::tr("North (↑)");
        case Rotation::East:  return QObject::tr("East (→)");
        case Rotation::South: return QObject::tr("South (↓)");
        case Rotation::West:  return QObject::tr("West (←)");
    }
    return "";
}

}  // namespace


SelectedBuildingPanel::SelectedBuildingPanel(Simulation* sim,
                                             const GameConfig* config,
                                             QWidget* parent)
    : QWidget(parent)
    , m_sim(sim)
    , m_config(config)
    , m_splitterTile(0, 0)
{
    m_layout = new QVBoxLayout(this);
    m_layout->setContentsMargins(8, 8, 8, 8);
    m_layout->setSpacing(4);
    m_layout->setAlignment(Qt::AlignTop);

    m_titleLabel   = new QLabel(this);
    m_recipeSelectButton = new QPushButton(this);
    m_clearBeltBtn = new QPushButton(tr("Clear Items"), this);
    m_filterALabel = new QLabel(this);
    m_filterAList  = new QListWidget(this);
    m_filterBLabel = new QLabel(this);
    m_filterBList  = new QListWidget(this);
    m_layoutPreview      = new ShipLayoutPreview(this);
    m_configureLayoutBtn = new QPushButton(tr("Configure Layout"), this);
    m_buffersLabel = new QLabel(this);
    m_buffersLabel->setWordWrap(true);

    m_filterAList->setMaximumHeight(100);
    m_filterBList->setMaximumHeight(100);

    m_layout->addWidget(m_titleLabel);
    m_layout->addWidget(m_recipeSelectButton);
    m_layout->addWidget(m_layoutPreview);
    m_layout->addWidget(m_configureLayoutBtn);
    m_layout->addWidget(m_clearBeltBtn);
    m_layout->addWidget(m_filterALabel);
    m_layout->addWidget(m_filterAList);
    m_layout->addWidget(m_filterBLabel);
    m_layout->addWidget(m_filterBList);
    m_layout->addWidget(m_buffersLabel);

    connect(m_recipeSelectButton, &QPushButton::clicked,
            this, &SelectedBuildingPanel::onSelectRecipeClicked);
    connect(m_clearBeltBtn, &QPushButton::clicked,
            this, &SelectedBuildingPanel::onClearBelt);
    connect(m_configureLayoutBtn, &QPushButton::clicked, this, [this]() {
        if (m_singleBuildingId.has_value())
        {
            EventManager::getInstance()->sendEventImmediately(
                std::make_shared<LayoutDialogRequestedEvent>(*m_singleBuildingId));
        }
    });
    connect(m_filterAList, &QListWidget::itemChanged,
            this, &SelectedBuildingPanel::onSplitterFilterChanged);
    connect(m_filterBList, &QListWidget::itemChanged,
            this, &SelectedBuildingPanel::onSplitterFilterChanged);

    // The field selection renders below the building content and hides itself while
    // nothing field-side is selected, so it costs no space then.
    m_fieldSelectionPanel = new FieldSelectionPanel(sim, config, this);
    m_layout->addWidget(m_fieldSelectionPanel);

    buildEmpty();

    registerForEvents();
}

SelectedBuildingPanel::~SelectedBuildingPanel()
{
    unregisterForEvents();
}

void SelectedBuildingPanel::onSelectionChanged(const std::vector<BuildingId>& ids)
{
    m_selectedBuildingIds = ids;
    if (!ids.empty())
    {
        // A building selection is exclusive: it supersedes any field selection —
        // actors and scrap (REQ-UI-SELECTION-CATEGORIES).
        m_fieldSelectionPanel->clearSelection();
    }
    rebuild();
}

void SelectedBuildingPanel::yieldToFieldSelection()
{
    // The mirror image of onSelectionChanged(): a field selection — actors, debris, or
    // both — supersedes any building selection (REQ-UI-SELECTION-CATEGORIES). An empty
    // field selection changes nothing here: the building content, if any, keeps the panel.
    if (!m_fieldSelectionPanel->hasSelection()) { return; }
    m_selectedBuildingIds.clear();
    buildEmpty();
}

void SelectedBuildingPanel::rebuild()
{
    if (m_selectedBuildingIds.empty())
    {
        buildEmpty();
    }
    else if (m_selectedBuildingIds.size() == 1)
    {
        buildSingle(m_selectedBuildingIds[0]);
    }
    else
    {
        buildMulti(m_selectedBuildingIds);
    }
}

void SelectedBuildingPanel::hideAllWidgets()
{
    m_titleLabel->hide();
    m_recipeSelectButton->hide();
    m_layoutPreview->hide();
    m_configureLayoutBtn->hide();
    m_clearBeltBtn->hide();
    m_filterALabel->hide();
    m_filterAList->hide();
    m_filterBLabel->hide();
    m_filterBList->hide();
    m_buffersLabel->hide();
}

void SelectedBuildingPanel::buildEmpty()
{
    // Shows nothing for the building category — either because nothing is selected or
    // because the field category has taken the panel over.
    m_singleBuildingId = std::nullopt;
    hideAllWidgets();
}

void SelectedBuildingPanel::buildSingle(BuildingId id)
{
    m_singleBuildingId = id;
    hideAllWidgets();

    const Building*         b = findBuilding(m_sim->getFactoryState(), id);
    const ConstructionSite* s = b ? nullptr : findSite(m_sim->getFactoryState(), id);
    if (!b && !s)
    {
        buildEmpty();
        return;
    }
    m_singleIsSite = (s != nullptr);

    // A construction site exposes the same configuration as the operational
    // building it will become (REQ-BLD-SITE-CONFIG). The only difference is
    // that its buffer/production rows are replaced by a construction-progress
    // line, since a site has no buffers and runs no production cycle.
    const BuildingType type     = b ? b->type     : s->type;
    const std::string& recipeId = b ? b->recipeId : s->recipeId;
    const std::optional<ShipLayoutConfig>& shipLayout =
        b ? b->shipLayout : s->shipLayout;
    const QPoint anchor = b ? b->anchor : s->anchor;

    m_titleLabel->setText(m_singleIsSite
        ? tr("(Building) %1").arg(buildingTypeName(type))
        : buildingTypeName(type));
    m_titleLabel->show();
    m_buffersLabel->show();

    if (hasRecipeSelection(type))
    {
        const std::vector<RecipeSelectionOption> options =
            buildRecipeSelectionOptions(type, *m_sim, *m_config);

        const RecipeSelectionOption* current = nullptr;
        for (const RecipeSelectionOption& option : options)
        {
            if (option.id == recipeId)
            {
                current = &option;
                break;
            }
        }

        if (current && !current->id.empty())
        {
            m_recipeSelectButton->setText(current->caption);
            m_recipeSelectButton->setToolTip(current->tooltip);
        }
        else
        {
            const QString placeholder = (type == BuildingType::Shipyard)
                ? tr("Select schematic")
                : tr("Select recipe");
            m_recipeSelectButton->setText(placeholder);
            m_recipeSelectButton->setToolTip(QString());
        }
        m_recipeSelectButton->show();

        updateShipyardLayoutWidgets(type, recipeId, shipLayout);
    }
    else
    {
        m_recipeSelectButton->hide();
        updateShipyardLayoutWidgets(type, recipeId, shipLayout);
    }

    // Belt "Clear" removes items from a live belt tile; a construction site has
    // none and is not registered with BeltSystem yet, so hide it for sites.
    if (isBeltSubsystemType(type) && !m_singleIsSite)
    {
        m_clearBeltBtn->show();
    }
    else
    {
        m_clearBeltBtn->hide();
    }

    if (type == BuildingType::Splitter)
    {
        std::optional<BeltSystem::SplitterInfo> info;
        if (m_singleIsSite)
        {
            info = getSiteSplitterInfo(m_sim->getFactoryState(), m_sim->getConfig(), id);
        }
        else
        {
            m_splitterTile = anchor;
            info = m_sim->getBelts().getSplitterInfo(m_splitterTile);
        }
        buildSplitterFilters(info);
    }
    else
    {
        m_filterALabel->hide();
        m_filterAList->hide();
        m_filterBLabel->hide();
        m_filterBList->hide();
    }

    if (m_singleIsSite)
    {
        refreshSiteProgress(s);
    }
    else
    {
        refreshBuffers(b);
    }
}

void SelectedBuildingPanel::refreshSiteProgress(const ConstructionSite* s)
{
    QString progress;
    if (s->completesAt == 0)
    {
        progress = tr("Queued");
    }
    else
    {
        const BuildingDef* def = nullptr;
        for (const BuildingDef& d : m_config->buildings.buildings)
        {
            if (d.type == s->type) { def = &d; break; }
        }
        if (def && def->constructionTimeSeconds > 0)
        {
            const Tick duration = secondsToTicks(def->constructionTimeSeconds);
            const Tick elapsed  = m_sim->getCurrentTick() - (s->completesAt - duration);
            const int pct = static_cast<int>(
                std::max(Tick(0), std::min(duration, elapsed)) * 100 / duration);
            progress = tr("%1% complete").arg(pct);
        }
        else
        {
            progress = tr("Building...");
        }
    }
    m_buffersLabel->setText(progress);
}

void SelectedBuildingPanel::refreshBuffers(const Building* b)
{
    const RecipeDef* recipe  = findRecipe(b);
    const ShipDef*   shipDef = (b->type == BuildingType::Shipyard)
                                ? findShipDef(b->recipeId)
                                : nullptr;

    // Auto-recipe buildings (Smelter, Reprocessing Plant) have no selected
    // recipe; while a cycle runs, resolve the recipe actually in production so
    // the cycle time and progress can be shown (REQ-UI-PRODUCTION-PROGRESS).
    if (!recipe && isAutoRecipeBuildingType(b->type) && b->production.has_value())
    {
        recipe = m_config->recipes.findRecipeDef(b->production->recipeId, b->type);
    }

    QString bufText;

    if (!b->inputBuffer.counts.empty())
    {
        bufText += tr("Input: ");
        for (const std::pair<const ItemType, int>& entry : b->inputBuffer.counts)
        {
            int perCycle = 0;
            if (recipe)
            {
                for (const RecipeIngredient& ing : recipe->inputs)
                {
                    if (ing.item == entry.first.id) { perCycle = ing.amount; break; }
                }
            }
            else if (shipDef)
            {
                for (const RecipeIngredient& mat : shipDef->schematic.materials)
                {
                    if (mat.item == entry.first.id) { perCycle = mat.amount; break; }
                }
                if (b->shipLayout.has_value())
                {
                    for (const PlacedModule& pm : b->shipLayout->placedModules)
                    {
                        const ModuleDef* modDef =
                            m_config->modules.findModuleDef(pm.moduleId);
                        if (!modDef) { continue; }
                        for (const RecipeIngredient& ing : modDef->materials)
                        {
                            if (ing.item == entry.first.id)
                            {
                                perCycle += ing.amount;
                            }
                        }
                    }
                }
            }
            bufText += QString::fromStdString(entry.first.id)
                + ": " + QString::number(entry.second);
            if (perCycle > 0)
            {
                bufText += "/" + QString::number(perCycle);
            }
            bufText += "  ";
        }
        bufText += "\n";
    }

    // Count output-side items: buffered plus still-emerging on the output belts.
    // An emerging item still belongs to the output buffer (REQ-MAT-OUTPUT-EMERGE),
    // so it must be included here or it would vanish from the panel while animating.
    std::map<std::string, int> outCounts;
    for (const Item& item : b->outputBuffer.items)
    {
        outCounts[item.type.id]++;
    }
    for (const std::vector<BeltItemSlot>& lane : b->emergingItems)
    {
        for (const BeltItemSlot& slot : lane)
        {
            outCounts[slot.item.type.id]++;
        }
    }

    if (recipe && !recipe->outputs.empty())
    {
        bufText += tr("Output: ");
        for (const RecipeOutput& out : recipe->outputs)
        {
            const std::map<std::string, int>::const_iterator it =
                outCounts.find(out.item);
            const int count = (it != outCounts.end()) ? it->second : 0;
            bufText += QString::fromStdString(out.item)
                + ": " + QString::number(count)
                + "/" + QString::number(out.amount) + "  ";
        }
    }
    else if (!outCounts.empty())
    {
        bufText += tr("Output: ");
        for (const std::pair<const std::string, int>& entry : outCounts)
        {
            bufText += QString::fromStdString(entry.first)
                + ": " + QString::number(entry.second) + "  ";
        }
    }

    if (isProductionBuilding(b->type)
        && (recipe || shipDef || isAutoRecipeBuildingType(b->type)))
    {
        if (recipe || shipDef)
        {
            double durationSeconds = recipe
                ? recipe->durationSeconds
                : shipDef->schematic.productionTimeSeconds;

            if (shipDef && b->shipLayout.has_value())
            {
                for (const PlacedModule& pm : b->shipLayout->placedModules)
                {
                    const ModuleDef* modDef =
                        m_config->modules.findModuleDef(pm.moduleId);
                    if (modDef)
                    {
                        durationSeconds += modDef->productionTimeSeconds;
                    }
                }
            }

            bufText += tr("Cycle: %1 s\n").arg(durationSeconds, 0, 'f', 1);

            if (b->production.has_value())
            {
                const Tick cycleTicks  = secondsToTicks(durationSeconds);
                const Tick completesAt = b->production->completesAt;
                const Tick currentTick = m_sim->getCurrentTick();
                const Tick elapsed     = currentTick - (completesAt - cycleTicks);
                const int  pct         = static_cast<int>(
                    std::max(Tick(0), std::min(cycleTicks, elapsed)) * 100 / cycleTicks);
                bufText += tr("Progress: %1%\n").arg(pct);
            }
            else
            {
                bufText += tr("Progress: idle\n");
            }
        }
        else
        {
            // Auto-recipe building with no active cycle: no single recipe to
            // show a cycle time for.
            bufText += tr("Progress: idle\n");
        }
    }

    m_buffersLabel->setText(bufText);

    // The recipe/schematic is applied via a queued command that only drains on a
    // later frame, so the per-tick refresh must own the shipyard preview and the
    // Configure Layout button's visibility; otherwise they stay hidden until the
    // building is re-selected (which re-runs buildSingle).
    updateShipyardLayoutWidgets(b->type, b->recipeId, b->shipLayout);
}

void SelectedBuildingPanel::updateShipyardLayoutWidgets(
    BuildingType type,
    const std::string& recipeId,
    const std::optional<ShipLayoutConfig>& shipLayout)
{
    // The preview and Configure button are shipyard-only controls; hide them
    // entirely for other building types.
    if (type != BuildingType::Shipyard)
    {
        m_layoutPreview->hide();
        m_configureLayoutBtn->hide();
        return;
    }

    const ShipDef* shipDef = findShipDef(recipeId);
    const bool hasSchematic = shipDef && !shipDef->layout.empty();

    // Always show the preview and Configure button for a shipyard; they are only
    // enabled once a schematic is selected (REQ-MOD-UI-PREVIEW).
    if (hasSchematic)
    {
        ShipLayoutConfig layout;
        if (shipLayout.has_value())
        {
            layout = *shipLayout;
        }
        m_layoutPreview->setShipAndLayout(
            shipDef->layout, layout, &m_config->modules);
    }
    else
    {
        m_layoutPreview->showPlaceholder();
    }

    m_layoutPreview->setEnabled(hasSchematic);
    m_configureLayoutBtn->setEnabled(hasSchematic);
    m_layoutPreview->show();
    m_configureLayoutBtn->show();
}

const RecipeDef* SelectedBuildingPanel::findRecipe(const Building* b) const
{
    if (b->recipeId.empty()) { return nullptr; }
    return m_config->recipes.findRecipeDef(b->recipeId, b->type);
}

const ShipDef* SelectedBuildingPanel::findShipDef(const std::string& id) const
{
    if (id.empty()) { return nullptr; }
    return m_config->ships.findShipDef(id);
}

void SelectedBuildingPanel::handleEvent(std::shared_ptr<const TickAdvancedEvent> /*event*/)
{
    refreshSelectionDisplay(RefreshReason::PeriodicTick);
}

void SelectedBuildingPanel::handleEvent(
    std::shared_ptr<const PlayerCommandsAppliedEvent> /*event*/)
{
    // Player commands (e.g. choosing a shipyard schematic) are applied by a
    // queued drain, not synchronously. When the game is paused no tick advances,
    // so TickAdvancedEvent never fires; refresh here too, otherwise the panel
    // would not reflect the change until the next tick or a re-selection.
    refreshSelectionDisplay(RefreshReason::CommandApplied);
}

void SelectedBuildingPanel::refreshSelectionDisplay(RefreshReason reason)
{
    // Only a single selected building has live content to refresh. While the field
    // category owns the panel there is none: yieldToFieldSelection() has cleared it, so
    // this returns immediately and the field panel refreshes itself off the same events.
    if (!m_singleBuildingId.has_value()) { return; }
    const Building* b = findBuilding(m_sim->getFactoryState(), *m_singleBuildingId);
    if (b)
    {
        if (m_titleLabel->text().startsWith(tr("(Building) ")))
        {
            rebuild();
        }
        else
        {
            refreshBuffers(b);
        }
        return;
    }
    const ConstructionSite* s = findSite(m_sim->getFactoryState(), *m_singleBuildingId);
    if (s)
    {
        // A periodic tick only advances construction progress, so update just the
        // progress label. Rebuilding every tick would hide/re-show all widgets and
        // cancel any in-progress click on the recipe button. An applied command
        // may have changed the site's recipe/layout, so rebuild in that case.
        if (reason == RefreshReason::CommandApplied)
        {
            rebuild();
        }
        else
        {
            refreshSiteProgress(s);
        }
        return;
    }
    buildEmpty();
}

void SelectedBuildingPanel::buildMulti(const std::vector<BuildingId>& ids)
{
    m_singleBuildingId = std::nullopt;
    m_recipeSelectButton->hide();
    m_clearBeltBtn->hide();
    m_filterALabel->hide();
    m_filterAList->hide();
    m_filterBLabel->hide();
    m_filterBList->hide();
    m_buffersLabel->hide();

    std::map<BuildingType, int> counts;
    for (BuildingId id : ids)
    {
        const Building* b = findBuilding(m_sim->getFactoryState(), id);
        if (b)
        {
            counts[b->type]++;
            continue;
        }
        const ConstructionSite* s = findSite(m_sim->getFactoryState(), id);
        if (s)
        {
            counts[s->type]++;
        }
    }

    bool hasBelt = false;
    int totalCost = 0;
    QString text;
    for (const std::pair<const BuildingType, int>& entry : counts)
    {
        text += buildingTypeName(entry.first) + " x "
            + QString::number(entry.second) + "\n";
        if (isBeltSubsystemType(entry.first))
        {
            hasBelt = true;
        }
        // Total placement cost counts only player-placeable buildings; the HQ
        // and defence stations are excluded (REQ-UI-MULTI-SELECTION).
        const BuildingDef* def = m_config->buildings.findBuildingDef(entry.first);
        if (def && def->playerPlaceable)
        {
            totalCost += def->cost * entry.second;
        }
    }
    text += tr("Total: %1 Building Blocks").arg(totalCost);
    m_titleLabel->setText(text.trimmed());
    m_titleLabel->show();

    if (hasBelt)
    {
        m_clearBeltBtn->show();
    }
}

void SelectedBuildingPanel::onSelectRecipeClicked()
{
    if (!m_singleBuildingId.has_value())
    {
        return;
    }
    // The emit is synchronous: MainWindow pauses the game, runs the modal
    // selection dialog, and restores the speed before this returns. The chosen
    // recipe/schematic is only *enqueued* as a command, though, and drains on a
    // later frame -- so this rebuild() still sees the old recipe. The per-tick
    // refreshBuffers() path picks up the new schematic (and shows the layout
    // preview + Configure Layout button) once the command has been applied.
    EventManager::getInstance()->sendEventImmediately(
        std::make_shared<RecipeSelectionRequestedEvent>(*m_singleBuildingId));
    rebuild();
}

void SelectedBuildingPanel::buildSplitterFilters(
    const std::optional<BeltSystem::SplitterInfo>& info)
{
    if (!info.has_value())
    {
        m_filterALabel->hide();
        m_filterAList->hide();
        m_filterBLabel->hide();
        m_filterBList->hide();
        return;
    }

    const std::vector<std::string> items = getAllItemIds();

    auto populateList = [&](QListWidget* list, QLabel* label,
                            const QString& dirLabel,
                            const std::vector<ItemType>& filter)
    {
        label->setText(tr("%1 filter (empty = all):").arg(dirLabel));
        list->blockSignals(true);
        list->clear();
        for (const std::string& itemId : items)
        {
            if (!m_sim->isItemUnlocked(itemId)) { continue; }
            QListWidgetItem* row = new QListWidgetItem(
                QString::fromStdString(itemId), list);
            const bool checked = filter.empty()
                ? false
                : std::find(filter.begin(), filter.end(),
                            ItemType{itemId}) != filter.end();
            row->setCheckState(checked ? Qt::Checked : Qt::Unchecked);
            row->setFlags(row->flags() | Qt::ItemIsUserCheckable);
        }
        list->blockSignals(false);
        label->show();
        list->show();
    };

    populateList(m_filterAList, m_filterALabel,
                 rotationLabel(info->outputA), info->filterA);
    populateList(m_filterBList, m_filterBLabel,
                 rotationLabel(info->outputB), info->filterB);
}

void SelectedBuildingPanel::onSplitterFilterChanged()
{
    if (!m_singleBuildingId.has_value())
    {
        return;
    }

    auto collectFilter = [](QListWidget* list) -> std::vector<ItemType>
    {
        std::vector<ItemType> filter;
        for (int i = 0; i < list->count(); ++i)
        {
            const QListWidgetItem* row = list->item(i);
            if (row->checkState() == Qt::Checked)
            {
                filter.push_back(ItemType{row->text().toStdString()});
            }
        }
        return filter;
    };

    if (m_singleIsSite)
    {
        std::shared_ptr<SetSiteSplitterFiltersCommand> command =
            std::make_shared<SetSiteSplitterFiltersCommand>();
        command->id      = *m_singleBuildingId;
        command->filterA = collectFilter(m_filterAList);
        command->filterB = collectFilter(m_filterBList);
        EventManager::getInstance()->sendEventImmediately(
            std::make_shared<CommandRequestedEvent>(command));
    }
    else
    {
        std::shared_ptr<SetSplitterFiltersCommand> command =
            std::make_shared<SetSplitterFiltersCommand>();
        command->tile    = m_splitterTile;
        command->filterA = collectFilter(m_filterAList);
        command->filterB = collectFilter(m_filterBList);
        EventManager::getInstance()->sendEventImmediately(
            std::make_shared<CommandRequestedEvent>(command));
    }
}

std::vector<std::string> SelectedBuildingPanel::getAllItemIds() const
{
    std::set<std::string> seen;
    for (const RecipeDef& recipe : m_config->recipes.recipes)
    {
        for (const RecipeIngredient& ing : recipe.inputs)
        {
            seen.insert(ing.item);
        }
        for (const RecipeOutput& out : recipe.outputs)
        {
            seen.insert(out.item);
        }
    }
    return std::vector<std::string>(seen.begin(), seen.end());
}

void SelectedBuildingPanel::onClearBelt()
{
    std::vector<QPoint> tiles;
    for (BuildingId id : m_selectedBuildingIds)
    {
        const Building* b = findBuilding(m_sim->getFactoryState(), id);
        if (b && isBeltSubsystemType(b->type))
        {
            for (const QPoint& cell : b->bodyCells)
            {
                tiles.push_back(cell);
            }
        }
    }
    if (!tiles.empty())
    {
        std::shared_ptr<ClearBeltTilesCommand> command =
            std::make_shared<ClearBeltTilesCommand>();
        command->tiles = std::move(tiles);
        EventManager::getInstance()->sendEventImmediately(
            std::make_shared<CommandRequestedEvent>(command));
    }
}

void SelectedBuildingPanel::handleEvent(std::shared_ptr<const EntitySelectionChangedEvent> event)
{
    m_fieldSelectionPanel->setSelectedEntities(event->entities);
    yieldToFieldSelection();
}

void SelectedBuildingPanel::handleEvent(std::shared_ptr<const SelectionChangedEvent> event)
{
    onSelectionChanged(event->ids);
}

void SelectedBuildingPanel::handleEvent(
    std::shared_ptr<const DebrisSelectionChangedEvent> event)
{
    // Debris is a field object: it supersedes any building selection but coexists
    // with actors (REQ-UI-SELECTION-CATEGORIES).
    m_fieldSelectionPanel->setSelectedDebris(event->debris);
    yieldToFieldSelection();
}
