using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEditor.Animations;
using UnityEditor.SceneManagement;
using UnityEngine;

namespace UnityAssistant
{
    public static class AnimationTools
    {
        public static string GetAnimationSetup(Dictionary<string, object> args)
        {
            var go = HierarchyTools.FindByPath(JsonUtil.Get(args, "path"));
            if (!go) return "{\"ok\":false,\"error\":\"object not found\"}";

            go.TryGetComponent(out Animator animator);
            go.TryGetComponent(out Animation animation);
            var sb = new StringBuilder("{\"ok\":true,\"path\":");
            sb.Append(JsonUtil.Quote(HierarchyTools.GetPath(go.transform)));
            sb.Append(",\"animator\":");
            if (animator)
            {
                var controller = animator.runtimeAnimatorController;
                sb.Append("{\"enabled\":").Append(animator.enabled ? "true" : "false");
                sb.Append(",\"controller\":").Append(JsonUtil.Quote(controller ? AssetDatabase.GetAssetPath(controller) : ""));
                sb.Append(",\"avatar\":").Append(JsonUtil.Quote(animator.avatar ? AssetDatabase.GetAssetPath(animator.avatar) : ""));
                sb.Append(",\"applyRootMotion\":").Append(animator.applyRootMotion ? "true" : "false");
                sb.Append(",\"updateMode\":").Append(JsonUtil.Quote(animator.updateMode.ToString()));
                sb.Append(",\"cullingMode\":").Append(JsonUtil.Quote(animator.cullingMode.ToString()));
                if (controller is AnimatorController ac) AppendControllerSummary(sb, ac);
                sb.Append("}");
            }
            else sb.Append("null");

            sb.Append(",\"legacyAnimation\":");
            if (animation)
            {
                sb.Append("{\"enabled\":").Append(animation.enabled ? "true" : "false");
                sb.Append(",\"playAutomatically\":").Append(animation.playAutomatically ? "true" : "false");
                sb.Append(",\"clipCount\":").Append(animation.GetClipCount()).Append("}");
            }
            else sb.Append("null");
            sb.Append("}");
            return sb.ToString();
        }

        public static string CreateAnimationClip(Dictionary<string, object> args)
        {
            string path = NormalizeAssetPath(JsonUtil.Get(args, "path"), ".anim");
            if (string.IsNullOrEmpty(path)) return "{\"ok\":false,\"error\":\"missing path\"}";
            if (AssetDatabase.LoadAssetAtPath<AnimationClip>(path))
                return "{\"ok\":false,\"error\":\"animation clip already exists: " + path + "\"}";

            var clip = new AnimationClip
            {
                frameRate = (float)JsonUtil.GetNum(args, "frame_rate", 60),
                legacy = JsonUtil.GetBool(args, "legacy", false),
                wrapMode = ParseEnum(JsonUtil.Get(args, "wrap_mode", "Default"), WrapMode.Default),
            };

            EnsureAssetFolder(path);
            AssetDatabase.CreateAsset(clip, path);
            ApplyClipSettings(clip, args);
            AssetDatabase.SaveAssets();
            return "{\"ok\":true,\"path\":" + JsonUtil.Quote(path) + "}";
        }

        public static string SetAnimationClipSettings(Dictionary<string, object> args)
        {
            var clip = LoadClip(JsonUtil.Get(args, "clip"));
            if (!clip) return "{\"ok\":false,\"error\":\"clip not found\"}";
            Undo.RecordObject(clip, "Set Animation Clip Settings");
            if (args.ContainsKey("frame_rate")) clip.frameRate = (float)JsonUtil.GetNum(args, "frame_rate", clip.frameRate);
            if (args.ContainsKey("legacy")) clip.legacy = JsonUtil.GetBool(args, "legacy", clip.legacy);
            if (args.ContainsKey("wrap_mode")) clip.wrapMode = ParseEnum(JsonUtil.Get(args, "wrap_mode"), clip.wrapMode);
            ApplyClipSettings(clip, args);
            EditorUtility.SetDirty(clip);
            AssetDatabase.SaveAssets();
            return "{\"ok\":true,\"clip\":" + JsonUtil.Quote(AssetDatabase.GetAssetPath(clip)) + "}";
        }

