using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;

namespace CatsUdonTools.Editor
{
    /// <summary>
    /// unitypackage 書き出しの結果。入稿フォーム（WebPage）へのアップロード対象を特定するために使う
    /// </summary>
    internal sealed class SubmissionPackageResult
    {
        /// <summary>入稿フォルダ・Prefab 名に使った識別子（Package Name そのもの。ランダムサフィックスは付けない）</summary>
        public string ModelNameId { get; set; } = "";

        /// <summary>書き出した unitypackage の絶対パス</summary>
        public string PackagePath { get; set; } = "";

        /// <summary>書き出した Prefab の Assets 相対パス</summary>
        public string PrefabAssetPath { get; set; } = "";

        /// <summary>ExportPackage に渡したアセット数（除外後）</summary>
        public int ExportedAssetCount { get; set; }
    }

    /// <summary>
    /// CatsUdonExporter 階層（Directional Light + 入稿対象の複製）を Prefab 化し、依存アセットごと unitypackage に書き出す。
    /// エンコードと構成検証は行わない（Worker 側が担当する）。エンコーダーのコードを配布物に含めないため、ここでは参照もしない。
    /// マテリアル・テクスチャは入稿フォルダへ複製して参照を差し替え、テクスチャに Crunch 圧縮をかける（元アセットは変更しない）。
    /// .blend など Unity 単体では読み込めない形式のメッシュは、Unity のメッシュとして入稿フォルダへ書き出して差し替える。
    /// 複数のクリップ（テンプレート MultiClip）の Controller とクリップは入稿フォルダへ複製し、複製したクリップの Loop Time をそろえる
    /// </summary>
    internal static class CatsUdonSubmissionPackager
    {
        /// <summary>入稿フォルダの親。書き出すたびに中身を作り直すので、入稿者のアセットはここに置かない</summary>
        internal const string SubmissionFolderPath = "Assets/CatsUdon_Submission/";
        private const string PackageOutputFolderName = "RemoteModelPublisher";
        private const string MaterialsFolderName = "Materials";
        private const string TexturesFolderName = "Textures";
        private const string MeshesFolderName = "Meshes";
        private const string AnimationsFolderName = "Animations";
        /// <summary>Package Name に使えない文字に一致する正規表現。ウィンドウの警告表示と検証で共有する</summary>
        internal const string PackageNameInvalidCharacterRule = @"[^a-zA-Z0-9-_]";

        /// <summary>Package Name の既定値</summary>
        internal const string DefaultPackageName = "CatsUdonPackage";

        // Worker 側でも除去されるが、入稿側でも安全側に倒して二重に除外する
        private static readonly string[] ExcludedScriptExtensions =
        {
            ".cs",
            ".dll",
            ".asmdef",
            ".asmref",
            ".js",
            ".boo"
        };

        /// <summary>
        /// unitypackage の出力先フォルダ（絶対パス）。Library 配下なので git 管理外
        /// </summary>
        public static string PackageOutputDirectory => Path.Combine(Directory.GetCurrentDirectory(), "Library", PackageOutputFolderName);

        /// <summary>
        /// 入稿対象を CatsUdonExporter 階層ごと Prefab 化し、依存アセットごと unitypackage に書き出す
        /// </summary>
        public static SubmissionPackageResult BuildUnityPackage(GameObject exportTarget, string packageName)
        {
            ValidateExportTarget(exportTarget);
            ValidatePackageName(packageName);

            var modelNameId = packageName.Trim();
            var submissionFolder = SubmissionFolderPath + modelNameId + "/";
            var submissionFolderTrimmed = submissionFolder.TrimEnd('/');
            var prefabAssetPath = submissionFolder + modelNameId + ".prefab";

            // 一時階層を使う理由: 元のシーンを汚さず、Prefab インスタンスのリンクを切って単体の Prefab にするため
            var exporterRoot = CatsUdonExporterBuilder.Build(exportTarget);
            try
            {
                RecreateSubmissionFolder(submissionFolderTrimmed);
                DuplicateAndCompressAssets(exporterRoot, submissionFolderTrimmed);
                SaveAsPrefab(exporterRoot, prefabAssetPath);
            }
            finally
            {
                UnityEngine.Object.DestroyImmediate(exporterRoot);
            }

            AssetDatabase.Refresh();

            var exportedPaths = CollectExportablePaths(prefabAssetPath);
            var packagePath = ExportPackage(modelNameId, exportedPaths);

            return new SubmissionPackageResult
            {
                ModelNameId = modelNameId,
                PackagePath = packagePath,
                PrefabAssetPath = prefabAssetPath,
                ExportedAssetCount = exportedPaths.Length
            };
        }

        private static void ValidateExportTarget(GameObject exportTarget)
        {
            if (exportTarget == null)
            {
                throw new InvalidOperationException("Export GameObject is required.");
            }

            if (EditorUtility.IsPersistent(exportTarget))
            {
                throw new InvalidOperationException("シーン上の GameObject を指定してください。");
            }

            // 除外対象は複製後に必ず消されるため、そのまま指定すると中身が空の入稿物になる
            if (CatsUdonExporterBuilder.IsExcluded(exportTarget))
            {
                throw new InvalidOperationException($"{CatsUdonExporterBuilder.GuidesName} という名前、または {CatsUdonExporterBuilder.EditorOnlyTag} タグの GameObject は Export に含まれません。CatsUdonExporter かアバターを指定してください。");
            }
        }

