InteractiveComputer/UI/ComputerDesktop.razor
@namespace PaneOS.InteractiveComputer
@using System
@using System.Collections.Generic
@using System.Linq
@using PaneOS.InteractiveComputer
@using PaneOS.InteractiveComputer.Core
@using Sandbox
@using Sandbox.UI
@inherits PanelComponent

@if ( Computer is null || Runtime is null )
{
	return;
}

<root class="computer-root @(Runtime.State.DarkModeEnabled ? "dark-mode" : "") @(Runtime.State.IsSleeping ? "is-sleeping" : "") @(!ShouldShowDesktop ? "is-hidden" : "")"
	  style="width: 100%; height: 100%; @GetDesktopBackgroundStyle()">
	@if ( ShouldRenderWallpaperImageLayer() )
	{
		<div class="wallpaper-image-layer"></div>
	}

	@if ( ShouldShowDesktop )
	{
		<div @ref="desktopSurfacePanel" class="desktop" onclick="@CloseStartMenu">
			@if ( Runtime.State.IsSleeping )
			{
				<div class="sleep-overlay" onclick="@Wake">
					<div class="sleep-card">
						<div class="system-logo-badge">PaneOS</div>
						<label>Computer is sleeping</label>
						<span>Click to wake</span>
					</div>
				</div>
			}
			else if ( Runtime.State.RestartLogSecondsRemaining > 0f )
			{
				<div class="restart-overlay">
					<div class="restart-log-window">
						<label>PaneOS restart sequence</label>
						<div class="restart-log-lines">
							@foreach ( var line in Runtime.State.RestartLogLines )
							{
								<div class="restart-log-line">@line</div>
							}
						</div>
					</div>
				</div>
			}
			else if ( Runtime.State.BootSplashSecondsRemaining > 0f )
			{
				<div class="boot-overlay">
					<div class="boot-card">
						<div class="system-logo-badge large">PaneOS</div>
						<label>PaneOS</label>
						<span>Starting up...</span>
					</div>
				</div>
			}
			else if ( Runtime.State.IsLocked )
			{
				<div class="sleep-overlay" onclick="@Unlock">
					<div class="sleep-card">
						<div class="system-logo-badge">PaneOS</div>
						<label>PaneOS Locked</label>
						<span>Click to unlock</span>
					</div>
				</div>
			}
			else
			{
				<div class="shortcut-grid">
					@foreach ( var shortcut in GetDesktopShortcuts() )
					{
						<button class="desktop-shortcut @(selectedDesktopShortcutIds.Contains( shortcut.Id ) ? "selected" : "")"
								style="@GetShortcutStyle( shortcut.Index )"
								onclick="@(() => ActivateDesktopShortcut( shortcut.Id ))"
								@onclick:stopPropagation="true"
								@onmousedown:stopPropagation="true"
								ondblclick="@(() => ActivateDesktopShortcut( shortcut.Id ))"
								oncontextmenu="@(() => OpenDesktopContextMenu( shortcut.Id ))">
							<div class="desktop-icon @(string.IsNullOrWhiteSpace( shortcut.IconTexture ) ? "" : "has-texture")"
								 style="@GetShortcutIconStyle( shortcut )">@GetShortcutIconText( shortcut )</div>
							<label>@shortcut.Label</label>
						</button>
					}
				</div>

				@if ( desktopContextMenuShortcutId is not null )
				{
					var contextShortcut = GetDesktopShortcutById( desktopContextMenuShortcutId );
					if ( contextShortcut is not null )
					{
						<div class="desktop-context-menu" style="@GetDesktopContextMenuStyle()">
							<button onclick="@(() => ActivateDesktopShortcut( contextShortcut.Id ))">Open</button>
							@if ( contextShortcut.CanRename )
							{
								<button onclick="@PromptRenameSelectedDesktopShortcut">Rename</button>
							}
							@if ( contextShortcut.CanRestore )
							{
								<button onclick="@RestoreSelectedDesktopShortcut">Restore</button>
							}
							@if ( contextShortcut.CanDelete )
							{
								<button onclick="@DeleteSelectedDesktopShortcuts">Delete</button>
							}
						</div>
					}
				}

				@foreach ( var app in Runtime.OpenApps )
				{
					if ( !app.Descriptor.HasWindow || app.State.IsMinimized )
						continue;

					<div @key="app.State.InstanceId"
						 class="xp-window @(app.State.IsMinimized ? "minimized" : "") @(Runtime.FocusedApp == app ? "focused" : "") @(Runtime.GetEffectiveStatus( app.State.InstanceId ) == ComputerProcessStatus.NotResponding ? "not-responding" : "")"
						 style="left: @(app.State.X)px; top: @(app.State.Y)px; width: @(app.State.Width)px; height: @(app.State.Height)px; z-index: @(app.State.ZIndex);"
						 onclick="@(() => Focus( app.State.InstanceId ))"
						 @onmousedown:stopPropagation="true">
						<div class="title-bar"
							 @onmousedown="@(() => StartWindowDrag( app.State.InstanceId ))"
							 @onmousedown:stopPropagation="true">
							<div class="title-text">
								<span>@app.State.Icon</span>
								<label>@app.State.Title</label>
							</div>
							<div class="window-controls"
								 @onclick:stopPropagation="true"
								 @onmousedown:stopPropagation="true">
								@if ( app.Session.CanMinimize )
								{
									<button onclick="@(() => Minimize( app.State.InstanceId ))"
											@onclick:stopPropagation="true">_</button>
								}
								@if ( app.Session.CanClose )
								{
									<button class="close"
											onclick="@(() => Close( app.State.InstanceId ))"
											@onclick:stopPropagation="true">x</button>
								}
							</div>
						</div>
						<ComputerAppHost @key="app.State.InstanceId" Instance=@app />
						<div class="window-resize-grip"
							 @onmousedown="@(() => StartWindowResize( app.State.InstanceId ))"
							 @onmousedown:stopPropagation="true"></div>
						@if ( Runtime.ShouldBlockInput( app.State.InstanceId ) )
						{
							<div class="window-input-overlay">
								<label>PaneOS is busy...</label>
							</div>
						}
					</div>
				}
			}
		</div>

		@if ( Runtime.State.StartMenuOpen && !Runtime.State.IsSleeping && !Runtime.State.IsLocked && !Runtime.IsRestarting )
		{
			<div class="start-menu">
				<div class="start-header">
					<div class="user-tile">e</div>
					<label>@Computer.ComputerId</label>
				</div>
				<div class="start-apps">
					@foreach ( var app in Runtime.Apps )
					{
						if ( !app.ShowInStartMenu )
							continue;

						<div class="start-app" onclick="@(() => OpenApp( app.Id ))">
							<span>@app.Icon</span>
							<label>@app.Title</label>
						</div>
					}
				</div>
				<div class="start-power">
					<button onclick="@Lock">Lock</button>
					<button onclick="@Restart">Restart</button>
					<button onclick="@Sleep">Sleep</button>
				</div>
			</div>
		}

		@if ( !Runtime.State.IsSleeping && !Runtime.State.IsLocked && !Runtime.IsRestarting )
		{
			<div class="taskbar">
				<button class="start-button @(Runtime.State.StartMenuOpen ? "active" : "")" onclick="@ToggleStart">
					<span>P</span>
					<label>start</label>
				</button>
				<div class="task-list">
					@foreach ( var app in Runtime.OpenApps )
					{
						if ( !app.Descriptor.ShowInTaskbar || !app.Descriptor.HasWindow )
							continue;

						<button class="task-button @(Runtime.FocusedApp == app && !app.State.IsMinimized ? "active" : "")"
								onclick="@(() => ToggleTaskbarApp( app.State.InstanceId ))">
							<span>@app.State.Icon</span>
							<label>@app.State.Title</label>
						</button>
					}
				</div>
				<div class="tray">
					<div class="tray-icons">
						<span>NET</span>
						<span>VOL</span>
					</div>
					<div class="tray-clock">
						<label>@System.DateTime.Now.ToString( "h:mm tt" )</label>
						<span>@System.DateTime.Now.ToString( "dd MMM yyyy" )</span>
					</div>
				</div>
			</div>
		}

		@if ( Runtime.Notifications.Count > 0 )
		{
			<div class="notification-stack">
				@foreach ( var notification in Runtime.Notifications )
				{
					<div class="notification-card">
						<div class="notification-icon">@notification.Icon</div>
						<div class="notification-copy">
							<label>@notification.Title</label>
							<span>@notification.Message</span>
						</div>
					</div>
				}
			</div>
		}

		@if ( Runtime.ActiveMessageBox is not null )
		{
			var messageBox = Runtime.ActiveMessageBox;
				<div class="message-box-overlay">
					<div class="message-box-window"
						 @onclick:stopPropagation="true"
						 @onmousedown:stopPropagation="true">
						<div class="message-box-title-bar">
							<label>@messageBox.Options.Title</label>
						</div>
					<div class="message-box-body">
						<div class="message-box-icon">@messageBox.Options.Icon</div>
						<div class="message-box-copy">
							<label>@messageBox.Options.Message</label>
							@if ( messageBox.Options.HasTextInput )
							{
								<ComputerMessageBoxTextEntry MessageBox=@messageBox OnTextChanged=@UpdateMessageBoxText />
							}
						</div>
					</div>
					<div class="message-box-actions">
						@foreach ( var button in messageBox.Options.Buttons )
						{
							<button onclick="@(() => CloseMessageBox( button ))">@button</button>
						}
					</div>
				</div>
			</div>
		}

		@if ( Runtime.ActiveFileDialog is not null )
		{
			var fileDialog = Runtime.ActiveFileDialog;
			<div class="message-box-overlay">
				<div class="file-dialog-window"
					 @onclick:stopPropagation="true"
					 @onmousedown:stopPropagation="true">
					<div class="message-box-title-bar">
						<label>@fileDialog.Options.Title</label>
					</div>
					<div class="file-dialog-toolbar">
						<button onclick="@FileDialogUp">Up</button>
						<button onclick="@(() => NavigateFileDialogTo( Computer?.Runtime?.GetDefaultDocumentsPath() ?? "" ))">My Documents</button>
						<label>@fileDialog.CurrentPathDisplay</label>
					</div>
					<div class="file-dialog-list">
						@foreach ( var item in fileDialog.VisibleItems )
						{
							<div class="file-dialog-row @(string.Equals( fileDialog.SelectedVirtualPath, item.VirtualPath, StringComparison.OrdinalIgnoreCase ) ? "selected" : "")"
								 onclick="@(() => SelectFileDialogItem( item.VirtualPath ))"
								 ondblclick="@(() => ActivateFileDialogItem( item.VirtualPath ))">
								<label>@item.Name</label>
								<span>@(item.IsDirectory ? "Folder" : item.Extension)</span>
							</div>
						}
					</div>
					@if ( fileDialog.Options.Mode == ComputerFileDialogMode.Save )
					{
						<div class="file-dialog-name-row">
							<label>File name</label>
							<ComputerFileDialogTextEntry FileDialog=@fileDialog OnTextChanged=@UpdateFileDialogFileName />
						</div>
					}
					<div class="message-box-actions">
						<button onclick="@ConfirmFileDialog">@(fileDialog.Options.ConfirmButtonText == "" ? (fileDialog.Options.Mode == ComputerFileDialogMode.Open ? "Open" : "Save") : fileDialog.Options.ConfirmButtonText)</button>
						<button onclick="@CancelFileDialog">Cancel</button>
					</div>
				</div>
			</div>
		}

		@if ( Runtime.IsScreenSaverActive && !Computer.IsPlayerInteracting )
		{
			var saver = Runtime.State.ScreenSaver;
			<div class="screen-saver" onclick="@ScreenSaverActivity">
				<div class="screen-saver-logo" style="@GetScreenSaverLogoStyle( saver )">
					<div class="screen-saver-logo-image"></div>
				</div>
			</div>
		}
	}
	else
	{
		<div class="desktop desktop-hidden"></div>
	}
