using UnityEngine;
using R3;
using System.Collections.Generic;
using System;
using UnityEngine.UIElements;
using UnityEngine.Pool;
using KitsuneCafe.System;
using System.Threading;

namespace KitsuneCafe.UI
{
    public class UISceneManager : MonoBehaviour
    {
        [SerializeField] private UIOrchestrator orchestrator;

        [SerializeField] private Transform root;
        [SerializeField] private PanelSettings panelSettings;
        [SerializeField] private GameObject prefab;

        private readonly Dictionary<ElementId, GameObject> activeElements = new();
        private readonly Dictionary<VisualTreeAsset, IObjectPool<GameObject>> pools = new();

        private void Awake()
        {
            var d = Disposable.CreateBuilder();

            Observable.FromEvent<EventHandler<SpawnElementRequest>, SpawnElementRequest>(
                h => (sender, e) => h(e),
                e => orchestrator.SpawnRequested += e,
                e => orchestrator.SpawnRequested -= e
            ).Subscribe(request =>
            {
                SpawnElement(request.Id, request.Element, request.Position);
            })
            .AddTo(ref d);

            Observable.FromEvent<EventHandler<DespawnElementRequest>, DespawnElementRequest>(
                h => (sender, e) => h(e),
                e => orchestrator.DespawnRequested += e,
                e => orchestrator.DespawnRequested -= e
            )
            .Select(request => request.Id)
            .Subscribe(DespawnElement)
            .AddTo(ref d);

            d.RegisterTo(destroyCancellationToken);
        }

        private IObjectPool<GameObject> GetOrCreatePool(IUIElement element)
        {
            if (!pools.TryGetValue(element.VisualTreeAsset, out var pool))
            {
                pool = new ObjectPool<GameObject>(
                    () => CreatePoolObject(pool),
                    GetPoolObject,
                    ReleasePoolObject,
                    DestroyPoolObject,
                    collectionCheck: false,
                    defaultCapacity: 10,
                    maxSize: 100
                );

                pools.Add(element.VisualTreeAsset, pool);
            }
            return pool;
        }

        private GameObject CreatePoolObject(IObjectPool<GameObject> pool)
        {
            GameObject obj;
            if (prefab != null)
            {
                obj = Instantiate(prefab);
            }
            else
            {
                obj = new GameObject("Pooled Object", typeof(UIDocument));
            }

            obj.transform.SetParent(root);

            var doc = obj.GetComponent<UIDocument>();
            doc.panelSettings = panelSettings;

            var poolObj = obj.AddComponent<PooledObject>();
            poolObj.objectPool = pool;

            return obj;
        }

        private void GetPoolObject(GameObject obj)
        {
            obj.SetActive(true);
        }

        private void ReleasePoolObject(GameObject obj)
        {
            obj.SetActive(false);
        }

        private void DestroyPoolObject(GameObject obj)
        {
            Destroy(obj);
        }

        private GameObject GetUiDocument(IUIElement element)
        {
            return GetOrCreatePool(element).Get();
        }

        private void SpawnElement(ElementId id, IUIElement element, Vector3 worldPosition)
        {
            var obj = GetUiDocument(element);

            var trans = obj.transform;
            trans.SetParent(root);
            trans.SetPositionAndRotation(worldPosition, Quaternion.identity);
            trans.localScale = Vector3.one;

            if (obj.TryGetComponent<UIDocument>(out var doc))
            {

                doc.visualTreeAsset = element.VisualTreeAsset;
                doc.panelSettings = panelSettings;
                ConfigureWhenReady(doc, element);
            }

            activeElements[id] = obj;

            //Observable.Timer(TimeSpan.FromSeconds(3))
            //    .Subscribe(_ => orchestrator.DespawnElement(id))
            //    .AddTo(obj);
        }

        private void ConfigureWhenReady(UIDocument document, IUIElement element)
        {
            var cts = new CancellationTokenSource();
            Observable.EveryValueChanged(document, d => d.didStart)
                .Where(started => started)
                .Take(1)
                .DoCancelOnCompleted(cts)
                .Subscribe(f =>
                {
                    element.Configure(document.rootVisualElement);
                })
                .AddTo(document.gameObject);
        }

        private void DespawnElement(ElementId id)
        {
            if (activeElements.TryGetValue(id, out GameObject obj))
            {
                obj.GetComponent<PooledObject>().Release();
                activeElements.Remove(id);
            }
        }
    }
}