
namespace SpaceGame.Api.Shared.Runtime;

internal static class SimulationRuntimeSupport
{
    internal static bool CanWarp(ShipDefinition definition) =>
        definition.Engines.Count > 0;

    internal static bool CanFtl(ShipDefinition definition) =>
        definition.Engines.Count > 0;

    internal static bool IsMiningShip(ShipDefinition definition) =>
        definition.Type is ShipType.Miner or ShipType.LargeMiner;

    internal static bool IsTransportShip(ShipDefinition definition) =>
        definition.Type is ShipType.Freighter or ShipType.Transporter or ShipType.Courier or ShipType.Resupplier;

    internal static bool IsConstructionShip(ShipDefinition definition) =>
        definition.Type == ShipType.Builder;

    internal static bool IsMilitaryShip(ShipDefinition definition) =>
        definition.Type is ShipType.Fighter
        or ShipType.HeavyFighter
        or ShipType.Destroyer
        or ShipType.Bomber
        or ShipType.Frigate
        or ShipType.Interceptor
        or ShipType.Corvette
        or ShipType.Battleship
        or ShipType.Gunboat;

    internal static string? GetShipCategory(ShipDefinition definition)
    {
        if (IsMilitaryShip(definition))
        {
            return "military";
        }

        if (IsConstructionShip(definition))
        {
            return "construction";
        }

        if (IsTransportShip(definition))
        {
            return "transport";
        }

        if (IsMiningShip(definition))
        {
            return "mining";
        }

        return null;
    }

    internal static int CountStationModules(StationRuntime station, ModuleType moduleType) =>
        station.Modules.Count(module => module.ModuleType == moduleType);

    internal static float GetStationSupportedPopulation(
        IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions,
        StationRuntime station) =>
        40f + station.Modules
            .Select(module => moduleDefinitions.TryGetValue(module.ModuleId, out var definition) && definition is HabitationModuleDefinition habitation
                ? habitation.SupportedPopulation
                : 0f)
            .Sum();

    internal static float GetStationRequiredWorkforce(
        IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions,
        StationRuntime station) =>
        MathF.Max(12f, station.Modules
            .Select(module => moduleDefinitions.TryGetValue(module.ModuleId, out var definition)
                && definition is ProductionLaneModuleDefinition productionLane
                    ? productionLane.RequiredWorkforce
                : 0f)
            .Sum());

    internal static float GetStationStorageCapacity(SimulationWorld world, StationRuntime station, StorageKind storageKind)
    {
        SyncStorageModuleLevels(world, station, storageKind);
        return GetStorageModules(world, station, storageKind)
            .Sum(entry => entry.Definition.StorageCapacity);
    }

    internal static bool HasStorageCapacity(SimulationWorld world, StationRuntime station, StorageKind storageKind)
    {
        SyncStorageModuleLevels(world, station, storageKind);
        return GetStorageModules(world, station, storageKind).Any();
    }

    private static IEnumerable<(StorageStationModuleRuntime Module, StorageModuleDefinition Definition)> GetStorageModules(
        SimulationWorld world,
        StationRuntime station,
        StorageKind storageKind) =>
        station.Modules
            .OfType<StorageStationModuleRuntime>()
            .Where(module => module.StorageKind == storageKind)
            .Select(module => world.ModuleDefinitions.TryGetValue(module.ModuleId, out var definition) && definition is StorageModuleDefinition storageDefinition
                ? (Module: module, Definition: storageDefinition)
                : ((StorageStationModuleRuntime Module, StorageModuleDefinition Definition)?)null)
            .Where(entry => entry is not null && entry.Value.Definition.StorageKind == storageKind)
            .Select(entry => entry!.Value);

