using static SpaceGame.Api.Factions.AI.CommanderPlanningService;
using static SpaceGame.Api.Shared.Runtime.KnownShipTypes;
using static SpaceGame.Api.Shared.Runtime.SimulationRuntimeSupport;
using SpaceGame.Api.Shared.Runtime;

namespace SpaceGame.Api.Stations.Simulation;

internal sealed class StationSimulationService
{
    internal const int StrategicControlTargetSystems = 5;
    private const string MilitaryShipCategory = "military";
    private const string ConstructionShipCategory = "construction";
    private const string TransportShipCategory = "transport";
    private const string MiningShipCategory = "mining";

    internal void ReviewStationMarketOrders(SimulationWorld world, StationRuntime station)
    {
        if (station.CommanderId is null)
        {
            return;
        }

        var desiredOrders = new List<DesiredMarketOrder>();
        var economy = FactionEconomyAnalyzer.Build(world, station.FactionId);
        var role = DetermineStationRole(station);
        var site = GetConstructionSiteForStation(world, station.Id);
        var waterReserve = MathF.Max(30f, station.Population * 3f);
        var constructionEnergyReserve = GetConstructionDemandForItem(world, site, "energycells");
        var constructionHullpartsReserve = GetConstructionDemandForItem(world, site, "hullparts");
        var constructionClayReserve = GetConstructionDemandForItem(world, site, "claytronics");
        var constructionRefinedReserve = GetConstructionDemandForItem(world, site, "refinedmetals");
        var iceReserve = role == "water" ? 260f : 0f;
        var methaneReserve = role == "graphene" ? 320f : 0f;
        var hydrogenReserve = role == "antimattercells" ? 320f : 0f;
        var heliumReserve = role == "superfluidcoolant" ? 320f : 0f;
        var siliconReserve = role == "siliconwafers" ? 240f : 0f;
        var grapheneInputReserve = role == "quantumtubes" ? 160f : 0f;
        var superfluidCoolantInputReserve = role == "quantumtubes" ? 120f : 0f;
        var antimatterCellsInputReserve = role == "claytronics" ? 120f : 0f;
        var quantumTubesInputReserve = role == "claytronics" ? 120f : 0f;
        var energyReserve = role switch
        {
            "power" => 120f,
            "refinery" => 160f,
            "hullparts" => 180f,
            "claytronics" => 220f,
            "graphene" => 160f,
            "siliconwafers" => 160f,
            "antimattercells" => 160f,
            "superfluidcoolant" => 160f,
            "quantumtubes" => 160f,
            "water" => 140f,
            _ => 60f,
        } + constructionEnergyReserve;
        var refinedReserve = role switch
        {
            "hullparts" => 220f,
            "shipyard" => 260f,
            "refinery" => 80f,
            _ => 0f,
        };
        var oreReserve = role == "refinery" ? 260f : 0f;
        var hullpartsReserve = MathF.Max(constructionHullpartsReserve, HasShipyardCapability(station) ? 120f : 0f);
        var claytronicsReserve = MathF.Max(constructionClayReserve, HasShipyardCapability(station) ? 120f : 0f);
        var grapheneReserve = role == "graphene" ? 120f : 0f;
        var siliconWafersReserve = role == "siliconwafers" ? 120f : 0f;
        var antimatterCellsReserve = role == "antimattercells" ? 120f : 0f;
        var superfluidCoolantReserve = role == "superfluidcoolant" ? 120f : 0f;
        var quantumTubesReserve = role == "quantumtubes" ? 120f : 0f;
        var shipPartsReserve = HasShipyardCapability(station)
            && GetShipProductionPressure(world, station.FactionId, MilitaryShipCategory) > 0.2f
            ? 90f
            : 0f;

        AddDemandOrder(desiredOrders, station, "water", ScaleReserveByEconomy(economy, "water", waterReserve), valuationBase: ScaleDemandValuation(economy, "water", 1.1f));
        AddDemandOrder(desiredOrders, station, "energycells", ScaleReserveByEconomy(economy, "energycells", energyReserve), valuationBase: ScaleDemandValuation(economy, "energycells", 1.0f));
        AddDemandOrder(desiredOrders, station, "ice", ScaleReserveByEconomy(economy, "ice", iceReserve), valuationBase: ScaleDemandValuation(economy, "ice", 1.0f));
        AddDemandOrder(desiredOrders, station, "methane", ScaleReserveByEconomy(economy, "methane", methaneReserve), valuationBase: ScaleDemandValuation(economy, "methane", 1.0f));
        AddDemandOrder(desiredOrders, station, "hydrogen", ScaleReserveByEconomy(economy, "hydrogen", hydrogenReserve), valuationBase: ScaleDemandValuation(economy, "hydrogen", 1.0f));
        AddDemandOrder(desiredOrders, station, "helium", ScaleReserveByEconomy(economy, "helium", heliumReserve), valuationBase: ScaleDemandValuation(economy, "helium", 1.0f));
        AddDemandOrder(desiredOrders, station, "ore", ScaleReserveByEconomy(economy, "ore", oreReserve), valuationBase: ScaleDemandValuation(economy, "ore", 1.0f));
        AddDemandOrder(desiredOrders, station, "silicon", ScaleReserveByEconomy(economy, "silicon", siliconReserve), valuationBase: ScaleDemandValuation(economy, "silicon", 1.0f));
        AddDemandOrder(desiredOrders, station, "refinedmetals", ScaleReserveByEconomy(economy, "refinedmetals", MathF.Max(refinedReserve, constructionRefinedReserve)), valuationBase: ScaleDemandValuation(economy, "refinedmetals", 1.15f));
        AddDemandOrder(desiredOrders, station, "hullparts", ScaleReserveByEconomy(economy, "hullparts", hullpartsReserve + shipPartsReserve), valuationBase: ScaleDemandValuation(economy, "hullparts", 1.3f));
        AddDemandOrder(desiredOrders, station, "claytronics", ScaleReserveByEconomy(economy, "claytronics", claytronicsReserve), valuationBase: ScaleDemandValuation(economy, "claytronics", 1.35f));
        AddDemandOrder(desiredOrders, station, "graphene", ScaleReserveByEconomy(economy, "graphene", grapheneReserve), valuationBase: ScaleDemandValuation(economy, "graphene", 1.05f));
        AddDemandOrder(desiredOrders, station, "siliconwafers", ScaleReserveByEconomy(economy, "siliconwafers", siliconWafersReserve), valuationBase: ScaleDemandValuation(economy, "siliconwafers", 1.05f));
        AddDemandOrder(desiredOrders, station, "antimattercells", ScaleReserveByEconomy(economy, "antimattercells", antimatterCellsReserve), valuationBase: ScaleDemandValuation(economy, "antimattercells", 1.05f));
        AddDemandOrder(desiredOrders, station, "superfluidcoolant", ScaleReserveByEconomy(economy, "superfluidcoolant", superfluidCoolantReserve), valuationBase: ScaleDemandValuation(economy, "superfluidcoolant", 1.05f));
        AddDemandOrder(desiredOrders, station, "graphene", ScaleReserveByEconomy(economy, "graphene", grapheneInputReserve), valuationBase: ScaleDemandValuation(economy, "graphene", 1.1f));
        AddDemandOrder(desiredOrders, station, "superfluidcoolant", ScaleReserveByEconomy(economy, "superfluidcoolant", superfluidCoolantInputReserve), valuationBase: ScaleDemandValuation(economy, "superfluidcoolant", 1.1f));
        AddDemandOrder(desiredOrders, station, "antimattercells", ScaleReserveByEconomy(economy, "antimattercells", antimatterCellsInputReserve), valuationBase: ScaleDemandValuation(economy, "antimattercells", 1.1f));
        AddDemandOrder(desiredOrders, station, "quantumtubes", ScaleReserveByEconomy(economy, "quantumtubes", quantumTubesInputReserve), valuationBase: ScaleDemandValuation(economy, "quantumtubes", 1.1f));
        AddDemandOrder(desiredOrders, station, "quantumtubes", ScaleReserveByEconomy(economy, "quantumtubes", quantumTubesReserve), valuationBase: ScaleDemandValuation(economy, "quantumtubes", 1.05f));

        AddSupplyOrder(desiredOrders, station, "water", ScaleSupplyTriggerByEconomy(economy, "water", waterReserve * 1.5f), reserveFloor: waterReserve, valuationBase: ScaleSupplyValuation(economy, "water", 0.65f));
        AddSupplyOrder(desiredOrders, station, "energycells", ScaleSupplyTriggerByEconomy(economy, "energycells", energyReserve * 1.4f), reserveFloor: energyReserve, valuationBase: ScaleSupplyValuation(economy, "energycells", 0.7f));
        AddSupplyOrder(desiredOrders, station, "ice", ScaleSupplyTriggerByEconomy(economy, "ice", iceReserve * 1.4f), reserveFloor: iceReserve, valuationBase: ScaleSupplyValuation(economy, "ice", 0.5f));
        AddSupplyOrder(desiredOrders, station, "methane", ScaleSupplyTriggerByEconomy(economy, "methane", methaneReserve * 1.4f), reserveFloor: methaneReserve, valuationBase: ScaleSupplyValuation(economy, "methane", 0.7f));
        AddSupplyOrder(desiredOrders, station, "hydrogen", ScaleSupplyTriggerByEconomy(economy, "hydrogen", hydrogenReserve * 1.4f), reserveFloor: hydrogenReserve, valuationBase: ScaleSupplyValuation(economy, "hydrogen", 0.7f));
        AddSupplyOrder(desiredOrders, station, "helium", ScaleSupplyTriggerByEconomy(economy, "helium", heliumReserve * 1.4f), reserveFloor: heliumReserve, valuationBase: ScaleSupplyValuation(economy, "helium", 0.7f));
        AddSupplyOrder(desiredOrders, station, "ore", ScaleSupplyTriggerByEconomy(economy, "ore", oreReserve * 1.4f), reserveFloor: oreReserve, valuationBase: ScaleSupplyValuation(economy, "ore", 0.7f));
        AddSupplyOrder(desiredOrders, station, "silicon", ScaleSupplyTriggerByEconomy(economy, "silicon", siliconReserve * 1.4f), reserveFloor: siliconReserve, valuationBase: ScaleSupplyValuation(economy, "silicon", 0.7f));
        AddSupplyOrder(desiredOrders, station, "refinedmetals", ScaleSupplyTriggerByEconomy(economy, "refinedmetals", MathF.Max(refinedReserve, constructionRefinedReserve) * 1.4f), reserveFloor: MathF.Max(refinedReserve, constructionRefinedReserve), valuationBase: ScaleSupplyValuation(economy, "refinedmetals", 0.95f));
        AddSupplyOrder(desiredOrders, station, "hullparts", ScaleSupplyTriggerByEconomy(economy, "hullparts", MathF.Max(hullpartsReserve * 1.35f, hullpartsReserve + 40f)), reserveFloor: hullpartsReserve, valuationBase: ScaleSupplyValuation(economy, "hullparts", 1.05f));
        AddSupplyOrder(desiredOrders, station, "claytronics", ScaleSupplyTriggerByEconomy(economy, "claytronics", MathF.Max(claytronicsReserve * 1.35f, claytronicsReserve + 30f)), reserveFloor: claytronicsReserve, valuationBase: ScaleSupplyValuation(economy, "claytronics", 1.1f));
        AddSupplyOrder(desiredOrders, station, "graphene", ScaleSupplyTriggerByEconomy(economy, "graphene", MathF.Max(grapheneReserve * 1.35f, grapheneReserve + 30f)), reserveFloor: grapheneReserve, valuationBase: ScaleSupplyValuation(economy, "graphene", 0.9f));
        AddSupplyOrder(desiredOrders, station, "siliconwafers", ScaleSupplyTriggerByEconomy(economy, "siliconwafers", MathF.Max(siliconWafersReserve * 1.35f, siliconWafersReserve + 30f)), reserveFloor: siliconWafersReserve, valuationBase: ScaleSupplyValuation(economy, "siliconwafers", 0.9f));
        AddSupplyOrder(desiredOrders, station, "antimattercells", ScaleSupplyTriggerByEconomy(economy, "antimattercells", MathF.Max(antimatterCellsReserve * 1.35f, antimatterCellsReserve + 30f)), reserveFloor: antimatterCellsReserve, valuationBase: ScaleSupplyValuation(economy, "antimattercells", 0.9f));
        AddSupplyOrder(desiredOrders, station, "superfluidcoolant", ScaleSupplyTriggerByEconomy(economy, "superfluidcoolant", MathF.Max(superfluidCoolantReserve * 1.35f, superfluidCoolantReserve + 30f)), reserveFloor: superfluidCoolantReserve, valuationBase: ScaleSupplyValuation(economy, "superfluidcoolant", 0.9f));
        AddSupplyOrder(desiredOrders, station, "quantumtubes", ScaleSupplyTriggerByEconomy(economy, "quantumtubes", MathF.Max(quantumTubesReserve * 1.35f, quantumTubesReserve + 30f)), reserveFloor: quantumTubesReserve, valuationBase: ScaleSupplyValuation(economy, "quantumtubes", 0.9f));

        desiredOrders = ApplyRegionalMarketModifiers(world, station, desiredOrders);
        ReconcileStationMarketOrders(world, station, desiredOrders);
    }

