/* Copyright (C) Itseez3D, Inc. - All Rights Reserved
* You may not use this file except in compliance with an authorized license
* Unauthorized copying of this file, via any medium is strictly prohibited
* Proprietary and confidential
* UNLESS REQUIRED BY APPLICABLE LAW OR AGREED BY ITSEEZ3D, INC. IN WRITING, SOFTWARE DISTRIBUTED UNDER THE LICENSE IS DISTRIBUTED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED
* See the License for the specific language governing permissions and limitations under the License.
* Written by Itseez3D, Inc. <support@avatarsdk.com>, July 2026
*/

using System;
using System.Reflection;
using UnityEditor;
using UnityEngine;

namespace AvatarSDKMove.Editor
{
	public class AnimationClipPreview : IDisposable
	{
		private const BindingFlags MemberLookup = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		private AnimationClip clip;
		private UnityEditor.Editor clipEditor;

		private FieldInfo avatarPreviewField;
		private FieldInfo timeControlField;
		private FieldInfo startTimeField;
		private FieldInfo stopTimeField;
		private PropertyInfo playingProperty;
		private FieldInfo playingField;
		private FieldInfo loopField;
		private MethodInfo setPreviewMethod;

		private bool timeRangeUnavailable;
		private bool previewModelUnavailable;
		private bool playbackUnavailable;
		private bool playbackStarted;

		private GameObject appliedPreviewModel;
		private bool useTargetModel = true;

		public bool IsValid { get { return clip != null; } }

		public void SetClip(AnimationClip value)
		{
			if (clip == value)
				return;

			clip = value;
			DestroyEditor();
		}

		public void Draw(float height, GameObject targetModel)
		{
			if (clip == null)
			{
				EditorGUILayout.HelpBox("No animation clip to preview.", MessageType.Info);
				return;
			}

			if (clipEditor == null || clipEditor.target != clip)
			{
				DestroyEditor();
				clipEditor = UnityEditor.Editor.CreateEditor(clip);
			}

			if (!clipEditor.HasPreviewGUI())
			{
				EditorGUILayout.HelpBox("This animation clip cannot be previewed.", MessageType.Warning);
				return;
			}

			GameObject previewModel = ResolvePreviewModel(targetModel);

			using (new EditorGUILayout.HorizontalScope(EditorStyles.toolbar))
			{
				GUILayout.Label($"{clip.name}   {clip.length:0.##}s", EditorStyles.miniLabel);
				GUILayout.FlexibleSpace();

				if (previewModel != null && !previewModelUnavailable)
				{
					bool useModel = GUILayout.Toggle(useTargetModel, "Target Model", EditorStyles.toolbarButton);
					if (useModel != useTargetModel)
					{
						useTargetModel = useModel;
						appliedPreviewModel = null;
					}
				}

				clipEditor.OnPreviewSettings();
			}

			object avatarPreview = GetAvatarPreview();
			ApplyTimeRange(avatarPreview);
			if (useTargetModel)
				ApplyPreviewModel(avatarPreview, previewModel);

			Rect previewRect = GUILayoutUtility.GetRect(0, height, GUILayout.ExpandWidth(true));
			clipEditor.OnInteractivePreviewGUI(previewRect, EditorStyles.helpBox);
		}

		public void Dispose()
		{
			DestroyEditor();
			clip = null;
		}

		private static GameObject ResolvePreviewModel(GameObject targetModel)
		{
			if (targetModel == null)
				return null;

			if (EditorUtility.IsPersistent(targetModel))
				return targetModel;

			GameObject source = PrefabUtility.GetCorrespondingObjectFromSource(targetModel);
			return source != null ? source : targetModel;
		}

		private object GetAvatarPreview()
		{
			if (avatarPreviewField == null)
			{
				foreach (FieldInfo field in clipEditor.GetType().GetFields(MemberLookup))
				{
					if (field.FieldType.Name == "AvatarPreview")
					{
						avatarPreviewField = field;
						break;
					}
				}
			}

