using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Text;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;

namespace UnityAssistant
{
    public static class InspectorTools
    {
        public static string GetInspector(Dictionary<string, object> args)
        {
            var go = HierarchyTools.FindByPath(JsonUtil.Get(args, "path"));
            if (!go) return "{\"ok\":false,\"error\":\"not found\"}";
            int maxDepth = Math.Max(1, Math.Min(8, (int)JsonUtil.GetNum(args, "max_depth", 5)));
            int maxArrayItems = Math.Max(1, Math.Min(250, (int)JsonUtil.GetNum(args, "max_array_items", 80)));

            var sb = new StringBuilder("{\"ok\":true,\"path\":");
            sb.Append(JsonUtil.Quote(HierarchyTools.GetPath(go.transform)));
            sb.Append(",\"components\":[");
            var comps = go.GetComponents<Component>();
            for (int i = 0; i < comps.Length; i++)
            {
                if (i > 0) sb.Append(",");
                if (comps[i] == null) { sb.Append("{\"type\":\"MissingScript\"}"); continue; }
                AppendComponent(sb, comps[i], i, maxDepth, maxArrayItems);
            }
            sb.Append("]}");
            return sb.ToString();
        }

        public static string GetAssetInspector(Dictionary<string, object> args)
        {
            string path = JsonUtil.Get(args, "path");
            if (string.IsNullOrEmpty(path)) return "{\"ok\":false,\"error\":\"missing path\"}";
            var asset = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(path);
            if (!asset) return "{\"ok\":false,\"error\":\"asset not found\"}";
            int maxDepth = Math.Max(1, Math.Min(8, (int)JsonUtil.GetNum(args, "max_depth", 6)));
            int maxArrayItems = Math.Max(1, Math.Min(500, (int)JsonUtil.GetNum(args, "max_array_items", 160)));

            var sb = new StringBuilder("{\"ok\":true,\"path\":");
            sb.Append(JsonUtil.Quote(path));
            sb.Append(",\"type\":").Append(JsonUtil.Quote(asset.GetType().FullName));
            sb.Append(",\"fields\":{");
            AppendSerializedFields(sb, asset, maxDepth, maxArrayItems);
            sb.Append("}}");
            return sb.ToString();
        }

        private static void AppendComponent(StringBuilder sb, Component c, int index, int maxDepth, int maxArrayItems)
        {
            sb.Append("{\"type\":").Append(JsonUtil.Quote(c.GetType().FullName));
            sb.Append(",\"index\":").Append(index);
            sb.Append(",\"fields\":{");
            AppendSerializedFields(sb, c, maxDepth, maxArrayItems);
            sb.Append("}}");
        }

        private static void AppendSerializedFields(StringBuilder sb, UnityEngine.Object target, int maxDepth, int maxArrayItems)
        {
            var so = new SerializedObject(target);
            var iter = so.GetIterator();
            bool first = true;
            iter.NextVisible(true);
            while (iter.NextVisible(false))
            {
                if (!first) sb.Append(",");
                first = false;
                sb.Append(JsonUtil.Quote(iter.name)).Append(":");
                AppendProp(sb, iter, 0, maxDepth, maxArrayItems);
            }
        }