    internal static float GetStationReserveFloor(SimulationWorld world, StationRuntime station, string itemId)
    {
        var role = DetermineStationRole(station);
        var site = GetConstructionSiteForStation(world, station.Id);
        var constructionEnergyReserve = GetConstructionDemandForItem(world, site, "energycells");
        var constructionHullpartsReserve = GetConstructionDemandForItem(world, site, "hullparts");
        var constructionClayReserve = GetConstructionDemandForItem(world, site, "claytronics");
        var constructionRefinedReserve = GetConstructionDemandForItem(world, site, "refinedmetals");
        var shipPartsReserve = HasShipyardCapability(station)
            && GetShipProductionPressure(world, station.FactionId, MilitaryShipCategory) > 0.2f
            ? 90f
            : 0f;

        return itemId switch
        {
            "water" => MathF.Max(30f, station.Population * 3f),
            "energycells" => role switch
            {
                "power" => 120f,
                "refinery" => 160f,
                "hullparts" => 180f,
                "claytronics" => 220f,
                "graphene" => 160f,
                "siliconwafers" => 160f,
                "antimattercells" => 160f,
                "superfluidcoolant" => 160f,
                "quantumtubes" => 160f,
                "water" => 140f,
                _ => 60f,
            } + constructionEnergyReserve,
            "ice" => role == "water" ? 260f : 0f,
            "methane" => role == "graphene" ? 320f : 0f,
            "hydrogen" => role == "antimattercells" ? 320f : 0f,
            "helium" => role == "superfluidcoolant" ? 320f : 0f,
            "ore" => role == "refinery" ? 260f : 0f,
            "silicon" => role == "siliconwafers" ? 240f : 0f,
            "refinedmetals" => MathF.Max(role switch
            {
                "hullparts" => 220f,
                "shipyard" => 260f,
                "refinery" => 80f,
                _ => 0f,
            }, constructionRefinedReserve),
            "hullparts" => MathF.Max(constructionHullpartsReserve, HasShipyardCapability(station) ? 120f : 0f) + shipPartsReserve,
            "claytronics" => MathF.Max(constructionClayReserve, HasShipyardCapability(station) ? 120f : 0f),
            "graphene" => MathF.Max(role == "graphene" ? 120f : 0f, role == "quantumtubes" ? 160f : 0f),
            "siliconwafers" => role == "siliconwafers" ? 120f : 0f,
            "antimattercells" => MathF.Max(role == "antimattercells" ? 120f : 0f, role == "claytronics" ? 120f : 0f),
            "superfluidcoolant" => MathF.Max(role == "superfluidcoolant" ? 120f : 0f, role == "quantumtubes" ? 120f : 0f),
            "quantumtubes" => MathF.Max(role == "quantumtubes" ? 120f : 0f, role == "claytronics" ? 120f : 0f),
            _ => 0f,
        };
    }

