UI/Library/GameEntry.cs
namespace Emulotl;

public sealed class GameEntry( string path, string displayTitle, string region,
	string gameCode, string noIntroName, BaseFileSystem fileSystem, string systemId )
{
	public string Path { get; } = path;
	public string DisplayTitle { get; } = displayTitle;
	public string Region { get; } = region;
	public string GameCode { get; } = gameCode;
	public string NoIntroName { get; } = noIntroName;
	public BaseFileSystem FileSystem { get; } = fileSystem;
	public string SystemId { get; } = systemId;

	private const string RomFolder = "roms";

	public static List<GameEntry> Discover()
	{
		EmulatorSystems.EnsureRegistered();
		List<GameEntry> entries = [];
		CollectFrom( Sandbox.FileSystem.Mounted, entries );
		CollectFrom( Sandbox.FileSystem.Data, entries );
		entries.Sort( ( a, b ) => string.Compare( a.DisplayTitle, b.DisplayTitle, StringComparison.OrdinalIgnoreCase ) );
		return entries;
	}

	public static HashSet<string> GetInstalledPaths()
	{
		EmulatorSystems.EnsureRegistered();
		var paths = new HashSet<string>( StringComparer.OrdinalIgnoreCase );
		CollectPathsFrom( Sandbox.FileSystem.Mounted, paths );
		CollectPathsFrom( Sandbox.FileSystem.Data, paths );
		return paths;
	}

	private static void CollectFrom( BaseFileSystem fileSystem, List<GameEntry> entries )
	{
		foreach ( SystemProfile profile in SystemRegistry.All )
		{
			foreach ( string extension in profile.RomExtensions )
			{
				IEnumerable<string> found;
				try { found = fileSystem.FindFile( RomFolder, $"*.{extension}" ) ?? []; }
				catch { continue; }

				foreach ( string fileName in found )
				{
					GameEntry entry = BuildEntry( fileSystem, $"{RomFolder}/{fileName}", fileName, profile );
					if ( entry is not null )
						entries.Add( entry );
				}
			}
		}
	}

	private static void CollectPathsFrom( BaseFileSystem fileSystem, HashSet<string> paths )
	{
		foreach ( SystemProfile profile in SystemRegistry.All )
		{
			foreach ( string extension in profile.RomExtensions )
			{
				try
				{
					foreach ( var fileName in fileSystem.FindFile( RomFolder, $"*.{extension}" ) ?? [] )
						paths.Add( $"{RomFolder}/{fileName}" );
				}
				catch
				{
				}
			}
		}
	}

	private static GameEntry BuildEntry( BaseFileSystem fileSystem, string fullPath, string fileName, SystemProfile profile )
	{
		string baseName = System.IO.Path.GetFileNameWithoutExtension( fileName );
		(string displayTitle, string region) = ParseNoIntroName( baseName );
		string gameCode = ReadGameCode( fileSystem, fullPath, profile );

		return new GameEntry( fullPath, displayTitle, region, gameCode, baseName, fileSystem, profile.Id );
	}

	private static (string DisplayTitle, string Region) ParseNoIntroName( string baseName )
	{
		int parenOpen = baseName.IndexOf( '(' );
		if ( parenOpen <= 0 )
			return (baseName, string.Empty);

		string displayTitle = baseName[..parenOpen].TrimEnd();
		int parenClose = baseName.IndexOf( ')', parenOpen );
		string region = parenClose > parenOpen ? baseName[(parenOpen + 1)..parenClose] : string.Empty;
		return (displayTitle, region);
	}

	private static string ReadGameCode( BaseFileSystem fileSystem, string path, SystemProfile profile )
	{
		try
		{
			using System.IO.Stream stream = fileSystem.OpenRead( path );
			int headerLength = (int)Math.Min( stream.Length, 512 );
			if ( headerLength <= 0 )
				return string.Empty;

			byte[] header = new byte[headerLength];
			stream.ReadExactly( header, 0, headerLength );

			return profile.ReadGameCode( header ) ?? string.Empty;
		}
		catch
		{
			return string.Empty;
		}
	}
}