#nullable enable
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using AdvChkSys.Interfaces;
using AdvChkSys.Threading;


namespace AdvChkSys.Spatial
{
    /// <summary>
    /// Provides spatial indexing for efficient region and distance queries.
    /// </summary>
    public class SpatialChunkIndex<T> where T : IChunk
    {
        // Grid-based spatial index for fast lookups
        private readonly Dictionary<(int, int), HashSet<T>> _grid = new();

        // All chunks in the index for iteration
        private readonly HashSet<T> _allChunks = new();

        // Lock for thread safety
        private readonly object _lock = new();

        /// <summary>
        /// Adds a chunk to the spatial index.
        /// </summary>
        /// <param name="chunk">The chunk to add</param>
        public void AddChunk(T chunk)
        {
            if (chunk == null)
                throw new ArgumentNullException(nameof(chunk));

            lock (_lock)
            {
                // Add to all chunks set
                _allChunks.Add(chunk);

                // Add to grid cells
                var key = (chunk.X, chunk.Y);
                if (!_grid.TryGetValue(key, out var chunks))
                {
                    chunks = new HashSet<T>();
                    _grid[key] = chunks;
                }
                chunks.Add(chunk);
            }
        }

        /// <summary>
        /// Removes a chunk from the spatial index.
        /// </summary>
        /// <param name="chunk">The chunk to remove</param>
        /// <returns>True if the chunk was found and removed, false otherwise</returns>
        public bool RemoveChunk(T chunk)
        {
            if (chunk == null)
                throw new ArgumentNullException(nameof(chunk));

            lock (_lock)
            {
                // Remove from all chunks set
                bool removed = _allChunks.Remove(chunk);

                // Remove from grid cells
                var key = (chunk.X, chunk.Y);
                if (_grid.TryGetValue(key, out var chunks))
                {
                    chunks.Remove(chunk);
                    if (chunks.Count == 0)
                    {
                        _grid.Remove(key);
                    }
                }

                return removed;
            }
        }

        /// <summary>
        /// Finds all chunks within a rectangular region.
        /// </summary>
        /// <param name="minX">Minimum X coordinate</param>
        /// <param name="minY">Minimum Y coordinate</param>
        /// <param name="maxX">Maximum X coordinate</param>
        /// <param name="maxY">Maximum Y coordinate</param>
        /// <returns>All chunks that intersect with the region</returns>
        public IEnumerable<T> FindChunksInRegion(int minX, int minY, int maxX, int maxY)
        {
            lock (_lock)
            {
                var result = new HashSet<T>();

                // Check each grid cell that overlaps with the region
                for (int x = minX; x <= maxX; x++)
                {
                    for (int y = minY; y <= maxY; y++)
                    {
                        var key = (x, y);
                        if (_grid.TryGetValue(key, out var chunks))
                        {
                            foreach (var chunk in chunks)
                            {
                                result.Add(chunk);
                            }
                        }
                    }
                }

                return result;
            }
        }

        /// <summary>
        /// Finds all chunks within a specified distance from a point.
        /// </summary>
        /// <param name="centerX">Center X coordinate</param>
        /// <param name="centerY">Center Y coordinate</param>
        /// <param name="radius">Radius to search within</param>
        /// <returns>All chunks that are within the radius of the point</returns>
        public IEnumerable<T> FindChunksInRadius(int centerX, int centerY, float radius)
        {
            lock (_lock)
            {
                var result = new HashSet<T>();
                float radiusSquared = radius * radius;

                // Calculate the bounding box of the circle
                int minX = (int)(centerX - radius);
                int minY = (int)(centerY - radius);
                int maxX = (int)(centerX + radius);
                int maxY = (int)(centerY + radius);

                // Check each grid cell that might overlap with the circle
                for (int x = minX; x <= maxX; x++)
                {
                    for (int y = minY; y <= maxY; y++)
                    {
                        var key = (x, y);
                        if (_grid.TryGetValue(key, out var chunks))
                        {
                            foreach (var chunk in chunks)
                            {
                                // Calculate distance from chunk center to query center
                                float dx = chunk.X - centerX;
                                float dy = chunk.Y - centerY;
                                float distanceSquared = dx * dx + dy * dy;

                                // Check if the chunk is within the radius
                                if (distanceSquared <= radiusSquared)
                                {
                                    result.Add(chunk);
                                }
                            }
                        }
                    }
                }

                return result;
            }
        }