    internal void RunStationProduction(SimulationWorld world, StationRuntime station, float deltaSeconds, ICollection<SimulationEventRecord> events)
    {
        var faction = world.Factions.FirstOrDefault(candidate => candidate.Id == station.FactionId);
        foreach (var laneKey in GetStationProductionLanes(world, station))
        {
            var recipe = SelectProductionRecipe(world, station, laneKey);
            if (recipe is null)
            {
                station.ProductionLaneTimers[laneKey] = 0f;
                continue;
            }

            var throughput = GetStationProductionThroughput(world, station, recipe);

            var produced = 0f;
            station.ProductionLaneTimers[laneKey] = GetStationProductionTimer(station, laneKey) + (deltaSeconds * station.WorkforceEffectiveRatio * throughput);
            while (station.ProductionLaneTimers[laneKey] >= recipe.Duration && CanRunRecipe(world, station, recipe))
            {
                station.ProductionLaneTimers[laneKey] -= recipe.Duration;
                foreach (var input in recipe.Inputs)
                {
                    RemoveInventory(station.Inventory, input.ItemId, input.Amount);
                }

                if (recipe.ShipOutputId is not null)
                {
                    produced += StationLifecycleService.CompleteShipRecipe(world, station, recipe, events);
                    continue;
                }

                foreach (var output in recipe.Outputs)
                {
                    produced += TryAddStationInventory(world, station, output.ItemId, output.Amount);
                }
            }

            if (produced <= 0.01f)
            {
                continue;
            }

            events.Add(new SimulationEventRecord("station", station.Id, "production-complete", $"{station.Label} completed {recipe.Label}.", DateTimeOffset.UtcNow));
            if (faction is not null)
            {
                faction.GoodsProduced += produced;
            }
        }
    }

