using SpaceGame.Api.Shared.Runtime;

namespace SpaceGame.Api.Universe.Scenario;

internal static class StarterStationLayoutResolver
{
    internal static string ResolveDockModuleId(
      string? factionId,
      IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions) =>
      SelectPreferredModule(
        moduleDefinitions.Values.Where(definition => definition.ModuleType == ModuleType.DockArea),
        factionId,
        "starter dock module").Id;

    internal static string ResolvePowerModuleId(
      string? factionId,
      IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions) =>
      ResolveProducerModuleId("energycells", factionId, moduleDefinitions);

    internal static string? ResolveObjectiveModuleId(
      string? objective,
      string? factionId,
      IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions)
    {
        var targetWareId = ResolveObjectiveWareId(objective);
        return targetWareId is null ? null : ResolveProducerModuleId(targetWareId, factionId, moduleDefinitions);
    }

    internal static IEnumerable<string> ResolveRequiredStorageModuleIds(
      string moduleId,
      string? factionId,
      IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions,
      IReadOnlyDictionary<string, ItemDefinition> itemDefinitions,
      IReadOnlyDictionary<string, RecipeDefinition> recipes)
    {
        if (!moduleDefinitions.TryGetValue(moduleId, out var moduleDefinition))
        {
            throw new InvalidOperationException($"Module '{moduleId}' is not defined in static data.");
        }

        foreach (var wareId in moduleDefinition.BuildRecipes
          .SelectMany(production => production.Wares.Select(ware => ware.ItemId))
          .Concat(moduleDefinition.ProductItemIds)
          .Concat(recipes.Values
            .Where(recipe => recipe.RequiredModules.Contains(moduleId, StringComparer.Ordinal))
            .SelectMany(recipe => recipe.Inputs.Select(input => input.ItemId)
              .Concat(recipe.Outputs.Select(output => output.ItemId))))
          .Distinct(StringComparer.Ordinal))
        {
            if (!itemDefinitions.TryGetValue(wareId, out var itemDefinition))
            {
                throw new InvalidOperationException($"Module '{moduleId}' references unknown ware '{wareId}'.");
            }

            if (itemDefinition.CargoKind is not { } storageKind)
            {
                continue;
            }

            yield return ResolveStorageModuleId(storageKind, factionId, moduleDefinitions);
        }
    }

    private static string? ResolveObjectiveWareId(string? objective) =>
      StationSimulationService.NormalizeStationObjective(objective) switch
      {
          "power" => "energycells",
          "refinery" => "refinedmetals",
          "graphene" => "graphene",
          "siliconwafers" => "siliconwafers",
          "hullparts" => "hullparts",
          "claytronics" => "claytronics",
          "quantumtubes" => "quantumtubes",
          "antimattercells" => "antimattercells",
          "superfluidcoolant" => "superfluidcoolant",
          "water" => "water",
          _ => null,
      };

    private static string ResolveProducerModuleId(
      string wareId,
      string? factionId,
      IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions) =>
      SelectPreferredModule(
        moduleDefinitions.Values
          .OfType<ProductionModuleDefinition>()
          .Where(definition => definition.ProductItemIds.Contains(wareId, StringComparer.Ordinal)),
        factionId,
        $"producer module for ware '{wareId}'").Id;

    private static string ResolveStorageModuleId(
      StorageKind storageKind,
      string? factionId,
      IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions) =>
      SelectPreferredModule(
        moduleDefinitions.Values
          .OfType<StorageModuleDefinition>()
          .Where(definition => definition.StorageKind == storageKind),
        factionId,
        $"storage module for cargo kind '{storageKind.ToDataValue()}'").Id;

    private static T SelectPreferredModule<T>(
      IEnumerable<T> candidates,
      string? factionId,
      string context)
      where T : ModuleDefinition
    {
        var ordered = candidates
          .OrderBy(definition => ComputeOwnerRank(definition, factionId))
          .ThenBy(definition => ComputeModuleRank(definition))
          .ThenBy(definition => definition.Id, StringComparer.Ordinal)
          .ToList();

        return ordered.FirstOrDefault()
          ?? throw new InvalidOperationException($"Unable to resolve {context}.");
    }

    private static int ComputeOwnerRank(ModuleDefinition definition, string? factionId)
    {
        if (string.IsNullOrWhiteSpace(factionId))
        {
            return 1;
        }

        return definition.Owners.Contains(factionId, StringComparer.Ordinal) ? 0 : 1;
    }

    private static int ComputeModuleRank(ModuleDefinition definition)
    {
        if (definition.ModuleType is ModuleType.DockArea or ModuleType.Storage)
        {
            if (definition.Id.Contains("_m_", StringComparison.Ordinal))
            {
                return 0;
            }

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

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

        return 3;
    }
}
