using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;

namespace CatsUdonTools.Editor
{
    /// <summary>
    /// 入稿用の一時階層にある、複数のクリップ（テンプレート MultiClip）の Controller と、その中のクリップを入稿フォルダへ複製し、
    /// Animator の参照を複製側へ差し替える。複製したクリップだけ、Loop Time をステートの位置にそろえる
    /// （LoopMeshAnimation に置くものは ON、それ以外は OFF。両方に置くものはループを優先）。
    /// 決まりは Worker 側エンコーダーの TemplateAnimatorUtility.TryNormalizeMeshAnimationClipLoopSettings と同じで、入稿者の元の Controller・クリップは変更しない。
    /// 今までの 1 種類のテンプレート（OneShot / Loop / IntroLoop）と、テンプレートでない Animator は今までどおり複製しない
    /// </summary>
    internal static class SubmissionAnimationDuplicator
    {
        private const string ControllerExtension = ".controller";
        private const string ClipExtension = ".anim";

        internal sealed class Result
        {
            public int ControllerCount { get; set; }

            public int ClipCount { get; set; }

            public int LoopTimeChangedCount { get; set; }
        }

        /// <summary>
        /// exporterRoot 配下の MultiClip の Controller を submissionFolder/animationsFolderName へ複製して差し替える。
        /// 対象が無ければフォルダは作らない
        /// </summary>
        public static Result ReplaceWithCopies(GameObject exporterRoot, string submissionFolder, string animationsFolderName)
        {
            var result = new Result();
            string animationsFolder = null;

            // 同じ Controller・クリップを複数の Animator・ステートが共有していても、複製は 1 回だけにするため
            var copiedControllers = new Dictionary<AnimatorController, AnimatorController>();
            var copiedClips = new Dictionary<AnimationClip, AnimationClip>();
            var desiredLoopByCopy = new Dictionary<AnimationClip, bool>();

            foreach (var animator in exporterRoot.GetComponentsInChildren<Animator>(true))
            {
                if (!MultiClipAnimatorTemplate.LooksLikeMultiClip(animator.runtimeAnimatorController))
                {
                    continue;
                }

                var original = (AnimatorController)animator.runtimeAnimatorController;
                if (!copiedControllers.TryGetValue(original, out var copy))
                {
                    if (animationsFolder == null)
                    {
                        animationsFolder = AssetDatabase.GUIDToAssetPath(AssetDatabase.CreateFolder(submissionFolder, animationsFolderName));
                    }

                    copy = CopyController(original, animationsFolder, copiedClips, desiredLoopByCopy);
                    copiedControllers.Add(original, copy);
                }

                animator.runtimeAnimatorController = copy;
            }

            foreach (var pair in desiredLoopByCopy)
            {
                var settings = AnimationUtility.GetAnimationClipSettings(pair.Key);
                if (settings.loopTime == pair.Value)
                {
                    continue;
                }

                settings.loopTime = pair.Value;
                AnimationUtility.SetAnimationClipSettings(pair.Key, settings);
                EditorUtility.SetDirty(pair.Key);
                result.LoopTimeChangedCount++;
            }

            result.ControllerCount = copiedControllers.Count;
            result.ClipCount = copiedClips.Count;
            return result;
        }

        private static AnimatorController CopyController(AnimatorController original, string animationsFolder,
            Dictionary<AnimationClip, AnimationClip> copiedClips, Dictionary<AnimationClip, bool> desiredLoopByCopy)
        {
            var originalPath = AssetDatabase.GetAssetPath(original);
            var copyPath = AssetDatabase.GenerateUniqueAssetPath($"{animationsFolder}/{ToSafeFileName(original.name)}{ControllerExtension}");
            if (string.IsNullOrEmpty(originalPath) || !AssetDatabase.CopyAsset(originalPath, copyPath))
            {
                throw new InvalidOperationException($"複数のクリップの Controller「{original.name}」を入稿フォルダへ複製できませんでした（{originalPath}）。");
            }

            var copy = AssetDatabase.LoadAssetAtPath<AnimatorController>(copyPath);
            foreach (var layer in copy.layers)
            {
                foreach (var state in EnumerateStates(layer.stateMachine))
                {
                    var originalClip = state.motion as AnimationClip;
                    if (originalClip == null)
                    {
                        continue;
                    }

                    if (!copiedClips.TryGetValue(originalClip, out var clipCopy))
                    {
                        clipCopy = CopyClip(originalClip, animationsFolder);
                        copiedClips.Add(originalClip, clipCopy);
                    }

                    state.motion = clipCopy;
                    var shouldLoop = (state.name ?? "").Trim() == MultiClipAnimatorTemplate.LoopStateName;
                    desiredLoopByCopy[clipCopy] = desiredLoopByCopy.TryGetValue(clipCopy, out var existing) ? existing || shouldLoop : shouldLoop;
                }
            }

            EditorUtility.SetDirty(copy);
            return copy;
        }

        private static IEnumerable<AnimatorState> EnumerateStates(AnimatorStateMachine stateMachine)
        {
            if (stateMachine == null)
            {
                yield break;
            }

            foreach (var child in stateMachine.states)
            {
                if (child.state != null)
                {
                    yield return child.state;
                }
            }

            foreach (var child in stateMachine.stateMachines)
            {
                foreach (var state in EnumerateStates(child.stateMachine))
                {
                    yield return state;
                }
            }
        }

        private static AnimationClip CopyClip(AnimationClip original, string animationsFolder)
        {
            var copyPath = AssetDatabase.GenerateUniqueAssetPath($"{animationsFolder}/{ToSafeFileName(original.name)}{ClipExtension}");
            var originalPath = AssetDatabase.GetAssetPath(original);

            if (IsStandaloneClipAsset(originalPath) && AssetDatabase.CopyAsset(originalPath, copyPath))
            {
                return AssetDatabase.LoadAssetAtPath<AnimationClip>(copyPath);
            }

            // FBX の中のクリップなどは .anim として直接コピーできないため、内容を複製して新規アセットにする
            var copy = UnityEngine.Object.Instantiate(original);
            copy.name = original.name;
            AssetDatabase.CreateAsset(copy, copyPath);
            return copy;
        }

        private static bool IsStandaloneClipAsset(string assetPath)
        {
            return !string.IsNullOrEmpty(assetPath)
                && string.Equals(Path.GetExtension(assetPath), ClipExtension, StringComparison.OrdinalIgnoreCase);
        }

        private static string ToSafeFileName(string assetName)
        {
            foreach (var invalidCharacter in Path.GetInvalidFileNameChars())
            {
                assetName = assetName.Replace(invalidCharacter, '_');
            }

            return assetName;
        }
    }
}