    internal static IEnumerable<string> GetStationProductionLanes(SimulationWorld world, StationRuntime station)
    {
        foreach (var moduleId in station.InstalledModules.Distinct(StringComparer.Ordinal))
        {
            if (!world.ModuleDefinitions.TryGetValue(moduleId, out var definition)
                || definition is not ProductionLaneModuleDefinition)
            {
                continue;
            }

            yield return moduleId;
        }
    }

    internal static float GetStationProductionTimer(StationRuntime station, string laneKey) =>
        station.ProductionLaneTimers.TryGetValue(laneKey, out var timer) ? timer : 0f;

    internal static RecipeDefinition? SelectProductionRecipe(SimulationWorld world, StationRuntime station, string laneKey) =>
        world.Recipes.Values
            .Where(recipe => RecipeAppliesToStation(station, recipe) && string.Equals(GetStationProductionLaneKey(world, recipe), laneKey, StringComparison.Ordinal))
            .OrderByDescending(recipe => GetStationRecipePriority(world, station, recipe))
            .FirstOrDefault(recipe => CanRunRecipe(world, station, recipe));

    internal static string? GetStationProductionLaneKey(SimulationWorld world, RecipeDefinition recipe) =>
        recipe.RequiredModules.FirstOrDefault(moduleId =>
            world.ModuleDefinitions.TryGetValue(moduleId, out var definition) && definition is ProductionLaneModuleDefinition);

    internal static float GetStationProductionThroughput(SimulationWorld world, StationRuntime station, RecipeDefinition recipe)
    {
        var laneModuleId = GetStationProductionLaneKey(world, recipe);
        if (laneModuleId is null)
        {
            return 1f;
        }

        return Math.Max(1, CountModules(station.InstalledModules, laneModuleId));
    }

    private static float GetStationRecipePriority(SimulationWorld world, StationRuntime station, RecipeDefinition recipe)
    {
        var priority = (float)recipe.Priority;

        var expansionPressure = GetFactionExpansionPressure(world, station.FactionId);
        var fleetPressure = GetShipProductionPressure(world, station.FactionId, MilitaryShipCategory);
        priority += GetStationRecipePriorityAdjustment(world, station, recipe, expansionPressure, fleetPressure);
        priority += GetStrategicRecipeBias(world, station, recipe);

        return priority;
    }

