using System;
using System.Collections.Generic;
using Assets.Source.CommonWorkers;
using Assets.Source.General;
using Assets.Source.Utils;
using UnityEngine;

namespace Assets.Source.Patterns.ObjectPool
{
    public class PoolManager : Singleton<PoolManager>
    {
        public Transform Root;

        // Key - source prefab (NOT CLONE!!!), Value - pool for source prefab clones
        private Dictionary<IPoolObject, ObjectPool> _poolLookup = new Dictionary<IPoolObject, ObjectPool>();

        // Key - clone (NOT PREFAB!!!), Value - pool
        private Dictionary<IPoolObject, ObjectPool> _cloneLookup = new Dictionary<IPoolObject, ObjectPool>();

        // TODO: clear when objects are destroyed in pools
        private Dictionary<IPoolObject, List<Action<IPoolObject>>> _onReleaseEvents = new Dictionary<IPoolObject, List<Action<IPoolObject>>>();

        public event Action<IPoolObject> OnAfterRelease;

        protected override void Init()
        {
            base.Init();
            if (Root == null)
            {
                Root = new GameObject("[PoolRoot]").transform;
                WorkerScenes.MoveToCommon(Root.gameObject);
            }
        }

        public void Warm(IPoolObject prefab, int count = 1)
        {
            var pool = !_poolLookup.ContainsKey(prefab) ? CreatePoolForPrefab(prefab) : _poolLookup[prefab];
            for (int i = 0; i < count; i++)
                pool.Warm();
        }

        public T Spawn<T>(T prefab) where T : IPoolObject
        {
            var pool = !_poolLookup.ContainsKey(prefab) ? CreatePoolForPrefab(prefab) : _poolLookup[prefab];
            return (T)pool.Spawn();
        }

        public T Spawn<T>(T prefab, Vector3 pos, Quaternion rotation) where T : IPoolObject
        {
            var clone = Spawn(prefab);
            clone.gameObject.transform.position = pos;
            clone.gameObject.transform.rotation = rotation;
            return clone;
        }

        public bool Release(IPoolObject clone)
        {
            if (!_cloneLookup.ContainsKey(clone))
            {
                Debug.LogWarning($"No pool found for clone: {clone.gameObject.name}");
                clone.gameObject.SetActive(false);
                return false;
            }

            if (_onReleaseEvents.ContainsKey(clone))
            {
                foreach (var action in _onReleaseEvents[clone])
                    action(clone);
                _onReleaseEvents.Remove(clone);
            }
            var b = _cloneLookup[clone].Release(clone);

            OnAfterRelease?.Invoke(clone);

            return b;
        }

        public void AddReleaseHandler(IPoolObject obj, Action<IPoolObject> action)
        {
            if (!_onReleaseEvents.ContainsKey(obj))
                _onReleaseEvents.Add(obj, new List<Action<IPoolObject>>());
            _onReleaseEvents[obj].Add(action);
        }

        private IPoolObject InstantiateFromPrefab(IPoolObject prefab)
        {
            var go = Instantiator.Instantiate(prefab.gameObject);
            if (Root != null)
                go.transform.SetParent(Root);
            return go.GetComponent<IPoolObject>();
        }

        private ObjectPool CreatePoolForPrefab(IPoolObject prefab)
        {
            var pool = new ObjectPool(Root, () => InstantiateFromPrefab(prefab));
            pool.OnWarm += o =>
            {
                _cloneLookup.Add(o, pool);
            };
            _poolLookup.Add(prefab, pool);
            return pool;
        }
    }
}
