using System.Collections.Generic;
using UnityEngine;

namespace InfiniteWorld.VoxelWorld
{
    public sealed partial class VoxelWorldGenerator
    {
        private readonly Dictionary<Vector2Int, WorldChunkPlacementPlan> chunkPlacementPlans = new Dictionary<Vector2Int, WorldChunkPlacementPlan>();
        private readonly object placementPlanLock = new object();
        private int placementValidationSignature = int.MinValue;
        private IReadOnlyList<WorldPrefabCollectionRuntime> placementCollections => settings.PlacementCollections;

        public WorldChunkPlacementPlan GetChunkPlacementPlan(Vector2Int chunkCoord)
        {
            return GetOrCreateChunkPlacementPlan(chunkCoord);
        }

        public int GetBaseHeightAtWorldCell(Vector2Int worldCell)
        {
            return SampleRock(worldCell) ? SampleHeight(worldCell) : 0;
        }

        public int GetFinalHeightAtWorldCell(Vector2Int worldCell)
        {
            return GetSampledFinalHeightAtWorldCell(worldCell);
        }

        public byte GetBiomeIndexAtWorldCell(Vector2Int worldCell)
        {
            return SampleBiomeIndex(worldCell);
        }

        private WorldChunkPlacementPlan GetOrCreateChunkPlacementPlan(Vector2Int chunkCoord)
        {
            lock (placementPlanLock)
            {
                if (chunkPlacementPlans.TryGetValue(chunkCoord, out WorldChunkPlacementPlan cachedPlan))
                {
                    return cachedPlan;
                }
            }

            WorldChunkPlacementPlan plan = WorldSpawnPlanner.PlanChunk(seed, chunkCoord, chunkSize, placementCollections, GetBaseHeightAtWorldCell);

            lock (placementPlanLock)
            {
                if (chunkPlacementPlans.TryGetValue(chunkCoord, out WorldChunkPlacementPlan cachedPlan))
                {
                    return cachedPlan;
                }

                chunkPlacementPlans[chunkCoord] = plan;
                return plan;
            }
        }

        private int GetSampledFinalHeightAtWorldCell(Vector2Int worldCell)
        {
            int baseHeight = GetBaseHeightAtWorldCell(worldCell);
            Vector2Int chunkCoord = new Vector2Int(
                Mathf.FloorToInt(worldCell.x / (float)chunkSize),
                Mathf.FloorToInt(worldCell.y / (float)chunkSize));
            return GetOrCreateChunkPlacementPlan(chunkCoord).GetFinalHeight(worldCell, baseHeight);
        }

        private void ApplyChunkPlacementHeights(Vector2Int chunkCoord, int[] heights)
        {
            WorldChunkPlacementPlan plan = GetOrCreateChunkPlacementPlan(chunkCoord);
            if (!plan.HasFlattening)
            {
                return;
            }

            Vector2Int chunkOrigin = new Vector2Int(chunkCoord.x * chunkSize, chunkCoord.y * chunkSize);
            for (int z = 0; z < chunkSize; z++)
            {
                for (int x = 0; x < chunkSize; x++)
                {
                    Vector2Int worldCell = new Vector2Int(chunkOrigin.x + x, chunkOrigin.y + z);
                    heights[z * chunkSize + x] = plan.GetFinalHeight(worldCell, heights[z * chunkSize + x]);
                }
            }
        }

        private void CleanupPlacementPlans()
        {
            lock (placementPlanLock)
            {
                chunkPlacementPlans.Clear();
            }
        }

        private void ValidatePlacementCollectionsIfNeeded()
        {
            int signature = ComputePlacementValidationSignature();
            if (signature == placementValidationSignature)
            {
                return;
            }

            placementValidationSignature = signature;
            ValidatePlacementCollections();
        }

        private int ComputePlacementValidationSignature()
        {
            unchecked
            {
                int hash = 17;
                hash = hash * 31 + (config != null ? config.GetInstanceID() : 0);
                if (config == null || config.placementCollections == null)
                {
                    return hash;
                }

                hash = hash * 31 + config.placementCollections.Count;
                for (int collectionIndex = 0; collectionIndex < config.placementCollections.Count; collectionIndex++)
                {
                    WorldPrefabCollection collection = config.placementCollections[collectionIndex];
                    hash = hash * 31 + (collection != null ? collection.GetInstanceID() : 0);
                    if (collection == null)
                    {
                        continue;
                    }

                    hash = hash * 31 + collection.maxPlacementsPerChunk;
                    hash = hash * 31 + collection.attemptsPerPlacement;
                    hash = hash * 31 + collection.chunkEdgePadding;
                    hash = hash * 31 + (collection.entries != null ? collection.entries.Count : 0);
                    if (collection.entries == null)
                    {
                        continue;
                    }

                    for (int entryIndex = 0; entryIndex < collection.entries.Count; entryIndex++)
                    {
                        WorldPrefabEntry entry = collection.entries[entryIndex];
                        hash = hash * 31 + (entry != null ? entry.id?.GetHashCode() ?? 0 : 0);
                        if (entry == null)
                        {
                            continue;
                        }

                        hash = hash * 31 + (entry.prefab != null ? entry.prefab.GetInstanceID() : 0);
                        hash = hash * 31 + entry.weight.GetHashCode();
                        hash = hash * 31 + entry.spawnChancePercent.GetHashCode();
                        hash = hash * 31 + (int)entry.placementMode;
                        hash = hash * 31 + entry.footprint.x;
                        hash = hash * 31 + entry.footprint.y;
                        hash = hash * 31 + entry.clearance;
                        hash = hash * 31 + entry.flattenPadding;
                        hash = hash * 31 + entry.flattenSearchRadius;
                        hash = hash * 31 + (entry.allowRotations ? 1 : 0);
                    }
                }

                return hash;
            }
        }

        private void ValidatePlacementCollections()
        {
            if (config == null || config.placementCollections == null || config.placementCollections.Count == 0)
            {
                return;
            }

            for (int collectionIndex = 0; collectionIndex < config.placementCollections.Count; collectionIndex++)
            {
                WorldPrefabCollection collection = config.placementCollections[collectionIndex];
                string collectionName = collection != null ? collection.name : $"Collection[{collectionIndex}]";
                if (WorldPlacementValidation.TryGetCollectionBlockerReason(collection, out string collectionReason))
                {
                    Debug.LogError($"[VoxelWorld] Collection '{collectionName}' will not spawn: {collectionReason}", collection != null ? collection : this);
                    continue;
                }

                HashSet<string> usedIds = new HashSet<string>();
                for (int entryIndex = 0; entryIndex < collection.entries.Count; entryIndex++)
                {
                    WorldPrefabEntry entry = collection.entries[entryIndex];
                    string entryId = entry != null && !string.IsNullOrWhiteSpace(entry.id) ? entry.id : $"entry #{entryIndex}";
                    if (!WorldPlacementValidation.TryGetEntryBlockerReason(entry, usedIds, out string entryReason))
                    {
                        continue;
                    }

                    Debug.LogError($"[VoxelWorld] Collection '{collectionName}' entry #{entryIndex} ('{entryId}') will not spawn: {entryReason}", collection);
                }
            }
        }
    }
}
