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

namespace UnityAssistant
{
    public static class AssetTools
    {
        public static string ListAssets(Dictionary<string, object> args)
        {
            string folder = JsonUtil.Get(args, "folder", "Assets");
            string filter = JsonUtil.Get(args, "filter", "");
            int limit = Math.Max(1, Math.Min(1000, (int)JsonUtil.GetNum(args, "limit", 250)));
            var guids = AssetDatabase.FindAssets(filter, new[] { folder });
            var sb = new StringBuilder("{\"ok\":true,\"assets\":[");
            int take = Math.Min(limit, guids.Length);
            for (int i = 0; i < take; i++)
            {
                if (i > 0) sb.Append(",");
                var path = AssetDatabase.GUIDToAssetPath(guids[i]);
                var type = AssetDatabase.GetMainAssetTypeAtPath(path);
                sb.Append("{\"path\":").Append(JsonUtil.Quote(path));
                sb.Append(",\"type\":").Append(JsonUtil.Quote(type?.Name ?? "Unknown"));
                sb.Append(",\"guid\":").Append(JsonUtil.Quote(guids[i])).Append("}");
            }
            sb.Append("],\"total\":").Append(guids.Length);
            sb.Append(",\"truncated\":").Append(guids.Length > take ? "true" : "false");
            sb.Append(",\"limit\":").Append(limit).Append("}");
            return sb.ToString();
        }

        public static string ReadScript(Dictionary<string, object> args)
        {
            string path = JsonUtil.Get(args, "path");
            string full = Path.Combine(Directory.GetCurrentDirectory(), path);
            if (!File.Exists(full)) return "{\"ok\":false,\"error\":\"not found\"}";
            int startLine = Math.Max(1, (int)JsonUtil.GetNum(args, "start_line", 1));
            int requestedEndLine = (int)JsonUtil.GetNum(args, "end_line", 0);
            int maxLines = Math.Max(1, Math.Min(5000, (int)JsonUtil.GetNum(args, "max_lines", 900)));
            var lines = File.ReadAllLines(full);
            int startIndex = Math.Min(lines.Length, startLine - 1);
            int endExclusive = requestedEndLine > 0
                ? Math.Min(lines.Length, Math.Max(startLine, requestedEndLine))
                : Math.Min(lines.Length, startIndex + maxLines);
            int take = Math.Max(0, endExclusive - startIndex);
            var sb = new StringBuilder("{\"ok\":true,\"path\":").Append(JsonUtil.Quote(path));
            sb.Append(",\"lines\":").Append(lines.Length);
            sb.Append(",\"start_line\":").Append(startIndex + 1);
            sb.Append(",\"end_line\":").Append(endExclusive);
            sb.Append(",\"truncated\":").Append(startIndex > 0 || endExclusive < lines.Length ? "true" : "false");
            sb.Append(",\"content\":").Append(JsonUtil.Quote(take > 0 ? string.Join("\n", lines, startIndex, take) : "")).Append("}");
            return sb.ToString();
        }

        public static string CreateScript(Dictionary<string, object> args)
        {
            string path = NormalizeScriptPath(JsonUtil.Get(args, "path"));
            string content = JsonUtil.Get(args, "content");
            string full = Path.Combine(Directory.GetCurrentDirectory(), path);
            Directory.CreateDirectory(Path.GetDirectoryName(full));
            File.WriteAllText(full, content);
            AssetDatabase.ImportAsset(path);
            return "{\"ok\":true,\"path\":" + JsonUtil.Quote(path)
                + ",\"new_content\":" + JsonUtil.Quote(content) + "}";
        }

