using System;
using JetBrains.Annotations;
using JohnsonUtils.Utilities;
using JohnsonUtils.Utilities.Icons;
using UnityEngine;
using UnityEngine.InputSystem;

namespace JohnsonUtils.Canvases
{
    public class RebindActionUI : MonoBehaviour
    {
        public event Action<string, string> UpdateBindingUIEvent;

        [SerializeField] private TextUIComponent actionLabel;
        [SerializeField] private RebindReferences mainBindingReferences;
        [SerializeField] private RebindReferences altBindingReferences;

        private TextUIComponent _rebindOverlay;
        private InputAction _action;
        private InputActionRebindingExtensions.RebindingOperation _rebindOperation;

        [PublicAPI]
        public void ResetToDefault()
        {
            _action.actionMap.Disable();
            _action.RemoveBindingOverride(mainBindingReferences.Index);
            _action.RemoveBindingOverride(altBindingReferences.Index);
            UpdateBindingDisplay();
            _action.actionMap.Enable();
        }

        private void StartInteractiveRebind(int index)
        {
            _action.actionMap.Disable();
            PerformInteractiveRebind(index);
            _action.actionMap.Enable();
        }

        private void PerformInteractiveRebind(int bindingIndex)
        {
            _rebindOperation?.Cancel(); // Will null out rebindOperation.

            void CleanUp(InputActionRebindingExtensions.RebindingOperation rebindingOperation)
            {
                if (_rebindOverlay != null)
                {
                    _rebindOverlay.SetActive(false);
                }

                UpdateBindingDisplay();
                _rebindOperation?.Dispose();
                _rebindOperation = null;
            }

            // Configure the rebind.
            _rebindOperation = _action.PerformInteractiveRebinding(bindingIndex)
                .OnCancel(CleanUp)
                .OnComplete(CleanUp);

            // If it's a part binding, show the name of the part in the UI.
            string partName = string.Empty;
            if (_action.bindings[bindingIndex].isPartOfComposite)
                partName = $"Binding '{_action.bindings[bindingIndex].name}'. ";

            // Bring up rebind overlay, if we have one.
            if (_rebindOverlay != null)
            {
                _rebindOverlay.Show();
                string text = !string.IsNullOrEmpty(_rebindOperation.expectedControlType)
                    ? $"{partName}Waiting for {_rebindOperation.expectedControlType} input..."
                    : $"{partName}Waiting for input...";
                _rebindOverlay.Text =text;
            }

            // If we have no rebind overlay and no callback but we have a binding text label,
            // temporarily set the binding text label to "<Waiting>".
            if (_rebindOverlay == null && mainBindingReferences.Text != null)
                mainBindingReferences.Text.Text = "<Waiting...>";

            _rebindOperation.Start();
        }

        protected void OnEnable()
        {
            mainBindingReferences.Button.OnClick += () => StartInteractiveRebind(mainBindingReferences.Index);
            altBindingReferences.Button.OnClick += () => StartInteractiveRebind(altBindingReferences.Index);
        }

        protected void OnDisable()
        {
            _rebindOperation?.Dispose();
            _rebindOperation = null;
        }

        private void UpdateActionLabel()
        {
            if (actionLabel == null) return;

            string actionLabelText = _action.bindings[mainBindingReferences.Index].isPartOfComposite
                ? _action.bindings[mainBindingReferences.Index].name.Capitalize()
                : _action.name;

            if (actionLabelText.Equals("Dash")) actionLabelText = "Tackle";
            actionLabel.Text = actionLabelText;
        }

        public void UpdateBindingDisplay(bool shouldCallEvent = true)
        {
            string mainDisplayString = string.Empty;
            string altDisplayString = string.Empty;
            string deviceLayoutName = string.Empty;
            string mainControlPath = string.Empty;
            string altControlPath = string.Empty;

            if (_action != null)
            {
                if (mainBindingReferences.Index != -1)
                    mainDisplayString = _action.GetBindingDisplayString(mainBindingReferences.Index,
                        out deviceLayoutName,
                        out mainControlPath);
                if (altBindingReferences.Index != -1)
                    altDisplayString = _action.GetBindingDisplayString(altBindingReferences.Index, out _,
                        out altControlPath);
            }

            UpdateDuplicateText(mainBindingReferences, mainDisplayString);
            UpdateDuplicateText(altBindingReferences, altDisplayString);

            Sprite mainIcon = BindingsIconsUtil.GetSprite(deviceLayoutName, mainControlPath);
            Sprite altIcon = BindingsIconsUtil.GetSprite(deviceLayoutName, altControlPath);

            DisplayIcon(mainBindingReferences, mainIcon);
            DisplayIcon(altBindingReferences, altIcon);

            if (shouldCallEvent)
            {
                UpdateBindingUIEvent?.Invoke(deviceLayoutName, mainControlPath);
            }
        }

        private void DisplayIcon(RebindReferences reference, Sprite mainIcon)
        {
            if (mainIcon != null)
            {
                reference.Text.Hide();
                reference.Image.Sprite = mainIcon;
                reference.Image.Show();
            }
            else
            {
                reference.Text.Show();
                reference.Image.Hide();
            }
        }

        private void UpdateDuplicateText(RebindReferences reference, string mainDisplayString)
        {
            if (reference.Text == null) return;
            reference.Text.Text = mainDisplayString == "Delta" ? "Mouse" : mainDisplayString;
            bool mainDuplicate = CheckDuplicateBindings(reference.Index);
            reference.Button.Color = mainDuplicate ? Color.red : Color.white;
        }

        private bool CheckDuplicateBindings(int bindingIndex)
        {
            if (_action == null) return false;

            InputBinding newBinding = _action.bindings[bindingIndex];

            if (newBinding.effectivePath.IsNullOrEmpty()) return false;

            foreach (InputBinding binding in _action.actionMap.bindings)
            {
                if (binding.action == newBinding.action) continue;

                if (binding.effectivePath == newBinding.effectivePath) return true;
            }

            if (!_action.bindings[0].isComposite) return false;

            for (int i = 1; i < _action.bindings.Count; i++)
            {
                if (i == bindingIndex) continue;
                if (_action.bindings[i].effectivePath == newBinding.effectivePath)
                {
                    return true;
                }
            }

            return false;
        }

        public void Initialize(int bindingIndex, InputAction inputAction, TextUIComponent overlay,
            Action<string, string> onUpdateBindingUIEvent)
        {
            mainBindingReferences.Index = bindingIndex;
            altBindingReferences.Index = bindingIndex + 1;
            _action = inputAction;
            _rebindOverlay = overlay;
            UpdateBindingUIEvent += onUpdateBindingUIEvent;
            UpdateActionLabel();
            UpdateBindingDisplay();
        }
    }
}