        public static string AddAnimationCurve(Dictionary<string, object> args)
        {
            var clip = LoadClip(JsonUtil.Get(args, "clip"));
            if (!clip) return "{\"ok\":false,\"error\":\"clip not found\"}";

            string relativePath = JsonUtil.Get(args, "relative_path");
            string componentTypeName = JsonUtil.Get(args, "component_type", "Transform");
            string property = JsonUtil.Get(args, "property");
            var keys = JsonUtil.GetArr(args, "keys");
            if (string.IsNullOrEmpty(property)) return "{\"ok\":false,\"error\":\"missing property\"}";
            if (keys == null || keys.Count == 0) return "{\"ok\":false,\"error\":\"missing keys\"}";

            var type = ResolveAnimationBindingType(componentTypeName);
            if (type == null) return "{\"ok\":false,\"error\":\"component type not found: " + componentTypeName + "\"}";

            var curve = new AnimationCurve();
            foreach (var item in keys)
            {
                if (!(item is Dictionary<string, object> k)) continue;
                float time = (float)JsonUtil.GetNum(k, "time");
                float value = (float)JsonUtil.GetNum(k, "value");
                float inTangent = (float)JsonUtil.GetNum(k, "in_tangent", 0);
                float outTangent = (float)JsonUtil.GetNum(k, "out_tangent", 0);
                curve.AddKey(new Keyframe(time, value, inTangent, outTangent));
            }
            if (curve.length == 0) return "{\"ok\":false,\"error\":\"no valid keys\"}";

            Undo.RecordObject(clip, "Add Animation Curve");
            var binding = EditorCurveBinding.FloatCurve(relativePath ?? "", type, property);
            AnimationUtility.SetEditorCurve(clip, binding, curve);
            EditorUtility.SetDirty(clip);
            AssetDatabase.SaveAssets();
            return "{\"ok\":true,\"clip\":" + JsonUtil.Quote(AssetDatabase.GetAssetPath(clip))
                + ",\"relative_path\":" + JsonUtil.Quote(relativePath ?? "")
                + ",\"component_type\":" + JsonUtil.Quote(type.FullName)
                + ",\"property\":" + JsonUtil.Quote(property)
                + ",\"keys\":" + curve.length + "}";
        }

