using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using UnityEngine;

namespace InfiniteWorld.VoxelWorld
{
    public sealed partial class VoxelWorldGenerator : MonoBehaviour
    {
        [Header("References")]
        [SerializeField] private Transform streamTarget;
        [SerializeField] private VoxelWorldConfig config;
        [SerializeField] private Shader _terrainShader;

        private readonly Dictionary<Vector2Int, ChunkRuntime> chunks = new Dictionary<Vector2Int, ChunkRuntime>();
        private readonly Dictionary<Vector2Int, RegionRuntime> regions = new Dictionary<Vector2Int, RegionRuntime>();
        private readonly Dictionary<Vector2Int, int> regionVersions = new Dictionary<Vector2Int, int>();
        private readonly Queue<ChunkBuildResult> completedBuilds = new Queue<ChunkBuildResult>();
        private readonly Queue<RegionBuildResult> completedRegionBuilds = new Queue<RegionBuildResult>();
        private readonly Queue<PendingColliderMeshApply> pendingColliderApplies = new Queue<PendingColliderMeshApply>();
        private readonly Queue<Vector2Int> dirtyChunkMeshes = new Queue<Vector2Int>();
        private readonly HashSet<Vector2Int> queuedChunkMeshes = new HashSet<Vector2Int>();
        private readonly Queue<Vector2Int> dirtyRegions = new Queue<Vector2Int>();
        private readonly HashSet<Vector2Int> queuedRegions = new HashSet<Vector2Int>();
        private readonly Queue<Vector2Int> pendingNeighborRefreshes = new Queue<Vector2Int>();
        private readonly HashSet<Vector2Int> queuedNeighborRefreshes = new HashSet<Vector2Int>();
        private readonly object generationLock = new object();
        private readonly object regionBuildLock = new object();

        private Transform chunkRoot;
        private Transform regionRoot;
        private Vector2Int lastGeneratedCenter = new Vector2Int(int.MinValue, int.MinValue);
        private int activeGenerationJobs;
        private int nextChunkRuntimeId;
        private int generationSession;
        private int regionRebuildSession;
        private VoxelWorldAtlas atlas;
        private int atlasBiomeCount;
        private bool regionRebuildLoopRunning;
        private VoxelWorldResolvedSettings settings = VoxelWorldResolvedSettings.Default;

        private int chunkSize => settings.ChunkSize;
        private int generationRadius => settings.GenerationRadius;
        private int blockingGenerationRadius => settings.BlockingGenerationRadius;
        private int seed => settings.Seed;
        private int maxMountainHeight => settings.MaxMountainHeight;
        private float macroNoiseScale => settings.MacroNoiseScale;
        private float detailNoiseScale => settings.DetailNoiseScale;
        private float ridgeNoiseScale => settings.RidgeNoiseScale;
        private float wallThreshold => settings.WallThreshold;
        private float rockBias => settings.RockBias;
        private int smoothingPasses => settings.SmoothingPasses;
        private float passNoiseScale => settings.PassNoiseScale;
        private float passDetailScale => settings.PassDetailScale;
        private float passThreshold => settings.PassThreshold;
        private float passFeather => settings.PassFeather;
        private float heightNoiseScale => settings.HeightNoiseScale;
        private float terraceNoiseScale => settings.TerraceNoiseScale;
        private float heightBias => settings.HeightBias;
        private IReadOnlyList<VoxelBiomeProfile> biomeProfiles => settings.BiomeProfiles;
        private float biomeNoiseScale => settings.BiomeNoiseScale;
        private float biomeSize => settings.BiomeSize;
        private int maxAsyncChunkJobs => settings.MaxAsyncChunkJobs;
        private int maxChunkBuildsPerFrame => settings.MaxChunkBuildsPerFrame;
        private int maxChunkMeshBuildsPerFrame => settings.MaxChunkMeshBuildsPerFrame;
        private int maxColliderAppliesPerFrame => settings.MaxColliderAppliesPerFrame;
        private int maxNeighborRefreshesPerFrame => settings.MaxNeighborRefreshesPerFrame;
        private int renderRegionSizeInChunks => settings.RenderRegionSizeInChunks;
        private int maxRegionBuildsPerFrame => settings.MaxRegionBuildsPerFrame;

        private void Awake()
        {
            generationSession++;
            regionRebuildSession++;
            EnsureRuntimeData();
            EnsureChunkRoot();
            EnsureRegionRoot();
            EnsurePlacementRoot();
            TryResolveStreamTarget();
        }

