using UnityEngine;
using System.Collections.Generic;
using System.Collections;

namespace SpecialMoveCards
{
    /// <summary>
    /// Spawns card pickups similar to common pickup systems:
    /// - Maintains a target count of active cards (default 2)
    /// - Spawns at random spawn points or within an area
    /// - When a card is picked up (destroyed), schedules a delayed respawn to keep the target count
    /// </summary>
    public class CardSpawner : MonoBehaviour
    {
        // ========== MASTER DISABLE FLAG ==========
        // Set to TRUE to completely disable the card/special move system
        // This is a quick toggle - you can implement a proper system later
        public static bool CARD_SYSTEM_DISABLED = true;
        // ==========================================
        
    private static readonly List<CardSpawner> s_instances = new List<CardSpawner>();
        [Header("Card Prefab & Data")]
    [Tooltip("Generic Card prefab that contains CardBehaviour (used if 'Card Prefabs' list is empty).")]
    public GameObject cardPrefab;
    [Tooltip("If provided, a random prefab from this list will be spawned instead of the single prefab above. Each prefab can carry its own CardData and visuals (e.g., per-color prefabs).")]
    public List<GameObject> cardPrefabs = new List<GameObject>();
        [Tooltip("Pool of possible CardData to spawn from.")]
        public List<CardData> cardPool = new List<CardData>();

        [Header("Spawning")]
        public List<Transform> spawnPoints = new List<Transform>();
        [Tooltip("If no spawn points, use a box area in local space.")]
        public Vector3 areaSize = new Vector3(10, 0, 10);
        [Tooltip("Number of cards to keep alive concurrently. Typical big-game behavior uses small numbers like 1-2.")]
        public int targetCardsInScene = 2;
        [Tooltip("Random delay before a replacement card is spawned after pickup (x..y seconds).")]
        public Vector2 respawnDelay = new Vector2(2f, 5f);
        [Tooltip("Ensure only one active card per spawn point at a time.")]
        public bool useUniqueSpawnPoints = true;

        private readonly HashSet<GameObject> alive = new HashSet<GameObject>();
        private readonly Dictionary<int, GameObject> occupiedPoints = new Dictionary<int, GameObject>();

        private void OnEnable()
        {
            // Skip if card system is disabled
            if (CARD_SYSTEM_DISABLED)
            {
                Debug.Log("[CardSpawner] Card System is DISABLED - skipping initialization");
                return;
            }
            
            if (!s_instances.Contains(this)) s_instances.Add(this);
            GameEvents.CardPicked += OnAnyCardPicked;
            MaintainTargetCount();
        }

        private void OnDisable()
        {
            GameEvents.CardPicked -= OnAnyCardPicked;
            alive.RemoveWhere(go => go == null);
            occupiedPoints.Clear();
            s_instances.Remove(this);
        }

        private void TrySpawnOnce()
        {
            CleanupDead();
            if (alive.Count >= targetCardsInScene) return;
            // Build disallowed type set to keep two cards different
            HashSet<CardType> disallowed = BuildDisallowedTypes();

            Vector3 pos;
            Quaternion rot = Quaternion.identity;
            int usedIndex = -1;

            if (spawnPoints != null && spawnPoints.Count > 0)
            {
                // Choose a free spawn point if required
                int safety = 25;
                int idx = -1;
                while (safety-- > 0)
                {
                    int r = Random.Range(0, spawnPoints.Count);
                    if (!useUniqueSpawnPoints || !occupiedPoints.ContainsKey(r)) { idx = r; break; }
                }
                if (idx < 0) idx = Random.Range(0, spawnPoints.Count); // fallback
                var t = spawnPoints[idx];
                pos = t.position;
                rot = t.rotation;
                usedIndex = idx;
            }
            else
            {
                Vector3 half = areaSize * 0.5f;
                Vector3 local = new Vector3(
                    Random.Range(-half.x, half.x),
                    Random.Range(-half.y, half.y),
                    Random.Range(-half.z, half.z));
                pos = transform.TransformPoint(local);
            }

            var go = SpawnCard(pos, rot, disallowed);
            if (go == null) return; // nothing feasible to spawn
            alive.Add(go);
            if (useUniqueSpawnPoints && usedIndex >= 0)
                occupiedPoints[usedIndex] = go;

            // Track destruction to free spawn point
            var token = go.AddComponent<SpawnedCardToken>();
            token.Init(this, usedIndex);
        }