        public static string AddTransformKeyframes(Dictionary<string, object> args)
        {
            var clip = LoadClip(JsonUtil.Get(args, "clip"));
            if (!clip) return "{\"ok\":false,\"error\":\"clip not found\"}";
            string relativePath = JsonUtil.Get(args, "relative_path");
            var keyframes = JsonUtil.GetArr(args, "keyframes");
            if (keyframes == null || keyframes.Count == 0) return "{\"ok\":false,\"error\":\"missing keyframes\"}";

            var posX = new AnimationCurve();
            var posY = new AnimationCurve();
            var posZ = new AnimationCurve();
            var rotX = new AnimationCurve();
            var rotY = new AnimationCurve();
            var rotZ = new AnimationCurve();
            var rotW = new AnimationCurve();
            var scaleX = new AnimationCurve();
            var scaleY = new AnimationCurve();
            var scaleZ = new AnimationCurve();
            bool hasPosition = false, hasRotation = false, hasScale = false;

            foreach (var item in keyframes)
            {
                if (!(item is Dictionary<string, object> k)) continue;
                float time = (float)JsonUtil.GetNum(k, "time");
                var position = JsonUtil.GetArr(k, "position");
                var rotation = JsonUtil.GetArr(k, "rotation");
                var scale = JsonUtil.GetArr(k, "scale");
                if (position != null && position.Count >= 3)
                {
                    var v = ToVec3(position);
                    AddLinear(posX, time, v.x); AddLinear(posY, time, v.y); AddLinear(posZ, time, v.z);
                    hasPosition = true;
                }
                if (rotation != null && rotation.Count >= 3)
                {
                    var q = Quaternion.Euler(ToVec3(rotation));
                    AddLinear(rotX, time, q.x); AddLinear(rotY, time, q.y); AddLinear(rotZ, time, q.z); AddLinear(rotW, time, q.w);
                    hasRotation = true;
                }
                if (scale != null && scale.Count >= 3)
                {
                    var v = ToVec3(scale);
                    AddLinear(scaleX, time, v.x); AddLinear(scaleY, time, v.y); AddLinear(scaleZ, time, v.z);
                    hasScale = true;
                }
            }

            Undo.RecordObject(clip, "Add Transform Keyframes");
            if (hasPosition)
            {
                SetTransformCurve(clip, relativePath, "m_LocalPosition.x", posX);
                SetTransformCurve(clip, relativePath, "m_LocalPosition.y", posY);
                SetTransformCurve(clip, relativePath, "m_LocalPosition.z", posZ);
            }
            if (hasRotation)
            {
                SetTransformCurve(clip, relativePath, "m_LocalRotation.x", rotX);
                SetTransformCurve(clip, relativePath, "m_LocalRotation.y", rotY);
                SetTransformCurve(clip, relativePath, "m_LocalRotation.z", rotZ);
                SetTransformCurve(clip, relativePath, "m_LocalRotation.w", rotW);
            }
            if (hasScale)
            {
                SetTransformCurve(clip, relativePath, "m_LocalScale.x", scaleX);
                SetTransformCurve(clip, relativePath, "m_LocalScale.y", scaleY);
                SetTransformCurve(clip, relativePath, "m_LocalScale.z", scaleZ);
            }

            EditorUtility.SetDirty(clip);
            AssetDatabase.SaveAssets();
            return "{\"ok\":true,\"clip\":" + JsonUtil.Quote(AssetDatabase.GetAssetPath(clip))
                + ",\"relative_path\":" + JsonUtil.Quote(relativePath ?? "")
                + ",\"position\":" + (hasPosition ? "true" : "false")
                + ",\"rotation\":" + (hasRotation ? "true" : "false")
                + ",\"scale\":" + (hasScale ? "true" : "false") + "}";
        }

        public static string AddObjectReferenceKeyframes(Dictionary<string, object> args)
        {
            var clip = LoadClip(JsonUtil.Get(args, "clip"));
            if (!clip) return "{\"ok\":false,\"error\":\"clip not found\"}";
            string relativePath = JsonUtil.Get(args, "relative_path");
            string componentTypeName = JsonUtil.Get(args, "component_type", "SpriteRenderer");
            string property = JsonUtil.Get(args, "property", "m_Sprite");
            var keys = JsonUtil.GetArr(args, "keys");
            if (keys == null || keys.Count == 0) return "{\"ok\":false,\"error\":\"missing keys\"}";
            var type = ResolveAnimationBindingType(componentTypeName);
            if (type == null) return "{\"ok\":false,\"error\":\"component type not found: " + componentTypeName + "\"}";

            var frames = new List<ObjectReferenceKeyframe>();
            foreach (var item in keys)
            {
                if (!(item is Dictionary<string, object> k)) continue;
                string assetPath = JsonUtil.Get(k, "asset");
                var obj = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(assetPath);
                if (!obj) return "{\"ok\":false,\"error\":\"object reference asset not found: " + assetPath + "\"}";
                frames.Add(new ObjectReferenceKeyframe { time = (float)JsonUtil.GetNum(k, "time"), value = obj });
            }
            frames.Sort((a, b) => a.time.CompareTo(b.time));
            Undo.RecordObject(clip, "Add Object Reference Keyframes");
            AnimationUtility.SetObjectReferenceCurve(clip, EditorCurveBinding.PPtrCurve(relativePath ?? "", type, property), frames.ToArray());
            EditorUtility.SetDirty(clip);
            AssetDatabase.SaveAssets();
            return "{\"ok\":true,\"clip\":" + JsonUtil.Quote(AssetDatabase.GetAssetPath(clip)) + ",\"keys\":" + frames.Count + "}";
        }

