UI/Modals/DetailsModal.razor
@using Sandbox.UI
@using System.Threading.Tasks

@namespace sGBA

@inherits PanelComponent

<root class="@(IsVisible ? "visible" : "")">
	<div class="modal-backdrop" onclick=@CloseWithMouse />

	<div class="details-card">
		@if (_game is not null)
		{
			<div class="details-sidebar">
				@if (_titleArt != null)
				{
					<TextureImage Texture=@_titleArt class="details-preview" />
				}
				else
				{
					<div class="details-preview missing">
						<div class="details-preview-icon-disc">
							<div class="details-preview-icon">question_mark</div>
						</div>
					</div>
				}

				<div class="details-tabs">
					<div class="details-tab selected">
						<TabRing class="details-tab-ring" />
						<div class="details-tab-fill" />
						<div class="details-tab-line" />
						<div class="details-tab-label">#details.information</div>
					</div>
				</div>
			</div>

			<div class="details-main">
				<div class="details-title">@Title</div>
				<div class="info-list">
					@foreach (var row in InfoRows)
					{
						<div class="info-row">
							<div class="info-label">@row.Label</div>
							<div class="info-value">@row.Value</div>
						</div>
					}
				</div>
			</div>
		}
	</div>
</root>

@code
{
	public static DetailsModal Current { get; private set; }

	private GameEntry _game;
	private Texture _titleArt;
	private GameInfo _metadata;
	private GameHashes _hashes;
	private readonly FocusInput _input = new();
	private readonly record struct InfoRow(string Label, string Value);

	public bool IsVisible => _game is not null;

	private string Title => Display( _metadata?.Name, _game?.DisplayTitle );

	private IEnumerable<InfoRow> InfoRows
	{
		get
		{
			if (_game is null) yield break;

			var developer = FirstValue(_metadata?.Developer);
			if (developer is not null) yield return new InfoRow("#details.info.developer", developer);

			var publisher = FirstValue(_metadata?.Publisher);
			if (publisher is not null) yield return new InfoRow("#details.info.publisher", publisher);

			var releaseDate = FirstValue(FormatReleaseDate(_metadata));
			if (releaseDate is not null) yield return new InfoRow("#details.info.release_date", releaseDate);

			var genre = FirstValue(_metadata?.Genre);
			if (genre is not null) yield return new InfoRow("#details.info.genre", genre);

			var franchise = FirstValue(_metadata?.Franchise);
			if (franchise is not null) yield return new InfoRow("#details.info.franchise", franchise);

			var esrb = FirstValue(_metadata?.EsrbRating);
			if (esrb is not null) yield return new InfoRow("#details.info.esrb", esrb);

			var players = FirstValue(FormatPlayers(_metadata?.MaxUsers));
			if (players is not null) yield return new InfoRow("#details.info.players", players);

			var region = FirstValue(_metadata?.Region, _game.Region);
			if (region is not null) yield return new InfoRow("#details.info.region", region);

			var serial = FirstValue(_metadata?.Serial, _game.GameCode);
			if (serial is not null) yield return new InfoRow("#details.info.serial", serial);

			if (!string.IsNullOrWhiteSpace(_hashes?.Crc)) yield return new InfoRow("#details.info.crc32", FormatHash(_hashes.Crc));
			if (!string.IsNullOrWhiteSpace(_hashes?.Sha1)) yield return new InfoRow("#details.info.sha1", FormatHash(_hashes.Sha1));
			if (!string.IsNullOrWhiteSpace(_hashes?.Md5)) yield return new InfoRow("#details.info.md5", FormatHash(_hashes.Md5));
		}
	}

	protected override void OnTreeFirstBuilt() => Current = this;

	protected override void OnDestroy()
	{
		if (Current == this) Current = null;
	}

	public void Open(GameEntry game, bool useGamepad = false)
	{
		_game = game;
		_titleArt = null;
		_metadata = null;
		_hashes = null;
		_input.Begin(useGamepad);
		Sound.Play("ui.popup.message.open");
		StateHasChanged();
		HomeScreen.Current?.RefreshControllerHints();
		_ = LoadAsync(game);
	}

	public void Close()
	{
		if (!IsVisible) return;
		var wasGamepad = _input.UseGamepad;
		_game = null;
		_titleArt = null;
		_metadata = null;
		_input.End();
		HomeScreen.Current?.RestoreInputMode(wasGamepad);
		Sound.Play("ui.popup.message.close");
		StateHasChanged();
		HomeScreen.Current?.RefreshControllerHints();
	}

	private void CloseWithMouse()
	{
		_input.ForceMouseMode();
		Close();
	}

	private async Task LoadAsync(GameEntry game)
	{
		var titleTask = LoadTitleArtAsync(game);
		var metadataTask = LoadMetadataAsync(game);
		var hashTask = LoadHashesAsync(game);

		await titleTask;
		await metadataTask;
		await hashTask;
	}

	private async Task LoadHashesAsync(GameEntry game)
	{
		await Task.Yield();

		var hashes = GameHasher.Compute(game);
		if (_game != game) return;

		_hashes = hashes;
		StateHasChanged();
	}

	private async Task LoadTitleArtAsync(GameEntry game)
	{
		var titleArt = await Thumbnails.LoadAsync(game, ThumbType.Title);
		if (_game != game) return;

		_titleArt = titleArt;
		StateHasChanged();
	}

	private async Task LoadMetadataAsync(GameEntry game)
	{
		try
		{
			var metadata = await LibretroDb.FetchAsync(game);
			if (_game == game)
			{
				_metadata = metadata;
				StateHasChanged();
			}
		}
		catch (Exception ex)
		{
			Log.Warning($"[sGBA] Failed to load Libretro metadata for {game.DisplayTitle}: {ex.Message}");
		}
	}

	protected override void OnUpdate()
	{
		if (!IsVisible) return;
		if (new Game.Overlay().IsOpen) return;

		var wasGamepad = _input.UseGamepad;
		_input.Tick();
		if (wasGamepad != _input.UseGamepad)
		{
			HomeScreen.Current?.RestoreInputMode(_input.UseGamepad);
			StateHasChanged();
			HomeScreen.Current?.RefreshControllerHints();
		}

		if (Input.Pressed("GBA_B"))
		{
			_input.ForceGamepadMode();
			Close();
		}
	}

	private static string Display(params string[] values)
	{
		return FirstValue(values) ?? "-";
	}

	private static string FirstValue(params string[] values)
	{
		foreach (var value in values)
		{
			if (!string.IsNullOrWhiteSpace(value)) return value;
		}

		return null;
	}

	private static string FormatReleaseDate(GameInfo metadata)
	{
		if (metadata is null || string.IsNullOrWhiteSpace(metadata.ReleaseYear)) return string.Empty;

		var value = metadata.ReleaseYear;
		if (!string.IsNullOrWhiteSpace(metadata.ReleaseMonth))
			value += "-" + metadata.ReleaseMonth.PadLeft(2, '0');

		return value;
	}

	private static string FormatHash(string value)
	{
		if (string.IsNullOrWhiteSpace(value)) return string.Empty;
		return value.ToUpperInvariant();
	}

	private static string FormatPlayers(string users)
	{
		if (string.IsNullOrWhiteSpace(users)) return string.Empty;
		return users == "1" ? Game.Language.GetPhrase("details.players.one") : Game.Language.GetPhrase("details.players.many", new() { { "count", users } });
	}

	protected override int BuildHash()
	{
		var hash = new HashCode();
		hash.Add(IsVisible);
		hash.Add(_game?.Path);
		hash.Add(_titleArt);
		hash.Add(_metadata);
		hash.Add(_hashes);
		hash.Add(_input.UseGamepad);
		return hash.ToHashCode();
	}
}