/* 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>, June 2026
*/

using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;

namespace AvatarSDKMove.Editor
{
	public class AnimationGeneratorWindow : EditorWindow
	{
		private GameObject targetModel;

		private string prompt = "a person is jumping";
		private float duration = 5.0f;

		private Connection connection = new Connection();
		private bool isBusy = false;
		private string statusMessage = string.Empty;
		private string controllerStatusMessage = string.Empty;

		[SerializeField] private AnimationClip[] previewClips = new AnimationClip[0];
		[SerializeField] private int previewClipIndex = 0;
		[SerializeField] private string importedAssetPath = string.Empty;

		private AnimationClipPreview preview = new AnimationClipPreview();
		private Vector2 scrollPosition = Vector2.zero;

		[MenuItem("Avatar SDK Move/Animation Generator", priority = 1)]
		public static void ShowWindow()
		{
			AnimationGeneratorWindow window = GetWindow<AnimationGeneratorWindow>("Animation Generator");
			window.minSize = new Vector2(340, 320);
			window.Show();
		}

		private void OnEnable()
		{
			UpdateWindowSize();
		}

		private void OnDisable()
		{
			preview.Dispose();
		}

		private void OnGUI()
		{
			scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);

			GUILayout.Label("Animation Generator", EditorStyles.boldLabel);
			GUILayout.Space(10);

			targetModel = (GameObject)EditorGUILayout.ObjectField("Target Model", targetModel, typeof(GameObject), true);
			GUILayout.Space(5);

			bool isValid = IsTargetModelValid();
			if (targetModel != null && !isValid)
			{
				EditorGUILayout.HelpBox(
					"The assigned model does not have a valid Humanoid Animator.\n" +
					"Make sure it contains an Animator component with an Avatar set to Humanoid.",
					MessageType.Error
				);
			}

			if (isValid)
			{
				GUILayout.Space(10);
				if (AuthUtils.HasSession())
					DrawGenerationGUI();
				else
					DrawAuthenticationGUI();
			}

			if (!string.IsNullOrEmpty(statusMessage))
			{
				GUILayout.Space(10);
				EditorGUILayout.HelpBox(statusMessage, MessageType.Info);
			}

			GUILayout.Space(10);
			DrawPreviewGUI();

			DrawAnimatorControllerGUI(isValid);

			EditorGUILayout.EndScrollView();

			if (GetPreviewClip() != null)
				Repaint();
		}

		private void DrawPreviewGUI()
		{
			AnimationClip previewClip = GetPreviewClip();
			if (previewClip == null)
				return;

			if (previewClips.Length > 1)
			{
				string[] clipNames = previewClips.Select(clip => clip == null ? "<missing>" : clip.name).ToArray();
				previewClipIndex = EditorGUILayout.Popup("Clip", previewClipIndex, clipNames);
				previewClip = GetPreviewClip();
			}

			preview.SetClip(previewClip);
			preview.Draw(260, targetModel);
		}

		private AnimationClip GetPreviewClip()
		{
			if (previewClips == null || previewClipIndex < 0 || previewClipIndex >= previewClips.Length)
				return null;

			return previewClips[previewClipIndex];
		}

		private void SetPreviewClips(AnimationClip[] clips)
		{
			previewClips = clips ?? new AnimationClip[0];
			previewClipIndex = 0;
			preview.SetClip(GetPreviewClip());
			UpdateWindowSize();
		}

		private void UpdateWindowSize()
		{
			minSize = new Vector2(340, GetPreviewClip() != null ? 640 : 320);
		}

		private void DrawAuthenticationGUI()
		{
			EditorGUILayout.HelpBox("Avatar SDK credentials are not provided.", MessageType.Warning);

			GUI.enabled = !isBusy;
			if (GUILayout.Button("Open Account Window", GUILayout.Height(30)))
			{
				AccountWindow.ShowWindow();
			}
			GUI.enabled = true;
		}

