// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //

#if UNITY_EDITOR

using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.AnimatedValues;
using UnityEngine;
using Object = UnityEngine.Object;

namespace Animancer.Editor.Tools
{
    partial class AnimancerToolsWindow
    {
        /// <summary>[Editor-Only] [Pro-Only] Base class for tools in the <see cref="AnimancerToolsWindow"/>.</summary>
        /// <remarks>
        /// <strong>Documentation:</strong>
        /// <see href="https://kybernetik.com.au/animancer/docs/manual/tools">
        /// Animancer Tools</see>
        /// </remarks>
        /// https://kybernetik.com.au/animancer/api/Animancer.Editor.Tools/Tool
        /// 
        [Serializable]
        public abstract class Tool : IComparable<Tool>
        {
            /************************************************************************************************************************/

            private AnimBool _FullAnimator;
            private AnimBool _BodyAnimator;

            private int _Index;

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

            /// <summary>Is this tool currently visible?</summary>
            public bool IsVisible => Instance._CurrentTool == _Index || Instance._CurrentTool < 0;

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

            /// <summary>Is the body of this tool currently visible?</summary>
            public bool IsExpanded
            {
                get { return Instance._CurrentTool == _Index; }
                set
                {
                    if (value)
                        Instance._CurrentTool = _Index;
                    else if (IsExpanded)
                        Instance._CurrentTool = -1;
                }
            }

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

            /// <summary>Lower numbers display first.</summary>
            public abstract int DisplayOrder { get; }

            /// <summary>Compares the <see cref="DisplayOrder"/> to put lower numbers first.</summary>
            public int CompareTo(Tool other)
                => DisplayOrder.CompareTo(other.DisplayOrder);

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

            /// <summary>The display name of this tool.</summary>
            public abstract string Name { get; }

            /// <summary>The usage instructions to display at the top of this tool.</summary>
            public abstract string Instructions { get; }

            /// <summary>The URL for the help button in the header to open.</summary>
            public virtual string HelpURL => Strings.DocsURLs.AnimancerTools;

            /// <summary>Called whenever the <see cref="Selection"/> changes.</summary>
            public virtual void OnSelectionChanged() { }

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

            /// <summary>Called by <see cref="AnimancerToolsWindow.OnEnable"/>.</summary>
            public virtual void OnEnable(int index)
            {
                _Index = index;

                _FullAnimator = new(IsVisible);
                _BodyAnimator = new(IsExpanded);
            }

            /// <summary>Called by <see cref="AnimancerToolsWindow.OnDisable"/>.</summary>
            public virtual void OnDisable() { }

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

            /// <summary>Draws the GUI for this tool.</summary>
            public virtual void DoGUI()
            {
                var enabled = GUI.enabled;

                _FullAnimator.target = IsVisible;

                if (EditorGUILayout.BeginFadeGroup(_FullAnimator.faded))
                {
                    GUILayout.BeginVertical(EditorStyles.helpBox);

                    DoHeaderGUI();

                    _BodyAnimator.target = IsExpanded;

                    if (EditorGUILayout.BeginFadeGroup(_BodyAnimator.faded))
                    {
                        var instructions = Instructions;
                        if (!string.IsNullOrEmpty(instructions))
                            EditorGUILayout.HelpBox(instructions, MessageType.Info);

                        DoBodyGUI();
                    }
                    EditorGUILayout.EndFadeGroup();

                    GUILayout.EndVertical();
                }
                EditorGUILayout.EndFadeGroup();

                if (_FullAnimator.isAnimating || _BodyAnimator.isAnimating)
                    Repaint();

                GUI.enabled = enabled;
            }

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

            /// <summary>
            /// Draws the Header GUI for this tool which is displayed regardless of whether it is expanded or not.
            /// </summary>
            public virtual void DoHeaderGUI()
            {
                var area = AnimancerGUI.LayoutSingleLineRect(AnimancerGUI.SpacingMode.BeforeAndAfter);
                var click = GUI.Button(area, Name, EditorStyles.boldLabel);

                area.xMin = area.xMax - area.height;
                GUI.DrawTexture(area, HelpIcon);

                if (click)
                {
                    if (area.Contains(Event.current.mousePosition))
                    {
                        Application.OpenURL(HelpURL);
                        return;
                    }
                    else
                    {
                        IsExpanded = !IsExpanded;
                    }
                }
            }

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

            /// <summary>Draws the Body GUI for this tool which is only displayed while it is expanded.</summary>
            public abstract void DoBodyGUI();

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

            /// <summary>Asks the user where they want to save a modified asset, calls `modify` on it, and saves it.</summary>
            public static bool SaveModifiedAsset<T>(string saveTitle, string saveMessage,
                T obj, Action<T> modify) where T : Object
            {
                var originalPath = AssetDatabase.GetAssetPath(obj);

                var extension = Path.GetExtension(originalPath);
                if (extension[0] == '.')
                    extension = extension[1..];

                var directory = Path.GetDirectoryName(originalPath);

                var newName = Path.GetFileNameWithoutExtension(AssetDatabase.GenerateUniqueAssetPath(originalPath));
                var savePath = EditorUtility.SaveFilePanelInProject(saveTitle, newName, extension, saveMessage, directory);
                if (string.IsNullOrEmpty(savePath))
                    return false;

                if (originalPath != savePath)
                {
                    obj = Instantiate(obj);
                    AssetDatabase.CreateAsset(obj, savePath);
                }

                modify(obj);

                AssetDatabase.SaveAssets();

                return true;
            }

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

            private static Texture _HelpIcon;

            /// <summary>The help icon image used in the tool header.</summary>
            public static Texture HelpIcon
            {
                get
                {
                    if (_HelpIcon == null)
                        _HelpIcon = AnimancerIcons.Load("_Help");
                    return _HelpIcon;
                }
            }

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

            /// <summary>Adds any objects dropped in the `area` to the `list`.</summary>
            protected void HandleDragAndDropIntoList<T>(
                Rect area,
                IList<T> list,
                bool overwrite)
                where T : Object
            {
                var dropIndex = 0;

                // No easy way to avoid this closure.
                AnimancerGUI.Handle<T>((obj, isDrop) =>
                {
                    if (!isDrop)
                        return true;

                    if (overwrite)
                    {
                        RecordUndo();
                        if (dropIndex < list.Count)
                        {
                            list[dropIndex++] = obj;
                        }
                        else
                        {
                            list.Add(obj);
                        }
                    }
                    else
                    {
                        list.Add(obj);
                    }

                    return true;
                }, area);
            }

            /************************************************************************************************************************/
        }
    }
}

#endif