			return avatarPreviewField == null ? null : avatarPreviewField.GetValue(clipEditor);
		}

		private void ApplyTimeRange(object avatarPreview)
		{
			if (timeRangeUnavailable || avatarPreview == null)
				return;

			if (timeControlField == null)
				timeControlField = avatarPreview.GetType().GetField("timeControl", MemberLookup);

			object timeControl = timeControlField == null ? null : timeControlField.GetValue(avatarPreview);
			if (timeControl == null)
			{
				timeRangeUnavailable = true;
				Debug.LogWarning("Could not access the animation preview time range. The preview may show only the first second of the clip.");
				return;
			}

			if (startTimeField == null)
				startTimeField = timeControl.GetType().GetField("startTime", MemberLookup);
			if (stopTimeField == null)
				stopTimeField = timeControl.GetType().GetField("stopTime", MemberLookup);

			if (startTimeField == null || stopTimeField == null)
			{
				timeRangeUnavailable = true;
				Debug.LogWarning("Could not access the animation preview time range. The preview may show only the first second of the clip.");
				return;
			}

			startTimeField.SetValue(timeControl, 0.0f);
			stopTimeField.SetValue(timeControl, clip.length);

			StartPlayback(timeControl);
		}

		private void StartPlayback(object timeControl)
		{
			if (playbackUnavailable || playbackStarted)
				return;

			if (playingProperty == null && playingField == null)
			{
				playingProperty = timeControl.GetType().GetProperty("playing", MemberLookup);
				if (playingProperty == null || !playingProperty.CanWrite)
				{
					playingProperty = null;
					playingField = timeControl.GetType().GetField("m_Playing", MemberLookup);
				}
			}

			if (playingProperty == null && playingField == null)
			{
				playbackUnavailable = true;
				Debug.LogWarning("Could not start the animation preview playback. Use the play button in the preview toolbar.");
				return;
			}

			if (loopField == null)
				loopField = timeControl.GetType().GetField("loop", MemberLookup);

			if (loopField != null)
				loopField.SetValue(timeControl, true);

			if (playingProperty != null)
				playingProperty.SetValue(timeControl, true);
			else
				playingField.SetValue(timeControl, true);

			playbackStarted = true;
		}

		private void ApplyPreviewModel(object avatarPreview, GameObject previewModel)
		{
			if (previewModelUnavailable || avatarPreview == null || previewModel == null)
				return;

			if (previewModel == appliedPreviewModel)
				return;

			if (setPreviewMethod == null)
			{
				foreach (MethodInfo method in avatarPreview.GetType().GetMethods(MemberLookup))
				{
					ParameterInfo[] parameters = method.GetParameters();
					if (method.Name == "SetPreview" && parameters.Length == 1 && parameters[0].ParameterType == typeof(GameObject))
					{
						setPreviewMethod = method;
						break;
					}
				}
			}

			if (setPreviewMethod == null)
			{
				previewModelUnavailable = true;
				Debug.LogWarning("Could not set the animation preview model. Use the avatar button in the preview toolbar to select it manually.");
				return;
			}

			setPreviewMethod.Invoke(avatarPreview, new object[] { previewModel });
			appliedPreviewModel = previewModel;
		}

		private void DestroyEditor()
		{
			avatarPreviewField = null;
			timeControlField = null;
			startTimeField = null;
			stopTimeField = null;
			playingProperty = null;
			playingField = null;
			loopField = null;
			setPreviewMethod = null;
			timeRangeUnavailable = false;
			previewModelUnavailable = false;
			playbackUnavailable = false;
			playbackStarted = false;
			appliedPreviewModel = null;

			if (clipEditor != null)
			{
				UnityEngine.Object.DestroyImmediate(clipEditor);
				clipEditor = null;
			}
		}
	}
}