        /// <summary>
        /// Finds the nearest chunk to a point.
        /// </summary>
        /// <param name="x">X coordinate</param>
        /// <param name="y">Y coordinate</param>
        /// <returns>The nearest chunk, or null if no chunks are in the index</returns>
        public T? FindNearestChunk(int x, int y)
        {
            lock (_lock)
            {
                if (_allChunks.Count == 0)
                    return default;

                T? nearest = default;
                float nearestDistanceSquared = float.MaxValue;

                foreach (var chunk in _allChunks)
                {
                    float dx = chunk.X - x;
                    float dy = chunk.Y - y;
                    float distanceSquared = dx * dx + dy * dy;

                    if (distanceSquared < nearestDistanceSquared)
                    {
                        nearestDistanceSquared = distanceSquared;
                        nearest = chunk;
                    }
                }

                return nearest;
            }
        }

        /// <summary>
        /// Finds all chunks that contain a point.
        /// </summary>
        /// <param name="x">X coordinate</param>
        /// <param name="y">Y coordinate</param>
        /// <returns>All chunks that contain the point</returns>
        public IEnumerable<T> FindChunksContainingPoint(int x, int y)
        {
            lock (_lock)
            {
                var result = new HashSet<T>();

                // Get the grid cell for this point
                var key = (x, y);
                if (_grid.TryGetValue(key, out var chunks))
                {
                    foreach (var chunk in chunks)
                    {
                        // Check if the point is within the chunk bounds
                        if (x >= chunk.X && x < chunk.X + chunk.Width &&
                            y >= chunk.Y && y < chunk.Y + chunk.Height)
                        {
                            result.Add(chunk);
                        }
                    }
                }

                return result;
            }
        }

        /// <summary>
        /// Gets all chunks in the index.
        /// </summary>
        public IEnumerable<T> GetAllChunks()
        {
            lock (_lock)
            {
                return _allChunks.ToList();
            }
        }

        /// <summary>
        /// Clears all chunks from the index.
        /// </summary>
        public void Clear()
        {
            lock (_lock)
            {
                _allChunks.Clear();
                _grid.Clear();
            }
        }

        /// <summary>
        /// Gets the number of chunks in the index.
        /// </summary>
        public int Count
        {
            get
            {
                lock (_lock)
                {
                    return _allChunks.Count;
                }
            }
        }

        /// <summary>
        /// Checks if a chunk is in the index.
        /// </summary>
        /// <param name="chunk">The chunk to check</param>
        /// <returns>True if the chunk is in the index, false otherwise</returns>
        public bool Contains(T chunk)
        {
            if (chunk == null)
                throw new ArgumentNullException(nameof(chunk));

            lock (_lock)
            {
                return _allChunks.Contains(chunk);
            }
        }

        /// <summary>
        /// Updates the position of a chunk in the spatial index.
        /// </summary>
        /// <param name="chunk">The chunk to update</param>
        /// <param name="oldX">The old X coordinate</param>
        /// <param name="oldY">The old Y coordinate</param>
        /// <returns>True if the chunk was found and updated, false otherwise</returns>
        public bool UpdateChunkPosition(T chunk, int oldX, int oldY)
        {
            if (chunk == null)
                throw new ArgumentNullException(nameof(chunk));

            lock (_lock)
            {
                // Remove from old grid cell
                var oldKey = (oldX, oldY);
                bool removed = false;

                if (_grid.TryGetValue(oldKey, out var chunks))
                {
                    removed = chunks.Remove(chunk);
                    if (chunks.Count == 0)
                    {
                        _grid.Remove(oldKey);
                    }
                }

                if (!removed)
                    return false;

                // Add to new grid cell
                var newKey = (chunk.X, chunk.Y);
                if (!_grid.TryGetValue(newKey, out var newChunks))
                {
                    newChunks = new HashSet<T>();
                    _grid[newKey] = newChunks;
                }
                newChunks.Add(chunk);

                return true;
            }
        }

        /// <summary>
        /// Performs a spatial query using a custom filter.
        /// </summary>
        /// <param name="filter">The filter function to apply to each chunk</param>
        /// <returns>All chunks that pass the filter</returns>
        public IEnumerable<T> Query(Func<T, bool> filter)
        {
            if (filter == null)
                throw new ArgumentNullException(nameof(filter));

            lock (_lock)
            {
                return _allChunks.Where(filter).ToList();
            }
        }

        /// <summary>
        /// Finds all chunks that intersect with a given chunk.
        /// </summary>
        /// <param name="chunk">The chunk to check for intersections</param>
        /// <returns>All chunks that intersect with the given chunk</returns>
        public IEnumerable<T> FindIntersectingChunks(IChunk chunk)
        {
            if (chunk == null)
                throw new ArgumentNullException(nameof(chunk));

            return FindChunksInRegion(
                chunk.X,
                chunk.Y,
                chunk.X + chunk.Width - 1,
                chunk.Y + chunk.Height - 1);
        }

