using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using KitsuneCafe.Sys;
using R3;
using UnityEngine;
using UnityEngine.UIElements;
using Unit = R3.Unit;

namespace KitsuneCafe.UI
{
    public class UiElementInstance : MonoBehaviour, IDisposable
    {
        public UIDocument Document;
        public PooledObject PooledObject;

        private IUiElement uiElement;
        private VisualElement visualElement;

        private IDisposable instance;

        private CancellationTokenSource cts;

        private void OnDisable()
        {
            Dispose();
        }

        /// <summary>
        /// Initial configuration of the UIDocument. Called by UISceneManager on spawn/reuse.
        /// </summary>
        public void Configure(IUiElement element, PanelSettings settings)
        {
            uiElement = element;

            cts = new();

            Document.visualTreeAsset = element.VisualTreeAsset;
            Document.panelSettings = settings;

            instance = ConfigureWhenReady(element)
                .TakeUntil(cts.Token)
                .Subscribe();
        }

        /// <summary>
        /// Internal configuration of the VisualElement. Called when rootVisualElement is ready.
        /// </summary>
        private Observable<Unit> Configure(IUiElement uiElement, VisualElement visualElement)
        {
            this.visualElement = visualElement;

            uiElement.Configure(visualElement);

            return Raise(UiEvent.OnCreate, cts.Token);
        }

        /// <summary>
        /// Waits for the UIDocument's rootVisualElement to be ready.
        /// </summary>
        private Observable<Unit> ConfigureWhenReady(IUiElement element)
        {
            return Observable.EveryValueChanged(Document, d => d.rootVisualElement)
                .WhereNotNull()
                .Take(1)
                .SelectMany(root => Configure(element, root));
        }

        /// <summary>
        /// Raises effects for a given UI event, returning an Observable that completes when all effects are done.
        /// </summary>
        private Observable<Unit> Raise(UiEvent evt, CancellationToken token = default)
        {
            var effects = uiElement.GetEffectsFor(evt).ToArray();
            var obs = new List<Observable<Unit>>(effects.Length);

            foreach (var effect in effects)
            {
                if (effect != null)
                {
                    obs.Add(effect
                        .Instantiate()
                        .Execute(visualElement, token));
                }
            }

            return Observable.Merge(obs);
        }

        /// <summary>
        /// Forcefully disposes of all active effects and instance lifecycle subscriptions.
        /// This is for internal cleanup. It does NOT release the object to the pool.
        /// </summary>
        public void Dispose()
        {
            cts?.Cancel();
            cts?.Dispose();
            cts = null;

            instance?.Dispose();

            uiElement = default;
            visualElement = default;
        }

        /// <summary>
        /// Initiates the despawn process: runs OnDestroy effects, waits for them to finish,
        /// then disposes everything and releases the object back to the pool.
        /// </summary>
        public Observable<Unit> Despawn(CancellationToken token = default)
        {
            cts.Cancel();
            return Raise(UiEvent.OnDestroy, token)
                .Do(onCompleted: _ => DespawnNow(),
                onErrorResume: _ => DespawnNow())
                .AsUnitObservable();
        }

        /// <summary>
        /// Immediately disposes of effects and releases the object to the pool,
        /// without awaiting OnDestroy effects.
        /// </summary>
        public void DespawnNow()
        {
            Dispose();
            PooledObject.Release();
        }
    }
}
