Editor/Launcher/HotCodeInstallUi.cs
using Sandbox;
using Editor;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

/// <summary>
/// The confirmations and messages around <see cref="HotCodeInstaller"/>, shared by the launcher and the editor.
/// </summary>
public static class HotCodeInstallUi
{
	/// <summary>
	/// Where this copy of HotCode lives: the loaded project whose .sbproj is HotCode's.
	/// </summary>
	public static string SourceRoot
	{
		get
		{
			var loaded = EditorUtility.Projects.GetAll()
				.Where( p => p.ConfigFilePath is not null && HotCodeInstaller.IsHotCode( p.ConfigFilePath ) )
				.Select( p => Path.GetDirectoryName( p.ConfigFilePath ) )
				.FirstOrDefault();
			if ( loaded is not null ) return loaded;

			// Libraries might not be in the project list: look in the open project's Libraries folder
			var current = Project.Current?.GetRootPath();
			return string.IsNullOrEmpty( current ) ? null : HotCodeInstaller.FindInstalled( current );
		}
	}

	public enum State
	{
		/// <summary>Not a project HotCode can go in (a solution, or HotCode's own source).</summary>
		NotApplicable,
		NotInstalled,
		Installed
	}

	public static State StateOf( HotCodeEntry entry )
	{
		if ( entry.Kind != HotCodeEntryKind.Project ) return State.NotApplicable;

		var source = SourceRoot;
		if ( source is not null && (ProjectFiles.IsUnder( entry.Directory, source ) || ProjectFiles.IsUnder( source, entry.Directory )) )
			return State.NotApplicable;

		try
		{
			return HotCodeInstaller.FindInstalled( entry.Directory ) is null ? State.NotInstalled : State.Installed;
		}
		catch ( Exception e ) when ( e is IOException or UnauthorizedAccessException )
		{
			return State.NotApplicable;
		}
	}

	/// <summary>
	/// Adds the install/update/remove options for a project to a menu.
	/// </summary>
	public static void AddMenuOptions( Menu menu, HotCodeEntry entry, Action changed )
	{
		switch ( StateOf( entry ) )
		{
			case State.NotInstalled:
				menu.AddOption( "Add HotCode to This Project", HotCodeLogo.IconName, () => AskInstall( entry, changed ) );
				break;

			case State.Installed:
				menu.AddOption( "Update HotCode in This Project", "system_update_alt", () => AskInstall( entry, changed ) );
				menu.AddOption( "Remove HotCode from This Project", "remove_circle_outline", () => AskUninstall( entry, changed ) );
				break;
		}
	}

	public static void AskInstall( HotCodeEntry entry, Action changed = null )
	{
		var source = SourceRoot;
		if ( source is null )
		{
			Show( "Add HotCode", "Couldn't find HotCode's own files. Open HotCode's project (or one that has it) in s&box first." );
			return;
		}

		var updating = StateOf( entry ) == State.Installed;
		var question = updating
			? $"Update the copy of HotCode in '{entry.Title}' to this version?\n\nThe old copy goes to the Recycle Bin."
			: $"Add HotCode to '{entry.Title}'?\n\nThis copies HotCode into its Libraries folder, the same place the Library Manager puts libraries. Next time you open that project in s&box, HotCode will be there.";

		Dialog.AskConfirm( () =>
		{
			try
			{
				var folder = HotCodeInstaller.Install( source, entry.Directory, ProjectFiles.Delete );
				Log.Info( $"[HotCode] {(updating ? "Updated" : "Added")} HotCode in {folder}" );
				Show( updating ? "HotCode Updated" : "HotCode Added",
					$"HotCode is now in '{entry.Title}'.\n\n{folder}\n\nOpen (or reopen) that project in s&box to use it." );
				changed?.Invoke();
			}
			catch ( Exception e ) when ( e is InvalidOperationException or IOException or UnauthorizedAccessException )
			{
				Show( "Couldn't Add HotCode", e.Message );
			}
		}, question, updating ? "Update HotCode" : "Add HotCode", updating ? "Update" : "Add", "Cancel" );
	}

	public static void AskUninstall( HotCodeEntry entry, Action changed = null )
	{
		var source = SourceRoot ?? "";

		Dialog.AskConfirm( () =>
		{
			try
			{
				if ( HotCodeInstaller.Uninstall( source, entry.Directory, ProjectFiles.Delete ) )
					Log.Info( $"[HotCode] Removed HotCode from {entry.Directory}" );
				changed?.Invoke();
			}
			catch ( Exception e ) when ( e is InvalidOperationException or IOException or UnauthorizedAccessException )
			{
				Show( "Couldn't Remove HotCode", e.Message );
			}
		}, $"Remove HotCode from '{entry.Title}'?\n\nIts copy goes to the Recycle Bin.", "Remove HotCode", "Remove", "Cancel" );
	}

	private static void Show( string title, string message ) =>
		new PopupWindow( title, message, "OK", new Dictionary<string, Action>() ).Show();
}