        /// <summary>
        /// Finds all chunks that are neighbors of a given chunk.
        /// </summary>
        /// <param name="chunk">The chunk to find neighbors for</param>
        /// <param name="includeDiagonals">Whether to include diagonal neighbors</param>
        /// <returns>All neighboring chunks</returns>
        public IEnumerable<T> FindNeighbors(IChunk chunk, bool includeDiagonals = false)
        {
            if (chunk == null)
                throw new ArgumentNullException(nameof(chunk));

            lock (_lock)
            {
                var result = new HashSet<T>();

                // Check the four adjacent cells
                CheckNeighbor(chunk.X - 1, chunk.Y, result);
                CheckNeighbor(chunk.X + 1, chunk.Y, result);
                CheckNeighbor(chunk.X, chunk.Y - 1, result);
                CheckNeighbor(chunk.X, chunk.Y + 1, result);

                // Check diagonal cells if requested
                if (includeDiagonals)
                {
                    CheckNeighbor(chunk.X - 1, chunk.Y - 1, result);
                    CheckNeighbor(chunk.X + 1, chunk.Y - 1, result);
                    CheckNeighbor(chunk.X - 1, chunk.Y + 1, result);
                    CheckNeighbor(chunk.X + 1, chunk.Y + 1, result);
                }

                return result;
            }
        }

        /// <summary>
        /// Helper method to check for chunks at a specific grid cell.
        /// </summary>
        private void CheckNeighbor(int x, int y, HashSet<T> result)
        {
            var key = (x, y);
            if (_grid.TryGetValue(key, out var chunks))
            {
                foreach (var chunk in chunks)
                {
                    result.Add(chunk);
                }
            }
        }

        /// <summary>
        /// Performs a spatial operation on chunks in parallel.
        /// </summary>
        /// <param name="operation">The operation to perform on each chunk</param>
        /// <param name="filter">Optional filter to apply before processing</param>
        /// <param name="maxDegreeOfParallelism">Maximum degree of parallelism (null for default)</param>
        /// <returns>A task that completes when all operations are done</returns>
        public Task ProcessChunksParallelAsync(
            Action<T> operation,
            Func<T, bool>? filter = null,
            int? maxDegreeOfParallelism = null)
        {
            if (operation == null)
                throw new ArgumentNullException(nameof(operation));

            IEnumerable<T> chunksToProcess;

            lock (_lock)
            {
                chunksToProcess = filter != null
                    ? _allChunks.Where(filter).ToList()
                    : _allChunks.ToList();
            }

            return ChunkTaskScheduler.RunBatchParallelAsync(
                chunksToProcess.Select(chunk => new Action(() => operation(chunk))).ToArray(),
                maxDegreeOfParallelism);
        }

        /// <summary>
        /// Finds all chunks in a region and performs an operation on them in parallel.
        /// </summary>
        /// <param name="minX">Minimum X coordinate</param>
        /// <param name="minY">Minimum Y coordinate</param>
        /// <param name="maxX">Maximum X coordinate</param>
        /// <param name="maxY">Maximum Y coordinate</param>
        /// <param name="operation">The operation to perform on each chunk</param>
        /// <param name="maxDegreeOfParallelism">Maximum degree of parallelism (null for default)</param>
        /// <returns>A task that completes when all operations are done</returns>
        public Task ProcessRegionParallelAsync(
            int minX, int minY, int maxX, int maxY,
            Action<T> operation,
            int? maxDegreeOfParallelism = null)
        {
            if (operation == null)
                throw new ArgumentNullException(nameof(operation));

            var chunksInRegion = FindChunksInRegion(minX, minY, maxX, maxY).ToList();

            return ChunkTaskScheduler.RunBatchParallelAsync(
                chunksInRegion.Select(chunk => new Action(() => operation(chunk))).ToArray(),
                maxDegreeOfParallelism);
        }

        /// <summary>
        /// Creates a quadtree-based spatial index for more efficient queries.
        /// </summary>
        /// <param name="minX">Minimum X coordinate of the world</param>
        /// <param name="minY">Minimum Y coordinate of the world</param>
        /// <param name="maxX">Maximum X coordinate of the world</param>
        /// <param name="maxY">Maximum Y coordinate of the world</param>
        /// <returns>A new quadtree-based spatial index</returns>
        public QuadtreeSpatialIndex<T> CreateQuadtreeIndex(int minX, int minY, int maxX, int maxY)
        {
            var quadtree = new QuadtreeSpatialIndex<T>(minX, minY, maxX, maxY);

            lock (_lock)
            {
                foreach (var chunk in _allChunks)
                {
                    quadtree.AddChunk(chunk);
                }
            }

            return quadtree;
        }
    }

