/*************************************************************************************************
 * Copyright 2022-2025 Theai, Inc. dba Inworld AI
 *
 * Use of this source code is governed by the Inworld.ai Software Development Kit License Agreement
 * that can be found in the LICENSE.md file or at https://www.inworld.ai/sdk-license
 *************************************************************************************************/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using UnityEditor;
using UnityEditor.Build.Reporting;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
using UnityEngine;

namespace Inworld
{
	/// <summary>
	///     This file would be called by commands, for auto-generate Unity packages.
	/// </summary>
	public static class UnityPackageExporter
    {
        const string k_EditorPath = "Assets/Editor";
        const string k_PackageName = "InworldRuntime";
        const string k_PackagePath = "Assets/InworldRuntime.unitypackage";

        // The path to the package under the `Assets/` folder.
        const string k_FullPackagePath = "Assets/InworldRuntime";
        const string k_ExtraPackagePath = "Assets/InworldExtraAssets.unitypackage";
        const string k_EssentialPackagePath = "Assets/InworldEssentialAssets.unitypackage";
        const string k_LocalModelsPackagePath = "Assets/InworldLocalModels.unitypackage";
        const string k_DefaultFrameworkAssetPath = "Assets/InworldRuntime/Resources/InworldRuntime.asset";
        const string k_ApiKeyPropertyName = "m_APIKey";
        const string k_PluginsFolder = "Assets/InworldRuntime/Plugins";
        const string k_StreamingAssetsFolder = "Assets/StreamingAssets";
        const string k_SafetyStreamingFolder = "Assets/StreamingAssets/safety";
        const string k_VadStreamingFolder = "Assets/StreamingAssets/vad";

        /// <summary>
        ///     Call it via outside command line to export package.
        /// </summary>
        [MenuItem("Inworld/Export Package/Runtime/Full", false, 102)]
        public static void ExportFull()
        {
            ExportExtraAssets();
            string[] assetPaths =
            {
                k_EditorPath,
                k_ExtraPackagePath
            }; 
            AssetDatabase.ExportPackage(assetPaths, k_PackagePath, ExportPackageOptions.Recurse);
        }

		[MenuItem("Inworld/Export Package/Runtime/Extra Assets", false, 101)]
		public static void ExportExtraAssets()
		{
			ScriptableObject frameworkAsset = null;
			string originalApiKey = null;
			try
			{
				originalApiKey = ClearApiKeyForExport(out frameworkAsset);
				List<string> folders = new List<string>(AssetDatabase.GetSubFolders("Assets/InworldRuntime"));
				folders.Remove("Assets/InworldRuntime/Plugins");
				AssetDatabase.ExportPackage(folders.ToArray(), k_ExtraPackagePath, ExportPackageOptions.Recurse);
			}
			finally
			{
				RestoreApiKey(frameworkAsset, originalApiKey);
			}
		}

        [MenuItem("Inworld/Export Package/Runtime/Essential Assets", false, 103)]
        public static void ExportEssentialAssets()
        {
            string[] essentialPaths = CollectExistingPaths(new[]
            {
                k_PluginsFolder,
                k_SafetyStreamingFolder,
                k_VadStreamingFolder
            });
            ExportPackageWithValidation(essentialPaths, k_EssentialPackagePath, "essential assets");
        }

        [MenuItem("Inworld/Export Package/Runtime/Local Model Assets", false, 104)]
        public static void ExportLocalModelAssets()
        {
            string[] localModelPaths = CollectLocalModelStreamingAssets();
            ExportPackageWithValidation(localModelPaths, k_LocalModelsPackagePath, "local model assets");
        }

		static string ClearApiKeyForExport(out ScriptableObject frameworkAsset)
		{
			frameworkAsset = LoadFrameworkAsset();
			if (!frameworkAsset)
			{
				Debug.LogWarning("Unable to locate InworldFramework asset, skip API key cleanup before export.");
				return null;
			}
			return SwapApiKey(frameworkAsset, string.Empty);
		}

		static void RestoreApiKey(ScriptableObject frameworkAsset, string originalKey)
		{
			if (!frameworkAsset || originalKey == null)
				return;
			SwapApiKey(frameworkAsset, originalKey);
		}

