using System.Threading;
using KitsuneCafe.System;
using KitsuneCafe.System.Attributes;
using R3;
using UnityEngine;
using UnityEngine.UIElements;
using KitsuneCafe.Extension;
using Unit = R3.Unit;
using System;

namespace KitsuneCafe.UI
{
    [CreateAssetMenu(menuName = KitsuneCafeMenu.UiEffect + "Fade")]
    public class FadeEffect : BaseUiEffect
    {
        [SerializeField]
        private bool addTransition = false;

        [SerializeField, ShowIf("addTransition")]
        private string propertyName = "opacity";

        [SerializeField, ShowIf("addTransition")]
        private Duration duration;

        [SerializeField, ShowIf("addTransition")]
        private EasingMode easing;

        [SerializeField, ShowIf("addTransition")]
        private Duration delay;

        [SerializeField, Range(0f, 1f)]
        private float from;

        [SerializeField, Range(0f, 1f)]
        private float to;

        public override IUiEffect Instantiate()
        {
            var fade = new FadeEffectInstance(from, to, duration + delay);

            if (addTransition)
            {
                return new AddTransitionEffect(propertyName, duration, easing, delay, fade);
            }

            return fade;
        }
    }

    public readonly struct FadeEffectInstance : IUiEffect
    {
        public readonly float From;
        public readonly float To;

        public readonly TimeSpan Timeout;

        public FadeEffectInstance(float from, float to, TimeSpan timeout)
        {
            From = from;
            To = to;
            Timeout = timeout;
        }

        private Observable<Unit> ObserveGeometryChange(VisualElement target, CancellationToken token)
        {
            return target.ObserveEvent<GeometryChangedEvent>(token)
                .OnErrorResumeAsFailure()
                .AsUnitObservable();
        }

        private Observable<Unit> Defer(VisualElement target, CancellationToken token)
        {
            return Observable.Merge(
                ObserveGeometryChange(target, token),
                Observable.EveryUpdate(UnityFrameProvider.PostLateUpdate, token)
            )
            .Where(_ => target.resolvedStyle.width > 0 && target.resolvedStyle.height > 0)
            .Take(1);
        }

        private bool IsOpacityEvent(TransitionEndEvent evt)
        {
            return evt.stylePropertyNames.Contains("opacity");
        }

        public Observable<Unit> Execute(VisualElement target, CancellationToken token)
        {
            target.style.opacity = From;

            var to = To;
            return Defer(target, token)
                .Do(_ => target.style.opacity = to)
                .Select(_ => target.ObserveEvent<TransitionEndEvent>(token))
                .Switch()
                .Where(IsOpacityEvent)
                .Take(1)
                .TakeUntil(token)
                .AsUnitObservable()
                .Race(Observable.Timer(Timeout));
        }
    }
}