        public static string EditScript(Dictionary<string, object> args)
        {
            string path = NormalizeScriptPath(JsonUtil.Get(args, "path"));
            string full = Path.Combine(Directory.GetCurrentDirectory(), path);
            if (!File.Exists(full)) return "{\"ok\":false,\"error\":\"not found\"}";

            string original = File.ReadAllText(full);
            string updated = original;
            if (args.ContainsKey("content"))
            {
                updated = JsonUtil.Get(args, "content");
            }
            else if (args.ContainsKey("old") && args.ContainsKey("new"))
            {
                string old = JsonUtil.Get(args, "old");
                string @new = JsonUtil.Get(args, "new");
                if (old == @new) return "{\"ok\":false,\"error\":\"no-op edit rejected; use read_script with start_line/end_line to inspect file ranges\"}";
                int count = 0;
                int idx = 0;
                while ((idx = updated.IndexOf(old, idx, StringComparison.Ordinal)) >= 0)
                { count++; idx += old.Length; }
                if (count == 0) return "{\"ok\":false,\"error\":\"old text not found\"}";
                if (count > 1 && !JsonUtil.GetBool(args, "replace_all"))
                    return "{\"ok\":false,\"error\":\"old text matches " + count + " places, set replace_all=true\"}";
                updated = updated.Replace(old, @new);
            }
            else
            {
                return "{\"ok\":false,\"error\":\"missing content or old/new edit\"}";
            }

            File.WriteAllText(full, updated);
            AssetDatabase.ImportAsset(path);
            return "{\"ok\":true,\"path\":" + JsonUtil.Quote(path)
                + ",\"old_content\":" + JsonUtil.Quote(original)
                + ",\"new_content\":" + JsonUtil.Quote(updated) + "}";
        }

        private static string NormalizeScriptPath(string raw)
        {
            string path = (raw ?? "").Replace('\\', '/').Trim().TrimStart('/');
            if (!path.StartsWith("Assets/")) path = "Assets/" + path;
            if (!path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase)) path += ".cs";
            return path;
        }

        public static string CreateMaterial(Dictionary<string, object> args)
        {
            string path = JsonUtil.Get(args, "path");
            string shader = JsonUtil.Get(args, "shader", "Standard");
            if (!path.StartsWith("Assets/")) path = "Assets/" + path;
            if (!path.EndsWith(".mat")) path += ".mat";
            var sh = Shader.Find(shader);
            if (sh == null) return "{\"ok\":false,\"error\":\"shader not found: " + shader + "\"}";
            var mat = new Material(sh);

            var color = JsonUtil.GetObj(args, "color");
            if (color != null) mat.color = new Color(
                (float)JsonUtil.GetNum(color, "r"), (float)JsonUtil.GetNum(color, "g"),
                (float)JsonUtil.GetNum(color, "b"), (float)JsonUtil.GetNum(color, "a", 1));

            Directory.CreateDirectory(Path.GetDirectoryName(Path.Combine(Directory.GetCurrentDirectory(), path)));
            AssetDatabase.CreateAsset(mat, path);
            AssetDatabase.SaveAssets();
            return "{\"ok\":true,\"path\":" + JsonUtil.Quote(path) + "}";
        }

        public static string CreateScriptableObject(Dictionary<string, object> args)
        {
            string path = JsonUtil.Get(args, "path");
            string typeName = JsonUtil.Get(args, "type");
            if (string.IsNullOrEmpty(path)) return "{\"ok\":false,\"error\":\"missing path\"}";
            if (!path.StartsWith("Assets/")) path = "Assets/" + path;
            if (!path.EndsWith(".asset")) path += ".asset";
            var type = InspectorTools.ResolveType(typeName);
            if (type == null) return "{\"ok\":false,\"error\":\"type not found: " + typeName + "\"}";
            if (!typeof(ScriptableObject).IsAssignableFrom(type)) return "{\"ok\":false,\"error\":\"type is not a ScriptableObject\"}";

            var asset = ScriptableObject.CreateInstance(type);
            Directory.CreateDirectory(Path.GetDirectoryName(Path.Combine(Directory.GetCurrentDirectory(), path)));
            AssetDatabase.CreateAsset(asset, path);
            AssetDatabase.SaveAssets();
            return "{\"ok\":true,\"path\":" + JsonUtil.Quote(path) + ",\"type\":" + JsonUtil.Quote(type.FullName) + "}";
        }