        private static void AppendProp(StringBuilder sb, SerializedProperty p, int depth, int maxDepth, int maxArrayItems)
        {
            if (depth >= maxDepth)
            {
                sb.Append("{\"type\":").Append(JsonUtil.Quote(p.propertyType.ToString()));
                sb.Append(",\"propertyPath\":").Append(JsonUtil.Quote(p.propertyPath));
                sb.Append(",\"truncated\":true}");
                return;
            }

            if (p.isArray && p.propertyType != SerializedPropertyType.String)
            {
                int count = p.arraySize;
                int take = Math.Min(count, maxArrayItems);
                sb.Append("{\"type\":\"Array\",\"propertyPath\":").Append(JsonUtil.Quote(p.propertyPath));
                sb.Append(",\"arraySize\":").Append(count).Append(",\"items\":[");
                for (int i = 0; i < take; i++)
                {
                    if (i > 0) sb.Append(",");
                    AppendProp(sb, p.GetArrayElementAtIndex(i), depth + 1, maxDepth, maxArrayItems);
                }
                sb.Append("]");
                if (take < count) sb.Append(",\"truncated\":true");
                sb.Append("}");
                return;
            }

            if (p.propertyType == SerializedPropertyType.Generic)
            {
                sb.Append("{\"type\":\"Generic\",\"propertyPath\":").Append(JsonUtil.Quote(p.propertyPath));
                sb.Append(",\"fields\":{");
                var copy = p.Copy();
                var end = copy.GetEndProperty();
                bool first = true;
                if (copy.NextVisible(true))
                {
                    do
                    {
                        if (SerializedProperty.EqualContents(copy, end)) break;
                        if (!IsDirectChild(p.propertyPath, copy.propertyPath)) continue;
                        if (!first) sb.Append(",");
                        first = false;
                        sb.Append(JsonUtil.Quote(copy.name)).Append(":");
                        AppendProp(sb, copy, depth + 1, maxDepth, maxArrayItems);
                    }
                    while (copy.NextVisible(false));
                }
                sb.Append("}}");
                return;
            }

            switch (p.propertyType)
            {
                case SerializedPropertyType.Integer: sb.Append(p.intValue); break;
                case SerializedPropertyType.Float: sb.Append(p.floatValue.ToString("R", CultureInfo.InvariantCulture)); break;
                case SerializedPropertyType.Boolean: sb.Append(p.boolValue ? "true" : "false"); break;
                case SerializedPropertyType.String: sb.Append(JsonUtil.Quote(p.stringValue)); break;
                case SerializedPropertyType.Color:
                    var c = p.colorValue;
                    sb.Append("{\"r\":").Append(c.r.ToString("R", CultureInfo.InvariantCulture))
                      .Append(",\"g\":").Append(c.g.ToString("R", CultureInfo.InvariantCulture))
                      .Append(",\"b\":").Append(c.b.ToString("R", CultureInfo.InvariantCulture))
                      .Append(",\"a\":").Append(c.a.ToString("R", CultureInfo.InvariantCulture)).Append("}");
                    break;
                case SerializedPropertyType.Vector2:
                    var v2 = p.vector2Value;
                    sb.Append("[").Append(v2.x.ToString("R", CultureInfo.InvariantCulture)).Append(",")
                      .Append(v2.y.ToString("R", CultureInfo.InvariantCulture)).Append("]");
                    break;
                case SerializedPropertyType.Vector3:
                    var v3 = p.vector3Value;
                    sb.Append("[").Append(v3.x.ToString("R", CultureInfo.InvariantCulture)).Append(",")
                      .Append(v3.y.ToString("R", CultureInfo.InvariantCulture)).Append(",")
                      .Append(v3.z.ToString("R", CultureInfo.InvariantCulture)).Append("]");
                    break;
                case SerializedPropertyType.Vector4:
                    var v4 = p.vector4Value;
                    sb.Append("[").Append(v4.x.ToString("R", CultureInfo.InvariantCulture)).Append(",")
                      .Append(v4.y.ToString("R", CultureInfo.InvariantCulture)).Append(",")
                      .Append(v4.z.ToString("R", CultureInfo.InvariantCulture)).Append(",")
                      .Append(v4.w.ToString("R", CultureInfo.InvariantCulture)).Append("]");
                    break;
                case SerializedPropertyType.ObjectReference:
                    if (p.objectReferenceValue != null)
                    {
                        var path = AssetDatabase.GetAssetPath(p.objectReferenceValue);
                        sb.Append("{\"name\":").Append(JsonUtil.Quote(p.objectReferenceValue.name));
                        sb.Append(",\"type\":").Append(JsonUtil.Quote(p.objectReferenceValue.GetType().Name));
                        sb.Append(",\"asset\":").Append(JsonUtil.Quote(path ?? "")).Append("}");
                    }
                    else sb.Append("null");
                    break;
                case SerializedPropertyType.Enum:
                    sb.Append(JsonUtil.Quote(p.enumValueIndex < p.enumNames.Length ? p.enumNames[p.enumValueIndex] : ""));
                    break;
                default:
                    sb.Append("{\"type\":").Append(JsonUtil.Quote(p.propertyType.ToString()));
                    sb.Append(",\"propertyPath\":").Append(JsonUtil.Quote(p.propertyPath)).Append("}");
                    break;
            }
        }

