using Theriapolis.Core.Data;
using Theriapolis.Core.Tactical;
using Theriapolis.Core.World;

namespace Theriapolis.Core.Dungeons;

/// <summary>
/// Phase 7 M1 — paints the rooms + corridors of a planned dungeon into a
/// tactical-tile array. Pure, deterministic given the same plan input.
///
/// The painter:
///   1. Allocates a <see cref="TacticalTile"/>[w, h] array of the planned
///      dungeon size.
///   2. For each room, copies its <see cref="RoomTemplateDef.Grid"/> char
///      by char into the array at the room's AABB top-left, mapping each
///      char to a <see cref="TacticalSurface"/> + <see cref="TacticalDeco"/>
///      pair via <see cref="MapChar"/>.
///   3. For each connection, runs a Manhattan-path corridor between the
///      two door tiles, writing <see cref="TacticalSurface.DungeonFloor"/>
///      and ensuring the door tiles themselves are walkable
///      (<see cref="TacticalDeco.DungeonDoor"/>).
///
/// The dungeon's surface family (DungeonFloor vs Cave vs MineFloor) is
/// chosen by dungeon type so different dungeons feel visually distinct
/// even before art lands.
/// </summary>
internal static class RoomTilePainter
{
    public static TacticalTile[,] Paint(
        RoomGraphAssembler.Plan plan,
        IReadOnlyDictionary<string, RoomTemplateDef> templatesById,
        PoiType dungeonType)
    {
        var tiles = new TacticalTile[plan.DungeonW, plan.DungeonH];

        // Default fill: solid wall (so any unpainted gap is automatically
        // impassable). The painter then carves rooms + corridors out of it.
        for (int y = 0; y < plan.DungeonH; y++)
        for (int x = 0; x < plan.DungeonW; x++)
        {
            tiles[x, y] = new TacticalTile
            {
                Surface = TacticalSurface.Wall,
                Deco    = TacticalDeco.None,
                Variant = 0,
                Flags   = 0,
            };
        }

        TacticalSurface defaultFloor = SurfaceForDungeonType(dungeonType);

        // Paint each room's grid.
        foreach (var room in plan.Rooms)
        {
            if (!templatesById.TryGetValue(room.TemplateId, out var def))
                continue; // shouldn't happen — ContentLoader validates these
            for (int gy = 0; gy < def.FootprintHTiles; gy++)
            {
                for (int gx = 0; gx < def.FootprintWTiles; gx++)
                {
                    int dx = room.AabbX + gx;
                    int dy = room.AabbY + gy;
                    if (dx < 0 || dy < 0 || dx >= plan.DungeonW || dy >= plan.DungeonH)
                        continue;
                    char ch = def.Grid[gy][gx];
                    var (surface, deco) = MapChar(ch, defaultFloor);
                    tiles[dx, dy].Surface = surface;
                    tiles[dx, dy].Deco    = deco;
                }
            }
        }

        // Carve corridors between door tiles.
        foreach (var conn in plan.Connections)
        {
            CarveCorridor(tiles, plan, conn, defaultFloor);
        }

        // Mark the entrance tile so the renderer can highlight it. Stairs
        // is the canonical "interactable enter / exit" deco.
        if (plan.EntranceTile.X >= 0 && plan.EntranceTile.X < plan.DungeonW
            && plan.EntranceTile.Y >= 0 && plan.EntranceTile.Y < plan.DungeonH)
        {
            tiles[plan.EntranceTile.X, plan.EntranceTile.Y].Surface = defaultFloor;
            tiles[plan.EntranceTile.X, plan.EntranceTile.Y].Deco    = TacticalDeco.Stairs;
        }

        return tiles;
    }

