using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Theriapolis.Core;
using Theriapolis.Core.Tactical;
using Theriapolis.Core.World.Generation;

namespace Theriapolis.Tools.Commands;

/// <summary>
/// tactical-dump --seed &lt;n&gt; --chunk cx,cy --out &lt;file.png&gt; [--data-dir &lt;dir&gt;]
///
/// Runs the full pipeline, then generates a single tactical chunk and exports
/// it as a PNG. Used during M2 to eyeball biome ground variants, polyline
/// burn-in, and settlement footprints without running the game.
///
/// Optional --grid 3 — render a 3x3 set of chunks centred on (cx, cy) and stitch
/// them so chunk-boundary continuity is also visible.
/// </summary>
public static class TacticalDump
{
    public static int Run(string[] args)
    {
        ulong seed = 12345;
        int   cx   = 0,  cy = 0;
        int   grid = 1;
        string outPath = "tactical.png";
        string dataDir = ResolveDataDir();

        for (int i = 0; i < args.Length; i++)
        {
            switch (args[i].ToLowerInvariant())
            {
                case "--seed":
                    if (i + 1 < args.Length)
                    {
                        string raw = args[++i];
                        seed = raw.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
                            ? Convert.ToUInt64(raw[2..], 16)
                            : ulong.Parse(raw);
                    }
                    break;
                case "--chunk":
                    if (i + 1 < args.Length)
                    {
                        var parts = args[++i].Split(',');
                        cx = int.Parse(parts[0]);
                        cy = int.Parse(parts[1]);
                    }
                    break;
                case "--grid":
                    if (i + 1 < args.Length) grid = int.Parse(args[++i]);
                    break;
                case "--out":
                    if (i + 1 < args.Length) outPath = args[++i];
                    break;
                case "--data-dir":
                    if (i + 1 < args.Length) dataDir = args[++i];
                    break;
            }
        }

        Console.WriteLine($"[tactical-dump] seed=0x{seed:X} chunk=({cx},{cy}) grid={grid} out={outPath}");
        if (!Directory.Exists(dataDir))
        {
            Console.Error.WriteLine($"Data directory not found: {dataDir}");
            return 1;
        }

        var ctx = new WorldGenContext(seed, dataDir)
        {
            Log = msg => Console.WriteLine(msg),
        };
        WorldGenerator.RunAll(ctx);

        int side = C.TACTICAL_CHUNK_SIZE * grid;
        using var img = new Image<Rgba32>(side, side);
        for (int gy = 0; gy < grid; gy++)
        for (int gx = 0; gx < grid; gx++)
        {
            int ccx = cx + gx - grid / 2;
            int ccy = cy + gy - grid / 2;
            var chunk = TacticalChunkGen.Generate(seed, new ChunkCoord(ccx, ccy), ctx.World);
            int ox = gx * C.TACTICAL_CHUNK_SIZE;
            int oy = gy * C.TACTICAL_CHUNK_SIZE;
            BlitChunk(img, chunk, ox, oy);
        }

        img.SaveAsPng(outPath);
        Console.WriteLine($"[tactical-dump] wrote {outPath} ({side}x{side})");
        return 0;
    }

    private static void BlitChunk(Image<Rgba32> img, TacticalChunk chunk, int ox, int oy)
    {
        for (int ly = 0; ly < C.TACTICAL_CHUNK_SIZE; ly++)
        for (int lx = 0; lx < C.TACTICAL_CHUNK_SIZE; lx++)
        {
            ref var t = ref chunk.Tiles[lx, ly];
            img[ox + lx, oy + ly] = ColorFor(t);
        }
    }

    private static Rgba32 ColorFor(TacticalTile t)
    {
        // Decoration overrides surface for visual punch.
        if (t.Deco == TacticalDeco.Tree)    return new Rgba32(20,  80, 30);
        if (t.Deco == TacticalDeco.Bush)    return new Rgba32(70, 110, 50);
        if (t.Deco == TacticalDeco.Boulder) return new Rgba32(110,100, 90);
        if (t.Deco == TacticalDeco.Rock)    return new Rgba32(140,130,110);
        if (t.Deco == TacticalDeco.Flower)  return new Rgba32(220,180,210);

        return t.Surface switch
        {
            TacticalSurface.DeepWater    => new Rgba32(20,  60, 130),
            TacticalSurface.ShallowWater => new Rgba32(60,  120, 180),
            TacticalSurface.Marsh        => new Rgba32(70,  100, 80),
            TacticalSurface.Mud          => new Rgba32(100, 80,  60),
            TacticalSurface.Sand         => new Rgba32(220, 200, 150),
            TacticalSurface.Snow         => new Rgba32(230, 235, 240),
            TacticalSurface.Rock         => new Rgba32(120, 115, 110),
            TacticalSurface.Cobble       => new Rgba32(170, 150, 120),
            TacticalSurface.Gravel       => new Rgba32(150, 140, 110),
            TacticalSurface.Wall         => new Rgba32(60,  55,  50),
            TacticalSurface.Floor        => new Rgba32(180, 160, 130),
            TacticalSurface.Dirt         => new Rgba32(120, 95,  60),
            TacticalSurface.TallGrass    => new Rgba32(80,  140, 60),
            TacticalSurface.Grass        => new Rgba32(110, 160, 70),
            _                             => new Rgba32(255, 0,   255),
        };
    }

    private static string ResolveDataDir()
    {
        string local = Path.Combine(AppContext.BaseDirectory, "Data");
        if (Directory.Exists(local)) return local;
        string? dir = AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
        for (int i = 0; i < 6; i++)
        {
            if (dir is null) break;
            string candidate = Path.Combine(dir, "Content", "Data");
            if (Directory.Exists(candidate)) return candidate;
            dir = Path.GetDirectoryName(dir);
        }
        return local;
    }
}