        private static bool IsDirectChild(string parentPath, string childPath)
        {
            if (string.IsNullOrEmpty(parentPath)) return childPath.IndexOf('.') < 0;
            if (!childPath.StartsWith(parentPath + ".", StringComparison.Ordinal)) return false;
            string tail = childPath.Substring(parentPath.Length + 1);
            return tail.IndexOf('.') < 0 || tail.StartsWith("Array.data[", StringComparison.Ordinal);
        }

        public static string SetField(Dictionary<string, object> args)
        {
            var go = HierarchyTools.FindByPath(JsonUtil.Get(args, "path"));
            if (!go) return "{\"ok\":false,\"error\":\"not found\"}";
            string compType = JsonUtil.Get(args, "component");
            string field = NormalizePropertyPath(JsonUtil.Get(args, "field"));
            int index = (int)JsonUtil.GetNum(args, "component_index", -1);

            Component target = null;
            var comps = go.GetComponents<Component>();
            if (index >= 0 && index < comps.Length) target = comps[index];
            else
            {
                foreach (var c in comps)
                    if (c != null && (c.GetType().Name == compType || c.GetType().FullName == compType)) { target = c; break; }
            }
            if (!target) return "{\"ok\":false,\"error\":\"component not found\"}";

            var so = new SerializedObject(target);
            var prop = so.FindProperty(field);
            if (prop == null) return "{\"ok\":false,\"error\":\"field not found: " + field + "\"}";

            args.TryGetValue("value", out var val);
            Undo.RecordObject(target, "SetField");
            var setError = SetSerializedPropertyValue(prop, val);
            if (!string.IsNullOrEmpty(setError)) return "{\"ok\":false,\"error\":" + JsonUtil.Quote(setError) + "}";

            so.ApplyModifiedProperties();
            EditorUtility.SetDirty(target);
            EditorSceneManager.MarkSceneDirty(go.scene);
            return "{\"ok\":true,\"field\":" + JsonUtil.Quote(field) + "}";
        }

        public static string SetAssetField(Dictionary<string, object> args)
        {
            string path = JsonUtil.Get(args, "path");
            string field = NormalizePropertyPath(JsonUtil.Get(args, "field"));
            if (string.IsNullOrEmpty(path)) return "{\"ok\":false,\"error\":\"missing path\"}";
            var asset = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(path);
            if (!asset) return "{\"ok\":false,\"error\":\"asset not found\"}";

            var so = new SerializedObject(asset);
            var prop = so.FindProperty(field);
            if (prop == null) return "{\"ok\":false,\"error\":\"field not found: " + field + "\"}";
            args.TryGetValue("value", out var val);

            Undo.RecordObject(asset, "SetAssetField");
            var setError = SetSerializedPropertyValue(prop, val);
            if (!string.IsNullOrEmpty(setError)) return "{\"ok\":false,\"error\":" + JsonUtil.Quote(setError) + "}";
            so.ApplyModifiedProperties();
            EditorUtility.SetDirty(asset);
            AssetDatabase.SaveAssets();
            return "{\"ok\":true,\"path\":" + JsonUtil.Quote(path) + ",\"field\":" + JsonUtil.Quote(field) + "}";
        }

        private static string NormalizePropertyPath(string field)
        {
            if (string.IsNullOrEmpty(field)) return "";
            // Let agents use friendly list syntax: batches[0].spawnLimit.
            return System.Text.RegularExpressions.Regex.Replace(field, @"(\w+)\[(\d+)\]", "$1.Array.data[$2]");
        }