        public static string CreateAnimatorController(Dictionary<string, object> args)
        {
            string path = NormalizeAssetPath(JsonUtil.Get(args, "path"), ".controller");
            if (string.IsNullOrEmpty(path)) return "{\"ok\":false,\"error\":\"missing path\"}";
            if (AssetDatabase.LoadAssetAtPath<AnimatorController>(path))
                return "{\"ok\":false,\"error\":\"animator controller already exists: " + path + "\"}";
            EnsureAssetFolder(path);
            var controller = AnimatorController.CreateAnimatorControllerAtPath(path);
            AddParameters(controller, JsonUtil.GetArr(args, "parameters"));
            AssetDatabase.SaveAssets();
            return "{\"ok\":true,\"path\":" + JsonUtil.Quote(path) + "}";
        }

        public static string AddAnimatorParameter(Dictionary<string, object> args)
        {
            var controller = LoadController(JsonUtil.Get(args, "controller"));
            if (!controller) return "{\"ok\":false,\"error\":\"controller not found\"}";
            string name = JsonUtil.Get(args, "name");
            if (string.IsNullOrEmpty(name)) return "{\"ok\":false,\"error\":\"missing name\"}";
            var type = ParseEnum(JsonUtil.Get(args, "type", "Trigger"), AnimatorControllerParameterType.Trigger);
            Undo.RecordObject(controller, "Add Animator Parameter");
            if (!controller.parameters.Any(p => p.name == name)) controller.AddParameter(name, type);
            var p = controller.parameters.FirstOrDefault(x => x.name == name);
            if (p != null)
            {
                if (args.ContainsKey("default_bool")) p.defaultBool = JsonUtil.GetBool(args, "default_bool");
                if (args.ContainsKey("default_float")) p.defaultFloat = (float)JsonUtil.GetNum(args, "default_float");
                if (args.ContainsKey("default_int")) p.defaultInt = (int)JsonUtil.GetNum(args, "default_int");
            }
            EditorUtility.SetDirty(controller);
            AssetDatabase.SaveAssets();
            return "{\"ok\":true}";
        }

        public static string AddAnimatorState(Dictionary<string, object> args)
        {
            var controller = LoadController(JsonUtil.Get(args, "controller"));
            if (!controller) return "{\"ok\":false,\"error\":\"controller not found\"}";
            string stateName = JsonUtil.Get(args, "state");
            if (string.IsNullOrEmpty(stateName)) return "{\"ok\":false,\"error\":\"missing state\"}";
            string clipPath = JsonUtil.Get(args, "clip");
            var motion = string.IsNullOrEmpty(clipPath) ? null : AssetDatabase.LoadAssetAtPath<Motion>(clipPath);
            if (!string.IsNullOrEmpty(clipPath) && !motion) return "{\"ok\":false,\"error\":\"clip/motion not found: " + clipPath + "\"}";

            var layer = GetLayer(controller, JsonUtil.Get(args, "layer", "Base Layer"));
            Undo.RecordObject(controller, "Add Animator State");
            var state = FindState(layer.stateMachine, stateName);
            if (state == null) state = layer.stateMachine.AddState(stateName);
            state.motion = motion;
            state.speed = (float)JsonUtil.GetNum(args, "speed", state.speed == 0 ? 1 : state.speed);
            if (args.ContainsKey("write_default_values")) state.writeDefaultValues = JsonUtil.GetBool(args, "write_default_values", state.writeDefaultValues);
            if (JsonUtil.GetBool(args, "default", false)) layer.stateMachine.defaultState = state;
            EditorUtility.SetDirty(controller);
            AssetDatabase.SaveAssets();
            return "{\"ok\":true,\"state\":" + JsonUtil.Quote(state.name) + "}";
        }

