using Intrepid.GameManagers;
using Intrepid.Gameplay.Entities.Player.ActionBelt.Events;
using Intrepid.Gameplay.Entities.Player.Tags;
using Intrepid.SimpleEvents;
using Intrepid.Utilities;
using Sirenix.OdinInspector;
using UnityEngine;

namespace Intrepid.Gameplay.Entities.Player.ActionBelt
{
    public class ActionBeltManager : EventListener
    {
        [GroupConfigurations, SerializeField] private ActionBeltManagerTag actionBeltManagerTag;
        [GroupConfigurations, SerializeField] private bool nextRight = true;
        
        [Title("Action Belt")]
        [GroupDebug, ShowInInspector, ReadOnly, HideLabel] private ActionStackBelt ActionBelt { get; } = new();
        
        [GroupDebug, ShowInInspector, ReadOnly, ToggleLeft, PropertyOrder(-1)] public bool IsOperational { get; private set; }
        public bool IsEmpty => ActionBelt.IsEmpty;
        public bool IsNotEmpty => !IsEmpty;
        public ActionBeltManagerTag ActionBeltManagerTag => actionBeltManagerTag;
        public ActionEnvelope Focus => ActionBelt.Focus;

        protected override void InitializeVariables()
        {
        }

        protected override void OnBootstrap()
        {
        }

        protected override void Subscriptions()
        {
            Subscribe<ActionBeltEnableEvent>(WhenActionBeltEnable);
            Subscribe<ActionBeltDisableEvent>(WhenActionBeltDisable);
            Subscribe<ActionBeltRefreshEvent>(WhenActionBeltRefresh);
            Subscribe<ActionBeltAddEvent>(WhenAddActionOnTheBelt);
            Subscribe<ActionBeltRemoveByIdEvent>(WhenActionBeltRemoveById);
            Subscribe<ActionBeltRemoveByTypeEvent>(WhenActionBeltRemoveByType);
            Subscribe<ActionBeltRemoveByIdAndTypeEvent>(WhenActionBeltRemoveByIdAndType);
            Subscribe<ActionBeltClearEvent>(WhenCleanActionBelt);
            Subscribe<ActionBeltDropEvent>(WhenActionBeltDrop);
            Subscribe<ActionBeltTakeActionEvent>(WhenActionBeltTakeAction);
            Subscribe<ActionBeltNextRightEvent>(WhenActionBeltNextRight);
            Subscribe<ActionBeltNextLeftEvent>(WhenActionBeltNextLeft);
        }

        private void WhenActionBeltDrop(ActionBeltDropEvent e)
        {
            if (!actionBeltManagerTag.Equals(e.ActionBeltManger)) return;
            if (ActionBelt.IsEmpty) return;
            if (!ActionBelt.Focus.CanBeDropped) return;
            if (!ActionBelt.RemoveById(ActionBelt.Focus.ActionIdentifier.Id, nextRight)) return;
            Proxy.Instance.EventBus.Raise(new ActionBeltUIDropEvent());
            CirculateUI();
        }

        private void WhenActionBeltEnable(ActionBeltEnableEvent e)
        {
            if (!actionBeltManagerTag.Equals(e.ActionBeltManger))  return;
            
            IsOperational = true;
            if (IsEmpty) return;
            OpenUI();
        }
        
        private void WhenActionBeltDisable(ActionBeltDisableEvent e)
        {
            if (!actionBeltManagerTag.Equals(e.ActionBeltManger)) return;
            IsOperational = false;
            Proxy.Instance.EventBus.Raise(new ActionBeltUICloseEvent());
        }
        
        private void WhenActionBeltRefresh(ActionBeltRefreshEvent e)
        {
            if (!actionBeltManagerTag.Equals(e.ActionBeltManger)) return;
            Proxy.Instance.EventBus.Raise(new ActionBeltUICloseEvent());
            ActionBelt.ResetIndex();
            // we should affect others managers as well...
            Proxy.Instance.EventBus.Raise(new ActionBeltEnableEvent
            {
                ActionBeltManger = e.ActionBeltManger,
            });
        }

        private void WhenAddActionOnTheBelt(ActionBeltAddEvent e)
        {
            if (!actionBeltManagerTag.Equals(e.ActionBeltManger)) return;
            ActionBelt.Push(e.ActionEnvelope);
            if (!IsOperational) return;
            OpenUI();
        }

        private void WhenActionBeltRemoveById(ActionBeltRemoveByIdEvent e)
        {
            if (!actionBeltManagerTag.Equals(e.ActionBeltManger)) return;
            if (!ActionBelt.RemoveById(e.ActionId, nextRight)) return;
            CirculateUI();
        }

        private void WhenActionBeltRemoveByType(ActionBeltRemoveByTypeEvent e)
        {
            if (!actionBeltManagerTag.Equals(e.ActionBeltManger)) return;
            if (!ActionBelt.RemoveByType(e.ActionCategory, nextRight)) return;
            CirculateUI();
        }
        
        private void WhenActionBeltRemoveByIdAndType(ActionBeltRemoveByIdAndTypeEvent e)
        {
            if (!actionBeltManagerTag.Equals(e.ActionBeltManger)) return;
            if (!ActionBelt.RemoveByIdAndType(e.ActionId, e.ActionCategory, nextRight)) return;
            CirculateUI();
        }
        
        private void WhenCleanActionBelt(ActionBeltClearEvent e)
        {
            if (!actionBeltManagerTag.Equals(e.ActionBeltManger)) return;
            ActionBelt.Clear();
        }

        private void WhenActionBeltTakeAction(ActionBeltTakeActionEvent e)
        {
            if (!actionBeltManagerTag.Equals(e.ActionBeltManger)) return;
            TakeAction();
            Proxy.Instance.EventBus.Raise(new ActionBeltUITakeActionEvent());
        }

        private void WhenActionBeltNextRight(ActionBeltNextRightEvent e)
        {
            if (!actionBeltManagerTag.Equals(e.ActionBeltManger)) return;
            ActionBelt.NextRight();
            CirculateUI();
        }

        private void WhenActionBeltNextLeft(ActionBeltNextLeftEvent e)
        {
            if (!actionBeltManagerTag.Equals(e.ActionBeltManger)) return;
            ActionBelt.NextLeft();
            CirculateUI();
        }

        private void TakeAction()
        {
            var actionEnvelope = ActionBelt.Focus;
            if (!actionEnvelope.Callback(actionEnvelope.ActionIdentifier)) return;
            if (!ActionBelt.RemoveById(actionEnvelope.ActionIdentifier.Id, nextRight)) return;
            CirculateUI();
        }

        private void OpenUI()
        {
            Proxy.Instance.EventBus.Raise(new ActionBeltUIOpenEvent
            {
                GetFocusActionEnvelope = () => ActionBelt.Focus,
                SneakPeekRightActionEnvelope = () => ActionBelt.PeekRight,
                SneakPeekLeftActionEnvelope = () => ActionBelt.PeekLeft,
                GetSize = () => ActionBelt.Count,
            });
        }
        
        private void CirculateUI()
        {
            if (nextRight)
            {
                Proxy.Instance.EventBus.Raise(new ActionBeltUIToRightEvent());
            }
            else
            {
                Proxy.Instance.EventBus.Raise(new ActionBeltUIToLeftEvent());
            }
        }

        public bool Contains(string id)
        {
            return ActionBelt.Contains(id);
        }
    }
}