using System.Collections.Generic;
using UnityEngine;

namespace InfiniteWorld.VoxelWorld
{
    public sealed partial class VoxelWorldGenerator
    {
        private readonly Dictionary<Vector2Int, List<SpawnedPlacementRuntime>> spawnedPlacementsByChunk = new Dictionary<Vector2Int, List<SpawnedPlacementRuntime>>();
        private readonly Dictionary<long, SpawnedPlacementRuntime> spawnedPlacementsById = new Dictionary<long, SpawnedPlacementRuntime>();
        private readonly HashSet<long> disabledSpawnIds = new HashSet<long>();

        private Transform placementRoot;

        public IReadOnlyCollection<long> DisabledSpawnIds => disabledSpawnIds;

        public bool IsSpawnDisabled(long spawnId)
        {
            return disabledSpawnIds.Contains(spawnId);
        }

        public bool DisableSpawn(long spawnId)
        {
            if (!disabledSpawnIds.Add(spawnId))
            {
                return false;
            }

            if (spawnedPlacementsById.TryGetValue(spawnId, out SpawnedPlacementRuntime runtime))
            {
                RemoveSpawnedPlacement(runtime);
            }

            return true;
        }

        public bool EnableSpawn(long spawnId)
        {
            return disabledSpawnIds.Remove(spawnId);
        }

        public void ClearDisabledSpawns()
        {
            disabledSpawnIds.Clear();
        }

        private void EnsurePlacementRoot()
        {
            if (placementRoot != null)
            {
                return;
            }

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

            GameObject root = new GameObject("WorldPlacements");
            root.transform.SetParent(transform, false);
            placementRoot = root.transform;
        }

        private void TrySpawnChunkPlacements(Vector2Int chunkCoord)
        {
            EnsurePlacementRoot();
            if (placementRoot == null || spawnedPlacementsByChunk.ContainsKey(chunkCoord))
            {
                return;
            }

            WorldChunkPlacementPlan plan = GetChunkPlacementPlan(chunkCoord);
            IReadOnlyList<WorldSpawnPoint> spawnPoints = plan.SpawnPoints;
            if (spawnPoints == null || spawnPoints.Count == 0)
            {
                return;
            }

            List<SpawnedPlacementRuntime> chunkPlacements = new List<SpawnedPlacementRuntime>(spawnPoints.Count);
            for (int i = 0; i < spawnPoints.Count; i++)
            {
                WorldSpawnPoint spawnPoint = spawnPoints[i];
                if (disabledSpawnIds.Contains(spawnPoint.SpawnId) || spawnedPlacementsById.ContainsKey(spawnPoint.SpawnId))
                {
                    continue;
                }

                if (!TryResolveSpawnPrefab(spawnPoint, out GameObject prefab) || prefab == null)
                {
                    continue;
                }

                GameObject instance = Object.Instantiate(prefab, spawnPoint.Position, spawnPoint.Rotation, placementRoot);
                instance.name = $"{prefab.name}_{spawnPoint.ChunkCoord.x}_{spawnPoint.ChunkCoord.y}_{spawnPoint.SpawnOrdinalInChunk}";
                SpawnedPlacementRuntime runtime = new SpawnedPlacementRuntime(spawnPoint.SpawnId, chunkCoord, spawnPoint.CollectionIndex, spawnPoint.EntryIndex, instance);
                chunkPlacements.Add(runtime);
                spawnedPlacementsById.Add(runtime.SpawnId, runtime);
            }

            if (chunkPlacements.Count > 0)
            {
                spawnedPlacementsByChunk[chunkCoord] = chunkPlacements;
            }
        }

        private bool TryResolveSpawnPrefab(WorldSpawnPoint spawnPoint, out GameObject prefab)
        {
            prefab = null;
            if (config == null || config.placementCollections == null)
            {
                return false;
            }

            if (spawnPoint.CollectionIndex < 0 || spawnPoint.CollectionIndex >= config.placementCollections.Count)
            {
                return false;
            }

            WorldPrefabCollection collection = config.placementCollections[spawnPoint.CollectionIndex];
            if (collection == null || collection.entries == null || spawnPoint.EntryIndex < 0 || spawnPoint.EntryIndex >= collection.entries.Count)
            {
                return false;
            }

            WorldPrefabEntry entry = collection.entries[spawnPoint.EntryIndex];
            if (entry == null)
            {
                return false;
            }

            prefab = entry.prefab;
            return prefab != null;
        }

        private void DespawnChunkPlacements(Vector2Int chunkCoord)
        {
            if (!spawnedPlacementsByChunk.TryGetValue(chunkCoord, out List<SpawnedPlacementRuntime> chunkPlacements))
            {
                return;
            }

            for (int i = 0; i < chunkPlacements.Count; i++)
            {
                SpawnedPlacementRuntime runtime = chunkPlacements[i];
                spawnedPlacementsById.Remove(runtime.SpawnId);
                DestroyPlacementInstance(runtime.Instance);
            }

            spawnedPlacementsByChunk.Remove(chunkCoord);
        }

        private void RemoveSpawnedPlacement(SpawnedPlacementRuntime runtime)
        {
            spawnedPlacementsById.Remove(runtime.SpawnId);
            if (spawnedPlacementsByChunk.TryGetValue(runtime.ChunkCoord, out List<SpawnedPlacementRuntime> chunkPlacements))
            {
                chunkPlacements.RemoveAll(item => item.SpawnId == runtime.SpawnId);
                if (chunkPlacements.Count == 0)
                {
                    spawnedPlacementsByChunk.Remove(runtime.ChunkCoord);
                }
            }

            DestroyPlacementInstance(runtime.Instance);
        }

        private void CleanupSpawnedPlacements()
        {
            foreach (KeyValuePair<Vector2Int, List<SpawnedPlacementRuntime>> pair in spawnedPlacementsByChunk)
            {
                List<SpawnedPlacementRuntime> chunkPlacements = pair.Value;
                for (int i = 0; i < chunkPlacements.Count; i++)
                {
                    DestroyPlacementInstance(chunkPlacements[i].Instance);
                }
            }

            spawnedPlacementsByChunk.Clear();
            spawnedPlacementsById.Clear();

            if (placementRoot != null)
            {
                if (Application.isPlaying)
                {
                    Object.Destroy(placementRoot.gameObject);
                }
                else
                {
                    Object.DestroyImmediate(placementRoot.gameObject);
                }

                placementRoot = null;
            }
        }

        private static void DestroyPlacementInstance(GameObject instance)
        {
            if (instance == null)
            {
                return;
            }

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

        private sealed class SpawnedPlacementRuntime
        {
            public SpawnedPlacementRuntime(long spawnId, Vector2Int chunkCoord, int collectionIndex, int entryIndex, GameObject instance)
            {
                SpawnId = spawnId;
                ChunkCoord = chunkCoord;
                CollectionIndex = collectionIndex;
                EntryIndex = entryIndex;
                Instance = instance;
            }

            public long SpawnId { get; }
            public Vector2Int ChunkCoord { get; }
            public int CollectionIndex { get; }
            public int EntryIndex { get; }
            public GameObject Instance { get; }
        }
    }
}