        public static string AddAnimatorTransition(Dictionary<string, object> args)
        {
            var controller = LoadController(JsonUtil.Get(args, "controller"));
            if (!controller) return "{\"ok\":false,\"error\":\"controller not found\"}";
            string from = JsonUtil.Get(args, "from");
            string to = JsonUtil.Get(args, "to");
            if (string.IsNullOrEmpty(from) || string.IsNullOrEmpty(to)) return "{\"ok\":false,\"error\":\"missing from/to\"}";
            var layer = GetLayer(controller, JsonUtil.Get(args, "layer", "Base Layer"));
            var fromState = FindState(layer.stateMachine, from);
            var toState = FindState(layer.stateMachine, to);
            if (fromState == null) return "{\"ok\":false,\"error\":\"from state not found: " + from + "\"}";
            if (toState == null) return "{\"ok\":false,\"error\":\"to state not found: " + to + "\"}";

            Undo.RecordObject(controller, "Add Animator Transition");
            var transition = fromState.AddTransition(toState);
            transition.hasExitTime = JsonUtil.GetBool(args, "has_exit_time", false);
            transition.exitTime = (float)JsonUtil.GetNum(args, "exit_time", transition.hasExitTime ? 1 : 0);
            transition.duration = (float)JsonUtil.GetNum(args, "duration", 0.1);
            transition.hasFixedDuration = JsonUtil.GetBool(args, "has_fixed_duration", true);
            transition.canTransitionToSelf = JsonUtil.GetBool(args, "can_transition_to_self", false);
            AddConditions(transition, JsonUtil.GetArr(args, "conditions"));
            EditorUtility.SetDirty(controller);
            AssetDatabase.SaveAssets();
            return "{\"ok\":true}";
        }

        public static string SetAnimatorDefaultState(Dictionary<string, object> args)
        {
            var controller = LoadController(JsonUtil.Get(args, "controller"));
            if (!controller) return "{\"ok\":false,\"error\":\"controller not found\"}";
            var layer = GetLayer(controller, JsonUtil.Get(args, "layer", "Base Layer"));
            string stateName = JsonUtil.Get(args, "state");
            var state = FindState(layer.stateMachine, stateName);
            if (state == null) return "{\"ok\":false,\"error\":\"state not found: " + stateName + "\"}";
            Undo.RecordObject(controller, "Set Animator Default State");
            layer.stateMachine.defaultState = state;
            EditorUtility.SetDirty(controller);
            AssetDatabase.SaveAssets();
            return "{\"ok\":true}";
        }

        public static string AssignAnimatorController(Dictionary<string, object> args)
        {
            var go = HierarchyTools.FindByPath(JsonUtil.Get(args, "path"));
            if (!go) return "{\"ok\":false,\"error\":\"object not found\"}";
            var controller = LoadController(JsonUtil.Get(args, "controller"));
            if (!controller) return "{\"ok\":false,\"error\":\"controller not found\"}";
            go.TryGetComponent(out Animator animator);
            if (!animator) animator = Undo.AddComponent<Animator>(go);
            Undo.RecordObject(animator, "Assign Animator Controller");
            animator.runtimeAnimatorController = controller;
            animator.applyRootMotion = JsonUtil.GetBool(args, "apply_root_motion", animator.applyRootMotion);
            if (args.ContainsKey("update_mode")) animator.updateMode = ParseEnum(JsonUtil.Get(args, "update_mode"), animator.updateMode);
            if (args.ContainsKey("culling_mode")) animator.cullingMode = ParseEnum(JsonUtil.Get(args, "culling_mode"), animator.cullingMode);
            string avatarPath = JsonUtil.Get(args, "avatar");
            if (!string.IsNullOrEmpty(avatarPath)) animator.avatar = AssetDatabase.LoadAssetAtPath<Avatar>(avatarPath);
            EditorUtility.SetDirty(animator);
            EditorSceneManager.MarkSceneDirty(go.scene);
            return "{\"ok\":true,\"path\":" + JsonUtil.Quote(HierarchyTools.GetPath(go.transform)) + "}";
        }

