// FlexiMotion // https://kybernetik.com.au/flexi-motion // Copyright 2023-2025 Kybernetik //

using FlexiMotion.Modifiers;
using System.Collections.Generic;
using UnityEngine;

namespace FlexiMotion
{

    /// <summary>
    /// The main component in <see cref="FlexiMotionComponent"/> which holds the core <see cref="FlexiMotionDefinition"/> and
    /// turns it into a <see cref="FlexiMotionRuntime"/> to run the simulation.
    /// </summary>
    /// https://kybernetik.com.au/flexi-motion/api/FlexiMotion/FlexiMotionComponent
    /// 
    [AddComponentMenu(FMStrings.MenuPrefix + "FlexiMotion Component")]
    [HelpURL(FMStrings.DocsURLs.APIDocumentation + "/" + nameof(FlexiMotionComponent))]
    public class FlexiMotionComponent : MonoBehaviour,
        IFlexiMotionComponent,
        IOnValidate
    {
        /************************************************************************************************************************/

        /// <summary>The name of the serialized backing field of <see cref="Definition"/>.</summary>
        public const string
            DefinitionFieldName = nameof(_Definition);

        [SerializeField]
        private FlexiMotionDefinition _Definition;

        /// <summary>[<see cref="SerializeField"/>] Data from which the <see cref="Runtime"/> will be initialized.</summary>
        public FlexiMotionDefinition Definition
        {
            get => _Definition;
            set
            {
                _Definition = value;
                FlexiMotionRuntimeTransfer.ReInitializeRuntime(this);
            }
        }

        /************************************************************************************************************************/

        /// <inheritdoc/>
        public FlexiMotionRuntime Runtime { get; set; }

        /// <summary>Was this component enabled previously and then disabled?</summary>
        public bool WasDisabled { get; private set; }

        /// <inheritdoc/>
        public JobModifierGroup Modifiers { get; } = new JobModifierGroup();

        /************************************************************************************************************************/

        /// <summary>
        /// Initializes the <see cref="Runtime"/>, handles <see cref="FlexiMotionDefinition.ActionOnDisable"/>, and
        /// runs any <see cref="JobModifierGroup.IOnReEnable"/> modifiers if it <see cref="WasDisabled"/>.
        /// </summary>
        protected virtual void OnEnable()
        {
            if (!Initialize())
                return;

            if (WasDisabled)
            {
                WasDisabled = false;

                _Definition?.OnReEnable(Runtime);

                Modifiers.ScheduleOnReEnableJobs().Complete();
            }

            Runtime.Enable();
        }

        /************************************************************************************************************************/

        /// <summary>
        /// Initializes the <see cref="Runtime"/> from the <see cref="Definition"/> if it hasn't already been created.
        /// </summary>
        public bool Initialize()
        {
            if (Runtime != null)
                return true;

            if (_Definition == null)
                return false;

            Runtime = new FlexiMotionRuntime(_Definition, Modifiers);
            Modifiers.ScheduleOnInitializeJobs().Complete();
            return true;
        }

        /************************************************************************************************************************/

        /// <summary>Executes the <see cref="FlexiMotionDefinition.ActionOnDisable"/>.</summary>
        protected virtual void OnDisable()
        {
            if (_Definition == null ||
                Runtime == null)
                return;

            WasDisabled = true;
            Modifiers.IsInitialized = false;
            _Definition.OnDisable(this);
        }

        /************************************************************************************************************************/

        /// <summary>Cleans up the <see cref="Runtime"/>.</summary>
        protected virtual void OnDestroy()
        {
            if (Runtime != null)
            {
                Runtime.Dispose();
                Runtime = null;

                Modifiers.IsInitialized = false;
            }
        }

        /************************************************************************************************************************/

        /// <inheritdoc/>
        IEnumerable<IFlexiMotionModifier> IFlexiMotionComponent.GetModifiers()
        {
            foreach (var modifier in Resources.FindObjectsOfTypeAll<FlexiMotionModifier>())
                if (modifier.Target == this)
                    yield return modifier;
        }

        /************************************************************************************************************************/

        void IOnValidate.OnValidate()
        {
#if UNITY_EDITOR
            OnValidate();
#endif
        }

        /************************************************************************************************************************/
#if UNITY_EDITOR
        /************************************************************************************************************************/

        /// <summary>Resets this component to its default values.</summary>
        protected virtual void Reset()
        {
            if (_Definition == null)
                _Definition = new FlexiMotionDefinition();

            var root = FMUtilities.GuessRoot(gameObject);
            if (root != null)
            {
                var animator = root.GetComponentInChildren<Animator>();
                if (animator != null)
                {
                    _Definition.UpdateMode = animator.updateMode;
                }
            }
        }

        /************************************************************************************************************************/

        /// <summary>Re-initializes the runtime to account for any modifications to the <see cref="Definition"/>.</summary>
        protected virtual void OnValidate()
            => UnityEditor.EditorApplication.delayCall +=
                () => FlexiMotionRuntimeTransfer.ReInitializeRuntime(this);

        /************************************************************************************************************************/

        /// <summary>Draws scene gizmos for the <see cref="Runtime"/>.</summary>
        protected virtual void OnDrawGizmosSelected()
            => Runtime?.DrawGizmos();

        /************************************************************************************************************************/
#endif
        /************************************************************************************************************************/
    }
}