        private void Update()
        {
            EnsureRuntimeData();
            EnsureChunkRoot();
            EnsureRegionRoot();
            EnsurePlacementRoot();
            if (!TryResolveStreamTarget())
            {
                return;
            }

            Vector2Int centerChunk = WorldToChunk(streamTarget.position);
            if (centerChunk != lastGeneratedCenter)
            {
                lastGeneratedCenter = centerChunk;
                UnloadDistantChunks(centerChunk);
            }

            DrainCompletedBuilds(maxChunkBuildsPerFrame);
            DrainDirtyChunkMeshes(maxChunkMeshBuildsPerFrame);
            DrainPendingColliderApplies(maxColliderAppliesPerFrame);
            DrainNeighborRefreshes(maxNeighborRefreshesPerFrame);
            DrainCompletedRegionBuilds(maxRegionBuildsPerFrame);
            ScheduleChunkGeneration(centerChunk);
            EnsureBlockingChunksLoaded(centerChunk);
        }

        private void OnDisable()
        {
            generationSession++;
            regionRebuildSession++;
            lock (generationLock)
            {
                completedBuilds.Clear();
                activeGenerationJobs = 0;
            }
            lock (regionBuildLock)
            {
                completedRegionBuilds.Clear();
            }

            CleanupChunks();
            CleanupRegions();
            CleanupPlacementPlans();
            CleanupSpawnedPlacements();
            atlas?.Dispose();
            atlas = null;
        }

        private void EnsureRuntimeData()
        {
            settings = VoxelWorldResolvedSettings.Resolve(config);
            ValidatePlacementCollectionsIfNeeded();
            int configuredBiomeCount = CountConfiguredBiomes();

            if (atlas != null && atlasBiomeCount == configuredBiomeCount)
            {
                return;
            }

            atlas?.Dispose();
            atlas = VoxelWorldAtlas.CreateRuntimeAtlas(biomeProfiles, _terrainShader);
            atlasBiomeCount = atlas.BiomeCount;
            RefreshRegionMaterials();
        }

        private void EnsureChunkRoot()
        {
            if (chunkRoot != null)
            {
                return;
            }

            Transform existing = transform.Find("VoxelChunks");
            if (existing != null)
            {
                chunkRoot = existing;
                return;
            }

            GameObject root = new GameObject("VoxelChunks");
            root.transform.SetParent(transform, false);
            chunkRoot = root.transform;
        }

        private void EnsureRegionRoot()
        {
            if (regionRoot != null)
            {
                return;
            }

            Transform existing = transform.Find("VoxelRenderRegions");
            if (existing != null)
            {
                regionRoot = existing;
                return;
            }

            GameObject root = new GameObject("VoxelRenderRegions");
            root.transform.SetParent(transform, false);
            regionRoot = root.transform;
        }

        private void RefreshRegionMaterials()
        {
            if (atlas == null)
            {
                return;
            }

            foreach (KeyValuePair<Vector2Int, RegionRuntime> pair in regions)
            {
                if (pair.Value.Renderer != null)
                {
                    pair.Value.Renderer.sharedMaterial = atlas.Material;
                }
            }
        }

        private bool TryResolveStreamTarget()
        {
            if (streamTarget != null)
            {
                return true;
            }

            Camera mainCamera = Camera.main;
            if (mainCamera == null)
            {
                return false;
            }

            streamTarget = mainCamera.transform;
            return true;
        }

        private void ScheduleChunkGeneration(Vector2Int centerChunk)
        {
            List<Vector2Int> coords = GetCoordsByPriority(centerChunk, generationRadius);
            for (int i = 0; i < coords.Count; i++)
            {
                Vector2Int coord = coords[i];
                ChunkRuntime runtime = GetOrCreateChunkRuntime(coord);
                if (runtime.IsRendered || runtime.HasData || runtime.State == ChunkState.Generating)
                {
                    continue;
                }

                if (IsWithinRadius(coord, centerChunk, blockingGenerationRadius))
                {
                    continue;
                }

                if (!TryReserveGenerationSlot())
                {
                    break;
                }

                runtime.State = ChunkState.Generating;
                runtime.Version++;
                GenerateChunkDataAsync(coord, runtime.Version, generationSession, runtime.RuntimeId).Forget();
            }
        }