    /// <summary>
    /// Provides a quadtree-based spatial index for more efficient region queries.
    /// </summary>
    public class QuadtreeSpatialIndex<T> where T : IChunk
    {
        private readonly QuadtreeNode _root;
        private readonly HashSet<T> _allChunks = new();
        private readonly object _lock = new();

        /// <summary>
        /// Initializes a new instance of the QuadtreeSpatialIndex class.
        /// </summary>
        /// <param name="minX">Minimum X coordinate of the world</param>
        /// <param name="minY">Minimum Y coordinate of the world</param>
        /// <param name="maxX">Maximum X coordinate of the world</param>
        /// <param name="maxY">Maximum Y coordinate of the world</param>
        public QuadtreeSpatialIndex(int minX, int minY, int maxX, int maxY)
        {
            _root = new QuadtreeNode(minX, minY, maxX, maxY);
        }

        /// <summary>
        /// Adds a chunk to the quadtree.
        /// </summary>
        /// <param name="chunk">The chunk to add</param>
        public void AddChunk(T chunk)
        {
            if (chunk == null)
                throw new ArgumentNullException(nameof(chunk));

            lock (_lock)
            {
                _allChunks.Add(chunk);
                _root.Insert(chunk);
            }
        }

        /// <summary>
        /// Removes a chunk from the quadtree.
        /// </summary>
        /// <param name="chunk">The chunk to remove</param>
        /// <returns>True if the chunk was found and removed, false otherwise</returns>
        public bool RemoveChunk(T chunk)
        {
            if (chunk == null)
                throw new ArgumentNullException(nameof(chunk));

            lock (_lock)
            {
                bool removed = _allChunks.Remove(chunk);
                if (removed)
                {
                    _root.Remove(chunk);
                }
                return removed;
            }
        }

        /// <summary>
        /// Finds all chunks within a rectangular region.
        /// </summary>
        /// <param name="minX">Minimum X coordinate</param>
        /// <param name="minY">Minimum Y coordinate</param>
        /// <param name="maxX">Maximum X coordinate</param>
        /// <param name="maxY">Maximum Y coordinate</param>
        /// <returns>All chunks that intersect with the region</returns>
        public IEnumerable<T> FindChunksInRegion(int minX, int minY, int maxX, int maxY)
        {
            lock (_lock)
            {
                var result = new HashSet<T>();
                _root.Query(minX, minY, maxX, maxY, result);
                return result;
            }
        }

        /// <summary>
        /// Gets all chunks in the quadtree.
        /// </summary>
        public IEnumerable<T> GetAllChunks()
        {
            lock (_lock)
            {
                return _allChunks.ToList();
            }
        }

        /// <summary>
        /// Clears all chunks from the quadtree.
        /// </summary>
        public void Clear()
        {
            lock (_lock)
            {
                _allChunks.Clear();
                _root.Clear();
            }
        }

        /// <summary>
        /// Gets the number of chunks in the quadtree.
        /// </summary>
        public int Count
        {
            get
            {
                lock (_lock)
                {
                    return _allChunks.Count;
                }
            }
        }

        /// <summary>
        /// Represents a node in the quadtree.
        /// </summary>
        private class QuadtreeNode
        {
            // Boundary of this node
            private readonly int _minX, _minY, _maxX, _maxY;

            // Children nodes (NW, NE, SW, SE)
            private QuadtreeNode? _northWest;
            private QuadtreeNode? _northEast;
            private QuadtreeNode? _southWest;
            private QuadtreeNode? _southEast;

            // Chunks in this node
            private readonly HashSet<T> _chunks = new();

            // Maximum number of chunks before splitting
            private const int MAX_CHUNKS = 8;

            /// <summary>
            /// Initializes a new instance of the QuadtreeNode class.
            /// </summary>
            public QuadtreeNode(int minX, int minY, int maxX, int maxY)
            {
                _minX = minX;
                _minY = minY;
                _maxX = maxX;
                _maxY = maxY;
            }