    private static float GetStationRecipePriorityAdjustment(SimulationWorld world, StationRuntime station, RecipeDefinition recipe, float expansionPressure, float fleetPressure)
    {
        if (recipe.ShipOutputId is not null && world.ShipDefinitions.TryGetValue(recipe.ShipOutputId, out var shipDefinition))
        {
            var shipPressure = GetShipProductionPressure(world, station.FactionId, GetShipCategory(shipDefinition));
            if (IsMilitaryShip(shipDefinition))
            {
                return recipe.Id switch
                {
                    "frigate-construction" => 320f * shipPressure,
                    "destroyer-construction" => 200f * shipPressure,
                    "cruiser-construction" => 120f * shipPressure,
                    _ => 160f * shipPressure,
                };
            }

            if (IsConstructionShip(shipDefinition))
            {
                return 260f * shipPressure;
            }

            if (IsMiningShip(shipDefinition))
            {
                return 250f * shipPressure;
            }

            if (IsTransportShip(shipDefinition))
            {
                return 230f * shipPressure;
            }

            return 0f;
        }

        var outputItemIds = recipe.Outputs
            .Select(output => output.ItemId)
            .ToHashSet(StringComparer.Ordinal);

        if (outputItemIds.Contains("hullparts"))
        {
            return HasShipyardCapability(station) && HasStationModules(station, "module_gen_prod_advancedelectronics_01")
                ? -140f * MathF.Max(expansionPressure, fleetPressure)
                : 280f * MathF.Max(expansionPressure, fleetPressure);
        }

        if (outputItemIds.Contains("refinedmetals"))
        {
            return 180f * expansionPressure;
        }

        if (outputItemIds.Overlaps(["advancedelectronics", "dronecomponents", "engineparts", "fieldcoils", "missilecomponents", "shieldcomponents", "smartchips"]))
        {
            return 170f * expansionPressure;
        }

        if (outputItemIds.Overlaps(["turretcomponents", "weaponcomponents"]))
        {
            return 160f * expansionPressure;
        }

        return recipe.Id switch
        {
            "command-bridge-module-assembly" or "reactor-core-module-assembly" or "capacitor-bank-module-assembly" or "ion-drive-module-assembly" or "ftl-core-module-assembly" or "gun-turret-module-assembly"
                => 220f * MathF.Max(expansionPressure, fleetPressure),
            "ammo-fabrication" => -80f * expansionPressure,
            "trade-hub-assembly" or "refinery-assembly" or "farm-ring-assembly" or "manufactory-assembly" or "shipyard-assembly" or "defense-grid-assembly" or "stargate-assembly"
                => -120f * expansionPressure,
            _ => 0f,
        };
    }

    private static float GetStrategicRecipeBias(SimulationWorld world, StationRuntime station, RecipeDefinition recipe)
    {
        var commander = station.CommanderId is null
          ? null
          : world.Commanders.FirstOrDefault(candidate => candidate.Id == station.CommanderId);
        var assignment = commander?.Assignment;
        if (assignment is null)
        {
            return 0f;
        }

        var outputItemIds = recipe.Outputs
          .Select(output => output.ItemId)
          .ToHashSet(StringComparer.Ordinal);

        if (string.Equals(assignment.Kind, "ship-production-focus", StringComparison.Ordinal)
            && recipe.ShipOutputId is not null
            && world.ShipDefinitions.TryGetValue(recipe.ShipOutputId, out var shipDefinition)
            && IsMilitaryShip(shipDefinition))
        {
            return 260f;
        }

        if (string.Equals(assignment.Kind, "commodity-focus", StringComparison.Ordinal)
            && assignment.ItemId is not null
            && outputItemIds.Contains(assignment.ItemId))
        {
            return 220f;
        }

        if (string.Equals(assignment.Kind, "expansion-support", StringComparison.Ordinal)
            && outputItemIds.Overlaps(["energycells", "refinedmetals", "hullparts", "claytronics"]))
        {
            return 180f;
        }

        if (string.Equals(assignment.Kind, "station-oversight", StringComparison.Ordinal)
            && assignment.ItemId is not null
            && outputItemIds.Contains(assignment.ItemId))
        {
            return 90f;
        }

        return 0f;
    }

    internal static bool RecipeAppliesToStation(StationRuntime station, RecipeDefinition recipe)
    {
        var categoryMatch = string.Equals(recipe.FacilityCategory, "station", StringComparison.Ordinal)
            || string.Equals(recipe.FacilityCategory, "farm", StringComparison.Ordinal)
            || string.Equals(recipe.FacilityCategory, station.Category, StringComparison.Ordinal);
        return categoryMatch && recipe.RequiredModules.All(moduleId => station.InstalledModules.Contains(moduleId, StringComparer.Ordinal));
    }

