#nullable enable
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Threading.Tasks;
using System;
using AdvChkSys.Chunk;
using AdvChkSys.Constraints;
using AdvChkSys.Events;
using AdvChkSys.Interfaces;
using AdvChkSys.Resources;
using AdvChkSys.Util;

namespace AdvChkSys.Manager
{
    /// <summary>
    /// Manages 3D chunks in memory. Handles loading, unloading, and access.
    /// </summary>
    public class ChunkManager3D<T> : IChunkManager
    {
        private readonly LRUCache<(int, int, int), Chunk3D<T>> _chunks;
        private readonly WorldConstraints? _constraints;
        private readonly int _capacity;

        /// <summary>
        /// Delegate for custom air check logic
        /// </summary>
        private Func<int, int, int, int, int, int, bool>? _airCheckDelegate;

        /// <summary>
        /// World generator for determining empty regions
        /// </summary>
        private IWorldGenerator3D? _worldGenerator;

        /// <summary>
        /// Data provider for determining empty regions
        /// </summary>
        private IDataProvider3D? _dataProvider;

        /// <summary>
        /// Height map for determining sky chunks
        /// </summary>
        private IHeightMap3D? _heightMap;

        /// <summary>
        /// Initializes a new instance of the 3D chunk manager.
        /// </summary>
        /// <param name="constraints">Optional world constraints</param>
        /// <param name="capacity">Maximum number of chunks to keep in memory</param>
        public ChunkManager3D(WorldConstraints? constraints = null, int capacity = 4096)
        {
            _constraints = constraints;
            _capacity = capacity;
            _chunks = new LRUCache<(int, int, int), Chunk3D<T>>(_capacity);
        }

        /// <summary>
        /// Returns true if the chunk at (x, y, z) is loaded.
        /// </summary>
        public bool IsChunkLoaded(int x, int y, int z) => _chunks.TryGet((x, y, z), out _);

        /// <summary>
        /// Gets the chunk at (x, y, z) if loaded, or null if not.
        /// </summary>
        public Chunk3D<T>? GetChunk(int x, int y, int z)
        {
            _chunks.TryGet((x, y, z), out var chunk);
            return chunk;
        }

        /// <summary>
        /// Determines if a region should be represented as an all-air chunk.
        /// </summary>
        private bool ShouldBeAllAir(int x, int y, int z, int width, int height, int depth)
        {
            // Check if we have a custom air check delegate
            if (_airCheckDelegate != null)
                return _airCheckDelegate(x, y, z, width, height, depth);

            // Check if we have a world generator
            if (_worldGenerator != null)
                return _worldGenerator.IsRegionEmpty(x, y, z, width, height, depth);

            // Check if we have a data provider
            if (_dataProvider != null)
                return _dataProvider.IsEmptyRegion(x, y, z, width, height, depth);

            // Check height-based air (for sky chunks)
            if (_heightMap != null)
                return y > _heightMap.GetMaxHeight(x, z, x + width, z + depth);

            // Default to non-air to ensure data integrity
            return false;
        }

        /// <summary>
        /// Loads or creates a chunk at (x, y, z) with the given size.
        /// Returns the loaded or newly created chunk.
        /// </summary>
        public Chunk3D<T> LoadOrCreateChunk(int x, int y, int z, int width, int height, int depth)
        {
            if (_constraints != null)
            {
                if (!_constraints.IsWithinBounds(x, y))
                    throw new InvalidOperationException("Chunk coordinates out of bounds.");
                if (!_constraints.IsWithinChunkLimit(_chunks.Count + 1))
                    throw new InvalidOperationException("Chunk limit exceeded.");
            }

            if (!_chunks.TryGet((x, y, z), out var chunk))
            {
                // If this region is all air, use the singleton
                if (ShouldBeAllAir(x, y, z, width, height, depth))
                    chunk = Chunk3D<T>.AllAir(x, y, z, width, height, depth);
                else
                    chunk = new Chunk3D<T>(x, y, z, width, height, depth);

                _chunks.Add((x, y, z), chunk, OnChunkEvicted);
                ChunkResourceManager.AllocateChunk(chunk);
                ChunkEvents.OnChunkLoaded(chunk);
            }
            return chunk;
        }