        private static string SetSerializedPropertyValue(SerializedProperty prop, object val)
        {
            try
            {
                if (prop.isArray && prop.propertyType != SerializedPropertyType.String)
                {
                    if (!(val is List<object> list)) return "array value must be a JSON array for " + prop.propertyPath;
                    prop.arraySize = list.Count;
                    for (int i = 0; i < list.Count; i++)
                    {
                        var err = SetSerializedPropertyValue(prop.GetArrayElementAtIndex(i), list[i]);
                        if (!string.IsNullOrEmpty(err)) return err;
                    }
                    return "";
                }

                if (prop.propertyType == SerializedPropertyType.Generic)
                {
                    if (!(val is Dictionary<string, object> dict)) return "generic value must be a JSON object for " + prop.propertyPath;
                    foreach (var kv in dict)
                    {
                        var child = prop.serializedObject.FindProperty(prop.propertyPath + "." + NormalizePropertyPath(kv.Key));
                        if (child == null) child = FindDirectChild(prop, kv.Key);
                        if (child == null) return "child field not found: " + prop.propertyPath + "." + kv.Key;
                        var err = SetSerializedPropertyValue(child, kv.Value);
                        if (!string.IsNullOrEmpty(err)) return err;
                    }
                    return "";
                }

                switch (prop.propertyType)
                {
                    case SerializedPropertyType.Integer:
                        prop.intValue = (int)Convert.ToInt64(val, CultureInfo.InvariantCulture);
                        return "";
                    case SerializedPropertyType.Float:
                        prop.floatValue = (float)Convert.ToDouble(val, CultureInfo.InvariantCulture);
                        return "";
                    case SerializedPropertyType.Boolean:
                        prop.boolValue = Convert.ToBoolean(val);
                        return "";
                    case SerializedPropertyType.String:
                        prop.stringValue = val?.ToString() ?? "";
                        return "";
                    case SerializedPropertyType.Vector3:
                        if (val is List<object> v3 && v3.Count >= 3)
                        {
                            prop.vector3Value = new Vector3(
                                (float)Convert.ToDouble(v3[0], CultureInfo.InvariantCulture),
                                (float)Convert.ToDouble(v3[1], CultureInfo.InvariantCulture),
                                (float)Convert.ToDouble(v3[2], CultureInfo.InvariantCulture));
                            return "";
                        }
                        return "Vector3 value must be [x,y,z]";
                    case SerializedPropertyType.Vector2:
                        if (val is List<object> v2 && v2.Count >= 2)
                        {
                            prop.vector2Value = new Vector2(
                                (float)Convert.ToDouble(v2[0], CultureInfo.InvariantCulture),
                                (float)Convert.ToDouble(v2[1], CultureInfo.InvariantCulture));
                            return "";
                        }
                        return "Vector2 value must be [x,y]";
                    case SerializedPropertyType.Color:
                        if (val is Dictionary<string, object> col)
                        {
                            prop.colorValue = new Color(
                                (float)JsonUtil.GetNum(col, "r"), (float)JsonUtil.GetNum(col, "g"),
                                (float)JsonUtil.GetNum(col, "b"), (float)JsonUtil.GetNum(col, "a", 1));
                            return "";
                        }
                        return "Color value must be {r,g,b,a}";
                    case SerializedPropertyType.Enum:
                        string ev = val?.ToString();
                        int idx = Array.IndexOf(prop.enumNames, ev);
                        if (idx < 0 && int.TryParse(ev, out var enumIndex)) idx = enumIndex;
                        if (idx >= 0 && idx < prop.enumNames.Length)
                        {
                            prop.enumValueIndex = idx;
                            return "";
                        }
                        return "enum value not found: " + ev;
                    case SerializedPropertyType.ObjectReference:
                        var assetPath = val?.ToString();
                        prop.objectReferenceValue = string.IsNullOrEmpty(assetPath) ? null
                            : AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(assetPath);
                        return "";
                    default:
                        return "unsupported type: " + prop.propertyType + " at " + prop.propertyPath;
                }
            }
            catch (Exception e)
            {
                return e.Message;
            }
        }