        private void EnsureBlockingChunksLoaded(Vector2Int centerChunk)
        {
            List<Vector2Int> requiredCoords = GetCoordsByPriority(centerChunk, blockingGenerationRadius);
            for (int i = 0; i < requiredCoords.Count; i++)
            {
                Vector2Int coord = requiredCoords[i];
                ChunkRuntime runtime = GetOrCreateChunkRuntime(coord);
                if (runtime.IsRendered || runtime.HasData || runtime.State == ChunkState.Generating || runtime.State == ChunkState.SyncBuilding)
                {
                    continue;
                }

                runtime.Version++;
                runtime.State = ChunkState.SyncBuilding;
                if (ApplyBuildResult(GenerateChunkData(coord, runtime.Version, generationSession, runtime.RuntimeId)))
                {
                    EnqueueChunkMeshBuild(coord);
                    QueueNeighborRefresh(coord);
                }
            }
        }

        private void UnloadDistantChunks(Vector2Int centerChunk)
        {
            List<Vector2Int> coordsToRemove = new List<Vector2Int>();
            foreach (KeyValuePair<Vector2Int, ChunkRuntime> pair in chunks)
            {
                if (IsWithinRadius(pair.Key, centerChunk, generationRadius))
                {
                    continue;
                }

                coordsToRemove.Add(pair.Key);
            }

            for (int i = 0; i < coordsToRemove.Count; i++)
            {
                Vector2Int coord = coordsToRemove[i];
                if (!chunks.TryGetValue(coord, out ChunkRuntime runtime))
                {
                    continue;
                }

                Vector2Int regionCoord = ChunkToRegion(coord);
                MarkRegionDirty(coord);
                chunks.Remove(coord);
                lock (placementPlanLock)
                {
                    chunkPlacementPlans.Remove(coord);
                }
                DespawnChunkPlacements(coord);
                runtime.Dispose();
                TryDisposeRegionIfEmpty(regionCoord);
                QueueNeighborRefresh(coord);
            }
        }

        private Vector2Int WorldToChunk(Vector3 position)
        {
            return new Vector2Int(
                Mathf.FloorToInt(position.x / chunkSize),
                Mathf.FloorToInt(position.z / chunkSize));
        }

        private async UniTaskVoid GenerateChunkDataAsync(Vector2Int coord, int version, int session, int runtimeId)
        {
            try
            {
                ChunkBuildResult result = await UniTask.RunOnThreadPool(() => GenerateChunkData(coord, version, session, runtimeId));
                lock (generationLock)
                {
                    if (session == generationSession)
                    {
                        completedBuilds.Enqueue(result);
                        activeGenerationJobs = Mathf.Max(0, activeGenerationJobs - 1);
                    }
                }
            }
            catch (Exception exception)
            {
                lock (generationLock)
                {
                    if (session == generationSession)
                    {
                        activeGenerationJobs = Mathf.Max(0, activeGenerationJobs - 1);
                    }
                }

                if (session == generationSession && chunks.TryGetValue(coord, out ChunkRuntime runtime) && runtime.Version == version && runtime.RuntimeId == runtimeId)
                {
                    runtime.State = ChunkState.None;
                }

                Debug.LogException(exception, this);
            }
        }

        private void DrainCompletedBuilds(int maxBuilds)
        {
            int builds = 0;
            while (builds < maxBuilds)
            {
                ChunkBuildResult result;
                lock (generationLock)
                {
                    if (completedBuilds.Count == 0)
                    {
                        break;
                    }

                    result = completedBuilds.Dequeue();
                }

                if (!ApplyBuildResult(result))
                {
                    continue;
                }

                EnqueueChunkMeshBuild(result.Coord);
                QueueNeighborRefresh(result.Coord);
                builds++;
            }
        }

        private void DrainDirtyChunkMeshes(int maxBuilds)
        {
            int builds = 0;
            while (builds < maxBuilds)
            {
                if (dirtyChunkMeshes.Count == 0)
                {
                    break;
                }

                Vector2Int coord = dirtyChunkMeshes.Dequeue();
                queuedChunkMeshes.Remove(coord);

                if (!chunks.TryGetValue(coord, out ChunkRuntime runtime) || !runtime.HasData)
                {
                    continue;
                }

                RenderChunk(coord);
                builds++;
            }
        }

        private void EnqueueChunkMeshBuild(Vector2Int coord)
        {
            if (!queuedChunkMeshes.Add(coord))
            {
                return;
            }

            dirtyChunkMeshes.Enqueue(coord);
        }

