using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class MapGenerator : MonoBehaviour
{
    public int totalRooms = 20;
    public int mainRoomsCount = 10;
    public float mapRadius = 50f;
    public float minRoomWidth = 4f;
    public float maxRoomWidth = 10f;
    public float minRoomHeight = 4f;
    public float maxRoomHeight = 10f;
    public int separationIterations = 10;

    private readonly List<Room> _rooms = new();
    private List<Room> _mainRooms = new();
    private readonly List<Connection> _connections = new();
    private readonly HashSet<Vector2Int> _corridorTiles = new();

    private void Start()
    {
        GenerateMap();
    }

    public void GenerateMap()
    {
        ClearAll();
        GenerateRooms();
        SeparateRooms();
        SelectMainRooms();
        ConnectRoomsWithMST();
        CreateCorridors();
        
        Debug.Log($"생성된 전체 방 수: {_rooms.Count}");
        Debug.Log($"메인 방 수: {_mainRooms.Count}");
        Debug.Log($"연결 수: {_connections.Count}");
    }

    private void ClearAll()
    {
        _rooms.Clear();
        _mainRooms.Clear();
        _connections.Clear();
    }

    private void GenerateRooms()
    {
        for (var i = 0; i < totalRooms; i++)
        {
            // 랜덤 위치 선택
            var angle = Random.Range(0f, Mathf.PI * 2);
            var distance = Random.Range(0f, mapRadius);
            var position = new Vector2(
                Mathf.Cos(angle) * distance,
                Mathf.Sin(angle) * distance
            );

            // 랜덤 크기 방 생성
            var width = Random.Range(minRoomWidth, maxRoomWidth);
            var height = Random.Range(minRoomHeight, maxRoomHeight);

            _rooms.Add(new Room(position, new Vector2(width, height)));
        }
    }

    private void SeparateRooms()
    {
        for (var iter = 0; iter < separationIterations; iter++)
        {
            var stillOverlapping = false;

            for (var i = 0; i < _rooms.Count; i++)
            for (var j = i + 1; j < _rooms.Count; j++)
                if (_rooms[i].Overlaps(_rooms[j]))
                {
                    stillOverlapping = true;

                    // 방들이 겹치면 서로 밀어냄
                    var direction = (_rooms[i].Position - _rooms[j].Position).normalized;
                    var overlap = _rooms[i].GetOverlapDistance(_rooms[j]);

                    _rooms[i].Position += direction * overlap * 0.5f;
                    _rooms[j].Position -= direction * overlap * 0.5f;
                }

            // 더 이상 겹치는 방이 없으면 종료
            if (!stillOverlapping) break;
        }
    }

    private void SelectMainRooms()
    {
        // 방 크기에 따라 정렬 (큰 것부터)
        _rooms.Sort((a, b) => (b.Size.x * b.Size.y).CompareTo(a.Size.x * a.Size.y));

        // 가장 큰 방 mainRoomsCount개만 선택
        _mainRooms = _rooms.Take(Mathf.Min(mainRoomsCount, _rooms.Count)).ToList();
    }

    private void ConnectRoomsWithMST()
    {
        var edges = new List<Edge>();

        // 모든 방 쌍에 대해 거리 계산
        for (var i = 0; i < _mainRooms.Count; i++)
        for (var j = i + 1; j < _mainRooms.Count; j++)
        {
            var dist = Vector2.Distance(_mainRooms[i].Position, _mainRooms[j].Position);
            edges.Add(new Edge(i, j, dist));
        }

        // 크루스칼 MST 알고리즘
        edges.Sort((a, b) => a.Weight.CompareTo(b.Weight));
        var ds = new DisjointSet(_mainRooms.Count);

        foreach (var edge in edges)
            if (ds.Find(edge.U) != ds.Find(edge.V))
            {
                ds.Union(edge.U, edge.V);
                _connections.Add(new Connection(_mainRooms[edge.U], _mainRooms[edge.V]));
            }
    }

    private void CreateCorridors()
    {
        foreach (var conn in _connections)
        {
            var roomA = conn.RoomA;
            var roomB = conn.RoomB;

            // 두 방의 중심점
            var startPos = Vector2Int.RoundToInt(roomA.Position);
            var endPos = Vector2Int.RoundToInt(roomB.Position);

            // 두 가지 L자 패턴
            if (Random.Range(0, 2) == 0)
            {
                // 수평 먼저, 수직 나중 ㄱ
                CreateCorridor(_corridorTiles, startPos, new Vector2Int(endPos.x, startPos.y));
                CreateCorridor(_corridorTiles, new Vector2Int(endPos.x, startPos.y), endPos);
            }
            else
            {
                // 수직 먼저, 수평 나중 ┌ 
                CreateCorridor(_corridorTiles, startPos, new Vector2Int(startPos.x, endPos.y));
                CreateCorridor(_corridorTiles, new Vector2Int(startPos.x, endPos.y), endPos);
            }
        }
    }

    /// <summary>
    /// 중복 없는 복도 생성
    /// </summary>
    /// <param name="corridorTiles"></param>
    /// <param name="start"></param>
    /// <param name="end"></param>
    private void CreateCorridor(HashSet<Vector2Int> corridorTiles, Vector2Int start, Vector2Int end)
    {
        var position = start;
        var direction = new Vector2Int(
            Mathf.Clamp(end.x - start.x, -1, 1),
            Mathf.Clamp(end.y - start.y, -1, 1)
        );
        
        while (position != end)
        {
            corridorTiles.Add(position);

            // 다음 위치로 이동
            if (position.x != end.x)
                position.x += direction.x;
            else if (position.y != end.y)
                position.y += direction.y;
        }
    }

    #region Debug 테스트용

    public Color roomColor = Color.blue;
    public Color mainRoomColor = Color.red;
    public Color corridorColor = Color.green;

    private void OnDrawGizmos()
    {
        // 방
        Gizmos.color = roomColor;
        foreach (var room in _rooms)
        {
            if (_mainRooms.Contains(room))
                Gizmos.color = mainRoomColor;
            else
                Gizmos.color = roomColor;

            Gizmos.DrawWireCube(room.Position, room.Size);
        }

        // 복도
        Gizmos.color = corridorColor;
        var tileSize = 1f; // 타일 크기

        if (_corridorTiles != null)
            foreach (var pos in _corridorTiles)
                Gizmos.DrawWireCube(new Vector3(pos.x, pos.y, 0),
                    new Vector3(tileSize, tileSize, 0));
    }

    #endregion

    #region Class

    // 방 클래스
    public class Room
    {
        public Vector2 Position;
        public Vector2 Size;

        public Room(Vector2 pos, Vector2 size)
        {
            Position = pos;
            Size = size;
        }

        public bool Overlaps(Room other)
        {
            return !(Position.x + Size.x / 2 < other.Position.x - other.Size.x / 2 ||
                     Position.x - Size.x / 2 > other.Position.x + other.Size.x / 2 ||
                     Position.y + Size.y / 2 < other.Position.y - other.Size.y / 2 ||
                     Position.y - Size.y / 2 > other.Position.y + other.Size.y / 2);
        }

        public float GetOverlapDistance(Room other)
        {
            var dx = Mathf.Min(Position.x + Size.x / 2, other.Position.x + other.Size.x / 2) -
                     Mathf.Max(Position.x - Size.x / 2, other.Position.x - other.Size.x / 2);
            var dy = Mathf.Min(Position.y + Size.y / 2, other.Position.y + other.Size.y / 2) -
                     Mathf.Max(Position.y - Size.y / 2, other.Position.y - other.Size.y / 2);

            // 실제 겹치는 거리 계산
            if (dx > 0 && dy > 0)
                return Mathf.Min(dx, dy) + 0.1f; // 약간의 여유 공간 추가

            return 0;
        }
    }

    // 연결 클래스
    private class Connection
    {
        public readonly Room RoomA, RoomB;

        public Connection(Room a, Room b)
        {
            RoomA = a;
            RoomB = b;
        }
    }

    // MST용 간선 클래스
    private class Edge
    {
        public readonly int U, V;
        public readonly float Weight;

        public Edge(int u, int v, float w)
        {
            U = u;
            V = v;
            Weight = w;
        }
    }

    #endregion

    // Disjoint Set 자료구조
    private class DisjointSet
    {
        private readonly int[] _parent;

        public DisjointSet(int n)
        {
            _parent = new int[n];
            for (var i = 0; i < n; i++) _parent[i] = i;
        }

        public int Find(int x)
        {
            if (_parent[x] == x) return x;
            return _parent[x] = Find(_parent[x]);
        }

        public void Union(int a, int b)
        {
            var rootA = Find(a);
            var rootB = Find(b);
            if (rootA != rootB) _parent[rootB] = rootA;
        }
    }
}