using Godot;
using System.Collections.Generic;

public partial class TileGeneration : Node3D
{
    private Dictionary<Vector2I, string> TileMap = new();
    private Dictionary<Vector2I, bool> BigTiles = new();
    private byte timesDone = 0;

    // Preload tile scenes once to reduce runtime load
    private readonly Dictionary<string, PackedScene> tileScenes = new();

    public override void _Ready()
    {
        // Cache tile scenes
        string[] tileTypes = { "Floor", "Wall", "Room1", "BigFloor", "Storage Room", "Hallway", "Entrance" };
        foreach (var type in tileTypes)
        {
            tileScenes[type] = GD.Load<PackedScene>($"res://Tiles/{type}.tscn");
        }

        // Place the "Entrance" at the center of the upper edge
        PlaceEntranceOnUpperEdge();

        // Fill initial TileMap
        for (byte x = 0; x < 32; x++)
        {
            for (byte y = 0; y < 32; y++)
            {
                if (TileMap.ContainsKey(new Vector2I(x, y))) continue; // Skip if it's the "Entrance"
                TileMap[new Vector2I(x, y)] = GD.RandRange(0, 100) < 20 ? "Floor" : "Wall";
            }
        }

        // Generate clusters and other map features
        while (timesDone < 15)
        {
            if (!CreateBigRoom())
            break;
        }

        EnsurePathToAllClusters();
        ConvertSmallGroupsToStorageRoom();
        ConvertFloorsToHallways();
        PlaceTiles();
    }

    private void PlaceEntranceOnUpperEdge()
    {
        // Define the center position on the upper edge (y = 10, x = 0)
        Vector2I entrancePosition = new Vector2I(0, 18);
        
        // Set this position as the "Entrance" in the TileMap
        TileMap[entrancePosition] = "Entrance";
        BigTiles[entrancePosition] = true; // Mark it as a big tile if needed in other logic
    }

    private void EnsurePathToAllClusters()
    {
        if (BigTiles.Count < 2) return;  // No need to connect if there's only one big tile

        List<Vector2I> bigTilePositions = new List<Vector2I>(BigTiles.Keys);
        HashSet<Vector2I> reachableTiles = new HashSet<Vector2I>();

        // Flood-fill from the first big tile
        Vector2I startTile = bigTilePositions[0];
        FloodFill(startTile, reachableTiles);

        // Ensure each big tile has at least one path connecting it
        foreach (var bigTilePos in bigTilePositions)
        {
            if (!reachableTiles.Contains(bigTilePos))
            {
                CreatePath(startTile, bigTilePos, reachableTiles);
            }
        }

        // Add random secondary paths between big tiles
        for (int i = 0; i < bigTilePositions.Count; i++)
        {
            for (int j = i + 1; j < bigTilePositions.Count; j++)
            {
                if (GD.RandRange(0, 100) < 30)
                CreatePath(bigTilePositions[i], bigTilePositions[j], reachableTiles);
            }
        }
    }

    private void FloodFill(Vector2I start, HashSet<Vector2I> reachableTiles)
    {
        Queue<Vector2I> queue = new Queue<Vector2I>();
        queue.Enqueue(start);
        reachableTiles.Add(start);

        while (queue.Count > 0)
        {
            Vector2I current = queue.Dequeue();

            foreach (Vector2I offset in new Vector2I[] { new Vector2I(1, 0), new Vector2I(-1, 0), new Vector2I(0, 1), new Vector2I(0, -1) })
            {
                Vector2I neighbor = current + offset;
                if (reachableTiles.Contains(neighbor)) continue;

                if (TileMap.ContainsKey(neighbor) && TileMap[neighbor] == "Floor")
                {
                    reachableTiles.Add(neighbor);
                    queue.Enqueue(neighbor);
                }
            }
        }
    }

    private void CreatePath(Vector2I from, Vector2I to, HashSet<Vector2I> reachableTiles)
    {
        Vector2I current = from;

        // Move horizontally until X matches the target
        while (current.X != to.X)
        {
            if (!TileMap.ContainsKey(current) || TileMap[current] == "Wall")
            {
                TileMap[current] = "Floor";
                reachableTiles.Add(current);
            }

            // Move one step horizontally toward the target
            current += new Vector2I(current.X < to.X ? 1 : -1, 0);
        }

        // Then, move vertically until Y matches the target
        while (current.Y != to.Y)
        {
            if (!TileMap.ContainsKey(current) || TileMap[current] == "Wall")
            {
                TileMap[current] = "Floor";
                reachableTiles.Add(current);
            }

            // Move one step vertically toward the target
            current += new Vector2I(0, current.Y < to.Y ? 1 : -1);
        }
    }

    private bool CreateBigRoom()
    {
        int attempts = 0;
        Vector2I randomPosition;
        bool isTooClose;

        do
        {
            if (attempts++ > 20)  // Limit attempts to avoid infinite loops
                return false;

            isTooClose = false;
            randomPosition = new Vector2I((byte)GD.RandRange(0, 32), (byte)GD.RandRange(0, 32));

            foreach (var bigTilePos in BigTiles.Keys)
            {
                if (randomPosition.DistanceTo(bigTilePos) < 4)
                {
                    isTooClose = true;
                    break;
                }
            }
        } while (isTooClose);

        // Create tile cluster at randomPosition
        for (int width = -1; width < 2; width++)
        {
            for (int height = -2; height < 3; height++)
            {
                if (height > -2 && height < 2 && width > -2 && width < 2)
                {
                    TileMap[new Vector2I(randomPosition.X + width, randomPosition.Y + height)] = "Empty";
                }
            }
        }

        TileMap[randomPosition] = FinalBigTile();
        BigTiles[randomPosition] = true;
        timesDone++;
        return true;
    }