        private static SerializedProperty FindDirectChild(SerializedProperty parent, string childName)
        {
            var copy = parent.Copy();
            var end = copy.GetEndProperty();
            if (!copy.NextVisible(true)) return null;
            do
            {
                if (SerializedProperty.EqualContents(copy, end)) break;
                if (copy.name == childName && IsDirectChild(parent.propertyPath, copy.propertyPath)) return copy.Copy();
            }
            while (copy.NextVisible(false));
            return null;
        }

        public static string AddComponent(Dictionary<string, object> args)
        {
            var go = HierarchyTools.FindByPath(JsonUtil.Get(args, "path"));
            if (!go) return "{\"ok\":false,\"error\":\"not found\"}";
            string typeName = JsonUtil.Get(args, "type");
            var t = ResolveType(typeName);
            if (t == null)
                return "{\"ok\":false,\"error\":\"type not found: " + typeName
                    + ". If this is a new script, compile first and check console errors. If the script exists but still is not found, the class name may not match the file, it may be inside a namespace, or it may not inherit MonoBehaviour.\""
                    + ",\"script_status\":" + FindMonoScriptStatus(typeName)
                    + ",\"candidates\":" + FindComponentCandidates(typeName) + "}";
            if (!typeof(Component).IsAssignableFrom(t)) return "{\"ok\":false,\"error\":\"not a component\"}";
            Undo.AddComponent(go, t);
            EditorSceneManager.MarkSceneDirty(go.scene);
            return "{\"ok\":true,\"type\":" + JsonUtil.Quote(t.FullName) + "}";
        }

        public static string RemoveComponent(Dictionary<string, object> args)
        {
            var go = HierarchyTools.FindByPath(JsonUtil.Get(args, "path"));
            if (!go) return "{\"ok\":false,\"error\":\"not found\"}";
            string typeName = JsonUtil.Get(args, "type");
            var comps = go.GetComponents<Component>();
            foreach (var c in comps)
            {
                if (c != null && (c.GetType().Name == typeName || c.GetType().FullName == typeName))
                {
                    Undo.DestroyObjectImmediate(c);
                    EditorSceneManager.MarkSceneDirty(go.scene);
                    return "{\"ok\":true}";
                }
            }
            return "{\"ok\":false,\"error\":\"component not found\"}";
        }

        public static Type ResolveType(string name)
        {
            name = NormalizeTypeName(name);
            if (string.IsNullOrEmpty(name)) return null;

            var scriptType = ResolveMonoScriptType(name);
            if (scriptType != null) return scriptType;

            var t = Type.GetType(name);
            if (t != null) return t;
            foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
            {
                t = asm.GetType(name);
                if (t != null) return t;
                Type[] types;
                try { types = asm.GetTypes(); }
                catch (ReflectionTypeLoadException e) { types = e.Types; }
                foreach (var candidate in types)
                    if (candidate != null && TypeNameMatches(candidate, name)) return candidate;
            }
            return null;
        }

        private static string NormalizeTypeName(string name)
        {
            if (string.IsNullOrEmpty(name)) return "";
            name = name.Trim().Trim('"', '\'');
            if (name.StartsWith("class ", StringComparison.OrdinalIgnoreCase)) name = name.Substring(6).Trim();
            if (name.Replace('\\', '/').EndsWith(".cs", StringComparison.OrdinalIgnoreCase))
            {
                if (!name.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase) && !name.Contains("/") && !name.Contains("\\"))
                    name = System.IO.Path.GetFileNameWithoutExtension(name);
            }
            return name;
        }

        private static Type ResolveMonoScriptType(string name)
        {
            string assetPath = name.Replace('\\', '/');
            if (!assetPath.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase) && assetPath.Contains("/"))
                assetPath = "Assets/" + assetPath.TrimStart('/');