        private void EnqueueColliderApply(Vector2Int coord, int version, int runtimeId, Mesh colliderMesh)
        {
            pendingColliderApplies.Enqueue(new PendingColliderMeshApply(coord, version, runtimeId, colliderMesh));
        }

        private void DrainPendingColliderApplies(int maxApplies)
        {
            int applies = 0;
            while (applies < maxApplies)
            {
                if (pendingColliderApplies.Count == 0)
                {
                    break;
                }

                PendingColliderMeshApply pending = pendingColliderApplies.Dequeue();
                if (!chunks.TryGetValue(pending.Coord, out ChunkRuntime runtime) || runtime.Version != pending.Version || runtime.RuntimeId != pending.RuntimeId)
                {
                    DestroyMeshAsset(pending.ColliderMesh);
                    applies++;
                    continue;
                }

                runtime.ApplyColliderMesh(pending.ColliderMesh);
                applies++;
            }
        }

        private void QueueNeighborRefresh(Vector2Int coord)
        {
            if (!queuedNeighborRefreshes.Add(coord))
            {
                return;
            }

            pendingNeighborRefreshes.Enqueue(coord);
        }

        private void DrainNeighborRefreshes(int maxRefreshes)
        {
            int refreshes = 0;
            while (refreshes < maxRefreshes)
            {
                if (pendingNeighborRefreshes.Count == 0)
                {
                    break;
                }

                Vector2Int coord = pendingNeighborRefreshes.Dequeue();
                queuedNeighborRefreshes.Remove(coord);
                RefreshNeighborBorders(coord);
                refreshes++;
            }
        }

        private void RefreshNeighborBorders(Vector2Int coord)
        {
            TryRenderNeighbor(coord + Vector2Int.up);
            TryRenderNeighbor(coord + Vector2Int.right);
            TryRenderNeighbor(coord + Vector2Int.down);
            TryRenderNeighbor(coord + Vector2Int.left);
        }

        private void TryRenderNeighbor(Vector2Int coord)
        {
            if (!chunks.TryGetValue(coord, out ChunkRuntime runtime) || !runtime.HasData)
            {
                return;
            }

            EnqueueChunkMeshBuild(coord);
        }

        private void MarkRegionDirty(Vector2Int chunkCoord)
        {
            Vector2Int regionCoord = ChunkToRegion(chunkCoord);
            regionVersions.TryGetValue(regionCoord, out int version);
            regionVersions[regionCoord] = version + 1;

            if (!queuedRegions.Add(regionCoord))
            {
                return;
            }

            dirtyRegions.Enqueue(regionCoord);
            EnsureRegionRebuildLoop();
        }

        private void EnsureRegionRebuildLoop()
        {
            if (regionRebuildLoopRunning)
            {
                return;
            }

            regionRebuildLoopRunning = true;
            ProcessDirtyRegionsAsync(regionRebuildSession).Forget();
        }

        private async UniTaskVoid ProcessDirtyRegionsAsync(int session)
        {
            try
            {
                while (dirtyRegions.Count > 0)
                {
                    if (!this || !isActiveAndEnabled || session != regionRebuildSession)
                    {
                        break;
                    }

                    int buildsThisFrame = 0;
                    while (buildsThisFrame < maxRegionBuildsPerFrame && dirtyRegions.Count > 0)
                    {
                        Vector2Int regionCoord = dirtyRegions.Dequeue();
                        queuedRegions.Remove(regionCoord);

                        if (!TryCaptureRegionBuildRequest(regionCoord, out RegionBuildRequest request))
                        {
                            buildsThisFrame++;
                            continue;
                        }

                        RegionBuildResult result = request.Chunks == null || request.Chunks.Length == 0
                            ? RegionBuildResult.CreateEmpty(request.RegionCoord, request.Version, request.Session)
                            : await UniTask.RunOnThreadPool(() => BuildRegionBuffers(request));

                        if (!IsRegionBuildStillCurrent(result.RegionCoord, result.Version, result.Session))
                        {
                            buildsThisFrame++;
                            continue;
                        }

                        lock (regionBuildLock)
                        {
                            completedRegionBuilds.Enqueue(result);
                        }

                        buildsThisFrame++;
                    }

                    if (dirtyRegions.Count > 0)
                    {
                        await UniTask.Yield(PlayerLoopTiming.Update);
                    }
                }
            }
            finally
            {
                if (session == regionRebuildSession)
                {
                    regionRebuildLoopRunning = false;
                    if (dirtyRegions.Count > 0)
                    {
                        EnsureRegionRebuildLoop();
                    }
                }
            }
        }

