namespace SpaceGame.Api.Industry.Planning;

internal static class ProductionGraphBuilder
{
    internal static ProductionGraph Build(
      IReadOnlyCollection<ItemDefinition> items,
      IReadOnlyCollection<RecipeDefinition> recipes,
      IReadOnlyCollection<ModuleDefinition> modules)
    {
        var commodities = items.ToDictionary(
          item => item.Id,
          item => new ProductionCommodityNode
          {
              ItemId = item.Id,
              Name = item.Name,
              Group = item.Group,
              CargoKind = item.CargoKind?.ToDataValue() ?? string.Empty,
          },
          StringComparer.Ordinal);

        var processes = new Dictionary<string, ProductionProcessNode>(StringComparer.Ordinal);
        var processesByOutputId = new Dictionary<string, List<ProductionProcessNode>>(StringComparer.Ordinal);
        var processesByInputId = new Dictionary<string, List<ProductionProcessNode>>(StringComparer.Ordinal);
        var outputsByModuleId = new Dictionary<string, HashSet<string>>(StringComparer.Ordinal);

        foreach (var recipe in recipes)
        {
            var outputs = recipe.Outputs
              .GroupBy(output => output.ItemId, StringComparer.Ordinal)
              .ToDictionary(group => group.Key, group => group.Sum(output => output.Amount), StringComparer.Ordinal);
            var inputs = recipe.Inputs
              .GroupBy(input => input.ItemId, StringComparer.Ordinal)
              .ToDictionary(group => group.Key, group => group.Sum(input => input.Amount), StringComparer.Ordinal);
            var process = new ProductionProcessNode
            {
                Id = recipe.Id,
                Label = recipe.Label,
                FacilityCategory = recipe.FacilityCategory,
                RequiredModuleIds = recipe.RequiredModules.ToList(),
                Inputs = inputs,
                Outputs = outputs,
                ProducesShip = recipe.ShipOutputId is not null,
            };

            processes[process.Id] = process;

            foreach (var output in outputs.Keys)
            {
                if (!commodities.ContainsKey(output))
                {
                    continue;
                }

                commodities[output].ProducerProcessIds.Add(process.Id);
                if (!processesByOutputId.TryGetValue(output, out var outputProcesses))
                {
                    outputProcesses = [];
                    processesByOutputId[output] = outputProcesses;
                }

                outputProcesses.Add(process);
            }

            foreach (var input in inputs.Keys)
            {
                if (!commodities.ContainsKey(input))
                {
                    continue;
                }

                commodities[input].ConsumerProcessIds.Add(process.Id);
                if (!processesByInputId.TryGetValue(input, out var inputProcesses))
                {
                    inputProcesses = [];
                    processesByInputId[input] = inputProcesses;
                }

                inputProcesses.Add(process);
            }
        }

        foreach (var module in modules)
        {
            if (!outputsByModuleId.TryGetValue(module.Id, out var outputs))
            {
                outputs = new HashSet<string>(StringComparer.Ordinal);
                outputsByModuleId[module.Id] = outputs;
            }

            foreach (var product in module.ProductItemIds)
            {
                outputs.Add(product);
            }
        }

        return new ProductionGraph
        {
            Commodities = commodities,
            Processes = processes,
            ProcessesByOutputId = processesByOutputId.ToDictionary(entry => entry.Key, entry => (IReadOnlyList<ProductionProcessNode>)entry.Value, StringComparer.Ordinal),
            ProcessesByInputId = processesByInputId.ToDictionary(entry => entry.Key, entry => (IReadOnlyList<ProductionProcessNode>)entry.Value, StringComparer.Ordinal),
            OutputsByModuleId = outputsByModuleId.ToDictionary(entry => entry.Key, entry => (IReadOnlyList<string>)entry.Value.OrderBy(value => value, StringComparer.Ordinal).ToList(), StringComparer.Ordinal),
        };
    }
}