            /// <summary>
            /// Inserts a chunk into the quadtree.
            /// </summary>
            public void Insert(T chunk)
            {
                // Check if the chunk is within this node's boundary
                if (!Intersects(chunk))
                    return;

                // If we haven't split yet and have room, add the chunk to this node
                if (_northWest == null && _chunks.Count < MAX_CHUNKS)
                {
                    _chunks.Add(chunk);
                    return;
                }

                // If we haven't split yet but need to, split the node
                if (_northWest == null)
                {
                    Split();
                }

                // Try to insert the chunk into the children
                _northWest?.Insert(chunk);
                _northEast?.Insert(chunk);
                _southWest?.Insert(chunk);
                _southEast?.Insert(chunk);
            }

            /// <summary>
            /// Removes a chunk from the quadtree.
            /// </summary>
            public bool Remove(T chunk)
            {
                // Check if the chunk is within this node's boundary
                if (!Intersects(chunk))
                    return false;

                // Try to remove from this node
                bool removed = _chunks.Remove(chunk);

                // If we have children, try to remove from them too
                if (_northWest != null)
                {
                    removed |= _northWest.Remove(chunk);
                    removed |= _northEast!.Remove(chunk);
                    removed |= _southWest!.Remove(chunk);
                    removed |= _southEast!.Remove(chunk);
                }

                return removed;
            }

            /// <summary>
            /// Queries the quadtree for chunks in a region.
            /// </summary>
            public void Query(int minX, int minY, int maxX, int maxY, HashSet<T> result)
            {
                // Check if the query region intersects this node
                if (maxX < _minX || minX > _maxX || maxY < _minY || minY > _maxY)
                    return;

                // Add chunks from this node that intersect the query region
                foreach (var chunk in _chunks)
                {
                    if (chunk.X <= maxX && chunk.X + chunk.Width >= minX &&
                        chunk.Y <= maxY && chunk.Y + chunk.Height >= minY)
                    {
                        result.Add(chunk);
                    }
                }

                // If we have children, query them too
                if (_northWest != null)
                {
                    _northWest.Query(minX, minY, maxX, maxY, result);
                    _northEast!.Query(minX, minY, maxX, maxY, result);
                    _southWest!.Query(minX, minY, maxX, maxY, result);
                    _southEast!.Query(minX, minY, maxX, maxY, result);
                }
            }

            /// <summary>
            /// Splits this node into four children.
            /// </summary>
            private void Split()
            {
                int midX = (_minX + _maxX) / 2;
                int midY = (_minY + _maxY) / 2;

                // Don't split if the node is too small
                if (midX == _minX || midY == _minY)
                    return;

                _northWest = new QuadtreeNode(_minX, _minY, midX, midY);
                _northEast = new QuadtreeNode(midX, _minY, _maxX, midY);
                _southWest = new QuadtreeNode(_minX, midY, midX, _maxY);
                _southEast = new QuadtreeNode(midX, midY, _maxX, _maxY);

                // Redistribute chunks to children
                var chunksToRedistribute = new List<T>(_chunks);
                _chunks.Clear();

                foreach (var chunk in chunksToRedistribute)
                {
                    Insert(chunk);
                }
            }

            /// <summary>
            /// Checks if a chunk intersects with this node's boundary.
            /// </summary>
            private bool Intersects(T chunk)
            {
                return chunk.X <= _maxX &&
                       chunk.X + chunk.Width >= _minX &&
                       chunk.Y <= _maxY &&
                       chunk.Y + chunk.Height >= _minY;
            }

            /// <summary>
            /// Clears all chunks from this node and its children.
            /// </summary>
            public void Clear()
            {
                _chunks.Clear();

                if (_northWest != null)
                {
                    _northWest.Clear();
                    _northEast!.Clear();
                    _southWest!.Clear();
                    _southEast!.Clear();

                    _northWest = null;
                    _northEast = null;
                    _southWest = null;
                    _southEast = null;
                }
            }
        }
    }

    /// <summary>
    /// Provides a spatial index for 3D chunks.
    /// </summary>
    public class SpatialChunkIndex3D<T> where T : IChunk3D
    {
        // Grid-based spatial index for fast lookups
        private readonly Dictionary<(int, int, int), HashSet<T>> _grid = new();

        // All chunks in the index for iteration
        private readonly HashSet<T> _allChunks = new();

        // Lock for thread safety
        private readonly object _lock = new();

        /// <summary>
        /// Adds a chunk to the spatial index.
        /// </summary>
        /// <param name="chunk">The chunk to add</param>
        public void AddChunk(T chunk)
        {
            if (chunk == null)
                throw new ArgumentNullException(nameof(chunk));

            lock (_lock)
            {
                // Add to all chunks set
                _allChunks.Add(chunk);

                // Add to grid cells
                var key = (chunk.X, chunk.Y, chunk.Z);
                if (!_grid.TryGetValue(key, out var chunks))
                {
                    chunks = new HashSet<T>();
                    _grid[key] = chunks;
                }
                chunks.Add(chunk);
            }
        }

