using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using Theriapolis.Core;
using Theriapolis.Core.Data;
using Theriapolis.Core.World;
using Theriapolis.Core.World.Generation;
using Theriapolis.Core.World.Generation.Stages;
using Theriapolis.Core.World.Polylines;

namespace Theriapolis.Tools.Commands;

/// <summary>
/// worldgen-dump --seed &lt;n&gt; --out &lt;file.png&gt; [--data-dir &lt;dir&gt;]
///
/// Runs the full pipeline headless and exports a PNG of the biome map overlaid
/// with rivers (blue), roads (tan), rail (dark), and settlement icons.
/// </summary>
public static class WorldgenDump
{
    public static int Run(string[] args)
    {
        ulong seed     = 12345;
        string outPath = "world.png";
        string dataDir = ResolveDataDir();
        bool showViolations = false;

        for (int i = 0; i < args.Length; i++)
        {
            switch (args[i].ToLowerInvariant())
            {
                case "--seed":
                    if (i + 1 < args.Length)
                    {
                        string raw = args[++i];
                        if (raw.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
                            seed = Convert.ToUInt64(raw[2..], 16);
                        else
                            seed = ulong.Parse(raw);
                    }
                    break;
                case "--out":
                    if (i + 1 < args.Length) outPath = args[++i];
                    break;
                case "--data-dir":
                    if (i + 1 < args.Length) dataDir = args[++i];
                    break;
                case "--show-violations":
                    showViolations = true;
                    break;
            }
        }

        Console.WriteLine($"[worldgen-dump] seed=0x{seed:X} out={outPath} data-dir={dataDir}");

        if (!Directory.Exists(dataDir))
        {
            Console.Error.WriteLine($"Data directory not found: {dataDir}");
            return 1;
        }

        var ctx = new WorldGenContext(seed, dataDir)
        {
            ProgressCallback = (name, frac) =>
                Console.Write($"\r  {name,-28} {frac * 100f:F0}%   "),
            Log = msg => Console.WriteLine(msg),
        };

        WorldGenerator.RunAll(ctx);
        Console.WriteLine();

        // Collect biome colours from the loaded BiomeDef array
        var colorMap = BuildColorMap(ctx.World.BiomeDefs!);

        int W = C.WORLD_WIDTH_TILES;
        int H = C.WORLD_HEIGHT_TILES;
        using var image = new Image<Rgb24>(W, H);

        // ── 1. Biome base layer ───────────────────────────────────────────────
        for (int ty = 0; ty < H; ty++)
        for (int tx = 0; tx < W; tx++)
        {
            var biome = ctx.World.Tiles[tx, ty].Biome;
            if (!colorMap.TryGetValue(biome, out var px))
                px = new Rgb24(255, 0, 255);
            image[tx, ty] = px;
        }

        if (showViolations)
            OverlayViolations(image, ctx, W, H);

        // ── 2. Roads ─────────────────────────────────────────────────────────
        foreach (var road in ctx.World.Roads)
        {
            var color = road.RoadClassification switch
            {
                RoadType.Highway  => new Rgb24(210, 180, 80),
                RoadType.PostRoad => new Rgb24(180, 155, 70),
                _                 => new Rgb24(150, 130, 90),
            };
            DrawPolyline(image, road, color, W, H);
        }

        // ── 3. Rivers ─────────────────────────────────────────────────────────
        foreach (var river in ctx.World.Rivers)
        {
            var color = river.RiverClassification switch
            {
                RiverClass.MajorRiver => new Rgb24(40, 100, 200),
                RiverClass.River      => new Rgb24(60, 120, 200),
                _                     => new Rgb24(100, 150, 220),
            };
            DrawPolyline(image, river, color, W, H);
        }

        // ── 4. Rail ───────────────────────────────────────────────────────────
        var railColor = new Rgb24(80, 65, 50);
        foreach (var rail in ctx.World.Rails)
            DrawPolyline(image, rail, railColor, W, H);

        // ── 4b. Bridges ──────────────────────────────────────────────────────
        var bridgeColor = new Rgb24(160, 140, 100);
        foreach (var bridge in ctx.World.Bridges)
        {
            int bx = (int)(bridge.WorldPixelX / C.WORLD_TILE_PIXELS);
            int by = (int)(bridge.WorldPixelY / C.WORLD_TILE_PIXELS);
            // Draw a small cross at the bridge location
            for (int d = -1; d <= 1; d++)
            {
                int px = bx + d, py = by;
                if ((uint)px < (uint)W && (uint)py < (uint)H) image[px, py] = bridgeColor;
                px = bx; py = by + d;
                if ((uint)px < (uint)W && (uint)py < (uint)H) image[px, py] = bridgeColor;
            }
        }

        // ── 5. Settlements ────────────────────────────────────────────────────
        foreach (var s in ctx.World.Settlements)
        {
            var (color, radius) = s.Tier switch
            {
                1 => (new Rgb24(255, 215, 0),   4),  // gold, capital
                2 => (new Rgb24(230, 230, 230),  3),  // white, city
                3 => (new Rgb24(150, 200, 255),  2),  // blue, town
                4 => (new Rgb24(200, 200, 200),  1),  // grey, village
                _ => (new Rgb24(200, 60, 60),    1),  // red, PoI
            };
            DrawDot(image, s.TileX, s.TileY, color, radius, W, H);
        }

        // ── 6. Biome coverage stats ────────────────────────────────────────────
        PrintBiomeCoverage(ctx, W, H);

        // ── 7. Settlement summary ─────────────────────────────────────────────
        PrintSettlementSummary(ctx);

        image.Save(outPath);
        Console.WriteLine($"[worldgen-dump] Saved {outPath} ({W}×{H} px)");
        return 0;
    }

    private static void DrawPolyline(Image<Rgb24> img, Polyline poly, Rgb24 color, int W, int H)
    {
        var pts = poly.Points;
        if (pts.Count < 2) return;
        for (int i = 0; i < pts.Count - 1; i++)
        {
            int x0 = (int)(pts[i].X     / C.WORLD_TILE_PIXELS);
            int y0 = (int)(pts[i].Y     / C.WORLD_TILE_PIXELS);
            int x1 = (int)(pts[i + 1].X / C.WORLD_TILE_PIXELS);
            int y1 = (int)(pts[i + 1].Y / C.WORLD_TILE_PIXELS);
            BresenhamLine(img, x0, y0, x1, y1, color, W, H);
        }
    }

    private static void BresenhamLine(Image<Rgb24> img, int x0, int y0, int x1, int y1, Rgb24 color, int W, int H)
    {
        int dx = Math.Abs(x1 - x0), dy = Math.Abs(y1 - y0);
        int sx = x0 < x1 ? 1 : -1, sy = y0 < y1 ? 1 : -1;
        int err = dx - dy;
        while (true)
        {
            if ((uint)x0 < (uint)W && (uint)y0 < (uint)H)
                img[x0, y0] = color;
            if (x0 == x1 && y0 == y1) break;
            int e2 = 2 * err;
            if (e2 > -dy) { err -= dy; x0 += sx; }
            if (e2 <  dx) { err += dx; y0 += sy; }
        }
    }

    private static void DrawDot(Image<Rgb24> img, int cx, int cy, Rgb24 color, int radius, int W, int H)
    {
        for (int dy = -radius; dy <= radius; dy++)
        for (int dx = -radius; dx <= radius; dx++)
        {
            if (dx * dx + dy * dy > radius * radius) continue;
            int nx = cx + dx, ny = cy + dy;
            if ((uint)nx < (uint)W && (uint)ny < (uint)H)
                img[nx, ny] = color;
        }
    }

    private static void OverlayViolations(Image<Rgb24> image, WorldGenContext ctx, int W, int H)
    {
        var viols  = BorderDistortionGenStage.FindStraightViolations(ctx);
        var byLen  = viols.OrderByDescending(v => v.len).ToList();
        Console.WriteLine($"[worldgen-dump] Total straight-run violations: {viols.Count}");

        var buckets = new (int min, int max, int count)[]
        {
            (6,   7, 0), (8,   9, 0), (10, 11, 0), (12, 14, 0),
            (15, 19, 0), (20, 29, 0), (30, 49, 0), (50, 99, 0),
        };
        foreach (var v in viols)
        {
            for (int i = 0; i < buckets.Length; i++)
            {
                if (v.len >= buckets[i].min && v.len <= buckets[i].max)
                {
                    buckets[i].count++;
                    break;
                }
            }
        }
        Console.WriteLine($"[worldgen-dump] Run length distribution:");
        foreach (var b in buckets)
            Console.WriteLine($"    len {b.min,2}-{b.max,2}: {b.count,5}");

        Console.WriteLine($"[worldgen-dump] Top 25 longest runs:");
        for (int i = 0; i < Math.Min(25, byLen.Count); i++)
        {
            var v = byLen[i];
            string orient = (v.dx, v.dy) switch
            {
                (1, 0)  => "→",
                (0, 1)  => "↓",
                (1, 1)  => "↘",
                (1,-1)  => "↗",
                _       => "?",
            };
            Console.WriteLine($"  #{i+1,2}  ({v.x,4},{v.y,4}) {orient} len={v.len}");
        }

        var red = new Rgb24(255, 0, 0);
        int overlayCount = Math.Min(50, byLen.Count);
        for (int i = 0; i < overlayCount; i++)
        {
            var v = byLen[i];
            int px = v.x, py = v.y;
            for (int step = 0; step < v.len; step++)
            {
                if ((uint)px < (uint)W && (uint)py < (uint)H)
                    image[px, py] = red;
                px += v.dx;
                py += v.dy;
            }
        }
    }

    private static void PrintBiomeCoverage(WorldGenContext ctx, int W, int H)
    {
        var biomeCounts = new Dictionary<BiomeId, int>();
        int total = W * H;
        for (int ty = 0; ty < H; ty++)
        for (int tx = 0; tx < W; tx++)
        {
            var b = ctx.World.Tiles[tx, ty].Biome;
            biomeCounts.TryGetValue(b, out int c);
            biomeCounts[b] = c + 1;
        }
        int oceanCount = biomeCounts.GetValueOrDefault(BiomeId.Ocean);
        int landTotal = total - oceanCount;
        Console.WriteLine($"[worldgen-dump] Biome coverage: ocean={100.0 * oceanCount / total:F1}%, land tiles={landTotal}");
        foreach (var kv in biomeCounts.OrderByDescending(kv => kv.Value))
        {
            if (kv.Key == BiomeId.Ocean) continue;
            double pct = landTotal > 0 ? 100.0 * kv.Value / landTotal : 0;
            Console.WriteLine($"  {kv.Key,-22} {pct,6:F2}% ({kv.Value} tiles)");
        }
    }

    private static void PrintSettlementSummary(WorldGenContext ctx)
    {
        var ss = ctx.World.Settlements;
        if (ss.Count == 0) return;
        Console.WriteLine($"[worldgen-dump] Settlements: {ss.Count} total");
        for (int tier = 1; tier <= 4; tier++)
        {
            var ts = ss.Where(s => s.Tier == tier && !s.IsPoi).ToList();
            Console.WriteLine($"  Tier {tier}: {ts.Count}");
            foreach (var s in ts)
                Console.WriteLine($"    [{s.TileX,4},{s.TileY,4}] {s.Name,-24} {s.Economy,-14} wealth={s.WealthLevel:F2}");
        }
        int poiCount = ss.Count(s => s.IsPoi);
        Console.WriteLine($"  PoIs: {poiCount}");
        Console.WriteLine($"[worldgen-dump] Rivers: {ctx.World.Rivers.Count}, Roads: {ctx.World.Roads.Count}, Rails: {ctx.World.Rails.Count}, Bridges: {ctx.World.Bridges.Count}");
    }

    private static Dictionary<BiomeId, Rgb24> BuildColorMap(BiomeDef[] defs)
    {
        var map = new Dictionary<BiomeId, Rgb24>();
        foreach (var def in defs)
        {
            var biomeId = ParseBiomeId(def.Id);
            var (r, g, b) = def.ParsedColor();
            map[biomeId] = new Rgb24(r, g, b);
        }
        return map;
    }

    private static BiomeId ParseBiomeId(string id) => id.ToLowerInvariant() switch
    {
        "ocean"               => BiomeId.Ocean,
        "tundra"              => BiomeId.Tundra,
        "boreal"              => BiomeId.Boreal,
        "temperate_deciduous" => BiomeId.TemperateDeciduous,
        "temperate_grassland" => BiomeId.TemperateGrassland,
        "mountain_alpine"     => BiomeId.MountainAlpine,
        "mountain_forested"   => BiomeId.MountainForested,
        "subtropical_forest"  => BiomeId.SubtropicalForest,
        "wetland"             => BiomeId.Wetland,
        "coastal"             => BiomeId.Coastal,
        "river_valley"        => BiomeId.RiverValley,
        "scrubland"           => BiomeId.Scrubland,
        "desert_cold"         => BiomeId.DesertCold,
        "forest_edge"         => BiomeId.ForestEdge,
        "foothills"           => BiomeId.Foothills,
        "marsh_edge"          => BiomeId.MarshEdge,
        "beach"               => BiomeId.Beach,
        "cliff"               => BiomeId.Cliff,
        "tidal_flat"          => BiomeId.TidalFlat,
        "mangrove"            => BiomeId.Mangrove,
        _                     => BiomeId.TemperateGrassland,
    };

    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;
    }
}
