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

#if UNITY_EDITOR && UNITY_IMGUI

#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.

using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;

namespace Animancer.Editor.Previews
{
    /// https://kybernetik.com.au/animancer/api/Animancer.Editor.Previews/TransitionPreviewWindow
    partial class TransitionPreviewWindow
    {
        /************************************************************************************************************************/

        /// <summary>The <see cref="Scene"/> of the current <see cref="TransitionPreviewWindow"/> instance.</summary>
        public static Scene InstanceScene
            => _Instance != null
            ? _Instance._Scene
            : null;

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

        /// <summary>Temporary scene management for the <see cref="TransitionPreviewWindow"/>.</summary>
        /// <remarks>
        /// <strong>Documentation:</strong>
        /// <see href="https://kybernetik.com.au/animancer/docs/manual/transitions#previews">
        /// Previews</see>
        /// </remarks>
        [Serializable]
        public class Scene :
            AnimancerPreviewObject.IEventHandler
        {
            /************************************************************************************************************************/
            #region Fields and Properties
            /************************************************************************************************************************/

            /// <summary>The scene displayed by the <see cref="TransitionPreviewWindow"/>.</summary>
            [SerializeField]
            private UnityEngine.SceneManagement.Scene _Scene;

            /// <summary>The root object in the preview scene.</summary>
            public Transform PreviewSceneRoot { get; private set; }

            /// <summary>The root of the model in the preview scene. A child of the <see cref="PreviewSceneRoot"/>.</summary>
            public Transform InstanceRoot { get; private set; }

            /// <summary>
            /// An instance of the <see cref="TransitionPreviewSettings.SceneEnvironment"/>.
            /// A child of the <see cref="PreviewSceneRoot"/>.
            /// </summary>
            public GameObject EnvironmentInstance { get; private set; }

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

            [SerializeField]
            private AnimancerPreviewObject _PreviewObject;

            /// <summary>[<see cref="SerializeField"/>] The object being previewed.</summary>
            public AnimancerPreviewObject PreviewObject
                => AnimancerPreviewObject.Initialize(ref _PreviewObject, this, PreviewSceneRoot);

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

            private Vector3 _PreviousPreviewObjectPosition;

            private Vector3 CurrentPreviewObjectPosition
                => PreviewObject.SelectedInstanceAnimator.transform.position;

            /************************************************************************************************************************/
            #endregion
            /************************************************************************************************************************/
            #region Initialization
            /************************************************************************************************************************/

            /// <summary>Initializes this <see cref="Scene"/>.</summary>
            public void OnEnable()
            {
                duringSceneGui += DoCustomGUI;

                CreateScene();

                PreviewObject.TrySelectBestModel(Transition);
            }

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

            private void CreateScene()
            {
                _Scene = EditorSceneManager.NewPreviewScene();
                _Scene.name = "Transition Preview";
                _Instance.customScene = _Scene;

                var root = AnimancerPreviewObject.CreateEmpty(nameof(TransitionPreviewWindow));
                PreviewSceneRoot = root.transform;
                SceneManager.MoveGameObjectToScene(root, _Scene);
                _Instance.customParentForDraggedObjects = PreviewSceneRoot;

                OnEnvironmentPrefabChanged();
            }

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

            internal void OnEnvironmentPrefabChanged()
            {
                DestroyImmediate(EnvironmentInstance);

                var prefab = TransitionPreviewSettings.SceneEnvironment;
                if (prefab != null)
                    EnvironmentInstance = Instantiate(prefab, PreviewSceneRoot);
            }

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

            /// <inheritdoc/>
            void AnimancerPreviewObject.IEventHandler.OnInstantiateObject()
            {
                FocusCamera();
                _Instance._Animations.GatherAnimations();
            }

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

            /// <inheritdoc/>
            void AnimancerPreviewObject.IEventHandler.OnSetSelectedAnimator()
            {
                _Instance.in2DMode = PreviewObject.SelectedInstanceType == AnimationType.Sprite;
            }

            /// <inheritdoc/>
            void AnimancerPreviewObject.IEventHandler.OnCreateGraph()
            {
                PreviewObject.Graph.RequirePostUpdate(new Animations.WindowMatchStateTime());
                _Instance._Animations.NormalizedTime = _Instance._Animations.NormalizedTime;
            }

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

            /// <summary>Called when the target transition property is changed.</summary>
            public void OnTargetPropertyChanged()
            {
                _ExpandedHierarchy?.Clear();

                var previewObject = PreviewObject;

                previewObject.OriginalObject = AnimancerUtilities.FindRoot(_Instance._TransitionProperty.TargetObject);
                previewObject.TrySelectBestModel(Transition);

                _Instance._Animations.NormalizedTime = 0;

                _Instance.in2DMode = previewObject.SelectedInstanceType == AnimationType.Sprite;
            }

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

            private void FocusCamera()
            {
                if (InstanceRoot == null)
                    return;

                var bounds = CalculateBounds(InstanceRoot);

                var rotation = _Instance.in2DMode ?
                    Quaternion.identity :
                    Quaternion.Euler(35, 135, 0);

                var size = bounds.extents.magnitude * 1.5f;
                if (size == float.PositiveInfinity)
                    return;
                else if (size == 0)
                    size = 10;

                _Instance.LookAt(bounds.center, rotation, size, _Instance.in2DMode, true);
            }

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

            private static Bounds CalculateBounds(Transform transform)
            {
                if (transform == null)
                    return default;

                var renderers = transform.GetComponentsInChildren<Renderer>();
                if (renderers.Length == 0)
                    return default;

                var bounds = renderers[0].bounds;
                for (int i = 1; i < renderers.Length; i++)
                {
                    bounds.Encapsulate(renderers[i].bounds);
                }
                return bounds;
            }

            /************************************************************************************************************************/
            #endregion
            /************************************************************************************************************************/
            #region Execution
            /************************************************************************************************************************/

            /// <summary>Called when the window GUI is drawn.</summary>
            public void OnGUI()
            {
                if (_PreviewObject != null &&
                    _PreviewObject.Graph != null &&
                    _PreviewObject.Graph.IsGraphPlaying)
                    AnimancerGUI.RepaintEverything();

                if (Selection.activeObject == _Instance &&
                    Event.current.type == EventType.KeyUp &&
                    Event.current.keyCode == KeyCode.F)
                    FocusCamera();
            }

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

            private void DoCustomGUI(SceneView sceneView)
            {
                FollowPreviewObject(sceneView);

                var animancer = PreviewObject.Graph;
                if (animancer == null ||
                    sceneView is not TransitionPreviewWindow instance ||
                    !AnimancerUtilities.TryGetWrappedObject(Transition, out ITransitionGUI gui) ||
                    instance._TransitionProperty == null)
                    return;

                EditorGUI.BeginChangeCheck();

                using (new TransitionDrawer.DrawerContext(instance._TransitionProperty))
                {
                    try
                    {
                        gui.OnPreviewSceneGUI(new(animancer));
                    }
                    catch (Exception exception)
                    {
                        Debug.LogException(exception);
                    }
                }

                if (EditorGUI.EndChangeCheck())
                    AnimancerGUI.RepaintEverything();
            }

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

            private void FollowPreviewObject(SceneView sceneView)
            {
                var currentPreviewObjectPosition = CurrentPreviewObjectPosition;
                if (_PreviousPreviewObjectPosition == currentPreviewObjectPosition)
                    return;

                sceneView.pivot += currentPreviewObjectPosition - _PreviousPreviewObjectPosition;

                _PreviousPreviewObjectPosition = currentPreviewObjectPosition;
            }

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

            /// <summary>Is the `obj` a <see cref="GameObject"/> in the preview scene?</summary>
            public bool IsSceneObject(Object obj)
                => obj is GameObject gameObject
                && gameObject.transform.IsChildOf(PreviewSceneRoot);

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

            [SerializeField]
            private List<Transform> _ExpandedHierarchy;

            /// <summary>A list of all objects with their child hierarchy expanded.</summary>
            public List<Transform> ExpandedHierarchy
                => _ExpandedHierarchy ??= new();

            /************************************************************************************************************************/
            #endregion
            /************************************************************************************************************************/
            #region Cleanup
            /************************************************************************************************************************/

            /// <summary>Called by <see cref="TransitionPreviewWindow.OnDisable"/>.</summary>
            public void OnDisable()
            {
                duringSceneGui -= DoCustomGUI;

                _PreviewObject?.Dispose();

                EditorSceneManager.ClosePreviewScene(_Scene);
            }

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

            /// <summary>Called by <see cref="TransitionPreviewWindow.OnDestroy"/>.</summary>
            public void OnDestroy()
            {
                if (PreviewSceneRoot != null)
                {
                    DestroyImmediate(PreviewSceneRoot.gameObject);
                    PreviewSceneRoot = null;
                }
            }

            /************************************************************************************************************************/
            #endregion
            /************************************************************************************************************************/
        }
    }
}

#endif