		private void DrawAnimatorControllerGUI(bool isTargetModelValid)
		{
			if (string.IsNullOrEmpty(importedAssetPath))
				return;

			GUILayout.Space(10);

			GUI.enabled = !isBusy && isTargetModelValid;
			if (GUILayout.Button("Create Animator Controller", GUILayout.Height(30)))
			{
				CreateAnimatorController(importedAssetPath);
			}
			GUI.enabled = true;

			if (!string.IsNullOrEmpty(controllerStatusMessage))
			{
				GUILayout.Space(5);
				EditorGUILayout.HelpBox(controllerStatusMessage, MessageType.Info);
			}
		}

		private void DrawGenerationGUI()
		{
			GUILayout.Label("Generate Animation", EditorStyles.boldLabel);

			EditorGUILayout.LabelField("Prompt");
			prompt = EditorGUILayout.TextArea(prompt, GUILayout.Height(50));

			duration = EditorGUILayout.FloatField("Duration, sec", duration);

			GUI.enabled = !isBusy && !string.IsNullOrEmpty(prompt);
			if (GUILayout.Button("Generate Animation", GUILayout.Height(30)))
			{
				GenerateAnimation();
			}
			GUI.enabled = true;
		}

		private async void GenerateAnimation()
		{
			isBusy = true;
			importedAssetPath = string.Empty;
			SetPreviewClips(null);
			SetControllerStatus(string.Empty);
			SetStatus("Generating animation...");
			try
			{
				GeneratedAnimation animation = await connection.GenerateAnimation(prompt, duration, "fbx", SetStatus);

				string assetPath = SaveAnimationAsset(animation);
				if (string.IsNullOrEmpty(assetPath))
				{
					SetStatus("Generated animation doesn't contain an fbx file.");
					return;
				}

				ImportAnimationAsset(assetPath);
				SetStatus("Animation saved: " + assetPath);
			}
			catch (Exception ex)
			{
				Debug.LogException(ex);
				SetStatus("Animation generation failed: " + ex.Message);
			}
			finally
			{
				isBusy = false;
				Repaint();
			}
		}

		private string SaveAnimationAsset(GeneratedAnimation animation)
		{
			Directory.CreateDirectory(AvatarSDKMoveSettings.TargetFolder);

			string animationDir = Path.Combine(AvatarSDKMoveSettings.TargetFolder, animation.code).Replace("\\", "/");
			if (Directory.Exists(animationDir))
				Directory.Delete(animationDir, true);
			Directory.CreateDirectory(animationDir);

			string zipFilename = Path.Combine(Path.GetTempPath(), animation.code + ".zip");
			File.WriteAllBytes(zipFilename, animation.data);
			ZipFile.ExtractToDirectory(zipFilename, animationDir);
			File.Delete(zipFilename);

			AssetDatabase.Refresh();

			string animationFile = Directory.GetFiles(animationDir, "*.fbx", SearchOption.AllDirectories).FirstOrDefault();
			return animationFile == null ? null : animationFile.Replace("\\", "/");
		}

		private bool IsTargetModelValid()
		{
			if (targetModel == null)
				return false;

			Animator anim = targetModel.GetComponent<Animator>();
			return anim != null && anim.isHuman;
		}

		private void SetStatus(string message)
		{
			statusMessage = message;
			Repaint();
		}

		private void SetControllerStatus(string message)
		{
			controllerStatusMessage = message;
			Repaint();
		}

		private void ImportAnimationAsset(string assetPath)
		{
			AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate);

			if (AssetImporter.GetAtPath(assetPath) is ModelImporter)
				ConfigureModelImporter(assetPath);

			AssetDatabase.Refresh();

			UnityEngine.Object animationAsset = AssetDatabase.LoadMainAssetAtPath(assetPath);
			Selection.activeObject = animationAsset;
			EditorGUIUtility.PingObject(animationAsset);

			importedAssetPath = assetPath;
			SetPreviewClips(LoadAnimationClips(assetPath));