        /// <summary>
        /// Removes a chunk from the spatial index.
        /// </summary>
        /// <param name="chunk">The chunk to remove</param>
        /// <returns>True if the chunk was found and removed, false otherwise</returns>
        public bool RemoveChunk(T chunk)
        {
            if (chunk == null)
                throw new ArgumentNullException(nameof(chunk));

            lock (_lock)
            {
                // Remove from all chunks set
                bool removed = _allChunks.Remove(chunk);

                // Remove from grid cells
                var key = (chunk.X, chunk.Y, chunk.Z);
                if (_grid.TryGetValue(key, out var chunks))
                {
                    chunks.Remove(chunk);
                    if (chunks.Count == 0)
                    {
                        _grid.Remove(key);
                    }
                }

                return removed;
            }
        }

        /// <summary>
        /// Finds all chunks within a rectangular region.
        /// </summary>
        /// <param name="minX">Minimum X coordinate</param>
        /// <param name="minY">Minimum Y coordinate</param>
        /// <param name="minZ">Minimum Z coordinate</param>
        /// <param name="maxX">Maximum X coordinate</param>
        /// <param name="maxY">Maximum Y coordinate</param>
        /// <param name="maxZ">Maximum Z coordinate</param>
        /// <returns>All chunks that intersect with the region</returns>
        public IEnumerable<T> FindChunksInRegion(int minX, int minY, int minZ, int maxX, int maxY, int maxZ)
        {
            lock (_lock)
            {
                var result = new HashSet<T>();

                // Check each grid cell that overlaps with the region
                for (int x = minX; x <= maxX; x++)
                {
                    for (int y = minY; y <= maxY; y++)
                    {
                        for (int z = minZ; z <= maxZ; z++)
                        {
                            var key = (x, y, z);
                            if (_grid.TryGetValue(key, out var chunks))
                            {
                                foreach (var chunk in chunks)
                                {
                                    result.Add(chunk);
                                }
                            }
                        }
                    }
                }

                return result;
            }
        }

        /// <summary>
        /// Finds all chunks within a specified distance from a point.
        /// </summary>
        /// <param name="centerX">Center X coordinate</param>
        /// <param name="centerY">Center Y coordinate</param>
        /// <param name="centerZ">Center Z coordinate</param>
        /// <param name="radius">Radius to search within</param>
        /// <returns>All chunks that are within the radius of the point</returns>
        public IEnumerable<T> FindChunksInRadius(int centerX, int centerY, int centerZ, float radius)
        {
            lock (_lock)
            {
                var result = new HashSet<T>();
                float radiusSquared = radius * radius;

                // Calculate the bounding box of the sphere
                int minX = (int)(centerX - radius);
                int minY = (int)(centerY - radius);
                int minZ = (int)(centerZ - radius);
                int maxX = (int)(centerX + radius);
                int maxY = (int)(centerY + radius);
                int maxZ = (int)(centerZ + radius);

                // Check each grid cell that might overlap with the sphere
                for (int x = minX; x <= maxX; x++)
                {
                    for (int y = minY; y <= maxY; y++)
                    {
                        for (int z = minZ; z <= maxZ; z++)
                        {
                            var key = (x, y, z);
                            if (_grid.TryGetValue(key, out var chunks))
                            {
                                foreach (var chunk in chunks)
                                {
                                    // Calculate distance from chunk center to query center
                                    float dx = chunk.X - centerX;
                                    float dy = chunk.Y - centerY;
                                    float dz = chunk.Z - centerZ;
                                    float distanceSquared = dx * dx + dy * dy + dz * dz;

                                    // Check if the chunk is within the radius
                                    if (distanceSquared <= radiusSquared)
                                    {
                                        result.Add(chunk);
                                    }
                                }
                            }
                        }
                    }
                }

                return result;
            }
        }

        /// <summary>
        /// Finds the nearest chunk to a point.
        /// </summary>
        /// <param name="x">X coordinate</param>
        /// <param name="y">Y coordinate</param>
        /// <param name="z">Z coordinate</param>
        /// <returns>The nearest chunk, or null if no chunks are in the index</returns>
        public T? FindNearestChunk(int x, int y, int z)
        {
            lock (_lock)
            {
                if (_allChunks.Count == 0)
                    return default;

                T? nearest = default;
                float nearestDistanceSquared = float.MaxValue;

                foreach (var chunk in _allChunks)
                {
                    float dx = chunk.X - x;
                    float dy = chunk.Y - y;
                    float dz = chunk.Z - z;
                    float distanceSquared = dx * dx + dy * dy + dz * dz;

                    if (distanceSquared < nearestDistanceSquared)
                    {
                        nearestDistanceSquared = distanceSquared;
                        nearest = chunk;
                    }
                }

                return nearest;
            }
        }

