Editor/Tools/EngineControlTools.cs
#nullable enable
using System;
using System.Collections;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Sandbox;

namespace Editor.Mcp
{
	public static partial class SboxMcpAssistant
	{




		[McpTool("pocketknife_switch_editor_tab")]
		public static string SwitchEditorTab([Description("Tab name, e.g. scene, code, assetbrowser")] string tabName)
		{
			try
			{
				var tab = (tabName ?? "").Trim().ToLowerInvariant();
				if (string.IsNullOrWhiteSpace(tab) || tab.Any(char.IsWhiteSpace)) return McpJson.Write(ToolOutcome<object>.Failure(null, ToolStatus.Failed, "editor.invalid_tab", "A single editor tab name is required."));

				if (tab.Contains("scene", StringComparison.OrdinalIgnoreCase))
				{
					var session = EditorRuntime.GetStaticProperty("Editor.SceneEditorSession", "Active");
					if (session is null) return McpJson.Write(ToolOutcome<object>.Failure(null, ToolStatus.Unavailable, "editor.scene_tab_unavailable", "No scene editor session is active."));
					EditorRuntime.InvokeInstance(session, "MakeActive", true);
					EditorRuntime.InvokeInstance(session, "BringToFront");
					var dock = EditorRuntime.GetMember(session, "SceneDock");
					var activeSession = EditorRuntime.GetStaticProperty("Editor.SceneEditorSession", "Active");
					var verified = ReferenceEquals(session, activeSession);
					return verified ? McpJson.Write(ToolOutcome<object>.Ready(new { Tab = "scene", Scene = EditorRuntime.GetMember(EditorRuntime.GetMember(session, "Scene"), "Name")?.ToString(), DockType = dock?.GetType().FullName, IsActiveWindow = EditorRuntime.GetMember(dock, "IsActiveWindow"), Verified = true })) : McpJson.Write(ToolOutcome<object>.Degraded(new { Tab = "scene", Verified = false }, "editor.scene_activation_unverified", "The scene session accepted activation but the active-session postcondition did not match.", "Use the live editor scene/list_scenes tools to verify the active tab."));
				}

				if (tab.Contains("code", StringComparison.OrdinalIgnoreCase))
				{
					var opened = EditorRuntime.InvokeStatic("Editor.CodeEditor", "OpenSolution");
					var current = EditorRuntime.GetStaticProperty("Editor.CodeEditor", "Current");
					return current is not null ? McpJson.Write(ToolOutcome<object>.Ready(new { Tab = "code", Opened = true, EditorType = current.GetType().FullName, Verified = true })) : McpJson.Write(ToolOutcome<object>.Failure(new { Tab = "code", Opened = opened is not null }, ToolStatus.Unavailable, "editor.code_tab_unavailable", "The code editor did not expose an active editor after OpenSolution."));
				}

				var mainWindow = EditorRuntime.GetStaticField("Editor.EditorMainWindow", "Current");
				var dockManager = EditorRuntime.GetMember(mainWindow, "DockManager");
				if (dockManager is null) return McpJson.Write(ToolOutcome<object>.Failure(new { Tab = tab }, ToolStatus.Unavailable, "editor.dock_manager_unavailable", "The editor main window did not expose its dock manager."));
				var dockTypes = EditorRuntime.GetMember(dockManager, "DockTypes") as IEnumerable;
				var candidates = new System.Collections.Generic.List<string>();
				if (dockTypes is not null)
				{
					foreach (var dockInfo in dockTypes)
					{
						var title = EditorRuntime.GetMember(dockInfo, "Title")?.ToString();
						if (!string.IsNullOrWhiteSpace(title)) candidates.Add(title);
					}
				}
				var match = candidates.FirstOrDefault(x => x.Contains(tab, StringComparison.OrdinalIgnoreCase) || (tab.Contains("asset") && x.Contains("asset", StringComparison.OrdinalIgnoreCase)) || (tab.Contains("material") && x.Contains("material", StringComparison.OrdinalIgnoreCase)) || (tab.Contains("hammer") && x.Contains("hammer", StringComparison.OrdinalIgnoreCase)));
				if (match is null) return McpJson.Write(ToolOutcome<object>.Failure(new { Tab = tab, AvailableDocks = candidates.Take(100).ToArray() }, ToolStatus.Unavailable, "editor.dock_not_found", $"No editor dock matched '{tab}'.", remediation: "Use a dock title returned by the editor's dock registry or the live editor UI tools."));
				var raised = EditorRuntime.InvokeInstance(dockManager, "RaiseDock", match) as bool?;
				if (raised != true)
				{
					var dockArea = EditorRuntime.FindType("Editor.DockArea");
					var center = dockArea is null ? null : Enum.Parse(dockArea, "Center");
					EditorRuntime.InvokeInstance(dockManager, "OpenDock", match, center, null);
					raised = EditorRuntime.InvokeInstance(dockManager, "RaiseDock", match) as bool?;
				}
				return raised == true ? McpJson.Write(ToolOutcome<object>.Ready(new { Tab = tab, DockTitle = match, Raised = true, Verified = true })) : McpJson.Write(ToolOutcome<object>.Degraded(new { Tab = tab, DockTitle = match, Raised = false }, "editor.dock_activation_unverified", "The matching editor dock was found, but the editor did not confirm it became active.", "Use the live editor UI bridge to inspect the active dock."));
			}
			catch (Exception ex) { return McpJson.Write(ToolOutcome<object>.Failure(null, ToolStatus.Failed, "editor.tab_failed", "Editor tab switch failed.", ex)); }
		}
	}
}