        private static void ValidatePackageName(string packageName)
        {
            var trimmedName = packageName.Trim();
            if (string.IsNullOrEmpty(trimmedName))
            {
                throw new InvalidOperationException("Package Name is required.");
            }

            if (Regex.IsMatch(trimmedName, PackageNameInvalidCharacterRule))
            {
                throw new InvalidOperationException("Package Name は英数字と - _ のみにしてください。");
            }
        }

        private static void RecreateSubmissionFolder(string submissionFolderTrimmed)
        {
            if (AssetDatabase.IsValidFolder(submissionFolderTrimmed))
            {
                AssetDatabase.DeleteAsset(submissionFolderTrimmed);
            }

            // SaveAsPrefabAsset は AssetDatabase が知っているフォルダにしか保存できないため、作成後に取り込む
            Directory.CreateDirectory(submissionFolderTrimmed);
            AssetDatabase.Refresh();
        }

        private static void DuplicateAndCompressAssets(GameObject exporterRoot, string submissionFolderTrimmed)
        {
            var materialsFolder = AssetDatabase.GUIDToAssetPath(AssetDatabase.CreateFolder(submissionFolderTrimmed, MaterialsFolderName));
            var texturesFolder = AssetDatabase.GUIDToAssetPath(AssetDatabase.CreateFolder(submissionFolderTrimmed, TexturesFolderName));

            var duplicatedMaterials = SubmissionMaterialDuplicator.ReplaceWithCopies(exporterRoot, materialsFolder);
            SubmissionTextureDuplicator.ReplaceWithCopies(duplicatedMaterials, texturesFolder);

            var meshesFolder = AssetDatabase.GUIDToAssetPath(AssetDatabase.CreateFolder(submissionFolderTrimmed, MeshesFolderName));
            var exportedMeshCount = SubmissionMeshDuplicator.ReplaceWithCopies(exporterRoot, meshesFolder);
            if (exportedMeshCount > 0)
            {
                Debug.Log($"UnityPackage Publisher: Unity 単体では読めない形式（.blend など）のメッシュ・アバター定義 {exportedMeshCount} 個を Unity のアセットとして書き出しました。");
            }

            var animations = SubmissionAnimationDuplicator.ReplaceWithCopies(exporterRoot, submissionFolderTrimmed, AnimationsFolderName);
            if (animations.ControllerCount > 0)
            {
                Debug.Log($"UnityPackage Publisher: 複数のクリップの Controller {animations.ControllerCount} 個とクリップ {animations.ClipCount} 個を入稿フォルダへ複製しました（Loop Time を直したクリップ {animations.LoopTimeChangedCount} 個。元のクリップは変えていません）。");
            }

            // Prefab 保存前に、マテリアルの差し替えと書き出したメッシュをディスクへ確定させるため
            AssetDatabase.SaveAssets();
            SubmissionTextureCompression.ApplyCrunch(texturesFolder);
        }

        private static void SaveAsPrefab(GameObject exporterRoot, string prefabAssetPath)
        {
            PrefabUtility.SaveAsPrefabAsset(exporterRoot, prefabAssetPath, out var success);
            if (!success)
            {
                throw new InvalidOperationException($"Prefab の保存に失敗しました: {prefabAssetPath}");
            }
        }

        // ExportPackage にフォルダごと Recurse させると Packages 配下や .cs 等も同梱されてしまうため、
        // 依存関係から対象アセットを明示的に絞り込んでから渡す
        private static string[] CollectExportablePaths(string prefabAssetPath)
        {
            var allDependencyPaths = SubmissionDependencyCollector.Collect(prefabAssetPath);

            var exportablePaths = new List<string>();
            var excludedCount = 0;

            foreach (var dependencyPath in allDependencyPaths)
            {
                if (IsExportable(dependencyPath))
                {
                    exportablePaths.Add(dependencyPath);
                }
                else
                {
                    excludedCount++;
                }
            }

            if (excludedCount > 0)
            {
                Debug.Log($"UnityPackage Publisher: excluded {excludedCount} asset(s) from export.");
            }

            return exportablePaths.ToArray();
        }

        // CatsUdonTools 配下のアニメーションテンプレート .controller はこの条件で許可される
        // （.cs は ExcludedScriptExtensions で除外済みのため、配下であることを理由に追加で弾く必要はない）
        private static bool IsExportable(string assetPath)
        {
            if (!assetPath.StartsWith("Assets/", StringComparison.Ordinal))
            {
                return false;
            }

            var extension = Path.GetExtension(assetPath);
            if (string.Equals(extension, ".unity", StringComparison.OrdinalIgnoreCase))
            {
                return false;
            }

            return !ExcludedScriptExtensions.Any(excludedExtension => string.Equals(excludedExtension, extension, StringComparison.OrdinalIgnoreCase));
        }

        private static string ExportPackage(string modelNameId, string[] exportedPaths)
        {
            var packageDirectory = PackageOutputDirectory;
            Directory.CreateDirectory(packageDirectory);
            var packagePath = Path.Combine(packageDirectory, $"{modelNameId}_{DateTime.Now:yyyyMMdd_HHmmss}.unitypackage");

            AssetDatabase.ExportPackage(exportedPaths, packagePath, ExportPackageOptions.Default);
            return packagePath;
        }
    }
}
