using Godot;
using System;
using System.Collections.Generic;
using Theriapolis.Core.Tactical;
using Theriapolis.GodotHost.Platform;

namespace Theriapolis.GodotHost.Rendering;

/// <summary>
/// Loads and caches PNG textures for every TacticalSurface and TacticalDeco
/// value, with optional per-variant alternates (name_0.png, name_1.png, ...).
/// Falls back to a procedurally generated solid-colour image when no art is
/// on disk so the renderer always has something to draw.
///
/// Mirrors Theriapolis.Game/Rendering/TacticalAtlas.cs lookup contract.
/// PNGs are loaded via ContentLoader, which caches at the file level — this
/// class just adds the per-enum dispatch and variant arrays.
/// </summary>
public static class TacticalAtlas
{
    private static readonly Dictionary<TacticalSurface, Texture2D[]> _surfaces = new();
    private static readonly Dictionary<TacticalDeco, Texture2D[]> _decos = new();
    private static bool _loaded;

    public static void EnsureLoaded()
    {
        if (_loaded) return;
        _loaded = true;

        foreach (TacticalSurface s in Enum.GetValues<TacticalSurface>())
            _surfaces[s] = LoadVariants("surface", s.ToString().ToLowerInvariant(), () => SurfacePlaceholder(s));

        foreach (TacticalDeco d in Enum.GetValues<TacticalDeco>())
        {
            if (d == TacticalDeco.None) continue;
            _decos[d] = LoadVariants("deco", d.ToString().ToLowerInvariant(), () => DecoPlaceholder(d));
        }
    }

    public static Texture2D GetSurface(TacticalSurface s, byte variant)
    {
        EnsureLoaded();
        if (!_surfaces.TryGetValue(s, out var arr) || arr.Length == 0)
            arr = _surfaces[TacticalSurface.None];
        return arr[variant % arr.Length];
    }

    public static Texture2D? GetDeco(TacticalDeco d, byte variant)
    {
        EnsureLoaded();
        if (d == TacticalDeco.None) return null;
        if (!_decos.TryGetValue(d, out var arr) || arr.Length == 0) return null;
        return arr[variant % arr.Length];
    }

    private static Texture2D[] LoadVariants(string subdir, string name, Func<Texture2D> placeholder)
    {
        var found = new List<Texture2D>();

        // Variant suffix files first: name_0.png, name_1.png, ...
        for (int i = 0; ; i++)
        {
            var tex = ContentLoader.LoadGfx($"tactical/{subdir}/{name}_{i}.png");
            if (tex is null) break;
            found.Add(tex);
        }
        // Fallback to a single name.png if no _N variants exist.
        if (found.Count == 0)
        {
            var tex = ContentLoader.LoadGfx($"tactical/{subdir}/{name}.png");
            if (tex is not null) found.Add(tex);
        }

        if (found.Count == 0) found.Add(placeholder());
        return found.ToArray();
    }

    private static Texture2D SurfacePlaceholder(TacticalSurface s) =>
        SolidTexture(SurfaceColor(s), Theriapolis.Core.C.TACTICAL_TILE_SPRITE_PX);

    private static Texture2D DecoPlaceholder(TacticalDeco d)
    {
        var (color, fillFraction) = DecoStyle(d);
        int px = Theriapolis.Core.C.TACTICAL_TILE_SPRITE_PX;
        var image = Image.CreateEmpty(px, px, false, Image.Format.Rgba8);
        float cx = (px - 1) * 0.5f;
        float r = px * 0.5f * fillFraction;
        for (int y = 0; y < px; y++)
        {
            for (int x = 0; x < px; x++)
            {
                float dx = x - cx, dy = y - cx;
                image.SetPixel(x, y, (dx * dx + dy * dy) <= r * r ? color : new Color(0, 0, 0, 0));
            }
        }
        return ImageTexture.CreateFromImage(image);
    }

    private static Texture2D SolidTexture(Color c, int size)
    {
        var image = Image.CreateEmpty(size, size, false, Image.Format.Rgba8);
        image.Fill(c);
        return ImageTexture.CreateFromImage(image);
    }

    // Mirrors Theriapolis.Game/Rendering/TacticalAtlas.SurfaceColor for placeholder parity.
    private static Color SurfaceColor(TacticalSurface s) => s switch
    {
        TacticalSurface.DeepWater    => ColorByte(20, 60, 130),
        TacticalSurface.ShallowWater => ColorByte(60, 120, 180),
        TacticalSurface.Marsh        => ColorByte(70, 100, 80),
        TacticalSurface.Mud          => ColorByte(100, 80, 60),
        TacticalSurface.Sand         => ColorByte(220, 200, 150),
        TacticalSurface.Snow         => ColorByte(230, 235, 240),
        TacticalSurface.Rock         => ColorByte(120, 115, 110),
        TacticalSurface.Cobble       => ColorByte(170, 150, 120),
        TacticalSurface.Gravel       => ColorByte(150, 140, 110),
        TacticalSurface.Wall         => ColorByte(60, 55, 50),
        TacticalSurface.Floor        => ColorByte(180, 160, 130),
        TacticalSurface.Dirt         => ColorByte(120, 95, 60),
        TacticalSurface.TroddenDirt  => ColorByte(140, 110, 70),
        TacticalSurface.TallGrass    => ColorByte(80, 140, 60),
        TacticalSurface.Grass        => ColorByte(110, 160, 70),
        TacticalSurface.DungeonFloor => ColorByte(110, 90, 70),
        TacticalSurface.DungeonRubble=> ColorByte(80, 70, 60),
        TacticalSurface.DungeonTile  => ColorByte(140, 130, 110),
        TacticalSurface.Cave         => ColorByte(70, 60, 55),
        TacticalSurface.MineFloor    => ColorByte(95, 85, 70),
        _                             => ColorByte(255, 0, 255),
    };

    private static (Color color, float fillFraction) DecoStyle(TacticalDeco d) => d switch
    {
        TacticalDeco.Tree    => (ColorByte(20, 80, 30), 0.85f),
        TacticalDeco.Bush    => (ColorByte(70, 110, 50), 0.55f),
        TacticalDeco.Boulder => (ColorByte(110, 100, 90), 0.65f),
        TacticalDeco.Rock    => (ColorByte(140, 130, 110), 0.35f),
        TacticalDeco.Flower  => (ColorByte(220, 180, 210), 0.25f),
        TacticalDeco.Crop    => (ColorByte(180, 160, 60), 0.40f),
        TacticalDeco.Reed    => (ColorByte(120, 140, 60), 0.40f),
        TacticalDeco.Snag    => (ColorByte(80, 60, 40), 0.45f),
        _                     => (ColorByte(255, 0, 255), 0.5f),
    };

    private static Color ColorByte(byte r, byte g, byte b) => new(r / 255f, g / 255f, b / 255f);
}
