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

namespace AdvChkSys.Chunk
{
    /// <summary>
    /// Represents a 3D chunk of data in the world.
    /// Perspective-agnostic and supports arbitrary data types and metadata.
    /// </summary>
    public class Chunk3D<T> : IChunk, IDisposable
    {
        // Array pool for chunk data arrays
        private static readonly ConcurrentBag<T[,,]> _arrayPool = new();

        // Flyweight: one all-air instance per size
        private static readonly Dictionary<(int, int, int), Chunk3D<T>> _allAirChunks = new();

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

        /// <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 chunk's Z position in chunk coordinates.
        /// </summary>
        public int Z { 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>
        /// The depth of the chunk in cells.
        /// </summary>
        public int Depth { get; }

        /// <summary>
        /// The chunk's data array.
        /// </summary>
        private readonly T[,,]? _data;

        /// <summary>
        /// Metadata dictionary for arbitrary chunk information (e.g., biome, tags).
        /// </summary>
        public Dictionary<string, object> Metadata { get; }

        /// <summary>
        /// Returns true if this chunk is the all-air singleton.
        /// </summary>
        public bool IsAllAir { get; private set; } = false;

        /// <summary>
        /// Tracks whether the chunk has been disposed.
        /// </summary>
        private bool _disposed;

        /// <summary>
        /// Creates a new 3D chunk at the specified position with the given dimensions.
        /// </summary>
        /// <param name="x">X coordinate in chunk space</param>
        /// <param name="y">Y coordinate in chunk space</param>
        /// <param name="z">Z coordinate in chunk space</param>
        /// <param name="width">Width of the chunk in cells</param>
        /// <param name="height">Height of the chunk in cells</param>
        /// <param name="depth">Depth of the chunk in cells</param>
        public Chunk3D(int x, int y, int z, int width, int height, int depth)
            : this(x, y, z, width, height, depth, false)
        {
        }

        /// <summary>
        /// Creates a new 3D chunk at the specified position with the given dimensions.
        /// </summary>
        /// <param name="x">X coordinate in chunk space</param>
        /// <param name="y">Y coordinate in chunk space</param>
        /// <param name="z">Z coordinate in chunk space</param>
        /// <param name="width">Width of the chunk in cells</param>
        /// <param name="height">Height of the chunk in cells</param>
        /// <param name="depth">Depth of the chunk in cells</param>
        /// <param name="isAllAir">Whether this is an all-air chunk</param>
        public Chunk3D(int x, int y, int z, int width, int height, int depth, bool isAllAir = false)
        {
            X = x;
            Y = y;
            Z = z;
            Width = width;
            Height = height;
            Depth = depth;
            IsAllAir = isAllAir;
            _data = isAllAir ? null : RentArray(width, height, depth);
            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, int z)
        {
            X = x;
            Y = y;
            Z = z;
        }

        /// <summary>
        /// Gets or sets the value at the given local chunk coordinates.
        /// </summary>
        public T this[int localX, int localY, int localZ]
        {
            get
            {
                if (IsAllAir)
                    return default!;
                return _data![localX, localY, localZ];
            }
            set
            {
                if (IsAllAir)
                    throw new InvalidOperationException("Cannot modify an all-air chunk.");
                _data![localX, localY, localZ] = value;
            }
        }

        /// <summary>
        /// Fills the chunk with a specified value.
        /// </summary>
        public void Fill(T value)
        {
            if (IsAllAir)
                throw new InvalidOperationException("Cannot fill an all-air chunk.");

            for (int x = 0; x < Width; x++)
                for (int y = 0; y < Height; y++)
                    for (int z = 0; z < Depth; z++)
                        _data![x, y, z] = value;
        }

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

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

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

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

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