InteractiveComputer/Core/ComputerFileAssociationPolicy.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace PaneOS.InteractiveComputer.Core;

public sealed class ComputerFileLaunchTarget
{
	public string AppId { get; init; } = "";
	public IReadOnlyDictionary<string, string> InitialData { get; init; } = new Dictionary<string, string>();
}

public sealed class ComputerFileOpenResult
{
	public ComputerFileLaunchTarget? LaunchTarget { get; init; }
	public string FailureTitle { get; init; } = "Cannot Open File";
	public string FailureMessage { get; init; } = "";
	public bool CanOpen => LaunchTarget is not null;
}

public static class ComputerFileAssociationPolicy
{
	public static ComputerFileLaunchTarget? ResolveLaunchTarget( string virtualPath, string fileName, string fileContent, IReadOnlyList<ComputerAppDescriptor> apps )
	{
		return ResolveOpenResult( virtualPath, fileName, fileContent, apps ).LaunchTarget;
	}

	public static ComputerFileOpenResult ResolveOpenResult( string virtualPath, string fileName, string fileContent, IReadOnlyList<ComputerAppDescriptor> apps )
	{
		var extension = Path.GetExtension( fileName ).ToLowerInvariant();
		if ( extension == ".url" )
			return ResolveUrlOpenResult( virtualPath, fileContent );

		if ( extension == ".exe" )
			return ResolveExecutableOpenResult( fileName, apps );

		if ( extension == ".txt" )
			return new ComputerFileOpenResult
			{
				LaunchTarget = new ComputerFileLaunchTarget
				{
					AppId = "system.notepad",
					InitialData = new Dictionary<string, string>
					{
						["file_path"] = virtualPath
					}
				}
			};

		var associatedApp = apps.FirstOrDefault( x => x.AssociatedFileExtensions.Any( y => NormalizeExtension( y ) == extension ) );
		if ( associatedApp is not null )
		{
			return new ComputerFileOpenResult
			{
				LaunchTarget = new ComputerFileLaunchTarget
				{
					AppId = associatedApp.Id,
					InitialData = new Dictionary<string, string>
					{
						["file_path"] = virtualPath
					}
				}
			};
		}

		return new ComputerFileOpenResult
		{
			FailureTitle = "Cannot Open File",
			FailureMessage = $"PaneOS does not know how to open {fileName}."
		};
	}

	public static string ResolveUrlFileTarget( string fileContent )
	{
		return TryResolveUrlFileTarget( fileContent, out var targetUrl ) ? targetUrl : "paneos://default";
	}

	public static bool TryResolveUrlFileTarget( string fileContent, out string targetUrl )
	{
		targetUrl = "";
		if ( string.IsNullOrWhiteSpace( fileContent ) )
			return false;

		foreach ( var line in fileContent.Split( new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries ) )
		{
			if ( line.StartsWith( "url=", StringComparison.OrdinalIgnoreCase ) )
			{
				var value = line["url=".Length..].Trim();
				if ( string.IsNullOrWhiteSpace( value ) )
					return false;

				targetUrl = value;
				return true;
			}
		}

		var fallback = fileContent.Trim();
		if ( string.IsNullOrWhiteSpace( fallback ) )
			return false;

		targetUrl = fallback;
		return true;
	}

	private static ComputerFileOpenResult ResolveUrlOpenResult( string virtualPath, string fileContent )
	{
		if ( !TryResolveUrlFileTarget( fileContent, out var urlTarget ) )
		{
			return new ComputerFileOpenResult
			{
				FailureTitle = "Corrupted Shortcut",
				FailureMessage = $"{Path.GetFileName( virtualPath )} appears to be empty or corrupted."
			};
		}

		return new ComputerFileOpenResult
		{
			LaunchTarget = new ComputerFileLaunchTarget
			{
				AppId = "system.ridge",
				InitialData = new Dictionary<string, string>
				{
					["url"] = urlTarget
				}
			}
		};
	}

	private static ComputerFileOpenResult ResolveExecutableOpenResult( string fileName, IReadOnlyList<ComputerAppDescriptor> apps )
	{
		var app = apps.FirstOrDefault( x => x.ResolvedExecutableName.Equals( fileName, StringComparison.OrdinalIgnoreCase ) );
		if ( app is not null )
		{
			return new ComputerFileOpenResult
			{
				LaunchTarget = new ComputerFileLaunchTarget
				{
					AppId = app.Id
				}
			};
		}

		return new ComputerFileOpenResult
		{
			FailureTitle = "Corrupted Application",
			FailureMessage = $"{fileName} points to an app that is missing or no longer installed."
		};
	}

	private static string NormalizeExtension( string extension )
	{
		if ( string.IsNullOrWhiteSpace( extension ) )
			return "";

		var value = extension.Trim().ToLowerInvariant();
		return value.StartsWith( ".", StringComparison.Ordinal ) ? value : $".{value}";
	}
}