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

namespace UnityAssistant
{
    public static class HierarchyTools
    {
        public static string GetHierarchy(Dictionary<string, object> args)
        {
            int maxDepth = (int)JsonUtil.GetNum(args, "max_depth", 6);
            int maxNodes = (int)JsonUtil.GetNum(args, "max_nodes", 2000);
            int nodeCount = 0;
            var sb = new StringBuilder("{\"ok\":true,\"scenes\":[");
            int sceneCount = SceneManager.sceneCount;
            for (int i = 0; i < sceneCount; i++)
            {
                var scene = SceneManager.GetSceneAt(i);
                if (i > 0) sb.Append(",");
                sb.Append("{\"name\":").Append(JsonUtil.Quote(scene.name));
                sb.Append(",\"path\":").Append(JsonUtil.Quote(scene.path));
                sb.Append(",\"roots\":[");
                var roots = scene.GetRootGameObjects();
                for (int r = 0; r < roots.Length; r++)
                {
                    if (r > 0) sb.Append(",");
                    AppendNode(sb, roots[r].transform, 0, maxDepth, maxNodes, ref nodeCount);
                    if (nodeCount >= maxNodes) break;
                }
                sb.Append("]}");
                if (nodeCount >= maxNodes) break;
            }
            sb.Append("],\"node_count\":").Append(nodeCount);
            if (nodeCount >= maxNodes) sb.Append(",\"truncated\":true");
            sb.Append("}");
            return sb.ToString();
        }

        private static void AppendNode(StringBuilder sb, Transform t, int depth, int maxDepth, int maxNodes, ref int nodeCount)
        {
            nodeCount++;
            sb.Append("{\"name\":").Append(JsonUtil.Quote(t.gameObject.name));
            sb.Append(",\"path\":").Append(JsonUtil.Quote(GetPath(t)));
            sb.Append(",\"active\":").Append(t.gameObject.activeSelf ? "true" : "false");
            sb.Append(",\"tag\":").Append(JsonUtil.Quote(t.gameObject.tag));
            sb.Append(",\"layer\":").Append(t.gameObject.layer);
            sb.Append(",\"components\":[");
            var comps = t.GetComponents<Component>();
            for (int i = 0; i < comps.Length; i++)
            {
                if (i > 0) sb.Append(",");
                sb.Append(JsonUtil.Quote(comps[i] == null ? "MissingScript" : comps[i].GetType().Name));
            }
            sb.Append("]");
            if (depth < maxDepth && t.childCount > 0)
            {
                sb.Append(",\"children\":[");
                for (int i = 0; i < t.childCount; i++)
                {
                    if (i > 0) sb.Append(",");
                    if (nodeCount >= maxNodes) { sb.Append("\"…truncated…\""); break; }
                    AppendNode(sb, t.GetChild(i), depth + 1, maxDepth, maxNodes, ref nodeCount);
                }
                sb.Append("]");
            }
            else if (t.childCount > 0)
            {
                sb.Append(",\"child_count\":").Append(t.childCount);
            }
            sb.Append("}");
        }

        public static string GetPath(Transform t)
        {
            var sb = new StringBuilder("/" + t.name);
            while (t.parent != null)
            {
                t = t.parent;
                sb.Insert(0, "/" + t.name);
            }
            return sb.ToString();
        }

