using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using Theriapolis.Core.Data;
using Theriapolis.Core.Dungeons;
using Theriapolis.Core.Tactical;
using Theriapolis.Core.World;

namespace Theriapolis.Tools.Commands;

/// <summary>
/// Phase 7 M0 / M1 — render a room template OR a fully-generated dungeon
/// to a PNG. Two modes:
///
///   Template mode (M0):
///     dotnet run --project Theriapolis.Tools -- dungeon-render \
///       --template imperium.entry_grand_hall --out hall.png \
///       [--data-dir ./Content/Data] [--cell 16]
///
///   Pipeline mode (M1):
///     dotnet run --project Theriapolis.Tools -- dungeon-render \
///       --seed 12345 --poi 42 --type ImperiumRuin --out d.png \
///       [--data-dir ./Content/Data] [--cell 8]
///
/// In pipeline mode, rooms are colour-tinted by role (entry blue, boss
/// red, narrative gold, dead-end grey) so designers can visually verify
/// generator output across seeds.
/// </summary>
public static class DungeonRender
{
    public static int Run(string[] args)
    {
        string templateId = "";
        string outPath    = "room.png";
        string dataDir    = "./Content/Data";
        int    cellPx     = 16;
        ulong? seed       = null;
        int?   poiId      = null;
        string typeName   = "ImperiumRuin";

        for (int i = 0; i < args.Length; i++)
        {
            switch (args[i].ToLowerInvariant())
            {
                case "--template":  if (i + 1 < args.Length) templateId = 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;
                case "--cell":      if (i + 1 < args.Length) cellPx     = int.Parse(args[++i]); break;
                case "--seed":      if (i + 1 < args.Length) seed       = ParseUlong(args[++i]); break;
                case "--poi":       if (i + 1 < args.Length) poiId      = int.Parse(args[++i]); break;
                case "--type":      if (i + 1 < args.Length) typeName   = args[++i]; break;
            }
        }

        // Pipeline mode takes precedence when seed + poi are supplied.
        if (seed is not null && poiId is not null)
            return RenderPipeline(seed.Value, poiId.Value, typeName, outPath, dataDir, cellPx);

        if (string.IsNullOrEmpty(templateId))
        {
            Console.Error.WriteLine("Either --template <id> OR --seed N --poi N required.");
            return 1;
        }

        var loader  = new ContentLoader(dataDir);
        var rooms   = loader.LoadRoomTemplates();
        var def     = Array.Find(rooms, r => string.Equals(r.Id, templateId, StringComparison.OrdinalIgnoreCase));
        if (def is null)
        {
            Console.Error.WriteLine($"Room template '{templateId}' not found.");
            Console.Error.WriteLine("Available:");
            foreach (var r in rooms.OrderBy(r => r.Id, StringComparer.Ordinal))
                Console.Error.WriteLine($"  {r.Id}");
            return 1;
        }

        int w = def.FootprintWTiles * cellPx;
        int h = def.FootprintHTiles * cellPx;
        using var img = new Image<Rgba32>(w, h);

        for (int ty = 0; ty < def.FootprintHTiles; ty++)
        for (int tx = 0; tx < def.FootprintWTiles; tx++)
        {
            char ch = def.Grid[ty][tx];
            Rgba32 fill = ColorForChar(ch);
            for (int py = 0; py < cellPx; py++)
            for (int px = 0; px < cellPx; px++)
                img[tx * cellPx + px, ty * cellPx + py] = fill;
        }

        // Thin grid lines so the ASCII grid is legible.
        var gridColor = new Rgba32(60, 60, 60, 255);
        for (int x = 0; x < w; x += cellPx)
            for (int y = 0; y < h; y++) img[x, y] = gridColor;
        for (int y = 0; y < h; y += cellPx)
            for (int x = 0; x < w; x++) img[x, y] = gridColor;

        img.SaveAsPng(outPath);
        Console.WriteLine($"Wrote {outPath} ({w}×{h}px) for template '{def.Id}' " +
                          $"({def.FootprintWTiles}×{def.FootprintHTiles} tiles, type={def.Type}, built_by={def.BuiltBy}).");
        return 0;
    }