        private bool TryCaptureRegionBuildRequest(Vector2Int regionCoord, out RegionBuildRequest request)
        {
            if (atlas == null || regionRoot == null)
            {
                request = new RegionBuildRequest();
                return false;
            }

            Vector2Int baseChunk = new Vector2Int(regionCoord.x * renderRegionSizeInChunks, regionCoord.y * renderRegionSizeInChunks);
            List<RegionChunkSnapshot> chunkSnapshots = new List<RegionChunkSnapshot>(renderRegionSizeInChunks * renderRegionSizeInChunks);

            for (int z = 0; z < renderRegionSizeInChunks; z++)
            {
                for (int x = 0; x < renderRegionSizeInChunks; x++)
                {
                    Vector2Int chunkCoord = new Vector2Int(baseChunk.x + x, baseChunk.y + z);
                    if (!chunks.TryGetValue(chunkCoord, out ChunkRuntime runtime) || runtime.RenderSnapshot == null || runtime.RenderSnapshot.IsEmpty)
                    {
                        continue;
                    }

                    chunkSnapshots.Add(new RegionChunkSnapshot(runtime.RenderSnapshot, new Vector3(x * chunkSize, 0f, z * chunkSize)));
                }
            }

            regionVersions.TryGetValue(regionCoord, out int version);
            request = new RegionBuildRequest(regionCoord, version, generationSession, chunkSnapshots.ToArray());
            return true;
        }

        private bool IsRegionBuildStillCurrent(Vector2Int regionCoord, int version, int session)
        {
            if (session != generationSession)
            {
                return false;
            }

            regionVersions.TryGetValue(regionCoord, out int currentVersion);
            return version == currentVersion;
        }

        private void TryDisposeRegionIfEmpty(Vector2Int regionCoord)
        {
            if (RegionHasVisibleChunks(regionCoord))
            {
                return;
            }

            if (regions.TryGetValue(regionCoord, out RegionRuntime region))
            {
                region.Dispose();
                regions.Remove(regionCoord);
            }

            regionVersions.Remove(regionCoord);
        }

        private bool RegionHasVisibleChunks(Vector2Int regionCoord)
        {
            Vector2Int baseChunk = new Vector2Int(regionCoord.x * renderRegionSizeInChunks, regionCoord.y * renderRegionSizeInChunks);
            for (int z = 0; z < renderRegionSizeInChunks; z++)
            {
                for (int x = 0; x < renderRegionSizeInChunks; x++)
                {
                    Vector2Int chunkCoord = new Vector2Int(baseChunk.x + x, baseChunk.y + z);
                    if (!chunks.TryGetValue(chunkCoord, out ChunkRuntime runtime) || runtime.RenderSnapshot == null || runtime.RenderSnapshot.IsEmpty)
                    {
                        continue;
                    }

                    return true;
                }
            }

            return false;
        }

        private Vector2Int ChunkToRegion(Vector2Int chunkCoord)
        {
            return new Vector2Int(
                Mathf.FloorToInt(chunkCoord.x / (float)renderRegionSizeInChunks),
                Mathf.FloorToInt(chunkCoord.y / (float)renderRegionSizeInChunks));
        }

        private RegionRuntime GetOrCreateRegionRuntime(Vector2Int regionCoord)
        {
            if (!regions.TryGetValue(regionCoord, out RegionRuntime region))
            {
                region = new RegionRuntime();
                regions.Add(regionCoord, region);
            }

            return region;
        }