        public static string GetSelection(Dictionary<string, object> args)
        {
            int limit = (int)JsonUtil.GetNum(args, "limit", 12);
            var objects = Selection.objects;
            var sb = new StringBuilder("{\"ok\":true,\"items\":[");
            int written = 0;
            for (int i = 0; i < objects.Length && written < limit; i++)
            {
                var obj = objects[i];
                if (obj == null) continue;
                string assetPath = AssetDatabase.GetAssetPath(obj);
                if (written > 0) sb.Append(",");

                if (!string.IsNullOrEmpty(assetPath))
                {
                    sb.Append("{\"kind\":\"asset\"");
                    sb.Append(",\"name\":").Append(JsonUtil.Quote(obj.name));
                    sb.Append(",\"path\":").Append(JsonUtil.Quote(assetPath.Replace('\\', '/')));
                    sb.Append(",\"type\":").Append(JsonUtil.Quote(System.IO.Path.GetExtension(assetPath).TrimStart('.')));
                    sb.Append("}");
                }
                else if (obj is GameObject go)
                {
                    sb.Append("{\"kind\":\"hierarchy\"");
                    sb.Append(",\"name\":").Append(JsonUtil.Quote(go.name));
                    sb.Append(",\"path\":").Append(JsonUtil.Quote(GetPath(go.transform)));
                    sb.Append(",\"type\":\"GameObject\"");
                    sb.Append(",\"tag\":").Append(JsonUtil.Quote(go.tag));
                    sb.Append(",\"components\":[");
                    var comps = go.GetComponents<Component>();
                    for (int c = 0; c < comps.Length; c++)
                    {
                        if (c > 0) sb.Append(",");
                        sb.Append(JsonUtil.Quote(comps[c] == null ? "MissingScript" : comps[c].GetType().Name));
                    }
                    sb.Append("]}");
                }
                else if (obj is Component component)
                {
                    var componentObject = component.gameObject;
                    sb.Append("{\"kind\":\"component\"");
                    sb.Append(",\"name\":").Append(JsonUtil.Quote(component.GetType().Name));
                    sb.Append(",\"path\":").Append(JsonUtil.Quote(GetPath(componentObject.transform)));
                    sb.Append(",\"type\":").Append(JsonUtil.Quote(component.GetType().Name));
                    sb.Append("}");
                }
                else
                {
                    sb.Append("{\"kind\":\"object\"");
                    sb.Append(",\"name\":").Append(JsonUtil.Quote(obj.name));
                    sb.Append(",\"path\":").Append(JsonUtil.Quote(obj.name));
                    sb.Append(",\"type\":").Append(JsonUtil.Quote(obj.GetType().Name));
                    sb.Append("}");
                }
                written++;
            }
            sb.Append("],\"count\":").Append(written);
            if (objects.Length > written) sb.Append(",\"truncated\":true");
            sb.Append("}");
            return sb.ToString();
        }

        public static GameObject FindByPath(string path)
        {
            if (string.IsNullOrEmpty(path)) return null;
            path = path.Trim('/');
            int sceneCount = SceneManager.sceneCount;
            for (int s = 0; s < sceneCount; s++)
            {
                var scene = SceneManager.GetSceneAt(s);
                foreach (var root in scene.GetRootGameObjects())
                {
                    var found = SearchTransform(root.transform, path);
                    if (found) return found.gameObject;
                }
            }
            return GameObject.Find(path);
        }

        private static Transform SearchTransform(Transform t, string path)
        {
            var parts = path.Split('/');
            if (parts.Length == 0) return null;
            if (t.name != parts[0]) return null;
            Transform current = t;
            for (int i = 1; i < parts.Length; i++)
            {
                var next = current.Find(parts[i]);
                if (next == null) return null;
                current = next;
            }
            return current;
        }

        public static string SelectObject(Dictionary<string, object> args)
        {
            var go = FindByPath(JsonUtil.Get(args, "path"));
            if (!go) return "{\"ok\":false,\"error\":\"not found\"}";
            Selection.activeGameObject = go;
            EditorGUIUtility.PingObject(go);
            return "{\"ok\":true,\"path\":" + JsonUtil.Quote(GetPath(go.transform)) + "}";
        }

        public static string SelectObjects(Dictionary<string, object> args)
        {
            var paths = JsonUtil.GetArr(args, "paths");
            if (paths == null) return "{\"ok\":false,\"error\":\"paths must be an array\"}";

            if (paths.Count == 0)
            {
                Selection.objects = new UnityEngine.Object[0];
                return "{\"ok\":true,\"count\":0,\"paths\":[],\"missing\":[]}";
            }

            var found = new List<GameObject>();
            var selectedPaths = new List<object>();
            var missing = new List<object>();
            var seen = new HashSet<string>();

            foreach (var value in paths)
            {
                string path = value == null ? "" : value.ToString();
                if (string.IsNullOrEmpty(path) || seen.Contains(path)) continue;
                seen.Add(path);

                var go = FindByPath(path);
                if (!go)
                {
                    missing.Add(path);
                    continue;
                }

                found.Add(go);
                selectedPaths.Add(GetPath(go.transform));
            }

            if (found.Count == 0)
            {
                return "{\"ok\":false,\"error\":\"no objects found\",\"missing\":" + JsonUtil.ToJson(missing) + "}";
            }

            var objects = new UnityEngine.Object[found.Count];
            for (int i = 0; i < found.Count; i++) objects[i] = found[i];
            Selection.objects = objects;
            // Do NOT set Selection.activeGameObject after setting Selection.objects — doing
            // so collapses the multi-selection to a single object in Unity's internal state,
            // which causes get_selection to return only the last-clicked item.
            EditorGUIUtility.PingObject(found[found.Count - 1]);

            return "{\"ok\":true,\"count\":" + found.Count
                + ",\"paths\":" + JsonUtil.ToJson(selectedPaths)
                + ",\"missing\":" + JsonUtil.ToJson(missing) + "}";
        }