</root>

@code
{
	[Property] public InteractiveComputerComponent? Computer { get; set; }
	[Property] public bool VisibleOnlyWhenInteracting { get; set; } = true;

	private const float TitleBarHeight = 29f;
	private const float WindowControlInset = 64f;
	private const float ResizeGripSize = 18f;
	private const float DesktopDoubleClickSeconds = 0.35f;

	private ComputerRuntime? Runtime => Computer?.Runtime;
	private int lastRenderHash;
	private string? draggingWindowId;
	private Vector2 draggingWindowOffset;
	private string? resizingWindowId;
	private Vector2 resizingStartMouse;
	private Vector2 resizingStartSize;
	private bool wasDraggingWindow;
	private Vector2 desktopContextMenuPosition;
	private string? desktopContextMenuShortcutId;
	private string? lastDesktopShortcutClickedId;
	private float lastDesktopShortcutClickedAt;
	private Panel? desktopSurfacePanel;
	private readonly HashSet<string> selectedDesktopShortcutIds = new( StringComparer.OrdinalIgnoreCase );
	private bool ShouldShowDesktop => Computer is not null && Runtime is not null && (!VisibleOnlyWhenInteracting || Computer.IsPlayerInteracting);

	protected override int BuildHash()
	{
		unchecked
		{
			var hash = 17;
			hash = hash * 31 + ((Computer?.IsPlayerInteracting ?? false) ? 1 : 0);
			hash = hash * 31 + (Runtime?.DesktopVersion ?? 0);
			hash = hash * 31 + ((Runtime?.State.ScreenSaver.IsActive ?? false) ? 1 : 0);
			hash = hash * 31 + System.DateTime.Now.Minute;
			return hash;
		}
	}

	protected override void OnUpdate()
	{
		EnsureAttachedPanelMatchesResolution();

		if ( draggingWindowId is not null )
		{
			ContinueWindowDrag( GetLocalMousePosition() );
			wasDraggingWindow = true;
		}
		else
		{
			wasDraggingWindow = false;
		}

		var hash = BuildHash();
		if ( hash == lastRenderHash )
			return;

		lastRenderHash = hash;
		StateHasChanged();
	}

	private void OpenApp( string appId )
	{
		Runtime?.OpenApp( appId );
		StateHasChanged();
	}

	private void Focus( string instanceId )
	{
		if ( wasDraggingWindow )
			return;

		Runtime?.Focus( instanceId );
		StateHasChanged();
	}

	private void Minimize( string instanceId )
	{
		Runtime?.Minimize( instanceId );
		StateHasChanged();
	}

	private void Close( string instanceId )
	{
		Runtime?.Close( instanceId );
		StateHasChanged();
	}

	private void ToggleTaskbarApp( string instanceId )
	{
		Runtime?.ToggleTaskbarApp( instanceId );
		StateHasChanged();
	}

	private void ToggleStart()
	{
		Runtime?.ToggleStartMenu();
		StateHasChanged();
	}

	private void CloseStartMenu()
	{
		if ( Runtime is null )
			return;

		Runtime.NotifyUserActivity();
		HideDesktopContextMenu();

		if ( !Runtime.State.StartMenuOpen )
		{
			StateHasChanged();
			return;
		}

		Runtime.State.StartMenuOpen = false;
		Runtime.MarkChanged();
		StateHasChanged();
	}

	private void StartWindowDrag( string instanceId )
	{
		if ( Runtime is null )
			return;

		var app = Runtime.OpenApps.FirstOrDefault( x => x.State.InstanceId == instanceId );
		if ( app is null )
			return;

		Runtime.Focus( instanceId );
		var localMousePosition = GetLocalMousePosition();
		draggingWindowId = instanceId;
		draggingWindowOffset = localMousePosition - new Vector2( app.State.X, app.State.Y );
		StateHasChanged();
	}

	private void StartWindowResize( string instanceId )
	{
		if ( Runtime is null )
			return;

		var app = Runtime.OpenApps.FirstOrDefault( x => x.State.InstanceId == instanceId );
		if ( app is null )
			return;

		Runtime.Focus( instanceId );
		resizingWindowId = instanceId;
		resizingStartMouse = GetLocalMousePosition();
		resizingStartSize = new Vector2( app.State.Width, app.State.Height );
		StateHasChanged();
	}

	private void Sleep()
	{
		Runtime?.Sleep();
		StateHasChanged();
	}

	private void Wake()
	{
		Runtime?.Wake();
		StateHasChanged();
	}

	private void Restart()
	{
		Runtime?.Restart();
		StateHasChanged();
	}

	private void Lock()
	{
		Runtime?.Lock();
		StateHasChanged();
	}

	private void Unlock()
	{
		Runtime?.Unlock();
		StateHasChanged();
	}

	private void ScreenSaverActivity()
	{
		Runtime?.NotifyUserActivity();
		StateHasChanged();
	}

	private void CloseMessageBox( string button )
	{
		Runtime?.CloseMessageBox( button );
		StateHasChanged();
	}

	private void UpdateMessageBoxText( string value )
	{
		Runtime?.UpdateMessageBoxText( value );
	}

	private void NavigateFileDialogTo( string virtualPath )
	{
		Runtime?.NavigateFileDialogTo( virtualPath );
		StateHasChanged();
	}

	private void FileDialogUp()
	{
		Runtime?.MoveFileDialogUp();
		StateHasChanged();
	}

	private void SelectFileDialogItem( string virtualPath )
	{
		Runtime?.SelectFileDialogItem( virtualPath );
		StateHasChanged();
	}

	private void ActivateFileDialogItem( string virtualPath )
	{
		Runtime?.ActivateFileDialogItem( virtualPath );
		StateHasChanged();
	}

	private void ConfirmFileDialog()
	{
		Runtime?.ConfirmFileDialog();
		StateHasChanged();
	}

	private void UpdateFileDialogFileName( string value )
	{
		Runtime?.UpdateFileDialogFileName( value );
	}

	private void CancelFileDialog()
	{
		Runtime?.CancelFileDialog();
		StateHasChanged();
	}

	protected override void OnMouseDown( MousePanelEvent e )
	{
		base.OnMouseDown( e );

		if ( !ShouldShowDesktop || Runtime is null || Runtime.State.IsSleeping || Runtime.State.IsLocked || Runtime.IsRestarting || Runtime.ActiveMessageBox is not null )
			return;

		HideDesktopContextMenu();
		selectedDesktopShortcutIds.Clear();
		StateHasChanged();
		e.StopPropagation();
	}

	protected override void OnMouseMove( MousePanelEvent e )
	{
		base.OnMouseMove( e );

		if ( resizingWindowId is not null )
		{
			ContinueWindowResize( GetLocalMousePosition( e ) );
			e.StopPropagation();
			return;
		}

		if ( draggingWindowId is null )
			return;

		ContinueWindowDrag( GetLocalMousePosition( e ) );
		e.StopPropagation();
	}

	protected override void OnMouseUp( MousePanelEvent e )
	{
		base.OnMouseUp( e );

		if ( draggingWindowId is null )
		{
			if ( resizingWindowId is null )
				return;

			resizingWindowId = null;
			e.StopPropagation();
			return;
		}

		draggingWindowId = null;
		e.StopPropagation();
	}

	private void ContinueWindowDrag( Vector2 localMousePosition )
	{
		if ( Runtime is null || draggingWindowId is null )
			return;

		var nextPosition = localMousePosition - draggingWindowOffset;
		Runtime.MoveWindow( draggingWindowId, (int)nextPosition.x, (int)nextPosition.y );
		StateHasChanged();
	}

	private void ContinueWindowResize( Vector2 localMousePosition )
	{
		if ( Runtime is null || resizingWindowId is null )
			return;

		var delta = localMousePosition - resizingStartMouse;
		var width = resizingStartSize.x + delta.x;
		var height = resizingStartSize.y + delta.y;
		Runtime.ResizeWindow( resizingWindowId, (int)width, (int)height );
		StateHasChanged();
	}

	private ComputerRunningApp? FindTopWindowAt( Vector2 localMousePosition )
	{
		if ( Runtime is null )
			return null;

		foreach ( var app in Runtime.OpenApps.OrderByDescending( x => x.State.ZIndex ) )
		{
			if ( app.State.IsMinimized )
				continue;

			if ( IsInsideWindowBounds( app, localMousePosition ) )
				return app;
		}

		return null;
	}

	private static bool IsInsideWindowBounds( ComputerRunningApp app, Vector2 localMousePosition )
	{
		return localMousePosition.x >= app.State.X &&
			localMousePosition.x <= app.State.X + app.State.Width &&
			localMousePosition.y >= app.State.Y &&
			localMousePosition.y <= app.State.Y + app.State.Height;
	}

	private static bool IsInsideWindowTitleBar( ComputerRunningApp app, Vector2 localMousePosition )
	{
		return localMousePosition.x >= app.State.X &&
			localMousePosition.x <= app.State.X + app.State.Width &&
			localMousePosition.y >= app.State.Y &&
			localMousePosition.y <= app.State.Y + TitleBarHeight;
	}

	private static bool IsInsideWindowControls( ComputerRunningApp app, Vector2 localMousePosition )
	{
		return IsInsideWindowTitleBar( app, localMousePosition ) &&
			localMousePosition.x >= app.State.X + app.State.Width - WindowControlInset;
	}

	private static bool IsInsideResizeGrip( ComputerRunningApp app, Vector2 localMousePosition )
	{
		return localMousePosition.x >= app.State.X + app.State.Width - ResizeGripSize &&
			localMousePosition.x <= app.State.X + app.State.Width &&
			localMousePosition.y >= app.State.Y + app.State.Height - ResizeGripSize &&
			localMousePosition.y <= app.State.Y + app.State.Height;
	}

	private Vector2 GetLocalMousePosition( MousePanelEvent? e = null )
	{
		var desktopSurface = GetDesktopSurfacePanel();
		if ( e is not null && desktopSurface is not null )
		{
			var screenPosition = e.Target is not null
				? e.Target.PanelPositionToScreenPosition( e.LocalPosition )
				: e.LocalPosition;
			return desktopSurface.ScreenPositionToPanelPosition( screenPosition );
		}

		return desktopSurface?.MousePosition ?? Panel?.MousePosition ?? Vector2.Zero;
	}

	private Panel? GetDesktopSurfacePanel()
	{
		if ( desktopSurfacePanel?.IsValid == true )
			return desktopSurfacePanel;

		return Panel?.Children.FirstOrDefault( child => child.HasClass( "desktop" ) );
	}

	private string GetDesktopBackgroundStyle()
	{
		return Runtime is null
			? ""
			: ComputerWallpaperPolicy.GetBackgroundStyle( Runtime.State.DesktopWallpaper );
	}

	private bool ShouldRenderWallpaperImageLayer()
	{
		return Runtime is not null && ComputerWallpaperPolicy.Normalize( Runtime.State.DesktopWallpaper ) == "default";
	}

	private IReadOnlyList<DesktopShortcutItem> GetDesktopShortcuts()
	{
		if ( Runtime is null )
			return Array.Empty<DesktopShortcutItem>();

		var shortcuts = new List<DesktopShortcutItem>();
		foreach ( var app in Runtime.Apps.Where( x => x.ShowOnDesktop ) )
		{
			shortcuts.Add( new DesktopShortcutItem
			{
				Id = $"app:{app.Id}",
				Label = app.Title,
				Icon = app.Icon,
				IconTexture = ResolveAppTexturePath( app ),
				AppId = app.Id
			} );
		}

		shortcuts.Add( new DesktopShortcutItem
		{
			Id = "special:recycle",
			Label = "Recycle Bin",
			Icon = "RB",
			IconTexture = ResolveThemeTexturePath( "App_recycleBin" ),
			VirtualPath = "/C:/Recycle Bin",
			IsDirectory = true
		} );

		foreach ( var item in PaneArchiveFileSystem.GetItems( Runtime.GetArchivePath(), ParseVirtualPath( Runtime.GetDefaultDocumentsPath() ) ) )
		{
			shortcuts.Add( new DesktopShortcutItem
			{
				Id = $"doc:{item.VirtualPath}",
				Label = item.Name,
				Icon = item.IsDirectory ? "FD" : "FI",
				IconTexture = ResolveArchiveItemTexturePath( item ),
				VirtualPath = item.VirtualPath,
				IsDirectory = item.IsDirectory,
				CanRename = true,
				CanDelete = true
			} );
		}

		for ( var index = 0; index < shortcuts.Count; index++ )
			shortcuts[index].Index = index;

		return shortcuts;
	}

	private DesktopShortcutItem? GetDesktopShortcutById( string shortcutId )
	{
		return GetDesktopShortcuts().FirstOrDefault( x => x.Id.Equals( shortcutId, StringComparison.OrdinalIgnoreCase ) );
	}

	private string GetShortcutStyle( int index )
	{
		var position = GetShortcutPosition( index );
		return $"left: {position.x}px; top: {position.y}px;";
	}

	private static string GetShortcutIconStyle( DesktopShortcutItem shortcut )
	{
		return string.IsNullOrWhiteSpace( shortcut.IconTexture )
			? ""
			: $"background-image: url(\"{shortcut.IconTexture}\");";
	}

	private static string GetShortcutIconText( DesktopShortcutItem shortcut )
	{
		return string.IsNullOrWhiteSpace( shortcut.IconTexture ) ? shortcut.Icon : "";
	}

	private Vector2 GetShortcutPosition( int index )
	{
		var position = DesktopShortcutLayoutPolicy.GetPosition( index, Runtime?.State.ResolutionY ?? 768 );
		return new Vector2( position.X, position.Y );
	}

	private bool TryGetDesktopShortcutAt( Vector2 localMousePosition, out DesktopShortcutItem? shortcut )
	{
		var resolutionY = Runtime?.State.ResolutionY ?? 768;
		foreach ( var item in GetDesktopShortcuts() )
		{
			if ( !DesktopShortcutLayoutPolicy.HitTest( item.Index, resolutionY, localMousePosition.x, localMousePosition.y ) )
				continue;

			shortcut = item;
			return true;
		}

		shortcut = null;
		return false;
	}

	private void SelectDesktopShortcut( string shortcutId )
	{
		var clickedAt = Time.Now;
		if ( selectedDesktopShortcutIds.Contains( shortcutId ) &&
			string.Equals( lastDesktopShortcutClickedId, shortcutId, StringComparison.OrdinalIgnoreCase ) &&
			clickedAt - lastDesktopShortcutClickedAt <= DesktopDoubleClickSeconds )
		{
			lastDesktopShortcutClickedId = null;
			lastDesktopShortcutClickedAt = 0f;
			ActivateDesktopShortcut( shortcutId );
			return;
		}

		selectedDesktopShortcutIds.Clear();
		selectedDesktopShortcutIds.Add( shortcutId );
		lastDesktopShortcutClickedId = shortcutId;
		lastDesktopShortcutClickedAt = clickedAt;
		HideDesktopContextMenu();
		StateHasChanged();
	}

	private void ActivateDesktopShortcut( string shortcutId )
	{
		var shortcut = GetDesktopShortcutById( shortcutId );
		if ( shortcut is null || Runtime is null )
			return;

		if ( !string.IsNullOrWhiteSpace( shortcut.AppId ) )
		{
			Runtime.OpenApp( shortcut.AppId );
		}
		else if ( shortcut.IsDirectory )
		{
			Runtime.OpenApp( "system.paneexplorer", new Dictionary<string, string>
			{
				["path"] = shortcut.VirtualPath ?? Runtime.GetDefaultDocumentsPath()
			} );
		}
		else if ( !string.IsNullOrWhiteSpace( shortcut.VirtualPath ) )
		{
			Runtime.OpenVirtualPath( shortcut.VirtualPath );
		}

		StateHasChanged();
	}

	private void OpenDesktopContextMenu( string shortcutId )
	{
		selectedDesktopShortcutIds.Clear();
		selectedDesktopShortcutIds.Add( shortcutId );
		desktopContextMenuShortcutId = shortcutId;
		desktopContextMenuPosition = GetLocalMousePosition();
		StateHasChanged();
	}

	private string GetDesktopContextMenuStyle()
	{
		return $"left: {desktopContextMenuPosition.x}px; top: {desktopContextMenuPosition.y}px;";
	}

	private void HideDesktopContextMenu()
	{
		desktopContextMenuShortcutId = null;
	}

	private void PromptRenameSelectedDesktopShortcut()
	{
		if ( Runtime is null || selectedDesktopShortcutIds.Count != 1 )
			return;

		var shortcut = GetDesktopShortcutById( selectedDesktopShortcutIds.First() );
		if ( shortcut is null || !shortcut.CanRename || string.IsNullOrWhiteSpace( shortcut.VirtualPath ) )
			return;

		HideDesktopContextMenu();
		Runtime.ShowMessageBox(
			new ComputerMessageBoxOptions
			{
				Title = "Rename Shortcut",
				Message = "Enter a new file or folder name.",
				Icon = "R",
				HasTextInput = true,
				TextInputValue = shortcut.Label,
				TextInputPlaceholder = shortcut.Label,
				Buttons = new[] { "Rename", "Cancel" }
			},
			result =>
			{
				if ( !result.ButtonPressed.Equals( "Rename", StringComparison.OrdinalIgnoreCase ) )
					return;

				var newName = result.TextValue.Trim();
				if ( string.IsNullOrWhiteSpace( newName ) )
					return;

				PaneArchiveFileSystem.Rename( Runtime.GetArchivePath(), ParseVirtualPath( shortcut.VirtualPath ), newName );
				selectedDesktopShortcutIds.Clear();
				Runtime.RefreshTransientUi();
				StateHasChanged();
			} );
	}

	private void DeleteSelectedDesktopShortcuts()
	{
		if ( Runtime is null )
			return;

		foreach ( var shortcutId in selectedDesktopShortcutIds.ToArray() )
		{
			var shortcut = GetDesktopShortcutById( shortcutId );
			if ( shortcut is null || !shortcut.CanDelete || string.IsNullOrWhiteSpace( shortcut.VirtualPath ) )
				continue;

			Runtime.DeleteVirtualPath( shortcut.VirtualPath );
		}

		selectedDesktopShortcutIds.Clear();
		HideDesktopContextMenu();
		StateHasChanged();
	}

	private void RestoreSelectedDesktopShortcut()
	{
		if ( Runtime is null )
			return;

		foreach ( var shortcutId in selectedDesktopShortcutIds.ToArray() )
		{
			var shortcut = GetDesktopShortcutById( shortcutId );
			if ( shortcut is null || !shortcut.CanRestore || string.IsNullOrWhiteSpace( shortcut.VirtualPath ) )
				continue;

			Runtime.RestoreVirtualPath( shortcut.VirtualPath );
		}

		selectedDesktopShortcutIds.Clear();
		HideDesktopContextMenu();
		StateHasChanged();
	}

	private static IReadOnlyList<string> ParseVirtualPath( string virtualPath )
	{
		return virtualPath
			.Trim()
			.TrimStart( '/' )
			.Split( '/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries );
	}

	private string ResolveAppTexturePath( ComputerAppDescriptor app )
	{
		if ( app.Id.Equals( "system.mediaplayer", StringComparison.OrdinalIgnoreCase ) )
			return ResolveThemeTexturePath( "Ext_mp4" );

		return ResolveThemeTexturePath( $"App_{ResolveAppTextureKey( app )}" );
	}

	private string ResolveArchiveItemTexturePath( PaneArchiveItem item )
	{
		if ( item.IsDirectory )
			return ResolveThemeTexturePath( "folder" );

		var extension = item.Extension.TrimStart( '.' ).ToLowerInvariant();
		if ( string.IsNullOrWhiteSpace( extension ) )
			return "";

		return ResolveThemeTexturePath( $"Ext_{extension}" );
	}

	private string ResolveThemeTexturePath( string textureName )
	{
		if ( Computer is null || string.IsNullOrWhiteSpace( textureName ) )
			return "";

		var themeName = string.IsNullOrWhiteSpace( Computer.ThemeName ) ? "default" : Computer.ThemeName.Trim();
		var path = $"textures/themes/{themeName}/{textureName}.png";
		try
		{
			return FileSystem.Mounted.FileExists( path ) ? path : "";
		}
		catch ( Exception )
		{
			return "";
		}
	}

	private static string ResolveAppTextureKey( ComputerAppDescriptor app )
	{
		return app.Id switch
		{
			"system.about" => "about",
			"system.calculator" => "calculator",
			"system.settings" => "controlPanel",
			"system.notepad" => "notepad",
			"system.paint" => "paint",
			"system.paneexplorer" => "paneExplorer",
			"system.ridge" => "ridge",
			"system.taskmanager" => "taskManager",
			_ => app.ResolvedExecutableName.EndsWith( ".exe", StringComparison.OrdinalIgnoreCase )
				? app.ResolvedExecutableName[..^4]
				: app.ResolvedExecutableName
		};
	}

	private static string GetScreenSaverLogoStyle( ComputerScreenSaverState saver )
	{
		return $"left: {saver.LogoX}px; top: {saver.LogoY}px; width: {saver.LogoWidth}px; height: {saver.LogoHeight}px;";
	}

	private void EnsureAttachedPanelMatchesResolution()
	{
		if ( Runtime is null )
			return;

		var worldPanel = GameObject.Components.Get<Sandbox.WorldPanel>( FindMode.InSelf );
		if ( worldPanel is null )
			return;

		var targetSize = new Vector2( Runtime.State.ResolutionX, Runtime.State.ResolutionY );
		if ( worldPanel.PanelSize == targetSize )
			return;

		worldPanel.PanelSize = targetSize;
	}

	private sealed class DesktopShortcutItem
	{
		public string Id { get; set; } = "";
		public int Index { get; set; }
		public string Label { get; set; } = "";
		public string Icon { get; set; } = "";
		public string IconTexture { get; set; } = "";
		public string? AppId { get; set; }
		public string? VirtualPath { get; set; }
		public bool IsDirectory { get; set; }
		public bool CanRename { get; set; }
		public bool CanDelete { get; set; }
		public bool CanRestore { get; set; }
	}
}