        /// <summary>
        /// Gets all chunks in the index.
        /// </summary>
        public IEnumerable<T> GetAllChunks()
        {
            lock (_lock)
            {
                return _allChunks.ToList();
            }
        }

        /// <summary>
        /// Clears all chunks from the index.
        /// </summary>
        public void Clear()
        {
            lock (_lock)
            {
                _allChunks.Clear();
                _grid.Clear();
            }
        }

        /// <summary>
        /// Gets the number of chunks in the index.
        /// </summary>
        public int Count
        {
            get
            {
                lock (_lock)
                {
                    return _allChunks.Count;
                }
            }
        }

        /// <summary>
        /// Checks if a chunk is in the index.
        /// </summary>
        /// <param name="chunk">The chunk to check</param>
        /// <returns>True if the chunk is in the index, false otherwise</returns>
        public bool Contains(T chunk)
        {
            if (chunk == null)
                throw new ArgumentNullException(nameof(chunk));

            lock (_lock)
            {
                return _allChunks.Contains(chunk);
            }
        }

        /// <summary>
        /// Updates the position of a chunk in the spatial index.
        /// </summary>
        /// <param name="chunk">The chunk to update</param>
        /// <param name="oldX">The old X coordinate</param>
        /// <param name="oldY">The old Y coordinate</param>
        /// <param name="oldZ">The old Z coordinate</param>
        /// <returns>True if the chunk was found and updated, false otherwise</returns>
        public bool UpdateChunkPosition(T chunk, int oldX, int oldY, int oldZ)
        {
            if (chunk == null)
                throw new ArgumentNullException(nameof(chunk));

            lock (_lock)
            {
                // Remove from old grid cell
                var oldKey = (oldX, oldY, oldZ);
                bool removed = false;

                if (_grid.TryGetValue(oldKey, out var chunks))
                {
                    removed = chunks.Remove(chunk);
                    if (chunks.Count == 0)
                    {
                        _grid.Remove(oldKey);
                    }
                }

                if (!removed)
                    return false;

                // Add to new grid cell
                var newKey = (chunk.X, chunk.Y, chunk.Z);
                if (!_grid.TryGetValue(newKey, out var newChunks))
                {
                    newChunks = new HashSet<T>();
                    _grid[newKey] = newChunks;
                }
                newChunks.Add(chunk);

                return true;
            }
        }

        /// <summary>
        /// Performs a spatial query using a custom filter.
        /// </summary>
        /// <param name="filter">The filter function to apply to each chunk</param>
        /// <returns>All chunks that pass the filter</returns>
        public IEnumerable<T> Query(Func<T, bool> filter)
        {
            if (filter == null)
                throw new ArgumentNullException(nameof(filter));

            lock (_lock)
            {
                return _allChunks.Where(filter).ToList();
            }
        }

        /// <summary>
        /// Finds all chunks that intersect with a given chunk.
        /// </summary>
        /// <param name="chunk">The chunk to check for intersections</param>
        /// <returns>All chunks that intersect with the given chunk</returns>
        public IEnumerable<T> FindIntersectingChunks(IChunk3D chunk)
        {
            if (chunk == null)
                throw new ArgumentNullException(nameof(chunk));

            return FindChunksInRegion(
                chunk.X,
                chunk.Y,
                chunk.Z,
                chunk.X + chunk.Width - 1,
                chunk.Y + chunk.Height - 1,
                chunk.Z + chunk.Depth - 1);
        }