        public static string CreateObject(Dictionary<string, object> args)
        {
            string name = JsonUtil.Get(args, "name", "GameObject");
            string parent = JsonUtil.Get(args, "parent");
            string primitive = JsonUtil.Get(args, "primitive");

            GameObject go;
            if (!string.IsNullOrEmpty(primitive))
            {
                if (System.Enum.TryParse<PrimitiveType>(primitive, true, out var pt))
                    go = GameObject.CreatePrimitive(pt);
                else
                    return "{\"ok\":false,\"error\":\"unknown primitive\"}";
                go.name = name;
            }
            else
            {
                go = new GameObject(name);
            }

            if (!string.IsNullOrEmpty(parent))
            {
                var p = FindByPath(parent);
                if (p) go.transform.SetParent(p.transform, false);
            }

            Undo.RegisterCreatedObjectUndo(go, "Create " + name);
            EditorSceneManager.MarkSceneDirty(go.scene);
            return "{\"ok\":true,\"path\":" + JsonUtil.Quote(GetPath(go.transform)) + "}";
        }

        public static string DeleteObject(Dictionary<string, object> args)
        {
            var go = FindByPath(JsonUtil.Get(args, "path"));
            if (!go) return "{\"ok\":false,\"error\":\"not found\"}";
            var scene = go.scene;
            Undo.DestroyObjectImmediate(go);
            EditorSceneManager.MarkSceneDirty(scene);
            return "{\"ok\":true}";
        }

        public static string RenameObject(Dictionary<string, object> args)
        {
            var go = FindByPath(JsonUtil.Get(args, "path"));
            if (!go) return "{\"ok\":false,\"error\":\"not found\"}";
            Undo.RecordObject(go, "Rename");
            go.name = JsonUtil.Get(args, "name");
            EditorSceneManager.MarkSceneDirty(go.scene);
            return "{\"ok\":true,\"path\":" + JsonUtil.Quote(GetPath(go.transform)) + "}";
        }

        public static string ReparentObject(Dictionary<string, object> args)
        {
            var go = FindByPath(JsonUtil.Get(args, "path"));
            if (!go) return "{\"ok\":false,\"error\":\"not found\"}";
            string newParent = JsonUtil.Get(args, "new_parent");
            Transform parent = null;
            if (!string.IsNullOrEmpty(newParent))
            {
                var p = FindByPath(newParent);
                if (!p) return "{\"ok\":false,\"error\":\"parent not found\"}";
                parent = p.transform;
            }
            Undo.SetTransformParent(go.transform, parent, "Reparent");
            EditorSceneManager.MarkSceneDirty(go.scene);
            return "{\"ok\":true,\"path\":" + JsonUtil.Quote(GetPath(go.transform)) + "}";
        }

        public static string SetTransform(Dictionary<string, object> args)
        {
            var go = FindByPath(JsonUtil.Get(args, "path"));
            if (!go) return "{\"ok\":false,\"error\":\"not found\"}";
            Undo.RecordObject(go.transform, "SetTransform");
            var pos = JsonUtil.GetArr(args, "position");
            var rot = JsonUtil.GetArr(args, "rotation");
            var scl = JsonUtil.GetArr(args, "scale");
            if (pos != null && pos.Count == 3) go.transform.localPosition = ToVec3(pos);
            if (rot != null && rot.Count == 3) go.transform.localEulerAngles = ToVec3(rot);
            if (scl != null && scl.Count == 3) go.transform.localScale = ToVec3(scl);
            EditorSceneManager.MarkSceneDirty(go.scene);
            return "{\"ok\":true}";
        }

        public static string DuplicateObject(Dictionary<string, object> args)
        {
            var go = FindByPath(JsonUtil.Get(args, "path"));
            if (!go) return "{\"ok\":false,\"error\":\"not found\"}";
            var copy = Object.Instantiate(go, go.transform.parent);
            copy.name = go.name + " (Copy)";
            Undo.RegisterCreatedObjectUndo(copy, "Duplicate");
            EditorSceneManager.MarkSceneDirty(copy.scene);
            return "{\"ok\":true,\"path\":" + JsonUtil.Quote(GetPath(copy.transform)) + "}";
        }

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