            if (assetPath.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase))
            {
                var script = AssetDatabase.LoadAssetAtPath<MonoScript>(assetPath);
                var cls = script ? script.GetClass() : null;
                if (cls != null) return cls;
                var caseMatchedPath = FindAssetPathCaseInsensitive(assetPath);
                if (!string.IsNullOrEmpty(caseMatchedPath) && caseMatchedPath != assetPath)
                {
                    script = AssetDatabase.LoadAssetAtPath<MonoScript>(caseMatchedPath);
                    cls = script ? script.GetClass() : null;
                    if (cls != null) return cls;
                }
            }

            string simpleName = System.IO.Path.GetFileNameWithoutExtension(name);
            foreach (var guid in AssetDatabase.FindAssets(simpleName + " t:MonoScript"))
            {
                var path = AssetDatabase.GUIDToAssetPath(guid);
                var script = AssetDatabase.LoadAssetAtPath<MonoScript>(path);
                var cls = script ? script.GetClass() : null;
                if (cls != null && TypeNameMatches(cls, name)) return cls;
            }
            return null;
        }

        private static string FindAssetPathCaseInsensitive(string requestedPath)
        {
            string simpleName = System.IO.Path.GetFileNameWithoutExtension(requestedPath);
            foreach (var guid in AssetDatabase.FindAssets(simpleName + " t:MonoScript"))
            {
                var path = AssetDatabase.GUIDToAssetPath(guid);
                if (string.Equals(path, requestedPath, StringComparison.OrdinalIgnoreCase))
                    return path;
            }
            return "";
        }

        private static string FindMonoScriptStatus(string name)
        {
            string simpleName = System.IO.Path.GetFileNameWithoutExtension(NormalizeTypeName(name));
            var sb = new StringBuilder("{\"query\":").Append(JsonUtil.Quote(simpleName)).Append(",\"matches\":[");
            int count = 0;
            foreach (var guid in AssetDatabase.FindAssets(simpleName + " t:MonoScript"))
            {
                var path = AssetDatabase.GUIDToAssetPath(guid);
                var script = AssetDatabase.LoadAssetAtPath<MonoScript>(path);
                var cls = script ? script.GetClass() : null;
                if (count++ > 0) sb.Append(",");
                sb.Append("{\"path\":").Append(JsonUtil.Quote(path));
                sb.Append(",\"compiled_class\":").Append(cls != null ? JsonUtil.Quote(cls.FullName) : "null");
                sb.Append(",\"is_component\":").Append(cls != null && typeof(Component).IsAssignableFrom(cls) ? "true" : "false");
                sb.Append("}");
                if (count >= 8) break;
            }
            sb.Append("]}");
            return sb.ToString();
        }

        private static bool TypeNameMatches(Type type, string name)
        {
            string simpleName = System.IO.Path.GetFileNameWithoutExtension(name);
            return type.Name == name || type.FullName == name || type.Name == simpleName || type.FullName == simpleName;
        }

        private static string FindComponentCandidates(string name)
        {
            var sb = new StringBuilder("[");
            string simpleName = System.IO.Path.GetFileNameWithoutExtension(NormalizeTypeName(name));
            int count = 0;
            foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
            {
                Type[] types;
                try { types = asm.GetTypes(); }
                catch (ReflectionTypeLoadException e) { types = e.Types; }
                foreach (var t in types)
                {
                    if (t == null || !typeof(Component).IsAssignableFrom(t)) continue;
                    if (simpleName.Length > 0 && t.Name.IndexOf(simpleName, StringComparison.OrdinalIgnoreCase) < 0
                        && (t.FullName == null || t.FullName.IndexOf(simpleName, StringComparison.OrdinalIgnoreCase) < 0))
                        continue;
                    if (count++ > 0) sb.Append(",");
                    sb.Append(JsonUtil.Quote(t.FullName));
                    if (count >= 8) { sb.Append("]"); return sb.ToString(); }
                }
            }
            sb.Append("]");
            return sb.ToString();
        }
    }
}