        public static string ConfigureModelAnimationImport(Dictionary<string, object> args)
        {
            string path = JsonUtil.Get(args, "path");
            var importer = AssetImporter.GetAtPath(path) as ModelImporter;
            if (!importer) return "{\"ok\":false,\"error\":\"model importer not found\"}";
            Undo.RecordObject(importer, "Configure Model Animation Import");
            if (args.ContainsKey("import_animations")) importer.importAnimation = JsonUtil.GetBool(args, "import_animations", importer.importAnimation);
            if (args.ContainsKey("animation_type")) importer.animationType = ParseAnimationType(JsonUtil.Get(args, "animation_type"), importer.animationType);
            if (args.ContainsKey("avatar_source")) importer.avatarSetup = ParseEnum(JsonUtil.Get(args, "avatar_source"), importer.avatarSetup);
            if (args.ContainsKey("optimize_game_objects")) importer.optimizeGameObjects = JsonUtil.GetBool(args, "optimize_game_objects", importer.optimizeGameObjects);
            var clips = JsonUtil.GetArr(args, "clips");
            if (clips != null && clips.Count > 0) importer.clipAnimations = BuildClipAnimations(clips);
            importer.SaveAndReimport();
            return "{\"ok\":true,\"path\":" + JsonUtil.Quote(path) + "}";
        }

        public static string SampleAnimationClip(Dictionary<string, object> args)
        {
            var go = HierarchyTools.FindByPath(JsonUtil.Get(args, "path"));
            if (!go) return "{\"ok\":false,\"error\":\"object not found\"}";
            var clip = LoadClip(JsonUtil.Get(args, "clip"));
            if (!clip) return "{\"ok\":false,\"error\":\"clip not found\"}";
            float time = (float)JsonUtil.GetNum(args, "time");
            Undo.RegisterFullObjectHierarchyUndo(go, "Sample Animation Clip");
            AnimationMode.StartAnimationMode();
            AnimationMode.SampleAnimationClip(go, clip, Mathf.Clamp(time, 0, clip.length));
            AnimationMode.StopAnimationMode();
            EditorSceneManager.MarkSceneDirty(go.scene);
            return "{\"ok\":true,\"sampled_time\":" + time.ToString("R", CultureInfo.InvariantCulture) + "}";
        }

        private static void AppendControllerSummary(StringBuilder sb, AnimatorController controller)
        {
            sb.Append(",\"parameters\":[");
            for (int i = 0; i < controller.parameters.Length; i++)
            {
                if (i > 0) sb.Append(",");
                var p = controller.parameters[i];
                sb.Append("{\"name\":").Append(JsonUtil.Quote(p.name));
                sb.Append(",\"type\":").Append(JsonUtil.Quote(p.type.ToString())).Append("}");
            }
            sb.Append("],\"layers\":[");
            for (int i = 0; i < controller.layers.Length; i++)
            {
                if (i > 0) sb.Append(",");
                var layer = controller.layers[i];
                sb.Append("{\"name\":").Append(JsonUtil.Quote(layer.name));
                sb.Append(",\"defaultState\":").Append(JsonUtil.Quote(layer.stateMachine.defaultState ? layer.stateMachine.defaultState.name : ""));
                sb.Append(",\"states\":[");
                var states = layer.stateMachine.states;
                for (int s = 0; s < states.Length; s++)
                {
                    if (s > 0) sb.Append(",");
                    var state = states[s].state;
                    sb.Append("{\"name\":").Append(JsonUtil.Quote(state.name));
                    sb.Append(",\"motion\":").Append(JsonUtil.Quote(state.motion ? AssetDatabase.GetAssetPath(state.motion) : ""));
                    sb.Append(",\"speed\":").Append(state.speed.ToString("R", CultureInfo.InvariantCulture)).Append("}");
                }
                sb.Append("]}");
            }
            sb.Append("]");
        }