    private static (TacticalSurface surface, TacticalDeco deco) MapChar(char ch, TacticalSurface defaultFloor) => ch switch
    {
        '#' => (TacticalSurface.Wall,            TacticalDeco.None),
        '.' => (defaultFloor,                    TacticalDeco.None),
        ',' => (TacticalSurface.DungeonRubble,   TacticalDeco.None),
        'D' => (defaultFloor,                    TacticalDeco.DungeonDoor),
        '@' => (defaultFloor,                    TacticalDeco.None),    // encounter slot — spawn placed by populator
        'C' => (defaultFloor,                    TacticalDeco.Container),
        'T' => (defaultFloor,                    TacticalDeco.Trap),
        'P' => (defaultFloor,                    TacticalDeco.Pillar),
        'B' => (defaultFloor,                    TacticalDeco.Brazier),
        'M' => (TacticalSurface.DungeonTile,     TacticalDeco.None),    // mosaic / narrative inlay
        'S' => (defaultFloor,                    TacticalDeco.Stairs),
        ' ' => (TacticalSurface.None,            TacticalDeco.None),
        _   => (defaultFloor,                    TacticalDeco.None),    // unknown → walkable floor
    };

    private static TacticalSurface SurfaceForDungeonType(PoiType type) => type switch
    {
        PoiType.AbandonedMine       => TacticalSurface.MineFloor,
        PoiType.NaturalCave         => TacticalSurface.Cave,
        PoiType.CultDen             => TacticalSurface.Cave,
        PoiType.OvergrownSettlement => TacticalSurface.DungeonFloor,
        PoiType.ImperiumRuin        => TacticalSurface.DungeonFloor,
        _                           => TacticalSurface.DungeonFloor,
    };

    /// <summary>
    /// Manhattan corridor from door A to door B. Picks one of the two L-bends
    /// deterministically (vertical-first if the connection is "more vertical"
    /// than horizontal; horizontal-first otherwise) so two seeds don't
    /// produce different routes through the same plan.
    /// </summary>
    private static void CarveCorridor(
        TacticalTile[,] tiles,
        RoomGraphAssembler.Plan plan,
        RoomConnection conn,
        TacticalSurface defaultFloor)
    {
        int x0 = conn.DoorAx, y0 = conn.DoorAy;
        int x1 = conn.DoorBx, y1 = conn.DoorBy;

        // Ensure the door tiles themselves are walkable.
        SafeSet(tiles, plan, x0, y0, defaultFloor, TacticalDeco.DungeonDoor);
        SafeSet(tiles, plan, x1, y1, defaultFloor, TacticalDeco.DungeonDoor);

        // Decide bend axis.
        int dx = Math.Abs(x1 - x0);
        int dy = Math.Abs(y1 - y0);
        bool horizontalFirst = dx >= dy;

        if (horizontalFirst)
        {
            int xa = Math.Min(x0, x1);
            int xb = Math.Max(x0, x1);
            for (int x = xa; x <= xb; x++)
                SafeSet(tiles, plan, x, y0, defaultFloor, TacticalDeco.None, preserveDoor: true);
            int ya = Math.Min(y0, y1);
            int yb = Math.Max(y0, y1);
            for (int y = ya; y <= yb; y++)
                SafeSet(tiles, plan, x1, y, defaultFloor, TacticalDeco.None, preserveDoor: true);
        }
        else
        {
            int ya = Math.Min(y0, y1);
            int yb = Math.Max(y0, y1);
            for (int y = ya; y <= yb; y++)
                SafeSet(tiles, plan, x0, y, defaultFloor, TacticalDeco.None, preserveDoor: true);
            int xa = Math.Min(x0, x1);
            int xb = Math.Max(x0, x1);
            for (int x = xa; x <= xb; x++)
                SafeSet(tiles, plan, x, y1, defaultFloor, TacticalDeco.None, preserveDoor: true);
        }
    }

    private static void SafeSet(
        TacticalTile[,] tiles, RoomGraphAssembler.Plan plan,
        int x, int y, TacticalSurface surface, TacticalDeco deco, bool preserveDoor = false)
    {
        if (x < 0 || y < 0 || x >= plan.DungeonW || y >= plan.DungeonH) return;
        // Don't bulldoze a room's interior decoration when the corridor
        // happens to clip through (carry-over from straightline paths
        // that cross a room edge). The painter already laid the room
        // first, so corridor only needs to convert Wall/None → Floor.
        var existing = tiles[x, y];
        if (existing.Surface != TacticalSurface.Wall
            && existing.Surface != TacticalSurface.None
            && !(preserveDoor && existing.Deco == TacticalDeco.DungeonDoor))
            return;
        tiles[x, y].Surface = surface;
        if (!preserveDoor || existing.Deco != TacticalDeco.DungeonDoor)
            tiles[x, y].Deco = deco;
    }
}
