Editor/AutoRestart.cs
using System.Collections.Generic;
using System.Linq;
using Editor;
using Sandbox;

namespace AutoRestart;

/// <summary>
/// Auto Restart - one-click editor restart with proper unsaved-changes handling.
///
/// Adds:
///   Editor > Auto Restart > Restart Editor                (handles unsaved scenes)
///   Editor > Auto Restart > Restart Editor (Force)        (discards unsaved scenes)
///   Editor > Auto Restart > About
///
/// Triggers a clean process restart and re-launches sbox-dev.exe with the same
/// command-line arguments + the current project, so the editor reopens straight
/// back into the project you're working on - no launcher detour.
///
/// Why this exists separately from the engine's <c>EditorUtility.RestartEditor()</c>:
/// the engine call kicks off <c>Process.Start("sbox-dev.exe", ...)</c> immediately after
/// <c>EditorWindow.Close()</c>. The close call is non-blocking, and if there are
/// unsaved scenes the engine's own popup is also non-blocking, so a second editor
/// process spawns while the old one is still showing the save dialog. We handle
/// the save prompt ourselves and only call <c>RestartEditor()</c> once the user
/// has decided what to do with unsaved work.
/// </summary>
public static class AutoRestartMenu
{
	// ── Public entry points ────────────────────────────────────────────────

	[Menu( "Editor", "Auto Restart/Restart Editor", "restart_alt" )]
	public static void Restart()
	{
		var unsaved = GetUnsavedSessions();

		if ( unsaved.Count == 0 )
		{
			Dialog.AskConfirm(
				DoRestartNow,
				"Restart the s&box editor now? It will relaunch straight back into this project.",
				"Auto Restart",
				"Restart",
				"Cancel" );
			return;
		}

		ShowUnsavedDialog( unsaved );
	}

	[Menu( "Editor", "Auto Restart/Restart Editor (Force)", "bolt" )]
	public static void RestartForce()
	{
		var unsaved = GetUnsavedSessions();
		if ( unsaved.Count > 0 )
		{
			Log.Warning( $"[Auto Restart] Forcing restart - discarding unsaved changes in {unsaved.Count} scene(s)." );
			DiscardUnsaved( unsaved );
		}
		DoRestartNow();
	}

	[Menu( "Editor", "Auto Restart/About", "info" )]
	public static void About()
	{
		Dialog.AskConfirm(
			() => { },
			"Auto Restart\n\n" +
			"One-click editor restart for s&box.\n\n" +
			"Menu: Editor > Auto Restart\n\n" +
			"Performs a clean process restart: prompts for unsaved changes, then " +
			"re-launches sbox-dev.exe with the same command-line arguments and the " +
			"current project so the editor automatically reopens back into the " +
			"project you're working on.",
			"Auto Restart",
			"OK" );
	}

	// ── Save-dialog flow ───────────────────────────────────────────────────

	private static void ShowUnsavedDialog( List<SceneEditorSession> unsaved )
	{
		var popup = new PopupDialogWidget( "💾" );
		popup.WindowTitle = "Auto Restart - Unsaved Changes";
		popup.FixedWidth = 520;
		popup.MessageLabel.Text =
			$"You have unsaved changes in {unsaved.Count} scene{(unsaved.Count == 1 ? "" : "s")}:\n\n" +
			"  • " + string.Join( "\n  • ", unsaved.Select( s => s.Scene?.Name ?? "<unnamed>" ) ) +
			"\n\nWhat would you like to do before restarting?";

		popup.ButtonLayout.Spacing = 4;
		popup.ButtonLayout.AddStretchCell();

		popup.ButtonLayout.Add( new Button.Primary( "Save and Restart" )
		{
			Clicked = () =>
			{
				foreach ( var s in unsaved )
				{
					try { s.Save( false ); }
					catch ( System.Exception ex ) { Log.Error( $"[Auto Restart] Failed to save '{s.Scene?.Name}': {ex.Message}" ); }
				}
				popup.Destroy();
				DoRestartNow();
			}
		} );

		popup.ButtonLayout.Add( new Button( "Restart Without Saving" )
		{
			Clicked = () =>
			{
				DiscardUnsaved( unsaved );
				popup.Destroy();
				DoRestartNow();
			}
		} );

		popup.ButtonLayout.Add( new Button( "Cancel" )
		{
			Clicked = popup.Destroy
		} );

		popup.SetModal( true, true );
		popup.Hide();
		popup.Show();
	}

	// ── Helpers ────────────────────────────────────────────────────────────

	private static List<SceneEditorSession> GetUnsavedSessions()
	{
		return SceneEditorSession.All
			.Where( s => s != null && s.HasUnsavedChanges )
			.ToList();
	}

	/// <summary>
	/// Mark sessions clean so the engine's own OnClose check returns true and we
	/// don't race the engine's non-blocking save popup against Process.Start.
	/// </summary>
	private static void DiscardUnsaved( List<SceneEditorSession> sessions )
	{
		foreach ( var s in sessions )
		{
			try { s.HasUnsavedChanges = false; } catch { }
		}
	}

	private static void DoRestartNow()
	{
		EditorUtility.RestartEditor();
	}
}