    private static int RenderPipeline(ulong seed, int poiId, string typeName, string outPath, string dataDir, int cellPx)
    {
        if (!Enum.TryParse<PoiType>(typeName, ignoreCase: true, out var type) || type == PoiType.None)
        {
            Console.Error.WriteLine($"--type '{typeName}' invalid. Expected one of: ImperiumRuin, AbandonedMine, CultDen, NaturalCave, OvergrownSettlement.");
            return 1;
        }

        var content = new ContentResolver(new ContentLoader(dataDir));
        var d = DungeonGenerator.Generate(seed, poiId, type, content);

        int w = d.W * cellPx;
        int h = d.H * cellPx;
        using var img = new Image<Rgba32>(w, h);

        // Pass 1: paint surface + deco from the dungeon's tile array.
        for (int ty = 0; ty < d.H; ty++)
        for (int tx = 0; tx < d.W; tx++)
        {
            var tile = d.Tiles[tx, ty];
            Rgba32 fill = ColorForTile(tile);
            for (int py = 0; py < cellPx; py++)
            for (int px = 0; px < cellPx; px++)
                img[tx * cellPx + px, ty * cellPx + py] = fill;
        }

        // Pass 2: tint room interiors by role with a translucent overlay so
        // designers can verify role placement at a glance.
        foreach (var room in d.Rooms)
        {
            var tint = ColorForRole(room.Role);
            for (int ty = room.AabbY + 1; ty < room.AabbY + room.AabbH - 1; ty++)
            for (int tx = room.AabbX + 1; tx < room.AabbX + room.AabbW - 1; tx++)
            {
                if (tx < 0 || ty < 0 || tx >= d.W || ty >= d.H) continue;
                if (!d.Tiles[tx, ty].IsWalkable) continue;
                for (int py = 0; py < cellPx; py++)
                for (int px = 0; px < cellPx; px++)
                {
                    var existing = img[tx * cellPx + px, ty * cellPx + py];
                    img[tx * cellPx + px, ty * cellPx + py] = Blend(existing, tint, 0.25f);
                }
            }
        }

        // Pass 3: highlight the entrance with a magenta border.
        var entrance = new Rgba32(255, 0, 200, 255);
        var (ex, ey) = d.EntranceTile;
        for (int dx = -1; dx <= 1; dx++)
        for (int dy = -1; dy <= 1; dy++)
        {
            int tx = ex + dx, ty = ey + dy;
            if (tx < 0 || ty < 0 || tx >= d.W || ty >= d.H) continue;
            if (Math.Abs(dx) + Math.Abs(dy) != 1) continue;
            for (int py = 0; py < cellPx; py++)
            for (int px = 0; px < cellPx; px++)
            {
                if ((px == 0 || py == 0 || px == cellPx - 1 || py == cellPx - 1))
                    img[tx * cellPx + px, ty * cellPx + py] = entrance;
            }
        }

        img.SaveAsPng(outPath);
        Console.WriteLine($"Wrote {outPath} ({w}×{h}px) — dungeon poi={poiId} seed=0x{seed:X} type={type}");
        Console.WriteLine($"  {d.Rooms.Length} rooms, {d.Connections.Length} connections, " +
                          $"{d.W}×{d.H} tactical tiles, entrance @ ({ex},{ey}).");
        foreach (var r in d.Rooms)
            Console.WriteLine($"  R{r.Id} [{r.Role}] {r.TemplateId} aabb=({r.AabbX},{r.AabbY},{r.AabbW}x{r.AabbH})");
        return 0;
    }

    private static ulong ParseUlong(string raw) =>
        raw.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
            ? Convert.ToUInt64(raw[2..], 16)
            : ulong.Parse(raw);