        private static void ApplyClipSettings(AnimationClip clip, Dictionary<string, object> args)
        {
            var so = new SerializedObject(clip);
            SetSerializedBool(so, "m_AnimationClipSettings.m_LoopTime", JsonUtil.GetBool(args, "loop_time", false), args.ContainsKey("loop_time"));
            SetSerializedBool(so, "m_AnimationClipSettings.m_LoopBlend", JsonUtil.GetBool(args, "loop_blend", false), args.ContainsKey("loop_blend"));
            SetSerializedBool(so, "m_AnimationClipSettings.m_KeepOriginalPositionY", JsonUtil.GetBool(args, "keep_original_position_y", false), args.ContainsKey("keep_original_position_y"));
            SetSerializedBool(so, "m_AnimationClipSettings.m_KeepOriginalPositionXZ", JsonUtil.GetBool(args, "keep_original_position_xz", false), args.ContainsKey("keep_original_position_xz"));
            SetSerializedBool(so, "m_AnimationClipSettings.m_KeepOriginalOrientation", JsonUtil.GetBool(args, "keep_original_orientation", false), args.ContainsKey("keep_original_orientation"));
            so.ApplyModifiedProperties();
        }

        private static void SetSerializedBool(SerializedObject so, string property, bool value, bool shouldSet)
        {
            if (!shouldSet) return;
            var p = so.FindProperty(property);
            if (p != null) p.boolValue = value;
        }

        private static string NormalizeAssetPath(string path, string extension)
        {
            if (string.IsNullOrEmpty(path)) return "";
            path = path.Replace('\\', '/');
            if (!path.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase)) path = "Assets/" + path.TrimStart('/');
            if (!path.EndsWith(extension, StringComparison.OrdinalIgnoreCase)) path += extension;
            return path;
        }

        private static void EnsureAssetFolder(string path)
        {
            string full = Path.Combine(Directory.GetCurrentDirectory(), path);
            Directory.CreateDirectory(Path.GetDirectoryName(full));
        }

        private static AnimationClip LoadClip(string path)
        {
            if (string.IsNullOrEmpty(path)) return null;
            return AssetDatabase.LoadAssetAtPath<AnimationClip>(path);
        }

        private static AnimatorController LoadController(string path)
        {
            if (string.IsNullOrEmpty(path)) return null;
            return AssetDatabase.LoadAssetAtPath<AnimatorController>(path);
        }

        private static Type ResolveAnimationBindingType(string name)
        {
            if (string.IsNullOrEmpty(name)) return typeof(Transform);
            if (name == "Transform" || name == "UnityEngine.Transform") return typeof(Transform);
            if (name == "RectTransform" || name == "UnityEngine.RectTransform") return typeof(RectTransform);
            if (name == "SpriteRenderer" || name == "UnityEngine.SpriteRenderer") return typeof(SpriteRenderer);
            if (name == "Renderer" || name == "UnityEngine.Renderer") return typeof(Renderer);
            return InspectorTools.ResolveType(name);
        }

        private static void SetTransformCurve(AnimationClip clip, string relativePath, string property, AnimationCurve curve)
        {
            AnimationUtility.SetEditorCurve(clip, EditorCurveBinding.FloatCurve(relativePath ?? "", typeof(Transform), property), curve);
        }