        public static string AssignMaterial(Dictionary<string, object> args)
        {
            var go = HierarchyTools.FindByPath(JsonUtil.Get(args, "path"));
            if (!go) return "{\"ok\":false,\"error\":\"object not found\"}";
            string matPath = JsonUtil.Get(args, "material");
            var mat = AssetDatabase.LoadAssetAtPath<Material>(matPath);
            if (!mat) return "{\"ok\":false,\"error\":\"material not found\"}";
            var renderer = go.GetComponent<Renderer>();
            if (!renderer) return "{\"ok\":false,\"error\":\"no renderer\"}";
            int slot = (int)JsonUtil.GetNum(args, "slot", 0);
            Undo.RecordObject(renderer, "AssignMaterial");
            var mats = renderer.sharedMaterials;
            if (slot >= mats.Length) slot = 0;
            mats[slot] = mat;
            renderer.sharedMaterials = mats;
            EditorSceneManager.MarkSceneDirty(go.scene);
            return "{\"ok\":true}";
        }

        public static string SetMaterialProperty(Dictionary<string, object> args)
        {
            string matPath = JsonUtil.Get(args, "material");
            var mat = AssetDatabase.LoadAssetAtPath<Material>(matPath);
            if (!mat) return "{\"ok\":false,\"error\":\"material not found\"}";
            string prop = JsonUtil.Get(args, "property");
            args.TryGetValue("value", out var val);

            Undo.RecordObject(mat, "SetMaterialProperty");
            if (val is Dictionary<string, object> dict && dict.ContainsKey("r"))
            {
                mat.SetColor(prop, new Color(
                    (float)JsonUtil.GetNum(dict, "r"), (float)JsonUtil.GetNum(dict, "g"),
                    (float)JsonUtil.GetNum(dict, "b"), (float)JsonUtil.GetNum(dict, "a", 1)));
            }
            else if (val is List<object> arr)
            {
                if (arr.Count == 2) mat.SetVector(prop, new Vector4(
                    (float)Convert.ToDouble(arr[0], CultureInfo.InvariantCulture),
                    (float)Convert.ToDouble(arr[1], CultureInfo.InvariantCulture)));
                else if (arr.Count == 3) mat.SetVector(prop, new Vector4(
                    (float)Convert.ToDouble(arr[0], CultureInfo.InvariantCulture),
                    (float)Convert.ToDouble(arr[1], CultureInfo.InvariantCulture),
                    (float)Convert.ToDouble(arr[2], CultureInfo.InvariantCulture)));
                else if (arr.Count == 4) mat.SetVector(prop, new Vector4(
                    (float)Convert.ToDouble(arr[0], CultureInfo.InvariantCulture),
                    (float)Convert.ToDouble(arr[1], CultureInfo.InvariantCulture),
                    (float)Convert.ToDouble(arr[2], CultureInfo.InvariantCulture),
                    (float)Convert.ToDouble(arr[3], CultureInfo.InvariantCulture)));
            }
            else if (val is string texPath)
            {
                var tex = AssetDatabase.LoadAssetAtPath<Texture>(texPath);
                mat.SetTexture(prop, tex);
            }
            else if (val != null)
            {
                mat.SetFloat(prop, (float)Convert.ToDouble(val, CultureInfo.InvariantCulture));
            }
            EditorUtility.SetDirty(mat);
            return "{\"ok\":true}";
        }

        public static string CreatePrefab(Dictionary<string, object> args)
        {
            var go = HierarchyTools.FindByPath(JsonUtil.Get(args, "path"));
            if (!go) return "{\"ok\":false,\"error\":\"object not found\"}";
            string assetPath = JsonUtil.Get(args, "asset_path");
            if (!assetPath.StartsWith("Assets/")) assetPath = "Assets/" + assetPath;
            if (!assetPath.EndsWith(".prefab")) assetPath += ".prefab";
            Directory.CreateDirectory(Path.GetDirectoryName(Path.Combine(Directory.GetCurrentDirectory(), assetPath)));
            var prefab = PrefabUtility.SaveAsPrefabAssetAndConnect(go, assetPath, InteractionMode.UserAction);
            return "{\"ok\":true,\"asset\":" + JsonUtil.Quote(assetPath) + "}";
        }

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

