#nullable enable
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using AdvChkSys.Interfaces;

namespace AdvChkSys.Chunk
{
    /// <summary>
    /// Represents a 2D chunk of data in the world.
    /// Supports all-air singleton for memory efficiency and chunk array pooling.
    /// </summary>
    public class Chunk2D<T> : IChunk, IDisposable
    {
        // Flyweight: one all-air instance per size
        private static readonly Dictionary<(int, int), Chunk2D<T>> _allAirChunks = new();

        // Array pool for chunk data arrays
        private static readonly ConcurrentBag<T[,]> _arrayPool = new();

        /// <summary>
        /// Returns a singleton all-air chunk for the given size and position.
        /// </summary>
        public static Chunk2D<T> AllAir(int x, int y, int width, int height)
        {
            var key = (width, height);
            if (!_allAirChunks.TryGetValue(key, out var chunk))
            {
                chunk = new Chunk2D<T>(x, y, width, height, isAllAir: true);
                _allAirChunks[key] = chunk;
            }
            chunk.SetPosition(x, y); // Use explicit method for position update
            return chunk;
        }

        private readonly bool _isAllAir;
        private T[,]? _data;

        // Properties required by IChunk
        /// <summary>
        /// The chunk's X position in chunk coordinates.
        /// </summary>
        public int X { get; private set; }
        /// <summary>
        /// The chunk's Y position in chunk coordinates.
        /// </summary>
        public int Y { get; private set; }
        /// <summary>
        /// The width of the chunk in cells.
        /// </summary>
        public int Width { get; }
        /// <summary>
        /// The height of the chunk in cells.
        /// </summary>
        public int Height { get; }
        /// <summary>
        /// Metadata dictionary for arbitrary chunk information.
        /// </summary>
        public Dictionary<string, object> Metadata { get; }

        private bool _disposed;

        /// <summary>
        /// Normal constructor (private for singleton/factory).
        /// </summary>
        public Chunk2D(int x, int y, int width, int height, bool isAllAir = false)
        {
            X = x;
            Y = y;
            Width = width;
            Height = height;
            _isAllAir = isAllAir;
            _data = isAllAir ? null : RentArray(width, height);
            Metadata = new Dictionary<string, object>();
        }

        /// <summary>
        /// Explicitly sets the chunk's position. Used internally for all-air singleton.
        /// </summary>
        internal void SetPosition(int x, int y)
        {
            X = x;
            Y = y;
        }

        /// <summary>
        /// Gets or sets the value at the given local chunk coordinates.
        /// </summary>
        public T this[int localX, int localY]
        {
            get
            {
                if (localX < 0 || localX >= Width || localY < 0 || localY >= Height)
                    throw new ArgumentOutOfRangeException($"Coordinates ({localX}, {localY}) out of bounds [0-{Width - 1}, 0-{Height - 1}]");
                return _isAllAir ? default! : _data![localX, localY];
            }
            set
            {
                if (localX < 0 || localX >= Width || localY < 0 || localY >= Height)
                    throw new ArgumentOutOfRangeException($"Coordinates ({localX}, {localY}) out of bounds [0-{Width - 1}, 0-{Height - 1}]");
                if (_isAllAir)
                    throw new InvalidOperationException("Cannot set cell in all-air chunk.");
                _data![localX, localY] = value;
            }
        }

        /// <summary>
        /// Fills the chunk with a specified value (no-op for all-air).
        /// </summary>
        public void Fill(T value)
        {
            if (_isAllAir) return;
            var data = _data!;
            for (int x = 0; x < Width; x++)
                for (int y = 0; y < Height; y++)
                    data[x, y] = value;
        }

        /// <summary>
        /// Returns true if this chunk is the all-air singleton.
        /// </summary>
        public bool IsAllAir => _isAllAir;

        /// <summary>
        /// Returns the underlying data array (for pooling).
        /// </summary>
        internal T[,]? GetDataArray() => _data;

        /// <summary>
        /// Releases the data array back to the pool (for chunk manager).
        /// </summary>
        internal void ReleaseDataArray()
        {
            if (_data != null)
            {
                if (!_isAllAir)
                {
                    ReturnArray(_data);
                }
                _data = null;
            }
        }

        /// <summary>
        /// Rents an array from the pool or creates a new one.
        /// </summary>
        private static T[,] RentArray(int width, int height)
        {
            if (_arrayPool.TryTake(out var arr) && arr.GetLength(0) == width && arr.GetLength(1) == height)
                return arr;
            return new T[width, height];
        }

        /// <summary>
        /// Returns an array to the pool.
        /// </summary>
        internal static void ReturnArray(T[,] arr)
        {
            _arrayPool.Add(arr);
        }

        /// <summary>
        /// Disposes the chunk and returns its resources to the pool.
        /// </summary>
        public void Dispose()
        {
            if (!_disposed)
            {
                ReleaseDataArray();
                _disposed = true;
            }
        }
    }
}