using System;
using System.IO;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
using UnityEngine.TestTools;
using UnityEngine.UIElements;
using MCPForUnity.Editor.Tools;
using static MCPForUnityTests.Editor.TestUtilities;

namespace MCPForUnityTests.Editor.Tools
{
    public class ManageUITests
    {
        private const string TempRoot = "Assets/Temp/ManageUITests";

        [SetUp]
        public void SetUp()
        {
            EnsureFolder(TempRoot);
        }

        [TearDown]
        public void TearDown()
        {
            if (AssetDatabase.IsValidFolder(TempRoot))
            {
                AssetDatabase.DeleteAsset(TempRoot);
            }
            CleanupEmptyParentFolders(TempRoot);
        }

        // ---- Action validation ----

        [Test]
        public void HandleCommand_MissingAction_ReturnsError()
        {
            var result = ToJObject(ManageUI.HandleCommand(new JObject()));
            Assert.IsFalse(result.Value<bool>("success"));
        }

        [Test]
        public void HandleCommand_UnknownAction_ReturnsError()
        {
            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "explode"
            }));
            Assert.IsFalse(result.Value<bool>("success"));
            Assert.That(result["error"].ToString(), Does.Contain("Unknown action"));
        }

        [Test]
        public void Ping_ReturnsPong()
        {
            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "ping"
            }));
            Assert.IsTrue(result.Value<bool>("success"));
            Assert.AreEqual("pong", result.Value<string>("message"));
        }

        // ---- Create file ----

        [Test]
        public void Create_Uxml_CreatesFile()
        {
            string path = $"{TempRoot}/Test_{Guid.NewGuid():N}.uxml";
            string content = "<ui:UXML xmlns:ui=\"UnityEngine.UIElements\"><ui:Label text=\"Hi\" /></ui:UXML>";

            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "create",
                ["path"] = path,
                ["contents"] = content,
            }));

            Assert.IsTrue(result.Value<bool>("success"), result.ToString());

            // Verify file was created on disk
            string fullPath = Path.Combine(Application.dataPath,
                path.Substring("Assets/".Length)).Replace('/', Path.DirectorySeparatorChar);
            Assert.IsTrue(File.Exists(fullPath), $"File should exist at {fullPath}");

            // EnsureEditorExtensionMode may inject editor-extension-mode attribute
            string actual = File.ReadAllText(fullPath);
            Assert.That(actual, Does.Contain("ui:UXML"));
            Assert.That(actual, Does.Contain("ui:Label"));
        }

        [Test]
        public void Create_Uss_CreatesFile()
        {
            string path = $"{TempRoot}/Test_{Guid.NewGuid():N}.uss";
            string content = ".root { background-color: red; }";

            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "create",
                ["path"] = path,
                ["contents"] = content,
            }));

            Assert.IsTrue(result.Value<bool>("success"), result.ToString());
        }

        [Test]
        public void Create_InvalidExtension_ReturnsError()
        {
            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "create",
                ["path"] = $"{TempRoot}/Test.txt",
                ["contents"] = "hello",
            }));

            Assert.IsFalse(result.Value<bool>("success"));
            Assert.That(result["error"].ToString(), Does.Contain(".uxml or .uss"));
        }

        [Test]
        public void Create_MissingContents_ReturnsError()
        {
            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "create",
                ["path"] = $"{TempRoot}/Test.uxml",
            }));

            Assert.IsFalse(result.Value<bool>("success"));
            Assert.That(result["error"].ToString(), Does.Contain("contents"));
        }

        [Test]
        public void Create_AlreadyExists_ReturnsError()
        {
            string path = $"{TempRoot}/Exists_{Guid.NewGuid():N}.uxml";
            string content = "<ui:UXML xmlns:ui=\"UnityEngine.UIElements\" />";

            // Create first time
            ManageUI.HandleCommand(new JObject
            {
                ["action"] = "create",
                ["path"] = path,
                ["contents"] = content,
            });

            // Try to create again
            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "create",
                ["path"] = path,
                ["contents"] = content,
            }));

            Assert.IsFalse(result.Value<bool>("success"));
            Assert.That(result["error"].ToString(), Does.Contain("already exists"));
        }

        [Test]
        public void Create_WithBase64EncodedContents_Decodes()
        {
            string path = $"{TempRoot}/Encoded_{Guid.NewGuid():N}.uxml";
            string content = "<ui:UXML xmlns:ui=\"UnityEngine.UIElements\" />";
            string encoded = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(content));

            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "create",
                ["path"] = path,
                ["encodedContents"] = encoded,
                ["contentsEncoded"] = true,
            }));

            Assert.IsTrue(result.Value<bool>("success"), result.ToString());

            string fullPath = Path.Combine(Application.dataPath,
                path.Substring("Assets/".Length)).Replace('/', Path.DirectorySeparatorChar);
            string actual = File.ReadAllText(fullPath);
            // EnsureEditorExtensionMode may inject editor-extension-mode attribute
            Assert.That(actual, Does.Contain("ui:UXML"));
            Assert.That(actual, Does.Contain("UnityEngine.UIElements"));
        }

        // ---- Read file ----

        [Test]
        public void Read_ExistingFile_ReturnsContents()
        {
            string path = $"{TempRoot}/ReadTest_{Guid.NewGuid():N}.uxml";
            string content = "<ui:UXML xmlns:ui=\"UnityEngine.UIElements\" />";

            ManageUI.HandleCommand(new JObject
            {
                ["action"] = "create",
                ["path"] = path,
                ["contents"] = content,
            });

            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "read",
                ["path"] = path,
            }));

            Assert.IsTrue(result.Value<bool>("success"), result.ToString());
            var data = result["data"] as JObject;
            Assert.IsNotNull(data);
            // EnsureEditorExtensionMode may inject editor-extension-mode attribute
            Assert.That(data.Value<string>("contents"), Does.Contain("ui:UXML"));
        }

        [Test]
        public void Read_NonExistentFile_ReturnsError()
        {
            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "read",
                ["path"] = $"{TempRoot}/DoesNotExist.uxml",
            }));

            Assert.IsFalse(result.Value<bool>("success"));
            Assert.That(result["error"].ToString(), Does.Contain("not found"));
        }

        // ---- Update file ----

        [Test]
        public void Update_ExistingFile_OverwritesContents()
        {
            string path = $"{TempRoot}/UpdateTest_{Guid.NewGuid():N}.uss";
            string original = ".root { color: red; }";
            string updated = ".root { color: blue; font-size: 20px; }";

            ManageUI.HandleCommand(new JObject
            {
                ["action"] = "create",
                ["path"] = path,
                ["contents"] = original,
            });

            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "update",
                ["path"] = path,
                ["contents"] = updated,
            }));

            Assert.IsTrue(result.Value<bool>("success"), result.ToString());

            // Verify content was updated
            var readResult = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "read",
                ["path"] = path,
            }));
            Assert.AreEqual(updated, readResult["data"].Value<string>("contents"));
        }

        [Test]
        public void Update_NonExistentFile_ReturnsError()
        {
            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "update",
                ["path"] = $"{TempRoot}/Missing.uxml",
                ["contents"] = "<ui:UXML />",
            }));

            Assert.IsFalse(result.Value<bool>("success"));
            Assert.That(result["error"].ToString(), Does.Contain("not found"));
        }

        // ---- Create PanelSettings ----

        [Test]
        public void CreatePanelSettings_CreatesAsset()
        {
            string path = $"{TempRoot}/TestPanel_{Guid.NewGuid():N}.asset";

            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "create_panel_settings",
                ["path"] = path,
            }));

            Assert.IsTrue(result.Value<bool>("success"), result.ToString());

            var ps = AssetDatabase.LoadAssetAtPath<PanelSettings>(path);
            Assert.IsNotNull(ps, "PanelSettings should exist at the path");
        }

        [Test]
        public void CreatePanelSettings_AlreadyExists_ReturnsError()
        {
            string path = $"{TempRoot}/ExistingPanel_{Guid.NewGuid():N}.asset";

            ManageUI.HandleCommand(new JObject
            {
                ["action"] = "create_panel_settings",
                ["path"] = path,
            });

            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "create_panel_settings",
                ["path"] = path,
            }));

            Assert.IsFalse(result.Value<bool>("success"));
            Assert.That(result["error"].ToString(), Does.Contain("already exists"));
        }

        // ---- Attach UIDocument ----

        [Test]
        public void AttachUIDocument_AddsComponent()
        {
            // Create a UXML file first
            string uxmlPath = $"{TempRoot}/Attach_{Guid.NewGuid():N}.uxml";
            ManageUI.HandleCommand(new JObject
            {
                ["action"] = "create",
                ["path"] = uxmlPath,
                ["contents"] = "<ui:UXML xmlns:ui=\"UnityEngine.UIElements\"><ui:Label text=\"Test\" /></ui:UXML>",
            });
            AssetDatabase.Refresh();

            // Create a test GameObject
            var go = new GameObject("UITestObject_Attach");
            try
            {
                var result = ToJObject(ManageUI.HandleCommand(new JObject
                {
                    ["action"] = "attach_ui_document",
                    ["target"] = go.name,
                    ["source_asset"] = uxmlPath,
                }));

                Assert.IsTrue(result.Value<bool>("success"), result.ToString());

                var uiDoc = go.GetComponent<UIDocument>();
                Assert.IsNotNull(uiDoc, "UIDocument component should be attached");
                Assert.IsNotNull(uiDoc.visualTreeAsset, "VisualTreeAsset should be assigned");
                Assert.IsNotNull(uiDoc.panelSettings, "PanelSettings should be assigned (auto-created)");
            }
            finally
            {
                UnityEngine.Object.DestroyImmediate(go);
            }
        }

        [Test]
        public void AttachUIDocument_MissingTarget_ReturnsError()
        {
            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "attach_ui_document",
                ["source_asset"] = "Assets/UI/Test.uxml",
            }));

            Assert.IsFalse(result.Value<bool>("success"));
        }

        [Test]
        public void AttachUIDocument_MissingSourceAsset_ReturnsError()
        {
            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "attach_ui_document",
                ["target"] = "SomeObject",
            }));

            Assert.IsFalse(result.Value<bool>("success"));
        }

        // ---- Get Visual Tree ----

        [Test]
        public void GetVisualTree_MissingTarget_ReturnsError()
        {
            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "get_visual_tree",
            }));

            Assert.IsFalse(result.Value<bool>("success"));
        }

        [Test]
        public void GetVisualTree_NoUIDocument_ReturnsError()
        {
            var go = new GameObject("UITestObject_NoDoc");
            try
            {
                var result = ToJObject(ManageUI.HandleCommand(new JObject
                {
                    ["action"] = "get_visual_tree",
                    ["target"] = go.name,
                }));

                Assert.IsFalse(result.Value<bool>("success"));
                Assert.That(result["error"].ToString(), Does.Contain("UIDocument"));
            }
            finally
            {
                UnityEngine.Object.DestroyImmediate(go);
            }
        }

        // ---- Delete file ----

        [Test]
        public void Delete_ExistingFile_DeletesFile()
        {
            string path = $"{TempRoot}/Delete_{Guid.NewGuid():N}.uss";
            ManageUI.HandleCommand(new JObject
            {
                ["action"] = "create",
                ["path"] = path,
                ["contents"] = ".root { color: red; }",
            });

            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "delete",
                ["path"] = path,
            }));

            Assert.IsTrue(result.Value<bool>("success"), result.ToString());

            string fullPath = Path.Combine(Application.dataPath,
                path.Substring("Assets/".Length)).Replace('/', Path.DirectorySeparatorChar);
            Assert.IsFalse(File.Exists(fullPath), "File should be deleted");
        }

        [Test]
        public void Delete_NonExistentFile_ReturnsError()
        {
            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "delete",
                ["path"] = $"{TempRoot}/Missing.uxml",
            }));

            Assert.IsFalse(result.Value<bool>("success"));
            Assert.That(result["error"].ToString(), Does.Contain("not found"));
        }

        [Test]
        public void Delete_InvalidExtension_ReturnsError()
        {
            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "delete",
                ["path"] = $"{TempRoot}/File.txt",
            }));

            Assert.IsFalse(result.Value<bool>("success"));
            Assert.That(result["error"].ToString(), Does.Contain(".uxml or .uss"));
        }

        // ---- List UI assets ----

        [Test]
        public void List_ReturnsUIAssets()
        {
            string uxmlPath = $"{TempRoot}/ListTest_{Guid.NewGuid():N}.uxml";
            string ussPath = $"{TempRoot}/ListTest_{Guid.NewGuid():N}.uss";

            ManageUI.HandleCommand(new JObject
            {
                ["action"] = "create",
                ["path"] = uxmlPath,
                ["contents"] = "<ui:UXML xmlns:ui=\"UnityEngine.UIElements\" />",
            });
            ManageUI.HandleCommand(new JObject
            {
                ["action"] = "create",
                ["path"] = ussPath,
                ["contents"] = ".root { }",
            });

            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "list",
                ["path"] = TempRoot,
            }));

            Assert.IsTrue(result.Value<bool>("success"), result.ToString());
            var data = result["data"] as JObject;
            Assert.IsNotNull(data);
            int total = data.Value<int>("total");
            Assert.GreaterOrEqual(total, 2, "Should find at least 2 UI assets");
        }

        [Test]
        public void List_WithFilterType_FiltersResults()
        {
            string uxmlPath = $"{TempRoot}/FilterTest_{Guid.NewGuid():N}.uxml";
            ManageUI.HandleCommand(new JObject
            {
                ["action"] = "create",
                ["path"] = uxmlPath,
                ["contents"] = "<ui:UXML xmlns:ui=\"UnityEngine.UIElements\" />",
            });

            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "list",
                ["path"] = TempRoot,
                ["filterType"] = "uxml",
            }));

            Assert.IsTrue(result.Value<bool>("success"), result.ToString());
            var assets = result["data"]["assets"] as JArray;
            Assert.IsNotNull(assets);
            foreach (var asset in assets)
            {
                Assert.AreEqual("uxml", asset.Value<string>("type"));
            }
        }

        // ---- Detach UIDocument ----

        [Test]
        public void DetachUIDocument_RemovesComponent()
        {
            string uxmlPath = $"{TempRoot}/Detach_{Guid.NewGuid():N}.uxml";
            ManageUI.HandleCommand(new JObject
            {
                ["action"] = "create",
                ["path"] = uxmlPath,
                ["contents"] = "<ui:UXML xmlns:ui=\"UnityEngine.UIElements\"><ui:Label text=\"Test\" /></ui:UXML>",
            });
            AssetDatabase.Refresh();

            var go = new GameObject("UITestObject_Detach");
            try
            {
                ManageUI.HandleCommand(new JObject
                {
                    ["action"] = "attach_ui_document",
                    ["target"] = go.name,
                    ["source_asset"] = uxmlPath,
                });
                Assert.IsNotNull(go.GetComponent<UIDocument>(), "UIDocument should be attached");

                var result = ToJObject(ManageUI.HandleCommand(new JObject
                {
                    ["action"] = "detach_ui_document",
                    ["target"] = go.name,
                }));

                Assert.IsTrue(result.Value<bool>("success"), result.ToString());
                Assert.IsNull(go.GetComponent<UIDocument>(), "UIDocument should be removed");
            }
            finally
            {
                UnityEngine.Object.DestroyImmediate(go);
            }
        }

        [Test]
        public void DetachUIDocument_NoUIDocument_ReturnsError()
        {
            var go = new GameObject("UITestObject_DetachNoDoc");
            try
            {
                var result = ToJObject(ManageUI.HandleCommand(new JObject
                {
                    ["action"] = "detach_ui_document",
                    ["target"] = go.name,
                }));

                Assert.IsFalse(result.Value<bool>("success"));
                Assert.That(result["error"].ToString(), Does.Contain("UIDocument"));
            }
            finally
            {
                UnityEngine.Object.DestroyImmediate(go);
            }
        }

        [Test]
        public void DetachUIDocument_MissingTarget_ReturnsError()
        {
            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "detach_ui_document",
            }));

            Assert.IsFalse(result.Value<bool>("success"));
        }

        // ---- Modify visual element ----

        [Test]
        public void ModifyVisualElement_MissingTarget_ReturnsError()
        {
            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "modify_visual_element",
                ["elementName"] = "test",
            }));

            Assert.IsFalse(result.Value<bool>("success"));
        }

        [Test]
        public void ModifyVisualElement_MissingElementName_ReturnsError()
        {
            var go = new GameObject("UITestObject_ModifyNoName");
            try
            {
                var result = ToJObject(ManageUI.HandleCommand(new JObject
                {
                    ["action"] = "modify_visual_element",
                    ["target"] = go.name,
                }));

                Assert.IsFalse(result.Value<bool>("success"));
                Assert.That(result["error"].ToString(), Does.Contain("element_name"));
            }
            finally
            {
                UnityEngine.Object.DestroyImmediate(go);
            }
        }

        [Test]
        public void ModifyVisualElement_NoUIDocument_ReturnsError()
        {
            var go = new GameObject("UITestObject_ModifyNoDoc");
            try
            {
                var result = ToJObject(ManageUI.HandleCommand(new JObject
                {
                    ["action"] = "modify_visual_element",
                    ["target"] = go.name,
                    ["elementName"] = "test",
                }));

                Assert.IsFalse(result.Value<bool>("success"));
                Assert.That(result["error"].ToString(), Does.Contain("UIDocument"));
            }
            finally
            {
                UnityEngine.Object.DestroyImmediate(go);
            }
        }

        // ---- UXML validation ----

        [Test]
        public void Create_MalformedXml_ReturnsError_FileNotWritten()
        {
            string path = $"{TempRoot}/Malformed_{Guid.NewGuid():N}.uxml";
            string badContent = "<ui:UXML><ui:Label text=\"unclosed\">";

            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "create",
                ["path"] = path,
                ["contents"] = badContent,
            }));

            Assert.IsFalse(result.Value<bool>("success"));
            Assert.That(result["error"].ToString(), Does.Contain("Malformed XML"));

            // Verify file was NOT written
            string fullPath = Path.Combine(Application.dataPath,
                path.Substring("Assets/".Length)).Replace('/', Path.DirectorySeparatorChar);
            Assert.IsFalse(File.Exists(fullPath), "Malformed UXML should not be written to disk");
        }

        [Test]
        public void Create_MissingNamespace_WritesWithWarning()
        {
            string path = $"{TempRoot}/NoNs_{Guid.NewGuid():N}.uxml";
            string content = "<ui:UXML><ui:Label text=\"hi\" /></ui:UXML>";

            // Unity's UXML importer logs an error for undeclared 'ui' prefix
            LogAssert.ignoreFailingMessages = true;
            try
            {
                var result = ToJObject(ManageUI.HandleCommand(new JObject
                {
                    ["action"] = "create",
                    ["path"] = path,
                    ["contents"] = content,
                }));

                Assert.IsTrue(result.Value<bool>("success"), result.ToString());
                var data = result["data"] as JObject;
                Assert.IsNotNull(data);
                var warnings = data["validationWarnings"] as JArray;
                Assert.IsNotNull(warnings, "Should have validationWarnings");
                Assert.That(warnings.ToString(), Does.Contain("Missing namespace"));
            }
            finally
            {
                LogAssert.ignoreFailingMessages = false;
            }
        }

        [Test]
        public void Create_ValidUxml_NoWarnings()
        {
            string path = $"{TempRoot}/Valid_{Guid.NewGuid():N}.uxml";
            string content = "<ui:UXML xmlns:ui=\"UnityEngine.UIElements\"><ui:Label text=\"ok\" /></ui:UXML>";

            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "create",
                ["path"] = path,
                ["contents"] = content,
            }));

            Assert.IsTrue(result.Value<bool>("success"), result.ToString());
            var data = result["data"] as JObject;
            Assert.IsNull(data?["validationWarnings"],
                "Fully valid UXML should not have validationWarnings");
        }

        [Test]
        public void Create_WrongRootElement_WritesWithWarning()
        {
            string path = $"{TempRoot}/WrongRoot_{Guid.NewGuid():N}.uxml";
            string content = "<div xmlns:ui=\"UnityEngine.UIElements\"><ui:Label text=\"hi\" /></div>";

            // Unity's UXML importer logs an error about expected root element
            LogAssert.ignoreFailingMessages = true;
            try
            {
                var result = ToJObject(ManageUI.HandleCommand(new JObject
                {
                    ["action"] = "create",
                    ["path"] = path,
                    ["contents"] = content,
                }));

                Assert.IsTrue(result.Value<bool>("success"), result.ToString());
                var data = result["data"] as JObject;
                var warnings = data["validationWarnings"] as JArray;
                Assert.IsNotNull(warnings, "Should have validationWarnings");
                Assert.That(warnings.ToString(), Does.Contain("Root element"));
            }
            finally
            {
                LogAssert.ignoreFailingMessages = false;
            }
        }

        [Test]
        public void Create_EmptyContent_ReturnsError()
        {
            string path = $"{TempRoot}/Empty_{Guid.NewGuid():N}.uxml";

            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "create",
                ["path"] = path,
                ["contents"] = "   ",
            }));

            Assert.IsFalse(result.Value<bool>("success"));
            Assert.That(result["error"].ToString(), Does.Contain("empty"));
        }

        [Test]
        public void Update_MalformedXml_ReturnsError_FileNotChanged()
        {
            string path = $"{TempRoot}/UpdateMalformed_{Guid.NewGuid():N}.uxml";
            string original = "<ui:UXML xmlns:ui=\"UnityEngine.UIElements\" />";

            ManageUI.HandleCommand(new JObject
            {
                ["action"] = "create",
                ["path"] = path,
                ["contents"] = original,
            });

            string badContent = "<ui:UXML><broken>";
            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "update",
                ["path"] = path,
                ["contents"] = badContent,
            }));

            Assert.IsFalse(result.Value<bool>("success"));
            Assert.That(result["error"].ToString(), Does.Contain("Malformed XML"));

            // Verify original content was preserved (EnsureEditorExtensionMode may have injected attribute)
            string fullPath = Path.Combine(Application.dataPath,
                path.Substring("Assets/".Length)).Replace('/', Path.DirectorySeparatorChar);
            string actual = File.ReadAllText(fullPath);
            Assert.That(actual, Does.Contain("ui:UXML"), "Original file content should be preserved");
            Assert.That(actual, Does.Not.Contain("<broken>"), "Malformed content should not be written");
        }

        [Test]
        public void Create_Uss_SkipsUxmlValidation()
        {
            string path = $"{TempRoot}/NoValidation_{Guid.NewGuid():N}.uss";
            // USS is CSS-like, not XML — validation should be skipped
            string content = "This is not valid XML <broken>";

            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "create",
                ["path"] = path,
                ["contents"] = content,
            }));

            Assert.IsTrue(result.Value<bool>("success"), result.ToString());
        }

        // ---- Path traversal validation ----

        [Test]
        public void Create_TraversalPath_ReturnsError()
        {
            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "create",
                ["path"] = "Assets/../etc/evil.uxml",
                ["contents"] = "<ui:UXML />",
            }));

            Assert.IsFalse(result.Value<bool>("success"));
            Assert.That(result["error"].ToString(), Does.Contain("traversal"));
        }

        [Test]
        public void Create_DotDotInMiddle_ReturnsError()
        {
            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "create",
                ["path"] = "Assets/UI/../../secret.uxml",
                ["contents"] = "<ui:UXML />",
            }));

            Assert.IsFalse(result.Value<bool>("success"));
            Assert.That(result["error"].ToString(), Does.Contain("traversal"));
        }

        [Test]
        public void Read_TraversalPath_ReturnsError()
        {
            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "read",
                ["path"] = "Assets/../secret.uxml",
            }));

            Assert.IsFalse(result.Value<bool>("success"));
            Assert.That(result["error"].ToString(), Does.Contain("traversal"));
        }

        [Test]
        public void Update_TraversalPath_ReturnsError()
        {
            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "update",
                ["path"] = "Assets/../../etc/passwd.uxml",
                ["contents"] = "overwrite",
            }));

            Assert.IsFalse(result.Value<bool>("success"));
            Assert.That(result["error"].ToString(), Does.Contain("traversal"));
        }

        [Test]
        public void Delete_TraversalPath_ReturnsError()
        {
            var result = ToJObject(ManageUI.HandleCommand(new JObject
            {
                ["action"] = "delete",
                ["path"] = "Assets/../outside.uxml",
            }));

            Assert.IsFalse(result.Value<bool>("success"));
            Assert.That(result["error"].ToString(), Does.Contain("traversal"));
        }
    }
}