        /// <summary>
        /// Finds all chunks that are neighbors of a given chunk.
        /// </summary>
        /// <param name="chunk">The chunk to find neighbors for</param>
        /// <param name="includeDiagonals">Whether to include diagonal neighbors</param>
        /// <param name="includeEdges">Whether to include edge neighbors</param>
        /// <returns>All neighboring chunks</returns>
        public IEnumerable<T> FindNeighbors(IChunk3D chunk, bool includeDiagonals = false, bool includeEdges = true)
        {
            if (chunk == null)
                throw new ArgumentNullException(nameof(chunk));

            lock (_lock)
            {
                var result = new HashSet<T>();

                // Check the six adjacent cells (faces)
                CheckNeighbor(chunk.X - 1, chunk.Y, chunk.Z, result);
                CheckNeighbor(chunk.X + 1, chunk.Y, chunk.Z, result);
                CheckNeighbor(chunk.X, chunk.Y - 1, chunk.Z, result);
                CheckNeighbor(chunk.X, chunk.Y + 1, chunk.Z, result);
                CheckNeighbor(chunk.X, chunk.Y, chunk.Z - 1, result);
                CheckNeighbor(chunk.X, chunk.Y, chunk.Z + 1, result);

                // Check edge neighbors if requested
                if (includeEdges)
                {
                    // X-Y edges
                    CheckNeighbor(chunk.X - 1, chunk.Y - 1, chunk.Z, result);
                    CheckNeighbor(chunk.X + 1, chunk.Y - 1, chunk.Z, result);
                    CheckNeighbor(chunk.X - 1, chunk.Y + 1, chunk.Z, result);
                    CheckNeighbor(chunk.X + 1, chunk.Y + 1, chunk.Z, result);

                    // X-Z edges
                    CheckNeighbor(chunk.X - 1, chunk.Y, chunk.Z - 1, result);
                    CheckNeighbor(chunk.X + 1, chunk.Y, chunk.Z - 1, result);
                    CheckNeighbor(chunk.X - 1, chunk.Y, chunk.Z + 1, result);
                    CheckNeighbor(chunk.X + 1, chunk.Y, chunk.Z + 1, result);

                    // Y-Z edges
                    CheckNeighbor(chunk.X, chunk.Y - 1, chunk.Z - 1, result);
                    CheckNeighbor(chunk.X, chunk.Y + 1, chunk.Z - 1, result);
                    CheckNeighbor(chunk.X, chunk.Y - 1, chunk.Z + 1, result);
                    CheckNeighbor(chunk.X, chunk.Y + 1, chunk.Z + 1, result);
                }

                // Check diagonal corners if requested
                if (includeDiagonals)
                {
                    CheckNeighbor(chunk.X - 1, chunk.Y - 1, chunk.Z - 1, result);
                    CheckNeighbor(chunk.X + 1, chunk.Y - 1, chunk.Z - 1, result);
                    CheckNeighbor(chunk.X - 1, chunk.Y + 1, chunk.Z - 1, result);
                    CheckNeighbor(chunk.X + 1, chunk.Y + 1, chunk.Z - 1, result);
                    CheckNeighbor(chunk.X - 1, chunk.Y - 1, chunk.Z + 1, result);
                    CheckNeighbor(chunk.X + 1, chunk.Y - 1, chunk.Z + 1, result);
                    CheckNeighbor(chunk.X - 1, chunk.Y + 1, chunk.Z + 1, result);
                    CheckNeighbor(chunk.X + 1, chunk.Y + 1, chunk.Z + 1, result);
                }

                return result;
            }
        }

        /// <summary>
        /// Helper method to check for chunks at a specific grid cell.
        /// </summary>
        private void CheckNeighbor(int x, int y, int z, HashSet<T> result)
        {
            var key = (x, y, z);
            if (_grid.TryGetValue(key, out var chunks))
            {
                foreach (var chunk in chunks)
                {
                    result.Add(chunk);
                }
            }
        }

        /// <summary>
        /// Performs a spatial operation on chunks in parallel.
        /// </summary>
        /// <param name="operation">The operation to perform on each chunk</param>
        /// <param name="filter">Optional filter to apply before processing</param>
        /// <param name="maxDegreeOfParallelism">Maximum degree of parallelism (null for default)</param>
        /// <returns>A task that completes when all operations are done</returns>
        public Task ProcessChunksParallelAsync(
            Action<T> operation,
            Func<T, bool>? filter = null,
            int? maxDegreeOfParallelism = null)
        {
            if (operation == null)
                throw new ArgumentNullException(nameof(operation));

            IEnumerable<T> chunksToProcess;

            lock (_lock)
            {
                chunksToProcess = filter != null
                    ? _allChunks.Where(filter).ToList()
                    : _allChunks.ToList();
            }

            return ChunkTaskScheduler.RunBatchParallelAsync(
                chunksToProcess.Select(chunk => new Action(() => operation(chunk))).ToArray(),
                maxDegreeOfParallelism);
        }
    }

    /// <summary>
    /// Interface for 3D chunks.
    /// </summary>
    public interface IChunk3D : IChunk
    {
        /// <summary>
        /// The chunk's Z position in chunk coordinates.
        /// </summary>
        int Z { get; }

        /// <summary>
        /// The depth of the chunk (in cells/tiles/units).
        /// </summary>
        int Depth { get; }
    }
}