        private static void AddLinear(AnimationCurve curve, float time, float value)
        {
            var key = new Keyframe(time, value) { inTangent = 0, outTangent = 0 };
            curve.AddKey(key);
        }

        private static Vector3 ToVec3(List<object> arr)
        {
            return new Vector3(
                (float)Convert.ToDouble(arr[0], CultureInfo.InvariantCulture),
                (float)Convert.ToDouble(arr[1], CultureInfo.InvariantCulture),
                (float)Convert.ToDouble(arr[2], CultureInfo.InvariantCulture));
        }

        private static AnimatorControllerLayer GetLayer(AnimatorController controller, string layerName)
        {
            foreach (var layer in controller.layers)
                if (layer.name == layerName) return layer;
            return controller.layers.Length > 0 ? controller.layers[0] : null;
        }

        private static AnimatorState FindState(AnimatorStateMachine sm, string name)
        {
            foreach (var child in sm.states)
                if (child.state.name == name) return child.state;
            return null;
        }

        private static void AddParameters(AnimatorController controller, List<object> parameters)
        {
            if (parameters == null) return;
            foreach (var item in parameters)
            {
                if (!(item is Dictionary<string, object> p)) continue;
                string name = JsonUtil.Get(p, "name");
                if (string.IsNullOrEmpty(name) || controller.parameters.Any(x => x.name == name)) continue;
                var type = ParseEnum(JsonUtil.Get(p, "type", "Trigger"), AnimatorControllerParameterType.Trigger);
                controller.AddParameter(name, type);
            }
        }

        private static void AddConditions(AnimatorStateTransition transition, List<object> conditions)
        {
            if (conditions == null) return;
            foreach (var item in conditions)
            {
                if (!(item is Dictionary<string, object> c)) continue;
                string parameter = JsonUtil.Get(c, "parameter");
                if (string.IsNullOrEmpty(parameter)) continue;
                var mode = ParseEnum(JsonUtil.Get(c, "mode", "If"), AnimatorConditionMode.If);
                float threshold = (float)JsonUtil.GetNum(c, "threshold", 0);
                transition.AddCondition(mode, threshold, parameter);
            }
        }

        private static ModelImporterClipAnimation[] BuildClipAnimations(List<object> clips)
        {
            var outClips = new List<ModelImporterClipAnimation>();
            foreach (var item in clips)
            {
                if (!(item is Dictionary<string, object> c)) continue;
                string name = JsonUtil.Get(c, "name", "Clip");
                var clip = new ModelImporterClipAnimation
                {
                    name = name,
                    takeName = JsonUtil.Get(c, "take_name", name),
                    firstFrame = (float)JsonUtil.GetNum(c, "first_frame", 0),
                    lastFrame = (float)JsonUtil.GetNum(c, "last_frame", 60),
                    loopTime = JsonUtil.GetBool(c, "loop_time", false),
                    loopPose = JsonUtil.GetBool(c, "loop_pose", false),
                    lockRootHeightY = JsonUtil.GetBool(c, "lock_root_height_y", false),
                    lockRootPositionXZ = JsonUtil.GetBool(c, "lock_root_position_xz", false),
                    lockRootRotation = JsonUtil.GetBool(c, "lock_root_rotation", false),
                };
                outClips.Add(clip);
            }
            return outClips.ToArray();
        }

        private static ModelImporterAnimationType ParseAnimationType(string value, ModelImporterAnimationType fallback)
        {
            if (string.IsNullOrEmpty(value)) return fallback;
            if (value.Equals("Humanoid", StringComparison.OrdinalIgnoreCase)) return ModelImporterAnimationType.Human;
            return ParseEnum(value, fallback);
        }

        private static T ParseEnum<T>(string value, T fallback) where T : struct
        {
            if (string.IsNullOrEmpty(value)) return fallback;
            return Enum.TryParse(value, true, out T parsed) ? parsed : fallback;
        }
    }
}