    private static void SyncStorageModuleLevels(SimulationWorld world, StationRuntime station, StorageKind storageKind)
    {
        var storageModules = GetStorageModules(world, station, storageKind)
            .OrderBy(entry => entry.Module.Id, StringComparer.Ordinal)
            .ToList();
        if (storageModules.Count == 0)
        {
            return;
        }

        var remaining = station.Inventory
            .Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var definition) && definition.CargoKind == storageKind)
            .Sum(entry => entry.Value);

        foreach (var (module, definition) in storageModules)
        {
            module.CurrentLevel = MathF.Min(remaining, definition.StorageCapacity);
            remaining = MathF.Max(0f, remaining - definition.StorageCapacity);
        }
    }

    internal static void AddStationModule(SimulationWorld world, StationRuntime station, string moduleId)
    {
        if (!world.ModuleDefinitions.TryGetValue(moduleId, out var definition))
        {
            return;
        }

        station.Modules.Add(StationModuleRuntime.Create($"{station.Id}-module-{station.Modules.Count + 1}", definition));
        station.Radius = GetStationRadius(world, station);
    }

    internal static float GetStationRadius(SimulationWorld world, StationRuntime station)
    {
        var totalArea = station.Modules
          .Select(module => world.ModuleDefinitions.TryGetValue(module.ModuleId, out var definition) ? definition.Radius * definition.Radius : 0f)
          .Sum();
        return MathF.Max(24f, MathF.Sqrt(MathF.Max(totalArea, 1f)));
    }

    internal static int CountModules(IEnumerable<string> modules, string moduleId) =>
        modules.Count(candidate => string.Equals(candidate, moduleId, StringComparison.Ordinal));

    internal static float GetInventoryAmount(IReadOnlyDictionary<string, float> inventory, string itemId) =>
        inventory.TryGetValue(itemId, out var amount) ? amount : 0f;

    internal static void AddInventory(IDictionary<string, float> inventory, string itemId, float amount)
    {
        if (amount <= 0f)
        {
            return;
        }

        inventory[itemId] = GetInventoryAmount((IReadOnlyDictionary<string, float>)inventory, itemId) + amount;
    }

    internal static float RemoveInventory(IDictionary<string, float> inventory, string itemId, float amount)
    {
        var current = GetInventoryAmount((IReadOnlyDictionary<string, float>)inventory, itemId);
        var removed = MathF.Min(current, amount);
        var remaining = current - removed;
        if (remaining <= 0.001f)
        {
            inventory.Remove(itemId);
        }
        else
        {
            inventory[itemId] = remaining;
        }

        return removed;
    }

    internal static bool HasStationModules(StationRuntime station, params string[] modules) =>
        modules.All(moduleId => station.Modules.Any(candidate => string.Equals(candidate.ModuleId, moduleId, StringComparison.Ordinal)));

    internal static bool CanExtractNode(ShipRuntime ship, ResourceNodeRuntime node, SimulationWorld world) =>
        IsMiningShip(ship.Definition)
        && world.ItemDefinitions.TryGetValue(node.ItemId, out var item)
        && item.CargoKind is not null
        && ship.Definition.SupportsCargoKind(item.CargoKind.Value);

    internal static bool CanBuildClaimBeacon(ShipRuntime ship) =>
        IsMilitaryShip(ship.Definition);

    internal static float ComputeWorkforceRatio(float population, float workforceRequired)
    {
        if (workforceRequired <= 0.01f)
        {
            return 1f;
        }

        var staffedRatio = MathF.Min(1f, population / workforceRequired);
        return 0.1f + (0.9f * staffedRatio);
    }

    internal static string? GetStorageRequirement(
        IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions,
        StorageKind? storageKind)
    {
        if (storageKind is not { } requiredStorageKind)
        {
            return null;
        }

        return moduleDefinitions.Values
            .OfType<StorageModuleDefinition>()
            .Where(definition => definition.StorageKind == requiredStorageKind)
            .OrderBy(definition => GetPreferredStorageModuleRank(definition.Id))
            .ThenBy(definition => definition.Id, StringComparer.Ordinal)
            .Select(definition => definition.Id)
            .FirstOrDefault();
    }

    private static int GetPreferredStorageModuleRank(string moduleId)
    {
        if (moduleId.Contains("_m_", StringComparison.Ordinal))
        {
            return 0;
        }

        if (moduleId.Contains("_s_", StringComparison.Ordinal))
        {
            return 1;
        }

        if (moduleId.Contains("_l_", StringComparison.Ordinal))
        {
            return 2;
        }

        return 3;
    }

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

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

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

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

        var used = station.Inventory
            .Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var definition) && definition.CargoKind == storageKind)
            .Sum(entry => entry.Value);
        var accepted = MathF.Min(amount, MathF.Max(0f, capacity - used));
        if (accepted <= 0.01f)
        {
            return 0f;
        }

        AddInventory(station.Inventory, itemId, accepted);
        return accepted;
    }

    internal static bool CanStartModuleConstruction(StationRuntime station, ModuleRecipeDefinition recipe) =>
        recipe.Inputs.All(input => GetInventoryAmount(station.Inventory, input.ItemId) + 0.001f >= input.Amount);

    internal static ConstructionSiteRuntime? GetConstructionSiteForStation(SimulationWorld world, string stationId) =>
        world.ConstructionSites.FirstOrDefault(site =>
            string.Equals(site.StationId, stationId, StringComparison.Ordinal)
            && site.State is not ConstructionSiteStateKinds.Completed and not ConstructionSiteStateKinds.Destroyed);

    internal static float GetConstructionDeliveredAmount(SimulationWorld world, ConstructionSiteRuntime site, string itemId)
    {
        if (site.StationId is not null
            && world.Stations.FirstOrDefault(candidate => candidate.Id == site.StationId) is { } station)
        {
            return GetInventoryAmount(station.Inventory, itemId);
        }

        return GetInventoryAmount(site.DeliveredItems, itemId);
    }

    internal static bool IsConstructionSiteReady(SimulationWorld world, ConstructionSiteRuntime site) =>
        site.RequiredItems.All(entry => GetConstructionDeliveredAmount(world, site, entry.Key) + 0.001f >= entry.Value);

    internal static float GetShipCargoAmount(ShipRuntime ship) =>
      ship.Inventory.Values.Sum();
}