        private static RegionBuildResult BuildRegionBuffers(RegionBuildRequest request)
        {
            if (request.Chunks == null || request.Chunks.Length == 0)
            {
                return RegionBuildResult.CreateEmpty(request.RegionCoord, request.Version, request.Session);
            }

            int totalVertexCount = 0;
            int totalTriangleCount = 0;
            for (int i = 0; i < request.Chunks.Length; i++)
            {
                ChunkRenderSnapshot snapshot = request.Chunks[i].Snapshot;
                totalVertexCount += snapshot.Vertices.Length;
                totalTriangleCount += snapshot.Triangles.Length;
            }

            if (totalVertexCount == 0 || totalTriangleCount == 0)
            {
                return RegionBuildResult.CreateEmpty(request.RegionCoord, request.Version, request.Session);
            }

            Vector3[] vertices = new Vector3[totalVertexCount];
            Vector3[] normals = new Vector3[totalVertexCount];
            Vector2[] uv0 = new Vector2[totalVertexCount];
            Vector2[] uv1 = new Vector2[totalVertexCount];
            int[] triangles = new int[totalTriangleCount];

            int vertexOffset = 0;
            int triangleOffset = 0;
            Vector3 min = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
            Vector3 max = new Vector3(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity);

            for (int chunkIndex = 0; chunkIndex < request.Chunks.Length; chunkIndex++)
            {
                RegionChunkSnapshot chunk = request.Chunks[chunkIndex];
                ChunkRenderSnapshot snapshot = chunk.Snapshot;
                Vector3 offset = chunk.LocalOffset;

                for (int vertexIndex = 0; vertexIndex < snapshot.Vertices.Length; vertexIndex++)
                {
                    Vector3 vertex = snapshot.Vertices[vertexIndex] + offset;
                    int writeIndex = vertexOffset + vertexIndex;
                    vertices[writeIndex] = vertex;
                    normals[writeIndex] = snapshot.Normals[vertexIndex];
                    uv0[writeIndex] = snapshot.Uv0[vertexIndex];
                    uv1[writeIndex] = snapshot.Uv1[vertexIndex];

                    min = Vector3.Min(min, vertex);
                    max = Vector3.Max(max, vertex);
                }

                for (int triangleIndex = 0; triangleIndex < snapshot.Triangles.Length; triangleIndex++)
                {
                    triangles[triangleOffset + triangleIndex] = snapshot.Triangles[triangleIndex] + vertexOffset;
                }

                vertexOffset += snapshot.Vertices.Length;
                triangleOffset += snapshot.Triangles.Length;
            }

            Bounds bounds = new Bounds((min + max) * 0.5f, max - min);
            return new RegionBuildResult(request.RegionCoord, request.Version, request.Session, vertices, normals, uv0, uv1, triangles, bounds);
        }

        private void DrainCompletedRegionBuilds(int maxBuilds)
        {
            int builds = 0;
            while (builds < maxBuilds)
            {
                RegionBuildResult result;
                lock (regionBuildLock)
                {
                    if (completedRegionBuilds.Count == 0)
                    {
                        break;
                    }

                    result = completedRegionBuilds.Dequeue();
                }

                ApplyRegionBuildResult(result);
                builds++;
            }
        }

        private void ApplyRegionBuildResult(RegionBuildResult result)
        {
            if (result.Session != generationSession || atlas == null || regionRoot == null)
            {
                return;
            }

            regionVersions.TryGetValue(result.RegionCoord, out int currentVersion);
            if (result.Version != currentVersion)
            {
                return;
            }

            if (result.IsEmpty)
            {
                if (regions.TryGetValue(result.RegionCoord, out RegionRuntime regionToRemove))
                {
                    regionToRemove.Dispose();
                    regions.Remove(result.RegionCoord);
                }

                regionVersions.Remove(result.RegionCoord);

                return;
            }

            RegionRuntime region = GetOrCreateRegionRuntime(result.RegionCoord);
            region.EnsureCreated(result.RegionCoord, regionRoot, chunkSize, renderRegionSizeInChunks, atlas.Material);

            Mesh mesh = new Mesh { name = $"Region_{result.RegionCoord.x}_{result.RegionCoord.y}" };
            mesh.indexFormat = result.Vertices.Length > 65535 ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16;
            mesh.vertices = result.Vertices;
            mesh.normals = result.Normals;
            mesh.uv = result.Uv0;
            mesh.uv2 = result.Uv1;
            mesh.triangles = result.Triangles;
            mesh.bounds = result.Bounds;
            region.ApplyMesh(mesh);
        }

        private bool ApplyBuildResult(ChunkBuildResult result)
        {
            if (result.Session != generationSession)
            {
                return false;
            }

            if (!chunks.TryGetValue(result.Coord, out ChunkRuntime runtime))
            {
                return false;
            }

            if (runtime.Version != result.Version)
            {
                return false;
            }

            if (runtime.RuntimeId != result.RuntimeId)
            {
                return false;
            }

            runtime.Heights = result.Heights;
            runtime.BiomeIndices = result.BiomeIndices;
            if (!runtime.IsRendered)
            {
                runtime.State = ChunkState.ReadyToRender;
            }

            TrySpawnChunkPlacements(result.Coord);

            return true;
        }

