
using SpaceGame.Api.Shared.Runtime;

namespace SpaceGame.Api.Universe.Scenario;

internal static class LoaderSupport
{
    internal const float MinimumFactionCredits = 0f;
    internal const float MinimumRefineryOre = 0f;
    internal const float MinimumRefineryStock = 0f;
    internal const float MinimumShipyardStock = 0f;
    internal const float MinimumSystemSeparation = 3.2f;
    internal const float LocalSpaceRadius = 10_000f;

    internal static readonly string[] GeneratedSystemNames =
    [
      "Aquila Verge",
    "Orion Fold",
    "Draco Span",
    "Lyra Shoal",
    "Cygnus March",
    "Vela Crossing",
    "Carina Wake",
    "Phoenix Rest",
    "Hydra Loom",
    "Cassio Reach",
    "Lupus Chain",
    "Pavo Line",
    "Serpens Rise",
    "Cetus Hollow",
    "Delphin Crown",
    "Volans Drift",
    "Ara Bastion",
    "Indus Veil",
    "Pyxis Trace",
    "Lacerta Bloom",
    "Columba Shroud",
    "Dorado Expanse",
    "Reticulum Run",
    "Norma Edge",
    "Crux Horizon",
    "Sagitta Corridor",
    "Monoceros Deep",
    "Eridan Spur",
    "Centauri Shelf",
    "Antlia Reach",
    "Horologium Gate",
    "Telescopium Strand",
  ];

    internal static readonly StarProfile[] StarProfiles =
    [
      new("main-sequence", "#ffd27a", "#ffb14a", 696340f),
    new("blue-white", "#9dc6ff", "#66a0ff", 930000f),
    new("white-dwarf", "#f1f5ff", "#b8caff", 12000f),
    new("brown-dwarf", "#b97d56", "#8a5438", 70000f),
    new("neutron-star", "#d9ebff", "#7ab4ff", 18f),
    new("binary-main-sequence", "#ffe09f", "#ffbe6b", 780000f),
    new("binary-white-dwarf", "#edf3ff", "#c8d6ff", 14000f),
  ];

    internal static readonly PlanetProfile[] PlanetProfiles =
    [
      new("barren", "sphere", "#bca48f", 2800f, 0.22f, 0, false),
    new("terrestrial", "sphere", "#58a36c", 6400f, 0.28f, 1, false),
    new("oceanic", "sphere", "#4f84c4", 7000f, 0.30f, 2, false),
    new("desert", "sphere", "#d4a373", 5200f, 0.26f, 0, false),
    new("ice", "sphere", "#c8e4ff", 5800f, 0.32f, 1, false),
    new("gas-giant", "oblate", "#d9b06f", 45000f, 1.40f, 8, true),
    new("ice-giant", "oblate", "#8fc0d8", 25000f, 1.00f, 5, true),
    new("lava", "sphere", "#db6846", 3200f, 0.20f, 0, false),
  ];

    internal static Vector3 ToVector(float[] values) => new(values[0], values[1], values[2]);

    internal static Vector3 NormalizeScenarioPoint(SystemRuntime system, float[] values)
    {
        var raw = ToVector(values);
        var relativeToSystem = new Vector3(
          raw.X - system.Position.X,
          raw.Y - system.Position.Y,
          raw.Z - system.Position.Z);

        return relativeToSystem.LengthSquared() < raw.LengthSquared()
          ? relativeToSystem
          : raw;
    }

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

    internal static void AddStationModule(StationRuntime station, IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions, string moduleId)
    {
        if (!moduleDefinitions.TryGetValue(moduleId, out var definition))
        {
            throw new InvalidOperationException($"Module '{moduleId}' is not defined in static data.");
        }

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

    internal static float GetStationRadius(IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions, StationRuntime station)
    {
        var totalArea = station.Modules
          .Select(module => 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 Vector3 Add(Vector3 left, Vector3 right) => new(left.X + right.X, left.Y + right.Y, left.Z + right.Z);

    internal static Vector3 Scale(Vector3 vector, float scale) => new(vector.X * scale, vector.Y * scale, vector.Z * scale);

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

    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 float GetInventoryAmount(IReadOnlyDictionary<string, float> inventory, string itemId) =>
      inventory.TryGetValue(itemId, out var amount) ? amount : 0f;

    internal static float DegreesToRadians(float degrees) => degrees * (MathF.PI / 180f);

    internal static Vector3 NormalizeOrFallback(Vector3 vector, Vector3 fallback)
    {
        var length = MathF.Sqrt(vector.LengthSquared());
        if (length <= 0.0001f)
        {
            return fallback;
        }

        return vector.Divide(length);
    }
}

internal sealed record StarProfile(
  string Kind,
  string StarColor,
  string StarGlow,
  float BaseSize);

internal sealed record PlanetProfile(
  string Type,
  string Shape,
  string Color,
  float BaseSize,
  float OrbitGapMin,
  int BaseMoonCount,
  bool CanHaveRing)
{
    public float OrbitGapMax => OrbitGapMin + MathF.Max(0.12f, OrbitGapMin * 0.45f);
}
