#if UNITY_EDITOR && UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
using CmdEvents = UnityEngine.InputSystem.Editor.InputActionsEditorConstants.CommandEvents;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.UIElements;

namespace UnityEngine.InputSystem.Editor
{
    /// <summary>
    /// A list view to display the action maps of the currently opened input actions asset.
    /// </summary>
    internal class ActionMapsView : ViewBase<ActionMapsView.ViewState>
    {
        public ActionMapsView(VisualElement root, StateContainer stateContainer)
            : base(root, stateContainer)
        {
            m_ListView = root.Q<ListView>("action-maps-list-view");
            m_ListView.selectionType = UIElements.SelectionType.Single;
            m_ListView.reorderable = true;
            m_ListViewSelectionChangeFilter = new CollectionViewSelectionChangeFilter(m_ListView);
            m_ListViewSelectionChangeFilter.selectedIndicesChanged += (selectedIndices) =>
            {
                Dispatch(Commands.SelectActionMap(((ActionMapData)m_ListView.selectedItem).mapName));
            };

            m_ListView.bindItem = (element, i) =>
            {
                var treeViewItem = (InputActionMapsTreeViewItem)element;
                var mapData = (ActionMapData)m_ListView.itemsSource[i];
                treeViewItem.label.text = mapData.mapName;
                treeViewItem.EditTextFinishedCallback = newName => ChangeActionMapName(i, newName);
                treeViewItem.EditTextFinished += treeViewItem.EditTextFinishedCallback;
                treeViewItem.userData = i;
                element.SetEnabled(!mapData.isDisabled);

                ContextMenu.GetContextMenuForActionMapItem(this, treeViewItem, i);
            };
            m_ListView.makeItem = () => new InputActionMapsTreeViewItem();
            m_ListView.unbindItem = (element, i) =>
            {
                var treeViewElement = (InputActionMapsTreeViewItem)element;
                treeViewElement.Reset();
                treeViewElement.EditTextFinished -= treeViewElement.EditTextFinishedCallback;
            };

            m_ListView.itemsChosen += objects =>
            {
                var item = m_ListView.GetRootElementForIndex(m_ListView.selectedIndex).Q<InputActionMapsTreeViewItem>();
                item.FocusOnRenameTextField();
            };

            m_ListView.RegisterCallback<ExecuteCommandEvent>(OnExecuteCommand);
            m_ListView.RegisterCallback<ValidateCommandEvent>(OnValidateCommand);
            m_ListView.RegisterCallback<PointerDownEvent>(OnPointerDown, TrickleDown.TrickleDown);

            // ISXB-748 - Scrolling the view causes a visual glitch with the rename TextField. As a work-around we
            // need to cancel the rename operation in this scenario.
            m_ListView.RegisterCallback<WheelEvent>(e => InputActionMapsTreeViewItem.CancelRename(), TrickleDown.TrickleDown);

            var treeView = root.Q<TreeView>("actions-tree-view");
            m_ListView.AddManipulator(new DropManipulator(OnDroppedHandler, treeView));
            m_ListView.itemIndexChanged += OnReorder;

            CreateSelector(Selectors.GetActionMapNames, Selectors.GetSelectedActionMap, (actionMapNames, actionMap, state) => new ViewState(actionMap, actionMapNames, state.GetDisabledActionMaps(actionMapNames.ToList())));

            m_AddActionMapButton = root.Q<Button>("add-new-action-map-button");
            m_AddActionMapButton.clicked += AddActionMap;

            ContextMenu.GetContextMenuForActionMapsEmptySpace(this, root.Q<VisualElement>("rclick-area-to-add-new-action-map"));
        }

        void OnDroppedHandler(int mapIndex)
        {
            Dispatch(Commands.PasteActionIntoActionMap(mapIndex));
        }

        void OnReorder(int oldIndex, int newIndex)
        {
            Dispatch(Commands.ReorderActionMap(oldIndex, newIndex));
        }

        public override void RedrawUI(ViewState viewState)
        {
            m_ListView.itemsSource = viewState.actionMapData?.ToList() ?? new List<ActionMapData>();
            if (viewState.selectedActionMap.HasValue)
            {
                var actionMapData = viewState.actionMapData?.Find(map => map.mapName.Equals(viewState.selectedActionMap.Value.name));
                if (actionMapData.HasValue)
                    m_ListView.SetSelection(viewState.actionMapData.IndexOf(actionMapData.Value));
            }
            // UI toolkit doesn't behave the same on 6000.0 way when refreshing items
            // On previous versions, we need to call Rebuild() to refresh the items since refreshItems() is less predicatable
#if UNITY_6000_0_OR_NEWER
            m_ListView.RefreshItems();
#else
            m_ListView.Rebuild();
#endif
            RenameNewActionMaps();
        }

        public override void DestroyView()
        {
            m_AddActionMapButton.clicked -= AddActionMap;
        }

