using System;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Assertions;
using UnityEngine.Playables;
using Logger = RebootKit.Engine.Foundation.Logger;

// @TODO:
//  - avoid boxing values
//  - think about refactoring in order to reduce gc allocs
namespace RebootKit.Engine.Animations {
    public interface IReAnimatorNode {
        string Name { get; }

        void Tick(float deltaTime);
        IPlayable Build(PlayableGraph graph);

        bool TryFindChild(string name, out IReAnimatorNode node);
    }

    public enum ReAnimatorTickMode {
        Update,
        Fixed,
        Manual
    }

    [DefaultExecutionOrder(-100)]
    public class ReAnimator : MonoBehaviour {
        static readonly Logger s_Logger = new Logger(nameof(ReAnimator));

        [SerializeField] Animator m_Animator;
        [SerializeField] LayerMixerNode m_Root;

        public PlayableGraph Graph { get; private set; }
        AnimationPlayableOutput m_Output;

        void Awake() {
            Assert.IsNotNull(m_Animator, "Animator is not set");

            m_Animator.runtimeAnimatorController = null;

            Graph = PlayableGraph.Create(name);
            m_Output = AnimationPlayableOutput.Create(Graph, "Animation Output", m_Animator);

            if (m_Root.Build(Graph) is AnimationLayerMixerPlayable layerMixer) {
                m_Output.SetSourcePlayable(layerMixer, 0);
            }

            Graph.Play();
        }

        void OnDestroy() {
            if (Graph.IsValid()) {
                Graph.Destroy();
            }
        }

        void Update() {
            float dt = Time.deltaTime;
            m_Root.Tick(dt);
            Graph.Evaluate(dt);
        }

        public void SetLayerWeight(int layer, float weight) {
            m_Root.SetLayerWeight(layer, weight);
        }

        public IReAnimatorNode FindNode(string nodeName) {
            if (m_Root.TryFindChild(nodeName, out IReAnimatorNode node)) {
                return node;
            }

            s_Logger.Error($"Couldn't find node with name: {nodeName}");
            return null;
        }

        public TNode FindNode<TNode>(string nodeName) where TNode : class, IReAnimatorNode {
            IReAnimatorNode node = FindNode(nodeName);
            if (node is TNode tnode) {
                return tnode;
            }

            s_Logger.Error($"Couldn't find node with name: {nodeName} of type {typeof(TNode).Name}");
            return null;
        }
    }
}