        private bool TryReserveGenerationSlot()
        {
            lock (generationLock)
            {
                if (activeGenerationJobs >= maxAsyncChunkJobs)
                {
                    return false;
                }

                activeGenerationJobs++;
                return true;
            }
        }

        private List<Vector2Int> GetCoordsByPriority(Vector2Int centerChunk, int radius)
        {
            List<Vector2Int> coords = new List<Vector2Int>((radius * 2 + 1) * (radius * 2 + 1));
            for (int z = -radius; z <= radius; z++)
            {
                for (int x = -radius; x <= radius; x++)
                {
                    coords.Add(new Vector2Int(centerChunk.x + x, centerChunk.y + z));
                }
            }

            coords.Sort((left, right) => CompareChunkPriority(centerChunk, left, right));
            return coords;
        }

        private static int CompareChunkPriority(Vector2Int centerChunk, Vector2Int left, Vector2Int right)
        {
            int leftDx = Mathf.Abs(left.x - centerChunk.x);
            int leftDz = Mathf.Abs(left.y - centerChunk.y);
            int rightDx = Mathf.Abs(right.x - centerChunk.x);
            int rightDz = Mathf.Abs(right.y - centerChunk.y);

            int leftChebyshev = Mathf.Max(leftDx, leftDz);
            int rightChebyshev = Mathf.Max(rightDx, rightDz);
            int chebyshevCompare = leftChebyshev.CompareTo(rightChebyshev);
            if (chebyshevCompare != 0)
            {
                return chebyshevCompare;
            }

            int leftDistance = leftDx * leftDx + leftDz * leftDz;
            int rightDistance = rightDx * rightDx + rightDz * rightDz;
            int distanceCompare = leftDistance.CompareTo(rightDistance);
            if (distanceCompare != 0)
            {
                return distanceCompare;
            }

            int zCompare = left.y.CompareTo(right.y);
            return zCompare != 0 ? zCompare : left.x.CompareTo(right.x);
        }

        private ChunkRuntime GetOrCreateChunkRuntime(Vector2Int coord)
        {
            if (!chunks.TryGetValue(coord, out ChunkRuntime runtime))
            {
                runtime = new ChunkRuntime();
                runtime.RuntimeId = ++nextChunkRuntimeId;
                chunks.Add(coord, runtime);
            }

            return runtime;
        }

        private static bool IsWithinRadius(Vector2Int coord, Vector2Int centerChunk, int radius)
        {
            int dx = Mathf.Abs(coord.x - centerChunk.x);
            int dz = Mathf.Abs(coord.y - centerChunk.y);
            return dx <= radius && dz <= radius;
        }

        private void CleanupChunks()
        {
            foreach (KeyValuePair<Vector2Int, ChunkRuntime> pair in chunks)
            {
                pair.Value.Dispose();
            }

            chunks.Clear();
            CleanupSpawnedPlacements();
            dirtyChunkMeshes.Clear();
            queuedChunkMeshes.Clear();
            pendingNeighborRefreshes.Clear();
            queuedNeighborRefreshes.Clear();
            while (pendingColliderApplies.Count > 0)
            {
                DestroyMeshAsset(pendingColliderApplies.Dequeue().ColliderMesh);
            }
        }

        private static void DestroyMeshAsset(Mesh mesh)
        {
            if (mesh == null)
            {
                return;
            }

            if (Application.isPlaying)
            {
                UnityEngine.Object.Destroy(mesh);
            }
            else
            {
                UnityEngine.Object.DestroyImmediate(mesh);
            }
        }

        private void CleanupRegions()
        {
            foreach (KeyValuePair<Vector2Int, RegionRuntime> pair in regions)
            {
                pair.Value.Dispose();
            }

            regions.Clear();
            regionVersions.Clear();
            dirtyRegions.Clear();
            queuedRegions.Clear();
            lock (regionBuildLock)
            {
                completedRegionBuilds.Clear();
            }
            regionRebuildLoopRunning = false;
        }

        private int CountConfiguredBiomes()
        {
            int count = 0;
            for (int i = 0; i < biomeProfiles.Count; i++)
            {
                if (biomeProfiles[i] != null)
                {
                    count++;
                }
            }

            return Mathf.Max(1, count);
        }

    }
}