        private void OnDrawGizmosSelected()
        {
            if (spawnPoints == null || spawnPoints.Count == 0)
            {
                Gizmos.color = Color.yellow;
                Gizmos.matrix = transform.localToWorldMatrix;
                Gizmos.DrawWireCube(Vector3.zero, areaSize);
            }
        }

        // --- Enemy-death drops API ---
        /// <summary>
        /// Called by external systems to attempt a drop at a world position with a given probability.
        /// Respects each spawner's targetCardsInScene limit.
        /// </summary>
        public static void TrySpawnOnEnemyDeath(Vector3 position, float chance = 0.25f)
        {
            if (s_instances.Count == 0) return;
            if (Random.value > Mathf.Clamp01(chance)) return;

            // Pick the first spawner that has capacity
            for (int i = 0; i < s_instances.Count; i++)
            {
                var sp = s_instances[i];
                if (sp == null || !sp.isActiveAndEnabled) continue;
                sp.CleanupDead();
                if (sp.alive.Count < sp.targetCardsInScene)
                {
                    sp.SpawnAtPosition(position + Vector3.up * 0.25f, Quaternion.identity);
                    return;
                }
            }
        }

        private void SpawnAtPosition(Vector3 pos, Quaternion rot)
        {
            // For death-drops, also try to keep existing type different if only one is alive
            var disallowed = BuildDisallowedTypes();
            var go = SpawnCard(pos, rot, disallowed);
            if (go == null) return;
            alive.Add(go);
            var token = go.AddComponent<SpawnedCardToken>();
            token.Init(this, -1); // no spawn point occupancy
        }

        private void MaintainTargetCount()
        {
            CleanupDead();
            while (alive.Count < targetCardsInScene)
                TrySpawnOnce();
        }

        private void CleanupDead()
        {
            // Remove destroyed refs and free occupied points
            if (alive.Count == 0) return;
            var toRemove = new List<GameObject>();
            foreach (var go in alive)
            {
                if (go == null) toRemove.Add(go);
            }
            for (int i = 0; i < toRemove.Count; i++)
                alive.Remove(toRemove[i]);

            // occupiedPoints are freed in NotifyCardDestroyed; this is extra safety if something slipped through
            var free = new List<int>();
            foreach (var kvp in occupiedPoints)
            {
                if (kvp.Value == null) free.Add(kvp.Key);
            }
            for (int i = 0; i < free.Count; i++)
                occupiedPoints.Remove(free[i]);
        }

        private void OnAnyCardPicked(CardData _)
        {
            // Schedule a replacement after a small random delay like big games do
            float delay = Random.Range(respawnDelay.x, respawnDelay.y);
            StartCoroutine(RespawnDelayed(delay));
        }

        private IEnumerator RespawnDelayed(float delay)
        {
            yield return new WaitForSeconds(delay);
            MaintainTargetCount();
        }

        internal void NotifyCardDestroyed(SpawnedCardToken token)
        {
            if (token == null) return;
            // Free occupancy & alive set; spawning replacement happens via CardPicked event or periodic Maintain
            if (token.spawnPointIndex >= 0 && occupiedPoints.ContainsKey(token.spawnPointIndex))
                occupiedPoints.Remove(token.spawnPointIndex);
            alive.Remove(token.gameObject);
        }

    public class SpawnedCardToken : MonoBehaviour
        {
            private CardSpawner owner;
            internal int spawnPointIndex = -1;
            public void Init(CardSpawner o, int index)
            {
                owner = o; spawnPointIndex = index;
            }
            private void OnDestroy()
            {
                if (owner != null) owner.NotifyCardDestroyed(this);
            }
        }

        // --- Helper logic to enforce different types when two are present ---
        private HashSet<CardType> BuildDisallowedTypes()
        {
            var disallowed = new HashSet<CardType>();
            // Only enforce strict difference when target is 2 and we currently have exactly 1 alive card
            if (targetCardsInScene == 2 && alive.Count == 1)
            {
                foreach (var go in alive)
                {
                    if (go == null) continue;
                    var cb = go.GetComponent<CardBehaviour>();
                    if (cb != null && cb.Data != null)
                    {
                        disallowed.Add(cb.Data.type);
                        break;
                    }
                }
            }
            return disallowed;
        }

