Editor/UI/MenuItems.cs
using System;
using System.Linq;
using Editor;
using Sandbox.SecBox.Bridge;
using Sandbox.SecBox.Bridge.Dto;
using Sandbox.SecBox.Lifecycle;

namespace Sandbox.SecBox.UI;

// Top-level menu entries under "secbox/" in the editor menu bar. Lets users
// trigger scans, manage runtime monitoring, and access dev tooling.
public static class MenuItems
{
	[Menu( "Editor", "secbox/Trusted Libraries..." )]
	public static void OpenTrustManager()
	{
		TrustManagerWindow.Open();
	}

	[Menu( "Editor", "secbox/Dev/Toggle Dev Mode" )]
	public static void ToggleDevMode()
	{
		bool wasActive = CorePolicy.DevModeActive;
		if (wasActive)
		{
			CorePolicy.DisableDevMode();
			SecboxCoreLoader.TryUnload();
			EditorUtility.DisplayDialog(
				"secbox: dev mode OFF",
				$"Production mode restored. Next scan loads Secbox.Core from the verified CDN cache.\n\nConfig: {SecboxConfig.FilePath}",
				icon: "🔒");
		}
		else
		{
			CorePolicy.EnableDevMode();
			SecboxCoreLoader.TryUnload();
			System.IO.Directory.CreateDirectory(CorePolicy.DevDefaultPath);
			EditorUtility.DisplayDialog(
				"secbox: dev mode ON",
				$"Hash verification SKIPPED. Loading Secbox.Core from:\n{CorePolicy.DevDefaultPath}\n\n"
				+ $"Build the Secbox solution (its AfterBuild target auto-copies here).\n\n"
				+ $"Config: {SecboxConfig.FilePath}\n"
				+ $"Edit 'devPath' in the JSON to point elsewhere.",
				icon: "🛠️");
		}
	}

	[Menu( "Editor", "secbox/Dev/Show Status" )]
	public static void ShowDevModeStatus()
	{
		var active = CorePolicy.DevModeActive;
		var resolved = CorePolicy.DevOverridePath ?? "(production mode - verified CDN cache)";
		var cfg = SecboxConfig.Load();
		var envOverride = System.Environment.GetEnvironmentVariable("SECBOX_DEV_PATH");

		var lines = new[]
		{
			$"Dev mode: {(active ? "ON" : "OFF")}",
			$"",
			$"Config file: {SecboxConfig.FilePath}",
			$"  exists: {System.IO.File.Exists(SecboxConfig.FilePath)}",
			$"  devMode: {cfg.DevMode}",
			$"  devPath: {(string.IsNullOrEmpty(cfg.DevPath) ? "(unset → DevDefaultPath)" : cfg.DevPath)}",
			$"  autoUpdate: {cfg.AutoUpdate}",
			$"",
			$"DevDefaultPath: {CorePolicy.DevDefaultPath}",
			$"  exists: {System.IO.Directory.Exists(CorePolicy.DevDefaultPath)}",
			$"",
			$"Resolved load path: {resolved}",
			$"",
			$"%SECBOX_DEV_PATH% override: {envOverride ?? "(not set)"}",
		};

		EditorUtility.DisplayDialog("secbox dev-mode status", string.Join("\n", lines), icon: active ? "🛠️" : "🔒");
	}