        // Note: This method may cause trimming warnings when publishing as a trimmed app
        private void OnChunkEvicted((int, int, int) key, Chunk3D<T> chunk)
        {
            if (chunk == null) return;

            // Instead of using reflection, use the GetDataArray method if available
            T[,,]? arr = chunk.GetDataArray();

            // If GetDataArray returned null, fall back to reflection
            if (arr == null)
            {
                var arrField = chunk.GetType().GetField("_data", BindingFlags.NonPublic | BindingFlags.Instance);
                if (arrField != null)
                {
                    arr = arrField.GetValue(chunk) as T[,,];
                }
            }

            if (!chunk.IsAllAir && arr != null)
            {
                Chunk3D<T>.ReturnArray(arr);
            }

            ChunkResourceManager.ReleaseChunk(chunk);
            ChunkEvents.OnChunkUnloaded(chunk);
        }

        /// <summary>
        /// Unloads (removes) the chunk at (x, y, z) if loaded.
        /// </summary>
        public bool UnloadChunk(int x, int y, int z)
        {
            if (_chunks.Remove((x, y, z), out var chunk) && chunk != null) // null check
            {
                ChunkResourceManager.ReleaseChunk(chunk);
                ChunkEvents.OnChunkUnloaded(chunk);
                return true;
            }
            return false;
        }

        /// <summary>
        /// Enumerates all loaded chunks.
        /// </summary>
        public IEnumerable<Chunk3D<T>> GetAllChunks() => _chunks.Values;

        // IChunkManager interface compatibility (for 2D interface)
        bool IChunkManager.IsChunkLoaded(int x, int y) => _chunks.TryGet((x, y, 0), out _);
        IChunk? IChunkManager.GetChunk(int x, int y) => _chunks.TryGet((x, y, 0), out var chunk) ? chunk : null;
        IChunk IChunkManager.LoadOrCreateChunk(int x, int y, int width, int height) =>
            LoadOrCreateChunk(x, y, 0, width, height, 1);
        bool IChunkManager.UnloadChunk(int x, int y) => UnloadChunk(x, y, 0);
        IEnumerable<IChunk> IChunkManager.GetAllChunks() => _chunks.Values;

        /// <summary>
        /// Sets a custom delegate for determining if a region should be all air.
        /// </summary>
        public void SetAirCheckDelegate(Func<int, int, int, int, int, int, bool> airCheckDelegate)
        {
            _airCheckDelegate = airCheckDelegate;
        }

        /// <summary>
        /// Sets a world generator for determining empty regions.
        /// </summary>
        public void SetWorldGenerator(IWorldGenerator3D worldGenerator)
        {
            _worldGenerator = worldGenerator;
        }

        /// <summary>
        /// Sets a data provider for determining empty regions.
        /// </summary>
        public void SetDataProvider(IDataProvider3D dataProvider)
        {
            _dataProvider = dataProvider;
        }

        /// <summary>
        /// Sets a height map for determining sky chunks.
        /// </summary>
        public void SetHeightMap(IHeightMap3D heightMap)
        {
            _heightMap = heightMap;
        }
    }

    /// <summary>
    /// Interface for 3D world generators that can determine if regions are empty.
    /// </summary>
    public interface IWorldGenerator3D
    {
        /// <summary>
        /// Determines if a region is empty (all air).
        /// </summary>
        bool IsRegionEmpty(int x, int y, int z, int width, int height, int depth);
    }

    /// <summary>
    /// Interface for 3D data providers that can determine if regions are empty.
    /// </summary>
    public interface IDataProvider3D
    {
        /// <summary>
        /// Determines if a region is empty (all air).
        /// </summary>
        bool IsEmptyRegion(int x, int y, int z, int width, int height, int depth);
    }

    /// <summary>
    /// Interface for 3D height maps that can determine the maximum height at a position.
    /// </summary>
    public interface IHeightMap3D
    {
        /// <summary>
        /// Gets the maximum height at a position range.
        /// </summary>
        int GetMaxHeight(int startX, int startZ, int endX, int endZ);
    }
}