		static ScriptableObject LoadFrameworkAsset()
		{
			ScriptableObject asset = AssetDatabase.LoadAssetAtPath<ScriptableObject>(k_DefaultFrameworkAssetPath);
			if (asset)
				return asset;
			string[] guids = AssetDatabase.FindAssets("InworldRuntime t:ScriptableObject");
			foreach (string guid in guids)
			{
				string path = AssetDatabase.GUIDToAssetPath(guid);
				asset = AssetDatabase.LoadAssetAtPath<ScriptableObject>(path);
				if (AssetContainsApiKeyField(asset))
					return asset;
			}
			return null;
		}

		static string SwapApiKey(ScriptableObject frameworkAsset, string targetValue)
		{
			SerializedObject serialized = new SerializedObject(frameworkAsset);
			SerializedProperty apiKeyProp = serialized.FindProperty(k_ApiKeyPropertyName);
			if (apiKeyProp == null)
			{
				Debug.LogWarning($"Unable to find API key field on asset {frameworkAsset.name}, abort cleanup.");
				return null;
			}

			string previousValue = apiKeyProp.stringValue;
			apiKeyProp.stringValue = targetValue ?? string.Empty;
			serialized.ApplyModifiedPropertiesWithoutUndo();
			PersistFrameworkAsset(frameworkAsset);
			return previousValue;
		}

		static bool AssetContainsApiKeyField(ScriptableObject asset)
		{
			if (!asset)
				return false;
			SerializedObject serialized = new SerializedObject(asset);
			return serialized.FindProperty(k_ApiKeyPropertyName) != null;
		}

		static void PersistFrameworkAsset(UnityEngine.Object frameworkAsset)
		{
			if (!frameworkAsset)
				return;
			EditorUtility.SetDirty(frameworkAsset);
			AssetDatabase.SaveAssets();
		}

        static string[] CollectExistingPaths(IEnumerable<string> candidatePaths)
        {
            List<string> paths = new List<string>();
            foreach (string path in candidatePaths)
            {
                if (string.IsNullOrEmpty(path))
                    continue;
                if (AssetDatabase.IsValidFolder(path) || AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(path))
                    paths.Add(path);
            }
            return paths.ToArray();
        }

        static string[] CollectLocalModelStreamingAssets()
        {
            if (!AssetDatabase.IsValidFolder(k_StreamingAssetsFolder))
                return Array.Empty<string>();

            List<string> entries = new List<string>();
            string[] subFolders = AssetDatabase.GetSubFolders(k_StreamingAssetsFolder);
            foreach (string folder in subFolders)
            {
                if (!IsEssentialStreamingFolder(folder))
                    entries.Add(folder);
            }

            string absoluteStreamingPath = Path.GetFullPath(k_StreamingAssetsFolder);
            if (Directory.Exists(absoluteStreamingPath))
            {
                string[] files = Directory.GetFiles(absoluteStreamingPath);
                foreach (string file in files)
                {
                    if (file.EndsWith(".meta", StringComparison.OrdinalIgnoreCase))
                        continue;
                    string assetPath = file.Replace('\\', '/');
                    assetPath = assetPath.Substring(assetPath.IndexOf("Assets/", StringComparison.Ordinal));
                    entries.Add(assetPath);
                }
            }

            return entries.ToArray();
        }

        static bool IsEssentialStreamingFolder(string folderPath)
        {
            return string.Equals(folderPath, k_SafetyStreamingFolder, StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(folderPath, k_VadStreamingFolder, StringComparison.OrdinalIgnoreCase);
        }

        static void ExportPackageWithValidation(IReadOnlyCollection<string> assetPaths, string packagePath, string description)
        {
            if (assetPaths == null || assetPaths.Count == 0)
            {
                Debug.LogWarning($"Unable to export {description}: no valid assets were found.");
                return;
            }
            AssetDatabase.ExportPackage(assetPaths.ToArray(), packagePath, ExportPackageOptions.Recurse);
            Debug.Log($"Exported {description} to {packagePath}.");
        }
    }
}