        private void RenameNewActionMaps()
        {
            if (!m_EnterRenamingMode)
                return;
            m_ListView.ScrollToItem(m_ListView.selectedIndex);
            var element = m_ListView.GetRootElementForIndex(m_ListView.selectedIndex);
            if (element == null)
                return;
            ((InputActionMapsTreeViewItem)element).FocusOnRenameTextField();
        }

        internal void RenameActionMap(int index)
        {
            m_ListView.ScrollToItem(index);
            var element = m_ListView.GetRootElementForIndex(index);
            if (element == null)
                return;
            ((InputActionMapsTreeViewItem)element).FocusOnRenameTextField();
        }

        internal void DeleteActionMap(int index)
        {
            Dispatch(Commands.DeleteActionMap(index));
        }

        internal void DuplicateActionMap(int index)
        {
            Dispatch(Commands.DuplicateActionMap(index));
        }

        internal void CopyItems()
        {
            Dispatch(Commands.CopyActionMapSelection());
        }

        internal void CutItems()
        {
            Dispatch(Commands.CutActionMapSelection());
        }

        internal void PasteItems(bool copiedAction)
        {
            Dispatch(copiedAction ? Commands.PasteActionFromActionMap(InputActionsEditorView.s_OnPasteCutElements) : Commands.PasteActionMaps(InputActionsEditorView.s_OnPasteCutElements));
        }

        private void ChangeActionMapName(int index, string newName)
        {
            m_EnterRenamingMode = false;
            Dispatch(Commands.ChangeActionMapName(index, newName));
        }

        internal void AddActionMap()
        {
            Dispatch(Commands.AddActionMap());
            m_EnterRenamingMode = true;
        }

        internal int GetMapCount()
        {
            return m_ListView.itemsSource.Count;
        }

        private void OnExecuteCommand(ExecuteCommandEvent evt)
        {
            var selectedItem = m_ListView.GetRootElementForIndex(m_ListView.selectedIndex);
            if (selectedItem == null)
                return;

            if (allowUICommandExecution)
            {
                switch (evt.commandName)
                {
                    case CmdEvents.Rename:
                        ((InputActionMapsTreeViewItem)selectedItem).FocusOnRenameTextField();
                        break;
                    case CmdEvents.Delete:
                    case CmdEvents.SoftDelete:
                        DeleteActionMap(m_ListView.selectedIndex);
                        break;
                    case CmdEvents.Duplicate:
                        DuplicateActionMap(m_ListView.selectedIndex);
                        break;
                    case CmdEvents.Copy:
                        CopyItems();
                        break;
                    case CmdEvents.Cut:
                        CutItems();
                        break;
                    case CmdEvents.Paste:
                        var isActionCopied = CopyPasteHelper.GetCopiedClipboardType() == typeof(InputAction);
                        if (CopyPasteHelper.HasPastableClipboardData(typeof(InputActionMap)))
                            PasteItems(isActionCopied);
                        break;
                    default:
                        return; // Skip StopPropagation if we didn't execute anything
                }

                // Prevent any UI commands from executing until after UI has been updated
                allowUICommandExecution = false;
            }
            evt.StopPropagation();
        }

        private void OnValidateCommand(ValidateCommandEvent evt)
        {
            // Mark commands as supported for Execute by stopping propagation of the event
            switch (evt.commandName)
            {
                case CmdEvents.Rename:
                case CmdEvents.Delete:
                case CmdEvents.SoftDelete:
                case CmdEvents.Duplicate:
                case CmdEvents.Copy:
                case CmdEvents.Cut:
                case CmdEvents.Paste:
                    evt.StopPropagation();
                    break;
            }
        }

        private void OnPointerDown(PointerDownEvent evt)
        {
            // Allow right clicks to select an item before we bring up the matching context menu.
            if (evt.button == (int)MouseButton.RightMouse && evt.clickCount == 1)
            {
                var actionMap = (evt.target as VisualElement).GetFirstAncestorOfType<InputActionMapsTreeViewItem>();
                if (actionMap != null)
                    m_ListView.SetSelection(actionMap.parent.IndexOf(actionMap));
            }
        }

        private readonly CollectionViewSelectionChangeFilter m_ListViewSelectionChangeFilter;
        private bool m_EnterRenamingMode;
        private readonly ListView m_ListView;
        private readonly Button m_AddActionMapButton;

        internal struct ActionMapData
        {
            internal string mapName;
            internal bool isDisabled;

            public ActionMapData(string mapName, bool isDisabled)
            {
                this.mapName = mapName;
                this.isDisabled = isDisabled;
            }
        }

        internal class ViewState
        {
            public SerializedInputActionMap? selectedActionMap;
            public List<ActionMapData> actionMapData;

            public ViewState(SerializedInputActionMap? selectedActionMap, IEnumerable<string> actionMapNames, IEnumerable<string> disabledActionMapNames)
            {
                this.selectedActionMap = selectedActionMap;
                actionMapData = new List<ActionMapData>();
                foreach (var name in actionMapNames)
                {
                    actionMapData.Add(new ActionMapData(name, disabledActionMapNames.Contains(name)));
                }
            }
        }
    }
}

#endif