#include "BuildingConfig.h"
#include "FactoryQueries.h"

#include <algorithm>
#include <climits>
#include <cstddef>
#include <map>

#include "BeltSystem.h"
#include "Building.h"
#include "BuildingsConfig.h"
#include "BuildingSystem.h"
#include "DisplayName.h"
#include "Simulation.h"

namespace
{
// The blueprint-relevant geometry shared by operational buildings and construction
// sites. Resolved from whichever of the two a selected id refers to.
struct SelectedBuilding
{
    BuildingId                id;
    BuildingType              type;
    Rotation                  rotation;
    QPoint                    anchor;
    const std::vector<QPoint>* bodyCells;
};

// Resolves a selected id to a player-placeable building or construction site, if it
// is one. Returns std::nullopt for an unknown id or a non-player-placeable building
// (the HQ and defence stations, per REQ-UI-BLUEPRINT-CREATE).
std::optional<SelectedBuilding> resolvePlaceable(const Simulation& sim, BuildingId id)
{
    const Building*         building = findBuilding(sim.getFactoryState(), id);
    const ConstructionSite* site     = building ? nullptr : findSite(sim.getFactoryState(), id);
    if (!building && !site)
    {
        return std::nullopt;
    }

    const BuildingType  type = building ? building->type : site->type;
    const BuildingDef*  def  = sim.getConfig().buildings.findBuildingDef(type);
    if (!def || !def->playerPlaceable)
    {
        return std::nullopt;
    }

    SelectedBuilding resolved;
    resolved.id        = id;
    resolved.type      = type;
    resolved.rotation  = building ? building->rotation : site->rotation;
    resolved.anchor    = building ? building->anchor : site->anchor;
    resolved.bodyCells = building ? &building->bodyCells : &site->bodyCells;
    return resolved;
}

// Position of a building type in buildings.toml, which is the order the build button
// bar lays out its buttons (REQ-UI-BUILD-BAR) and so the tie-break order for a
// blueprint's contents line. A type with no config entry sorts after every known one.
std::size_t configOrderIndex(BuildingType type, const BuildingsConfig& buildings)
{
    for (std::size_t i = 0; i < buildings.buildings.size(); ++i)
    {
        if (buildings.buildings[i].type == type)
        {
            return i;
        }
    }
    return buildings.buildings.size();
}
} // namespace

std::optional<BuildingConfig> readBuildingConfig(const Simulation& sim, BuildingId id)
{
    const Building*         building = findBuilding(sim.getFactoryState(), id);
    const ConstructionSite* site     = building ? nullptr : findSite(sim.getFactoryState(), id);
    if (!building && !site)
    {
        return std::nullopt;
    }

    BuildingConfig config;
    config.type = building ? building->type : site->type;

    const std::string& recipeId = building ? building->recipeId : site->recipeId;
    if (!recipeId.empty())
    {
        config.recipeId = recipeId;
    }

    config.shipLayout = building ? building->shipLayout : site->shipLayout;

    if (config.type == BuildingType::Splitter)
    {
        config.isSplitter = true;
        if (building)
        {
            // Operational splitter filters live in the BeltSystem, keyed by tile.
            const std::optional<BeltSystem::SplitterInfo> info =
                sim.getBelts().getSplitterInfo(building->anchor);
            if (info.has_value())
            {
                config.splitterFilterA = info->filterA;
                config.splitterFilterB = info->filterB;
            }
        }
        else
        {
            // A site keeps its pre-completion filters on the ConstructionSite.
            config.splitterFilterA = site->splitterFilterA;
            config.splitterFilterB = site->splitterFilterB;
        }
    }

    return config;
}

Blueprint captureBlueprintFromSelection(const Simulation&              sim,
                                        const std::vector<BuildingId>& selectedIds)
{
    std::vector<SelectedBuilding> entries;
    entries.reserve(selectedIds.size());
    for (const BuildingId id : selectedIds)
    {
        const std::optional<SelectedBuilding> resolved = resolvePlaceable(sim, id);
        if (resolved.has_value())
        {
            entries.push_back(*resolved);
        }
    }

    if (entries.empty())
    {
        return Blueprint{};
    }

    int minX = INT_MAX, maxX = INT_MIN;
    int minY = INT_MAX, maxY = INT_MIN;
    for (const SelectedBuilding& e : entries)
    {
        for (const QPoint& cell : *e.bodyCells)
        {
            minX = std::min(minX, cell.x());
            maxX = std::max(maxX, cell.x());
            minY = std::min(minY, cell.y());
            maxY = std::max(maxY, cell.y());
        }
    }

    const QPoint center((minX + maxX) / 2, (minY + maxY) / 2);

    Blueprint blueprint;
    blueprint.buildings.reserve(entries.size());
    for (const SelectedBuilding& e : entries)
    {
        BlueprintBuilding building;
        building.type     = e.type;
        building.rotation = e.rotation;
        building.offset   = e.anchor - center;
        // Recipe / schematic / layout / splitter-filter capture goes through
        // readBuildingConfig, which handles operational buildings and construction
        // sites alike.
        const std::optional<BuildingConfig> config = readBuildingConfig(sim, e.id);
        if (config.has_value())
        {
            building.recipeId        = config->recipeId.value_or(std::string());
            building.shipLayout      = config->shipLayout;
            building.splitterFilterA = config->splitterFilterA;
            building.splitterFilterB = config->splitterFilterB;
        }
        blueprint.buildings.push_back(building);
    }
    return blueprint;
}

bool selectionHasPlaceableBuilding(const Simulation&              sim,
                                   const std::vector<BuildingId>& selectedIds)
{
    for (const BuildingId id : selectedIds)
    {
        if (resolvePlaceable(sim, id).has_value())
        {
            return true;
        }
    }
    return false;
}

std::vector<BlueprintContentEntry> summarizeBlueprintContents(
    const Blueprint& blueprint, const BuildingsConfig& buildings)
{
    std::map<BuildingType, int> counts;
    for (const BlueprintBuilding& building : blueprint.buildings)
    {
        ++counts[building.type];
    }

    // The config index is carried through the sort so the comparator stays a strict
    // total order; the enum value is the final tie-break, which only two config-less
    // types could ever reach.
    struct RankedType
    {
        BuildingType type;
        int          count;
        std::size_t  order;
    };
    std::vector<RankedType> ranked;
    ranked.reserve(counts.size());
    for (const std::pair<const BuildingType, int>& entry : counts)
    {
        ranked.push_back({entry.first, entry.second,
                          configOrderIndex(entry.first, buildings)});
    }
    std::sort(ranked.begin(), ranked.end(),
              [](const RankedType& left, const RankedType& right)
    {
        if (left.count != right.count) { return left.count > right.count; }
        if (left.order != right.order) { return left.order < right.order; }
        return static_cast<int>(left.type) < static_cast<int>(right.type);
    });

    std::vector<BlueprintContentEntry> summary;
    summary.reserve(ranked.size());
    for (const RankedType& entry : ranked)
    {
        summary.push_back({toDisplayName(buildingTypeId(entry.type)), entry.count});
    }
    return summary;
}

int computeBlueprintCost(const Blueprint& blueprint, const BuildingsConfig& buildings)
{
    int total = 0;
    for (const BlueprintBuilding& building : blueprint.buildings)
    {
        const BuildingDef* def = buildings.findBuildingDef(building.type);
        if (def) { total += def->cost; }
    }
    return total;
}
