#include "ProductionRules.h"

#include "BuildingType.h"
#include "ItemType.h"
#include "ModulesConfig.h"
#include "ShipsConfig.h"

std::vector<const RecipeDef*>
gatherCandidateRecipes(const GameConfig& config, const Building& b)
{
    std::vector<const RecipeDef*> candidates;
    if (isAutoRecipeBuildingType(b.type))
    {
        for (const RecipeDef& r : config.recipes.recipes)
        {
            if (r.building == b.type && !r.inputs.empty())
            {
                candidates.push_back(&r);
            }
        }
    }
    else
    {
        const RecipeDef* recipe = config.recipes.findRecipeDef(b.recipeId, b.type);
        if (recipe)
        {
            candidates.push_back(recipe);
        }
    }
    return candidates;
}
bool recipeInputsAvailable(const Building& b, const RecipeDef& recipe)
{
    for (const RecipeIngredient& ing : recipe.inputs)
    {
        const std::map<ItemType, int>::const_iterator it =
            b.inputBuffer.counts.find(ItemType{ing.item});
        const int have = (it != b.inputBuffer.counts.end()) ? it->second : 0;
        if (have < ing.amount)
        {
            return false;
        }
    }
    return true;
}
std::map<std::string, int>
computeShipyardRequiredMaterials(const GameConfig& config, const Building& b)
{
    return computeShipyardRequiredMaterials(config, b.recipeId, b.shipLayout);
}

std::map<std::string, int>
computeShipyardRequiredMaterials(const GameConfig& config,
                                 const std::string& recipeId,
                                 const std::optional<ShipLayoutConfig>& shipLayout)
{
    std::map<std::string, int> requiredMaterials;
    const ShipDef* shipDef = config.ships.findShipDef(recipeId);
    if (!shipDef)
    {
        return requiredMaterials;
    }
    for (const RecipeIngredient& ing : shipDef->schematic.materials)
    {
        requiredMaterials[ing.item] += ing.amount;
    }
    if (shipLayout.has_value())
    {
        for (const PlacedModule& pm : shipLayout->placedModules)
        {
            const ModuleDef* modDef = config.modules.findModuleDef(pm.moduleId);
            if (!modDef)
            {
                continue;
            }
            for (const RecipeIngredient& ing : modDef->materials)
            {
                requiredMaterials[ing.item] += ing.amount;
            }
        }
    }
    return requiredMaterials;
}

double computeShipyardProductionTimeSeconds(
    const GameConfig& config, const std::string& recipeId,
    const std::optional<ShipLayoutConfig>& shipLayout)
{
    const ShipDef* shipDef = config.ships.findShipDef(recipeId);
    if (!shipDef)
    {
        return 0.0;
    }

    double seconds = shipDef->schematic.productionTimeSeconds;
    if (shipLayout.has_value())
    {
        for (const PlacedModule& pm : shipLayout->placedModules)
        {
            const ModuleDef* modDef = config.modules.findModuleDef(pm.moduleId);
            if (!modDef)
            {
                continue;
            }
            seconds += modDef->productionTimeSeconds;
        }
    }
    return seconds;
}

bool hasInputsToStart(const GameConfig& config, const Building& b)
{
    if (b.type == BuildingType::Shipyard)
    {
        const std::map<std::string, int> required =
            computeShipyardRequiredMaterials(config, b);
        for (const std::pair<const std::string, int>& req : required)
        {
            const std::map<ItemType, int>::const_iterator it =
                b.inputBuffer.counts.find(ItemType{req.first});
            const int have = (it != b.inputBuffer.counts.end()) ? it->second : 0;
            if (have < req.second)
            {
                return false;
            }
        }
        return true;
    }

    // Recipe buildings: startable if any candidate recipe's inputs are satisfied.
    // A Miner recipe has no inputs, so an idle Miner is always startable and its
    // only idle reason is a full output buffer.
    for (const RecipeDef* recipe : gatherCandidateRecipes(config, b))
    {
        if (recipeInputsAvailable(b, *recipe))
        {
            return true;
        }
    }
    return false;
}
std::optional<ProductionStatus>
getProductionStatus(const GameConfig& config, const Building& building)
{
    // Salvage Bay has no recipe or production cycle (REQ-BLD-SALVAGE-BAY): it is
    // "producing" while it holds scrap to push out, and starved when empty.
    if (building.type == BuildingType::SalvageBay)
    {
        return building.getOutputItemCount() >= 1 ? ProductionStatus::Producing
                                                  : ProductionStatus::Starved;
    }

    // Only the five recipe/cycle production types show a status light besides the
    // Salvage Bay; belts, splitters, tunnels, HQ, and stations show none.
    if (!isProductionBuildingType(building.type))
    {
        return std::nullopt;
    }

    // Grey only applies to player-configured types; auto-recipe buildings
    // (Smelter, Reprocessing Plant) always run an implicit recipe.
    if (!isAutoRecipeBuildingType(building.type) && building.recipeId.empty())
    {
        return ProductionStatus::Unconfigured;
    }

    if (building.production.has_value())
    {
        return ProductionStatus::Producing;
    }

    // Idle: missing inputs (red) take precedence over a full output buffer
    // (yellow). If inputs are present yet the building is idle, the only remaining
    // reason it could not start a cycle is a full output buffer (REQ-MAT-CYCLE).
    return hasInputsToStart(config, building) ? ProductionStatus::Blocked
                                      : ProductionStatus::Starved;
}