        private GameObject SpawnCard(Vector3 pos, Quaternion rot, HashSet<CardType> disallowed)
        {
            bool multiPrefabs = cardPrefabs != null && cardPrefabs.Count > 0;
            if (!multiPrefabs && cardPrefab == null) return null;

            // Collect prefab capabilities
            var fixedPrefabs = new List<(GameObject prefab, CardType type)>();
            var genericPrefabs = new List<GameObject>();

            if (multiPrefabs)
            {
                for (int i = 0; i < cardPrefabs.Count; i++)
                {
                    var pf = cardPrefabs[i];
                    if (pf == null) continue;
                    var cb = pf.GetComponent<CardBehaviour>();
                    if (cb != null && cb.Data != null)
                        fixedPrefabs.Add((pf, cb.Data.type));
                    else
                        genericPrefabs.Add(pf);
                }
            }
            else
            {
                var pf = cardPrefab;
                var cb = pf != null ? pf.GetComponent<CardBehaviour>() : null;
                if (cb != null && cb.Data != null)
                    fixedPrefabs.Add((pf, cb.Data.type));
                else if (pf != null)
                    genericPrefabs.Add(pf);
            }

            // Determine feasible types
            var feasibleTypes = new List<CardType>();
            var fixedTypes = new HashSet<CardType>();
            for (int i = 0; i < fixedPrefabs.Count; i++) fixedTypes.Add(fixedPrefabs[i].type);
            foreach (var t in fixedTypes)
                if (!disallowed.Contains(t)) feasibleTypes.Add(t);

            bool hasGeneric = genericPrefabs.Count > 0;
            if (hasGeneric && cardPool != null && cardPool.Count > 0)
            {
                var poolTypesSet = new HashSet<CardType>();
                for (int i = 0; i < cardPool.Count; i++) poolTypesSet.Add(cardPool[i].type);
                foreach (var t in poolTypesSet)
                {
                    if (!disallowed.Contains(t) && !feasibleTypes.Contains(t))
                        feasibleTypes.Add(t);
                }
            }

            // If nothing feasible with disallowed constraint, relax constraint
            if (feasibleTypes.Count == 0)
            {
                foreach (var t in fixedTypes)
                    if (!feasibleTypes.Contains(t)) feasibleTypes.Add(t);
                if (hasGeneric && cardPool != null && cardPool.Count > 0)
                {
                    var poolTypesSet = new HashSet<CardType>();
                    for (int i = 0; i < cardPool.Count; i++) poolTypesSet.Add(cardPool[i].type);
                    foreach (var t in poolTypesSet)
                        if (!feasibleTypes.Contains(t)) feasibleTypes.Add(t);
                }
            }

            // Fallback: if still empty (e.g., no pool and only generic prefabs), just spawn any prefab without enforcing type
            if (feasibleTypes.Count == 0)
            {
                GameObject anyPrefab = multiPrefabs ? (cardPrefabs.Count > 0 ? cardPrefabs[Random.Range(0, cardPrefabs.Count)] : null) : cardPrefab;
                if (anyPrefab == null) return null;
                return Instantiate(anyPrefab, pos, rot);
            }

            // Choose a target type randomly
            var targetType = feasibleTypes[Random.Range(0, feasibleTypes.Count)];

            // Choose a matching prefab if possible; else a generic prefab
            GameObject chosenPrefab = null;
            var matches = new List<GameObject>();
            for (int i = 0; i < fixedPrefabs.Count; i++)
                if (fixedPrefabs[i].type.Equals(targetType)) matches.Add(fixedPrefabs[i].prefab);
            if (matches.Count > 0)
                chosenPrefab = matches[Random.Range(0, matches.Count)];
            else if (hasGeneric)
                chosenPrefab = genericPrefabs[Random.Range(0, genericPrefabs.Count)];
            else
                chosenPrefab = multiPrefabs ? cardPrefabs[Random.Range(0, cardPrefabs.Count)] : cardPrefab; // last resort

            var go = Instantiate(chosenPrefab, pos, rot);
            var behaviour = go.GetComponent<CardBehaviour>();
            if (behaviour != null && behaviour.Data == null)
            {
                // Assign data matching chosen type
                CardData assign = null;
                if (cardPool != null && cardPool.Count > 0)
                {
                    // collect indices with targetType
                    var idxs = new List<int>();
                    for (int i = 0; i < cardPool.Count; i++) if (cardPool[i].type.Equals(targetType)) idxs.Add(i);
                    if (idxs.Count > 0) assign = cardPool[idxs[Random.Range(0, idxs.Count)]];
                    else assign = cardPool[Random.Range(0, cardPool.Count)];
                }
                if (assign != null) behaviour.SetData(assign);
            }
            return go;
        }
    }
}
