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

namespace InfiniteWorld
{
    [CreateAssetMenu(menuName = "Infinite World/Chunk Template", fileName = "ChunkTemplate")]
    public class ChunkTemplate : ScriptableObject
    {
        [Min(4)] public int width = 16;
        [Min(4)] public int height = 16;

        [Header("Exits")]
        public bool exitTop = true;
        public bool exitRight = true;
        public bool exitBottom = true;
        public bool exitLeft = true;

        [SerializeField] private List<ChunkCellData> cells = new List<ChunkCellData>();

        public int CellCount => width * height;

        public void EnsureCellData()
        {
            int target = Mathf.Max(1, width * height);
            while (cells.Count < target)
            {
                cells.Add(new ChunkCellData());
            }

            while (cells.Count > target)
            {
                cells.RemoveAt(cells.Count - 1);
            }
        }

        public void Resize(int newWidth, int newHeight)
        {
            newWidth = Mathf.Max(4, newWidth);
            newHeight = Mathf.Max(4, newHeight);

            List<ChunkCellData> oldCells = new List<ChunkCellData>(cells);
            int oldWidth = width;
            int oldHeight = height;

            width = newWidth;
            height = newHeight;
            cells = new List<ChunkCellData>(newWidth * newHeight);

            for (int i = 0; i < newWidth * newHeight; i++)
            {
                cells.Add(new ChunkCellData());
            }

            for (int y = 0; y < Mathf.Min(oldHeight, newHeight); y++)
            {
                for (int x = 0; x < Mathf.Min(oldWidth, newWidth); x++)
                {
                    int oldIndex = y * oldWidth + x;
                    if (oldIndex < oldCells.Count)
                    {
                        cells[Index(x, y)] = oldCells[oldIndex];
                    }
                }
            }
        }

        public void Clear()
        {
            EnsureCellData();
            for (int i = 0; i < cells.Count; i++)
            {
                cells[i] = new ChunkCellData();
            }
        }

        public bool GetWall(int x, int y)
        {
            return IsInBounds(x, y) && cells[Index(x, y)].wall;
        }

        public bool GetEnvironment(int x, int y)
        {
            return IsInBounds(x, y) && cells[Index(x, y)].environment;
        }

        public void SetWall(int x, int y, bool value)
        {
            if (!IsInBounds(x, y))
            {
                return;
            }

            EnsureCellData();
            ChunkCellData data = cells[Index(x, y)];
            data.wall = value;
            if (value)
            {
                data.environment = false;
            }
            cells[Index(x, y)] = data;
        }

        public void SetEnvironment(int x, int y, bool value)
        {
            if (!IsInBounds(x, y))
            {
                return;
            }

            EnsureCellData();
            ChunkCellData data = cells[Index(x, y)];
            data.environment = value;
            if (value)
            {
                data.wall = false;
            }
            cells[Index(x, y)] = data;
        }

        public bool GetExit(ChunkExit exit)
        {
            return exit switch
            {
                ChunkExit.Top => exitTop,
                ChunkExit.Right => exitRight,
                ChunkExit.Bottom => exitBottom,
                ChunkExit.Left => exitLeft,
                _ => false
            };
        }

        public int ExitCount()
        {
            int count = 0;
            count += exitTop ? 1 : 0;
            count += exitRight ? 1 : 0;
            count += exitBottom ? 1 : 0;
            count += exitLeft ? 1 : 0;
            return count;
        }

        public void ApplyBorderWallsFromExits(int openingWidth = 3)
        {
            EnsureCellData();
            Clear();

            for (int x = 0; x < width; x++)
            {
                SetWall(x, 0, true);
                SetWall(x, height - 1, true);
            }

            for (int y = 0; y < height; y++)
            {
                SetWall(0, y, true);
                SetWall(width - 1, y, true);
            }

            CarveExit(ChunkExit.Top, openingWidth);
            CarveExit(ChunkExit.Right, openingWidth);
            CarveExit(ChunkExit.Bottom, openingWidth);
            CarveExit(ChunkExit.Left, openingWidth);
        }

        public void CarveExit(ChunkExit exit, int openingWidth = 3)
        {
            if (!GetExit(exit))
            {
                return;
            }

            int half = Mathf.Max(1, openingWidth) / 2;
            switch (exit)
            {
                case ChunkExit.Top:
                    for (int x = width / 2 - half; x <= width / 2 + half; x++)
                    {
                        SetWall(x, height - 1, false);
                        SetWall(x, height - 2, false);
                    }
                    break;
                case ChunkExit.Right:
                    for (int y = height / 2 - half; y <= height / 2 + half; y++)
                    {
                        SetWall(width - 1, y, false);
                        SetWall(width - 2, y, false);
                    }
                    break;
                case ChunkExit.Bottom:
                    for (int x = width / 2 - half; x <= width / 2 + half; x++)
                    {
                        SetWall(x, 0, false);
                        SetWall(x, 1, false);
                    }
                    break;
                case ChunkExit.Left:
                    for (int y = height / 2 - half; y <= height / 2 + half; y++)
                    {
                        SetWall(0, y, false);
                        SetWall(1, y, false);
                    }
                    break;
            }
        }

        private int Index(int x, int y)
        {
            return y * width + x;
        }

        private bool IsInBounds(int x, int y)
        {
            return x >= 0 && y >= 0 && x < width && y < height;
        }
    }

    public enum ChunkExit
    {
        Top,
        Right,
        Bottom,
        Left
    }

    [Serializable]
    public struct ChunkCellData
    {
        public bool wall;
        public bool environment;
    }
}