    private string FinalBigTile()
    {
        return timesDone < 6 ? "Room1" : "BigFloor";
    }

    private void ConvertFloorsToHallways()
    {
        foreach (var position in TileMap.Keys)
        {
            if (TileMap[position] == "Floor")
            {
                // Check for the pattern of two walls on opposite sides and two floors on opposite sides
                if (IsOppositeWallAndFloor(position, new Vector2I(1, 0), new Vector2I(-1, 0)) ||
                    IsOppositeWallAndFloor(position, new Vector2I(0, 1), new Vector2I(0, -1)))
                {
                    TileMap[position] = "Hallway";
                }
            }
        }
    }

    private bool IsOppositeWallAndFloor(Vector2I position, Vector2I offset1, Vector2I offset2)
    {
        bool hasOppositeWalls = IsWall(position + offset1) && IsWall(position + offset2);
        Vector2I perpendicularOffset1 = new Vector2I(offset1.Y, offset1.X);  // Rotate 90 degrees
        Vector2I perpendicularOffset2 = new Vector2I(-offset1.Y, -offset1.X); // Rotate 90 degrees opposite direction
        bool hasOppositeFloors = IsFloor(position + perpendicularOffset1) && IsFloor(position + perpendicularOffset2);

        return hasOppositeWalls && hasOppositeFloors;
    }

    private bool IsWall(Vector2I position)
    {
        return TileMap.TryGetValue(position, out string tileType) && tileType == "Wall";
    }

    private bool IsFloor(Vector2I position)
    {
        return TileMap.TryGetValue(position, out string tileType) && (tileType == "Floor" || tileType == "Hallway" || tileType == "Empty");
    }

    private void ConvertSmallGroupsToStorageRoom()
    {
        HashSet<Vector2I> visited = new HashSet<Vector2I>();

        foreach (var position in TileMap.Keys)
        {
            if (TileMap[position] == "Floor" && !visited.Contains(position))
            {
                List<Vector2I> group = new List<Vector2I>();
                FloodFillGroup(position, group, visited);
                if (group.Count < 5 && group.Count > 1 && GD.RandRange(0, 100) < 80)
                {
                    foreach (var tilePos in group)
                    {
                        TileMap[tilePos] = "Storage Room";
                    }
                }
            }
        }
    }

    private void FloodFillGroup(Vector2I start, List<Vector2I> group, HashSet<Vector2I> visited)
    {
        Queue<Vector2I> queue = new Queue<Vector2I>();
        queue.Enqueue(start);
        visited.Add(start);

        while (queue.Count > 0)
        {
            Vector2I current = queue.Dequeue();
            group.Add(current);

            foreach (Vector2I offset in new Vector2I[] { new Vector2I(1, 0), new Vector2I(-1, 0), new Vector2I(0, 1), new Vector2I(0, -1) })
            {
                Vector2I neighbor = current + offset;
                if (visited.Contains(neighbor)) continue;

                if (TileMap.ContainsKey(neighbor) && TileMap[neighbor] == "Floor")
                {
                    visited.Add(neighbor);
                    queue.Enqueue(neighbor);
                }
            }
        }
    }

    private void PlaceTiles()
    {
        foreach (var entry in TileMap)
        {
            if (entry.Value == "Empty") continue;

            Vector2I position = entry.Key;
            if (!tileScenes.ContainsKey(entry.Value)) continue;

            Node3D finalTile = (Node3D)tileScenes[entry.Value].Instantiate();
            finalTile.Position = new Vector3(position.X * 4, 0, position.Y * 4);

            if (entry.Value == "Hallway")
            {
                bool hasWallLeft = IsWall(position + new Vector2I(-1, 0));

                finalTile.RotationDegrees = new Vector3(0, hasWallLeft ? 0 : 90, 0);
            }

            AddChild(finalTile);
            PlaceConnectorBetweenFloorAndEmpty(position);
        }
    }

    private void PlaceConnectorBetweenFloorAndEmpty(Vector2I position)
    {
        Vector2I[] offsets = { new Vector2I(1, 0), new Vector2I(-1, 0), new Vector2I(0, 1), new Vector2I(0, -1) };

        foreach (var offset in offsets)
        {
            Vector2I neighborPos = position + offset;

            if (TileMap.TryGetValue(position, out string currentTile) && currentTile == "Floor" &&
                TileMap.TryGetValue(neighborPos, out string neighborTile) && neighborTile == "Empty")
            {
                Node3D WoodenWall = (Node3D)GD.Load<PackedScene>("res://Tiles/Things/WoodenWall.tscn").Instantiate();
                WoodenWall.Position = new Vector3((position.X + neighborPos.X) * 2, 2, (position.Y + neighborPos.Y) * 2);

                if (offset == new Vector2I(1, 0) || offset == new Vector2I(-1, 0))
                {
                    WoodenWall.RotationDegrees = new Vector3(0, 90, 0);
                }
                else
                {
                    WoodenWall.RotationDegrees = new Vector3(0, 0, 0);
                }

                AddChild(WoodenWall);
            }
        }
    }

    public string GetTile(Vector2I position)
    {
        return TileMap.TryGetValue(position, out string tileType) ? tileType : "Empty";
    }
}
Can you optimize this? (its godot 4.3 in c#)