            var root = PrefabUtility.GetOutermostPrefabInstanceRoot(go);
            if (!root) return "{\"ok\":false,\"error\":\"object is not part of a prefab instance\"}";

            var source = PrefabUtility.GetCorrespondingObjectFromSource(root);
            string assetPath = source ? AssetDatabase.GetAssetPath(source) : "";
            if (string.IsNullOrEmpty(assetPath))
                return "{\"ok\":false,\"error\":\"prefab source asset not found\"}";

            PrefabUtility.ApplyPrefabInstance(root, InteractionMode.UserAction);
            AssetDatabase.SaveAssets();
            EditorSceneManager.MarkSceneDirty(root.scene);
            return "{\"ok\":true,\"path\":" + JsonUtil.Quote(HierarchyTools.GetPath(root.transform))
                + ",\"asset\":" + JsonUtil.Quote(assetPath) + "}";
        }

        public static string InstantiatePrefab(Dictionary<string, object> args)
        {
            string assetPath = JsonUtil.Get(args, "asset_path");
            var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
            if (!prefab) return "{\"ok\":false,\"error\":\"prefab not found\"}";
            var instance = PrefabUtility.InstantiatePrefab(prefab) as GameObject;
            string parent = JsonUtil.Get(args, "parent");
            if (!string.IsNullOrEmpty(parent))
            {
                var p = HierarchyTools.FindByPath(parent);
                if (p) instance.transform.SetParent(p.transform, false);
            }
            Undo.RegisterCreatedObjectUndo(instance, "Instantiate");
            EditorSceneManager.MarkSceneDirty(instance.scene);
            return "{\"ok\":true,\"path\":" + JsonUtil.Quote(HierarchyTools.GetPath(instance.transform)) + "}";
        }

        public static string MoveAsset(Dictionary<string, object> args)
        {
            string from = JsonUtil.Get(args, "from");
            string to = JsonUtil.Get(args, "to");
            string err = AssetDatabase.MoveAsset(from, to);
            if (!string.IsNullOrEmpty(err)) return "{\"ok\":false,\"error\":" + JsonUtil.Quote(err) + "}";
            return "{\"ok\":true}";
        }

        public static string DeleteAsset(Dictionary<string, object> args)
        {
            string path = JsonUtil.Get(args, "path");
            if (AssetDatabase.DeleteAsset(path)) return "{\"ok\":true}";
            return "{\"ok\":false,\"error\":\"delete failed\"}";
        }

        public static string ImportAsset(Dictionary<string, object> args)
        {
            string path = JsonUtil.Get(args, "path");
            AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
            return "{\"ok\":true}";
        }

        public static string TakeAssetPreview(Dictionary<string, object> args)
        {
            string path = JsonUtil.Get(args, "path");
            var asset = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(path);
            if (!asset) return "{\"ok\":false,\"error\":\"asset not found\"}";
            Texture2D tex = null;
            for (int i = 0; i < 20; i++)
            {
                tex = AssetPreview.GetAssetPreview(asset) ?? AssetPreview.GetMiniThumbnail(asset);
                if (tex != null) break;
                System.Threading.Thread.Sleep(50);
            }
            if (tex == null) return "{\"ok\":false,\"error\":\"preview unavailable\"}";
            var copy = new Texture2D(tex.width, tex.height, TextureFormat.RGBA32, false);
            copy.SetPixels(tex.GetPixels());
            copy.Apply();
            var bytes = copy.EncodeToPNG();
            UnityEngine.Object.DestroyImmediate(copy);
            return "{\"ok\":true,\"path\":" + JsonUtil.Quote(path)
                + ",\"width\":" + tex.width
                + ",\"height\":" + tex.height
                + ",\"mime\":\"image/png\",\"base64\":"
                + JsonUtil.Quote(Convert.ToBase64String(bytes)) + "}";
        }
    }
}