    private static Rgba32 ColorForTile(TacticalTile tile)
    {
        // Surface decides base, deco overrides if present (overlay color).
        if (tile.Deco != TacticalDeco.None)
        {
            return tile.Deco switch
            {
                TacticalDeco.Stairs        => new Rgba32(120, 60, 150, 255),
                TacticalDeco.DungeonDoor   => new Rgba32(120, 90, 40,  255),
                TacticalDeco.Container     => new Rgba32(220, 180, 50, 255),
                TacticalDeco.Trap          => new Rgba32(200, 100, 100, 255),
                TacticalDeco.Pillar        => new Rgba32(110, 110, 130, 255),
                TacticalDeco.Brazier       => new Rgba32(220, 120, 50,  255),
                TacticalDeco.ImperiumStatue=> new Rgba32(160, 140, 110, 255),
                _                           => ColorForSurface(tile.Surface),
            };
        }
        return ColorForSurface(tile.Surface);
    }

    private static Rgba32 ColorForSurface(TacticalSurface s) => s switch
    {
        TacticalSurface.Wall          => new Rgba32(45,  45,  60,  255),
        TacticalSurface.DungeonFloor  => new Rgba32(180, 165, 130, 255),
        TacticalSurface.DungeonRubble => new Rgba32(140, 125, 100, 255),
        TacticalSurface.DungeonTile   => new Rgba32(100, 130, 200, 255),
        TacticalSurface.MineFloor     => new Rgba32(120, 100, 80,  255),
        TacticalSurface.Cave          => new Rgba32(90,  85,  75,  255),
        TacticalSurface.None          => new Rgba32(0,   0,   0,   255),
        _                              => new Rgba32(80,  80,  80,  255),
    };

    private static Rgba32 ColorForRole(RoomRole role) => role switch
    {
        RoomRole.Entry     => new Rgba32(50,  130, 220, 255),  // blue
        RoomRole.Transit   => new Rgba32(0,   0,   0,   0),    // no tint
        RoomRole.Narrative => new Rgba32(220, 180, 50,  255),  // gold
        RoomRole.Loot      => new Rgba32(180, 220, 80,  255),  // green
        RoomRole.Boss      => new Rgba32(220, 60,  60,  255),  // red
        RoomRole.DeadEnd   => new Rgba32(120, 120, 120, 255),  // grey
        _                  => new Rgba32(0,   0,   0,   0),
    };

    private static Rgba32 Blend(Rgba32 dst, Rgba32 src, float t)
    {
        // Linear blend with the tint's alpha + the t factor.
        if (src.A == 0) return dst;
        float a = (src.A / 255f) * t;
        byte r = (byte)Math.Clamp(dst.R + (src.R - dst.R) * a, 0, 255);
        byte g = (byte)Math.Clamp(dst.G + (src.G - dst.G) * a, 0, 255);
        byte b = (byte)Math.Clamp(dst.B + (src.B - dst.B) * a, 0, 255);
        return new Rgba32(r, g, b, (byte)255);
    }

    private static Rgba32 ColorForChar(char ch) => ch switch
    {
        '#' => new Rgba32(45,  45,  60,  255),  // wall — dark slate
        '.' => new Rgba32(180, 165, 130, 255),  // floor — warm sand
        ',' => new Rgba32(140, 125, 100, 255),  // rubble
        'D' => new Rgba32(120, 90,  40,  255),  // door — chestnut
        'S' => new Rgba32(120, 60,  150, 255),  // stairs — purple
        '@' => new Rgba32(200, 60,  60,  255),  // encounter slot — red
        'C' => new Rgba32(220, 180, 50,  255),  // container — gold
        'T' => new Rgba32(200, 100, 100, 255),  // trap — coral
        'P' => new Rgba32(110, 110, 130, 255),  // pillar — grey-blue
        'B' => new Rgba32(220, 120, 50,  255),  // brazier — fire orange
        'M' => new Rgba32(100, 130, 200, 255),  // mosaic — sky blue
        ' ' => new Rgba32(0,   0,   0,   0),    // unused/transparent
        _   => new Rgba32(255, 0,   200, 255),  // unknown — magenta marker
    };
}
