using System;
using System.Collections.Generic;
using System.Linq;
using Sirenix.OdinInspector;
using Sirenix.Utilities;
using UnityEngine;
using VOID.Generic.Objects.Unit.Actions;
using VOID.Generic.Objects.Unit.Actions.Exceptions;
using VOID.Generic.Objects.Unit.Events;

namespace VOID.Generic.Objects.Unit
{
    [Serializable]
    public class UnitObjectActionQueue : ObjectComponent<UnitObject>
    {
        #region Initialize

        protected override void SelfInitialize()
        {
            parent.basicEvents.onFixedUpdate.AddListener(CheckRunningAction);
        }

        #endregion
        
        // Getters
        public List<AbstractAction> actionQueue => _actionQueue;
        public AbstractAction actionCurrent => _actionCurrent;
        
        [ShowInInspector, ReadOnly] private readonly List<AbstractAction> _actionQueue = new();
        [ShowInInspector, ReadOnly] private AbstractAction _actionCurrent;

        /// <summary>
        /// 1. Checks if the action can be executed outside the queue.
        /// 2. If allowed, executes the action and handles potential errors.
        /// </summary>
        public void ExecuteInstantly(AbstractAction action)
        {
            if (!action.CanSkipQueue())
            {
                Debug.LogError($"Action: {action.GetType().Name} cannot be executed via {nameof(ExecuteInstantly)}");
                return;
            }

            try
            {
                action.DoIt();
            }
            catch (UnitActionErrorException e)
            {
                parent.unitEvents.onActionError.Invoke(new ActionErrorEvent
                {
                    unit = parent,
                    action = e.action,
                    cause = e.unitActionErrorType
                });
            }
        }

        /// <summary>
        /// 1. Adds given action to queue
        /// 2. Starts it if queue is empty
        /// </summary>
        public void AddAction(AbstractAction action) => AddActionChain(new List<AbstractAction> { action });

        /// <summary>
        /// 1. Adds given actions to queue
        /// 2. Runs only when previous action is finished successfully
        /// 3. Starts it if queue is empty
        /// </summary>
        public void AddActionChain(List<AbstractAction> actions)
        {
            // Unit is currently not controllable (stunned? doing something unstoppable? magically frozen by dev?)
            if (!parent.unitState.isControllable)
            {
                parent.unitEvents.onActionError.Invoke(new ActionErrorEvent
                {
                    unit = parent,
                    action = actions.First(),
                    cause = UnitActionErrorType.UnitStateIsNotControllable
                });
                return;
            }
            
            actions.ForEach((action, actionIndex) =>
            {
                if (actionIndex != 0) action.runAfterAction = actions[actionIndex - 1];
                _actionQueue.Add(action);
            });
            
            if (_actionCurrent == null) NextAction();
        }

        /// <summary>
        /// 1. Clear whole action queue
        /// 2. Cancel current action IF specified by argument
        /// 3. Some actions cant be stopped once fired by normal means, BUT we can force stopping it
        /// </summary>
        public void StopQueue(bool stopCurrent = true, bool stopForced = false)
        {
            _actionQueue.Clear();

            if (!stopCurrent) return;
            if (!stopForced && _actionCurrent != null && !_actionCurrent.CanBeStopped()) return;
            
            _actionCurrent?.CancelIt();
            _actionCurrent = null;
        }

        /// <summary>
        /// 1. Gets next action to do
        /// 2. If there is no more actions - do nothing
        /// 3. Proceed to execute it
        /// </summary>
        private void NextAction()
        {
            _actionCurrent = _actionQueue.FirstOrDefault();
            _actionQueue.Remove(_actionCurrent);

            if (_actionCurrent == null) return;

            try
            {
                _actionCurrent.DoIt();
            }
            catch (UnitActionErrorException e)
            {
                parent.unitEvents.onActionError.Invoke(new ActionErrorEvent
                {
                    unit = parent,
                    action = e.action,
                    cause = e.unitActionErrorType
                });
            }
            
            if (_actionCurrent.state is ActionState.Ready) Debug.LogWarning($"Action {_actionCurrent.GetType().Name} is still ready after starting. Skipping.");
            if (_actionCurrent.state is not ActionState.Running) NextAction();
        }

        private void CheckRunningAction()
        {
            if (_actionCurrent?.state is ActionState.Running) return;
            NextAction();
        }
    }
}