    private static bool CanRunRecipe(SimulationWorld world, StationRuntime station, RecipeDefinition recipe)
    {
        if (recipe.ShipOutputId is not null)
        {
            if (!world.ShipDefinitions.TryGetValue(recipe.ShipOutputId, out var shipDefinition))
            {
                return false;
            }

            if (GetShipProductionPressure(world, station.FactionId, GetShipCategory(shipDefinition)) <= 0.05f)
            {
                return false;
            }
        }

        if (recipe.Inputs.Any(input => GetInventoryAmount(station.Inventory, input.ItemId) + 0.001f < input.Amount))
        {
            return false;
        }

        return recipe.Outputs.All(output => CanAcceptStationInventory(world, station, output.ItemId, output.Amount));
    }

    private static bool CanAcceptStationInventory(SimulationWorld world, StationRuntime station, string itemId, float amount)
    {
        if (amount <= 0f || !world.ItemDefinitions.TryGetValue(itemId, out var itemDefinition))
        {
            return false;
        }

        var storageKind = itemDefinition.CargoKind;
        if (storageKind is null)
        {
            return false;
        }

        if (!HasStorageCapacity(world, station, storageKind.Value))
        {
            return false;
        }

        var capacity = GetStationStorageCapacity(world, station, storageKind.Value);
        if (capacity <= 0.01f)
        {
            return false;
        }

        var used = station.Inventory
            .Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var definition) && definition.CargoKind == storageKind)
            .Sum(entry => entry.Value);
        return used + amount <= capacity + 0.001f;
    }

    private static bool HasRefineryCapability(StationRuntime station) =>
        HasStationModules(station, "module_gen_prod_refinedmetals_01", "module_gen_prod_energycells_01", "module_arg_stor_solid_m_01");

    internal static string NormalizeStationObjective(string? objective)
    {
        return objective?.Trim().ToLowerInvariant() switch
        {
            "power" or "energy" or "energycells" => "power",
            "water" or "ice-refinery" => "water",
            "refinery" or "refinedmetals" => "refinery",
            "hullparts" or "hull" => "hullparts",
            "claytronics" or "clay" => "claytronics",
            "graphene" => "graphene",
            "siliconwafers" or "silicon-wafers" or "silicon" => "siliconwafers",
            "antimattercells" or "antimatter-cells" => "antimattercells",
            "superfluidcoolant" or "superfluid-coolant" => "superfluidcoolant",
            "quantumtubes" or "quantum-tubes" => "quantumtubes",
            "shipyard" or "ship-production" => "shipyard",
            _ => "general",
        };
    }

    internal static string DetermineStationRole(StationRuntime station)
    {
        var objective = NormalizeStationObjective(station.Objective);
        if (!string.Equals(objective, "general", StringComparison.Ordinal))
        {
            return objective;
        }

        if (HasShipyardCapability(station))
        {
            return "shipyard";
        }

        if (HasStationModules(station, "module_gen_prod_water_01"))
        {
            return "water";
        }

        if (HasStationModules(station, "module_gen_prod_superfluidcoolant_01"))
        {
            return "superfluidcoolant";
        }

        if (HasStationModules(station, "module_gen_prod_quantumtubes_01"))
        {
            return "quantumtubes";
        }

        if (HasStationModules(station, "module_gen_prod_antimattercells_01"))
        {
            return "antimattercells";
        }

        if (HasStationModules(station, "module_gen_prod_siliconwafers_01"))
        {
            return "siliconwafers";
        }

        if (HasStationModules(station, "module_gen_prod_graphene_01"))
        {
            return "graphene";
        }

        if (HasStationModules(station, "module_gen_prod_claytronics_01"))
        {
            return "claytronics";
        }

        if (HasStationModules(station, "module_gen_prod_hullparts_01"))
        {
            return "hullparts";
        }

        if (HasStationModules(station, "module_gen_prod_refinedmetals_01"))
        {
            return "refinery";
        }

        if (HasStationModules(station, "module_gen_prod_energycells_01"))
        {
            return "power";
        }

        return "general";
    }

    private static bool HasShipyardCapability(StationRuntime station) =>
        CountStationModules(station, ModuleType.BuildModule) > 0;

    private static float GetConstructionDemandForItem(SimulationWorld world, ConstructionSiteRuntime? site, string itemId)
    {
        if (site is null || !site.RequiredItems.TryGetValue(itemId, out var required))
        {
            return 0f;
        }

        return MathF.Max(0f, required - GetConstructionDeliveredAmount(world, site, itemId));
    }

    private static void AddDemandOrder(ICollection<DesiredMarketOrder> desiredOrders, StationRuntime station, string itemId, float targetAmount, float valuationBase)
    {
        var current = GetInventoryAmount(station.Inventory, itemId);
        if (current >= targetAmount - 0.01f)
        {
            return;
        }

        var deficit = targetAmount - current;
        var scarcity = targetAmount <= 0.01f ? 1f : MathF.Min(1f, deficit / targetAmount);
        desiredOrders.Add(new DesiredMarketOrder(MarketOrderKinds.Buy, itemId, deficit, valuationBase + scarcity, null));
    }

    private static void AddSupplyOrder(ICollection<DesiredMarketOrder> desiredOrders, StationRuntime station, string itemId, float triggerAmount, float reserveFloor, float valuationBase)
    {
        var current = GetInventoryAmount(station.Inventory, itemId);
        if (current <= triggerAmount + 0.01f)
        {
            return;
        }

        var surplus = current - reserveFloor;
        if (surplus <= 0.01f)
        {
            return;
        }

        var surplusRatio = triggerAmount <= 0.01f ? 1f : MathF.Min(1f, surplus / triggerAmount);
        var liquidationValuation = MathF.Max(0.05f, valuationBase * (1f - (0.85f * surplusRatio)));
        desiredOrders.Add(new DesiredMarketOrder(MarketOrderKinds.Sell, itemId, surplus, liquidationValuation, reserveFloor));
    }

    private static void ReconcileStationMarketOrders(SimulationWorld world, StationRuntime station, IReadOnlyCollection<DesiredMarketOrder> desiredOrders)
    {
        var existingOrders = world.MarketOrders
            .Where(order => order.StationId == station.Id && order.ConstructionSiteId is null)
            .ToList();

        foreach (var desired in desiredOrders)
        {
            var order = existingOrders.FirstOrDefault(candidate =>
                candidate.Kind == desired.Kind &&
                candidate.ItemId == desired.ItemId &&
                candidate.ConstructionSiteId is null);

            if (order is null)
            {
                order = new MarketOrderRuntime
                {
                    Id = $"market-order-{station.Id}-{desired.Kind}-{desired.ItemId}",
                    FactionId = station.FactionId,
                    StationId = station.Id,
                    Kind = desired.Kind,
                    ItemId = desired.ItemId,
                    Amount = desired.Amount,
                    RemainingAmount = desired.Amount,
                    Valuation = desired.Valuation,
                    ReserveThreshold = desired.ReserveThreshold,
                    State = MarketOrderStateKinds.Open,
                };
                world.MarketOrders.Add(order);
                station.MarketOrderIds.Add(order.Id);
                existingOrders.Add(order);
                continue;
            }

            order.RemainingAmount = desired.Amount;
            order.Valuation = desired.Valuation;
            order.ReserveThreshold = desired.ReserveThreshold;
            order.State = desired.Amount <= 0.01f ? MarketOrderStateKinds.Cancelled : MarketOrderStateKinds.Open;
        }

        foreach (var order in existingOrders.Where(order => desiredOrders.All(desired => desired.Kind != order.Kind || desired.ItemId != order.ItemId)))
        {
            order.RemainingAmount = 0f;
            order.State = MarketOrderStateKinds.Cancelled;
        }
    }

    internal static float GetFactionExpansionPressure(SimulationWorld world, string factionId)
    {
        var targetSystems = Math.Max(1, Math.Min(StrategicControlTargetSystems, world.Systems.Count));
        var controlledSystems = GetFactionControlledSystemsCount(world, factionId);
        var deficit = Math.Max(0, targetSystems - controlledSystems);
        var contestedSystems = world.Geopolitics?.Territory.ControlStates.Count(state =>
          state.IsContested
          && (string.Equals(state.ControllerFactionId, factionId, StringComparison.Ordinal)
            || string.Equals(state.PrimaryClaimantFactionId, factionId, StringComparison.Ordinal)
            || state.ClaimantFactionIds.Contains(factionId, StringComparer.Ordinal))) ?? 0;
        var frontierSystems = world.Geopolitics?.Territory.Zones.Count(zone =>
          string.Equals(zone.FactionId, factionId, StringComparison.Ordinal)
          && zone.Kind is "frontier" or "corridor" or "contested") ?? 0;
        return Math.Clamp((deficit / (float)targetSystems) + (contestedSystems * 0.12f) + (frontierSystems * 0.04f), 0f, 1f);
    }

    internal static int GetFactionControlledSystemsCount(SimulationWorld world, string factionId)
    {
        return GeopoliticalSimulationService.GetControlledSystems(world, factionId).Count;
    }

    private static float ScaleReserveByEconomy(FactionEconomySnapshot economy, string itemId, float baseReserve)
    {
        var commodity = economy.GetCommodity(itemId);
        if (commodity.Level == CommodityLevelKind.Critical)
        {
            return baseReserve * 1.6f;
        }

        return commodity.Level switch
        {
            CommodityLevelKind.Low => baseReserve * 1.25f,
            CommodityLevelKind.Stable when commodity.ProjectedNetRatePerSecond < -0.01f => baseReserve * 1.1f,
            _ => MathF.Max(0f, baseReserve),
        };
    }

    private static float ScaleSupplyTriggerByEconomy(FactionEconomySnapshot economy, string itemId, float baseTrigger)
    {
        var commodity = economy.GetCommodity(itemId);
        return commodity.NetRatePerSecond < -0.01f ? baseTrigger * 1.2f : baseTrigger;
    }

    private static float ScaleDemandValuation(FactionEconomySnapshot economy, string itemId, float baseValuation)
    {
        var commodity = economy.GetCommodity(itemId);
        return commodity.Level switch
        {
            CommodityLevelKind.Critical => baseValuation * 1.6f,
            CommodityLevelKind.Low => baseValuation * 1.3f,
            CommodityLevelKind.Stable when commodity.ProjectedNetRatePerSecond < -0.01f => baseValuation * 1.15f,
            CommodityLevelKind.Surplus when commodity.ProjectedNetRatePerSecond > 0.01f => baseValuation * 0.9f,
            _ => commodity.ProductionRatePerSecond > 0.01f ? baseValuation : baseValuation * 1.15f,
        };
    }

    private static float ScaleSupplyValuation(FactionEconomySnapshot economy, string itemId, float baseValuation)
    {
        var commodity = economy.GetCommodity(itemId);
        return commodity.Level == CommodityLevelKind.Surplus && commodity.NetRatePerSecond > 0.01f
          ? baseValuation * 0.75f
          : commodity.Level == CommodityLevelKind.Critical
            ? baseValuation * 1.15f
            : baseValuation;
    }

    private static List<DesiredMarketOrder> ApplyRegionalMarketModifiers(SimulationWorld world, StationRuntime station, IReadOnlyCollection<DesiredMarketOrder> desiredOrders)
    {
        var region = GeopoliticalSimulationService.GetPrimaryEconomicRegion(world, station.FactionId, station.SystemId);
        if (region is null)
        {
            return desiredOrders.ToList();
        }

        var security = world.Geopolitics?.EconomyRegions.SecurityAssessments.FirstOrDefault(assessment => string.Equals(assessment.RegionId, region.Id, StringComparison.Ordinal));
        var economic = world.Geopolitics?.EconomyRegions.EconomicAssessments.FirstOrDefault(assessment => string.Equals(assessment.RegionId, region.Id, StringComparison.Ordinal));
        var bottlenecks = world.Geopolitics?.EconomyRegions.Bottlenecks
          .Where(bottleneck => string.Equals(bottleneck.RegionId, region.Id, StringComparison.Ordinal))
          .ToDictionary(bottleneck => bottleneck.ItemId, StringComparer.Ordinal) ?? new Dictionary<string, RegionalBottleneckRuntime>(StringComparer.Ordinal);
        var riskMultiplier = 1f + ((security?.SupplyRisk ?? 0f) * 0.3f) + ((security?.AccessFriction ?? 0f) * 0.2f);
        var sustainmentFloor = 1f + MathF.Max(0f, 0.55f - (economic?.SustainmentScore ?? 1f));

        return desiredOrders
          .Select(order =>
          {
              bottlenecks.TryGetValue(order.ItemId, out var bottleneck);
              var severity = bottleneck?.Severity ?? 0f;
              var buyBias = order.Kind == MarketOrderKinds.Buy ? 1f + (severity * 0.08f) : 1f;
              var sellBias = order.Kind == MarketOrderKinds.Sell && severity > 0f ? MathF.Max(0.35f, 1f - (severity * 0.07f)) : 1f;
              var amount = order.Amount * (order.Kind == MarketOrderKinds.Buy ? riskMultiplier * buyBias * sustainmentFloor : sellBias);
              var valuation = order.Valuation * (order.Kind == MarketOrderKinds.Buy
            ? 1f + (severity * 0.06f) + ((security?.SupplyRisk ?? 0f) * 0.18f)
            : 1f + (severity * 0.04f));
              float? reserveThreshold = order.ReserveThreshold.HasValue
            ? order.ReserveThreshold.Value * (1f + ((security?.SupplyRisk ?? 0f) * 0.15f))
            : null;
              return new DesiredMarketOrder(order.Kind, order.ItemId, amount, valuation, reserveThreshold);
          })
          .ToList();
    }

    private static float GetShipProductionPressure(SimulationWorld world, string factionId, string? shipCategory)
    {
        var economic = FindFactionEconomicAssessment(world, factionId);
        var threat = FindFactionThreatAssessment(world, factionId);
        if (economic is null || threat is null)
        {
            return 0f;
        }

        return shipCategory switch
        {
            MilitaryShipCategory => threat.EnemyFactionCount > 0
              ? economic.MilitaryShipCount < Math.Max(4, economic.ControlledSystemCount * 2) ? 1f : 0.25f
              : 0.1f,
            ConstructionShipCategory => economic.PrimaryExpansionSiteId is not null
              ? economic.ConstructorShipCount < 1 ? 1f : 0.35f
              : economic.ConstructorShipCount < 1 ? 0.5f : 0f,
            TransportShipCategory => economic.TransportShipCount < Math.Max(2, economic.ControlledSystemCount) ? 0.8f : 0.2f,
            MiningShipCategory => economic.MinerShipCount < Math.Max(2, economic.ControlledSystemCount) ? 0.85f : 0.2f,
            _ => 0.15f,
        };
    }

    private static bool FactionControlsSystem(SimulationWorld world, string factionId, string systemId)
      => GeopoliticalSimulationService.FactionControlsSystem(world, factionId, systemId);

    private sealed record DesiredMarketOrder(string Kind, string ItemId, float Amount, float Valuation, float? ReserveThreshold);
}