			Debug.Log($"Animation imported and configured: {assetPath}");
			LogAnimationInfo(assetPath, previewClips);
		}

		private static void LogAnimationInfo(string assetPath, AnimationClip[] clips)
		{
			foreach (AnimationClip clip in clips)
				Debug.Log($"Clip '{clip.name}': length={clip.length:0.###}s frameRate={clip.frameRate} frames={clip.length * clip.frameRate:0.##}");

			ModelImporter importer = AssetImporter.GetAtPath(assetPath) as ModelImporter;
			if (importer == null)
				return;

			foreach (ModelImporterClipAnimation take in importer.defaultClipAnimations)
				Debug.Log($"Take '{take.takeName}': firstFrame={take.firstFrame} lastFrame={take.lastFrame}");
		}

		private static AnimationClip[] LoadAnimationClips(string animationAssetPath)
		{
			return AssetDatabase.LoadAllAssetsAtPath(animationAssetPath)
								.OfType<AnimationClip>()
								.Where(clip => !clip.name.StartsWith("__preview__"))
								.ToArray();
		}

		private void CreateAnimatorController(string animationAssetPath)
		{
			AnimationClip[] clips = LoadAnimationClips(animationAssetPath);

			if (clips.Length == 0)
			{
				Debug.LogWarning("No usable animation clips found in the imported asset.");
				SetControllerStatus("No usable animation clips found in the imported asset.");
				return;
			}

			string controllerFileName = Path.GetFileNameWithoutExtension(animationAssetPath) + "_Animator.controller";
			string controllerPath = Path.Combine(Path.GetDirectoryName(animationAssetPath), controllerFileName).Replace("\\", "/");

			if (File.Exists(controllerPath))
				AssetDatabase.DeleteAsset(controllerPath);

			AnimatorController controller = AnimatorController.CreateAnimatorControllerAtPath(controllerPath);
			if (controller == null)
			{
				Debug.LogError("Failed to create Animator Controller.");
				SetControllerStatus("Failed to create Animator Controller.");
				return;
			}

			var stateMachine = controller.layers[0].stateMachine;
			foreach (AnimationClip clip in clips)
			{
				var state = stateMachine.AddState(clip.name);
				state.motion = clip;
				if (clips[0] == clip)
					stateMachine.defaultState = state;
			}

			EditorUtility.SetDirty(controller);
			AssetDatabase.SaveAssets();

			Animator anim = targetModel.GetComponent<Animator>();
			if (anim != null)
			{
				anim.runtimeAnimatorController = controller;
				EditorUtility.SetDirty(anim);
			}

			Selection.activeObject = controller;
			EditorGUIUtility.PingObject(controller);

			Debug.Log($"Animator Controller created and assigned to {targetModel.name}: {controllerPath}");
			SetControllerStatus("Animator Controller assigned: " + controllerPath);
		}

		public static void ConfigureModelImporter(string assetPath)
		{
			ModelImporter importer = AssetImporter.GetAtPath(assetPath) as ModelImporter;
			if (importer == null)
			{
				Debug.LogError($"Could not get ModelImporter for {assetPath}");
				return;
			}

			importer.animationType = ModelImporterAnimationType.Human;

			string avatarDefinitionPath = AssetDatabase.GUIDToAssetPath(AvatarSDKMoveSettings.AvatarDefinitionGuid);
			Avatar avatarDefinition = string.IsNullOrEmpty(avatarDefinitionPath)
				? null
				: AssetDatabase.LoadAssetAtPath<Avatar>(avatarDefinitionPath);

			if (avatarDefinition == null)
			{
				Debug.LogError($"Could not load the avatar definition asset (guid {AvatarSDKMoveSettings.AvatarDefinitionGuid})");
				importer.avatarSetup = ModelImporterAvatarSetup.CreateFromThisModel;
			}
			else
			{
				importer.avatarSetup = ModelImporterAvatarSetup.CopyFromOther;
				importer.sourceAvatar = avatarDefinition;
			}

			importer.importAnimation = true;
			importer.motionNodeName = string.Empty;
			importer.resampleCurves = true;
			importer.animationCompression = ModelImporterAnimationCompression.Off;

			importer.SaveAndReimport();
		}
	}
}