	[Menu( "Editor", "secbox/Dev/Open Config File" )]
	public static void OpenConfigFile()
	{
		var path = SecboxConfig.FilePath;
		if (!System.IO.File.Exists(path))
		{
			// Create with defaults so the user has something to edit.
			new SecboxConfig().Save();
		}
		try
		{
			System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
			{
				FileName = path,
				UseShellExecute = true,
			});
		}
		catch (System.Exception ex)
		{
			EditorUtility.DisplayDialog("secbox", $"Could not open: {ex.Message}\n\nPath: {path}");
		}
	}

	[Menu( "Editor", "secbox/Dev/Open Diagnostics Log" )]
	public static void OpenDiagnosticsLog()
	{
		var path = DiagnosticsLog.FilePath;
		if (!System.IO.File.Exists(path))
		{
			EditorUtility.DisplayDialog("secbox", $"Log file does not exist yet:\n{path}");
			return;
		}
		try
		{
			System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
			{
				FileName = path,
				UseShellExecute = true,
			});
		}
		catch (System.Exception ex)
		{
			EditorUtility.DisplayDialog("secbox", $"Could not open: {ex.Message}\n\nPath: {path}");
		}
	}

	[Menu( "Editor", "secbox/Dev/Open Diagnostics Log Folder" )]
	public static void OpenDiagnosticsLogFolder()
	{
		var folder = System.IO.Path.GetDirectoryName(DiagnosticsLog.FilePath);
		try
		{
			System.IO.Directory.CreateDirectory(folder);
			System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
			{
				FileName = folder,
				UseShellExecute = true,
			});
		}
		catch (System.Exception ex)
		{
			EditorUtility.DisplayDialog("secbox", $"Could not open: {ex.Message}\n\nFolder: {folder}");
		}
	}

	[Menu( "Editor", "secbox/Dev/Reload Core Now" )]
	public static void ReloadCore()
	{
		var unloaded = SecboxCoreLoader.TryUnload();
		try
		{
			SecboxCoreClient.EnsureReadyAsync().GetAwaiter().GetResult();
			var info = SecboxCoreClient.GetInfo();
			EditorUtility.DisplayDialog(
				"secbox: core reloaded",
				$"{(unloaded ? "Unloaded previous instance.\n\n" : "")}Loaded Secbox.Core v{info.ScannerVersion}\n"
				+ $"Protocol: {info.ProtocolVersion}\n"
				+ $"Finders: {string.Join(", ", info.AvailableFinders)}\n"
				+ $"Packs: {info.AvailableRulePacks.Count}",
				icon: "♻️");
		}
		catch (System.Exception ex)
		{
			EditorUtility.DisplayDialog(
				"secbox: core reload failed",
				$"{ex.Message}\n\nCheck the configured dev path or production hash pinning.",
				icon: "😬");
		}
	}

	[Menu( "Editor", "secbox/Scan now" )]
	public static void ScanNow()
	{
		global::Sandbox.Internal.GlobalGameNamespace.Log.Info(
			"[secbox] manual scan triggered" );

		var root = PackageLocator.CurrentProjectRoot();
		if ( string.IsNullOrEmpty( root ) )
		{
			EditorUtility.DisplayDialog( "secbox", "No current project. Open a project first." );
			return;
		}

		// Open the results window in its scanning state, then run the scan off the
		// UI thread (ScanFolder / EnsureReadyAsync block internally and would
		// deadlock here) and feed results back on the main thread.
		var window = ScanResultsWindow.OpenScanning();
		System.Threading.Tasks.Task.Run( () =>
		{
			try
			{
				var results = BootAudit.ScanAllLibraries();
				MainThread.Queue( () => { try { window.SetResults( results ); } catch { } } );
			}
			catch ( System.Exception ex )
			{
				DiagnosticsLog.Error( "[secbox] manual scan failed", ex );
				MainThread.Queue( () => { try { window.SetResults( null ); } catch { } } );
			}
		} );
	}

	// ============================================================
	// Runtime monitoring (Tier E managed-call enforcement)
	// ============================================================

	[Menu( "Editor", "secbox/Runtime Monitoring/Show Status" )]
	public static void ShowRuntimeMonitorStatus()
	{
		var cfg = SecboxConfig.Load();
		var attached = Lifecycle.RuntimeMonitorCoordinator.IsAttached;

		string sensors = "(not attached)";
		if ( attached )
		{
			try
			{
				var s = Bridge.RuntimeMonitorBridge.GetStatus();
				sensors = string.Join( "\n  ",
					s.Select( x => $"{x.Id}: {x.Status}{(string.IsNullOrEmpty(x.LastError) ? "" : " - " + x.LastError)}" ) );
			}
			catch ( System.Exception ex ) { sensors = $"(status query failed: {ex.Message})"; }
		}

		var lines = new[]
		{
			$"Runtime enforcement (Tier E): {(cfg.RuntimeMonitoringEnabled ? "enabled" : "disabled")}",
			$"Block library Process.Start:  {cfg.BlockLibraryProcessStart}",
			$"Attached:          {attached}",
			$"Recent findings:   {Lifecycle.RuntimeMonitorCoordinator.RecentCount}",
			"",
			"Sensors:",
			"  " + sensors,
		};

		EditorUtility.DisplayDialog( "secbox: runtime monitoring", string.Join( "\n", lines ), icon: attached ? "monitor_heart" : "monitor" );
	}

	[Menu( "Editor", "secbox/Runtime Monitoring/Attach Now" )]
	public static void AttachRuntimeNow()
	{
		Lifecycle.RuntimeMonitorCoordinator.EnsureAttached();
		ShowRuntimeMonitorStatus();
	}

	[Menu( "Editor", "secbox/Runtime Monitoring/Detach Now" )]
	public static void DetachRuntimeNow()
	{
		Lifecycle.RuntimeMonitorCoordinator.Detach();
		EditorUtility.DisplayDialog( "secbox", "Runtime sensors detached." );
	}
}