using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using CommunityToolkit.Mvvm.Input; using LANCommander.Launcher.ViewModels.Components; using LANCommander.Launcher.Models; using LANCommander.Launcher.Services; using LANCommander.SDK.Enums; using LANCommander.SDK.Models; using LANCommander.SDK.Services; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace LANCommander.Launcher.ViewModels; public partial class GamesListViewModel : GamesCollectionViewModel { private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; private IEnumerable? _depotItems; public override string ViewTitle => "Depot — All Games"; public override bool ShowInLibraryFilter => true; public override bool ShowInstalledFilter => false; public GamesListViewModel(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; _logger = serviceProvider.GetRequiredService>(); } public override Task LoadGamesAsync() => LoadGamesInternalAsync(); /// Returns a snapshot of all loaded games for use by depot browse views. public IEnumerable GetAllGames() => _allGames; [RelayCommand] private async Task LoadGamesInternalAsync() { IsLoading = true; HasError = false; StatusMessage = IsOfflineMode ? "Loading local library..." : "Loading games..."; Games.Clear(); _allGames.Clear(); AvailableGenres.Clear(); AvailableTags.Clear(); AvailableDevelopers.Clear(); AvailablePublishers.Clear(); _logger.LogInformation("Loading games from depot (offline: {IsOffline})...", IsOfflineMode); try { using var scope = _serviceProvider.CreateScope(); var depotService = scope.ServiceProvider.GetRequiredService(); var libraryService = scope.ServiceProvider.GetRequiredService(); var mediaClient = scope.ServiceProvider.GetRequiredService(); if (IsOfflineMode) await LoadFromLocalLibraryAsync(scope, libraryService); else { _depotItems = await depotService.GetItemsAsync(); // Collect games + covers on a thread-pool thread so the UI stays responsive var (collected, collectedGenres) = await Task.Run(async () => { var items = new List(); var genres = new HashSet(new GenreComparer()); foreach (var item in _depotItems ?? []) { if (item.DataItem is SDK.Models.DepotGame depotGame) { if (depotGame.Type == GameType.Mod || depotGame.Type == GameType.Expansion) continue; var inLibrary = await libraryService.IsInLibraryAsync(depotGame.Id); var coverPath = await GetOrDownloadCoverAsync(depotGame.Cover, mediaClient); items.Add(new GameItemViewModel(depotGame, coverPath, depotGame.Cover?.MimeType, inLibrary)); if (depotGame.Genres != null) foreach (var genre in depotGame.Genres) genres.Add(genre); } } return (items, genres); }); foreach (var vm in collected) _allGames.Add(vm); foreach (var genre in collectedGenres.OrderBy(g => g.Name)) AvailableGenres.Add(genre); } PopulateTags(); PopulateDevelopers(); PopulatePublishers(); ApplyFilters(); _logger.LogInformation("Loaded {Count} games from depot", _allGames.Count); } catch (Exception ex) { _logger.LogError(ex, "Failed to load games from depot"); StatusMessage = $"Failed to load games: {ex.Message}"; HasError = true; } finally { IsLoading = false; } } private async Task LoadFromLocalLibraryAsync(IServiceScope scope, LibraryService libraryService) { var gameService = scope.ServiceProvider.GetRequiredService(); var mediaService = scope.ServiceProvider.GetRequiredService(); var localGames = await gameService.GetAsync(); foreach (var game in localGames ?? []) { string? coverPath = null; var coverMedia = game.Media?.FirstOrDefault(m => m.Type == MediaType.Cover); if (coverMedia != null && mediaService.FileExists(coverMedia)) coverPath = mediaService.GetImagePath(coverMedia); _allGames.Add(new GameItemViewModel(game, coverPath, coverMedia?.MimeType, inLibrary: true)); } } protected override async Task ViewGameDetailsAsync(GameItemViewModel? gameItem) { if (gameItem == null) return; _logger.LogDebug("Viewing game details for {GameId}", gameItem.Id); try { using var scope = _serviceProvider.CreateScope(); if (IsOfflineMode) { var gameService = scope.ServiceProvider.GetRequiredService(); var localGame = await gameService.GetAsync(gameItem.Id); if (localGame != null) { var sdkGame = new SDK.Models.Game { Id = localGame.Id, Title = localGame.Title ?? "Unknown", SortTitle = localGame.SortTitle, Description = localGame.Description, ReleasedOn = localGame.ReleasedOn ?? DateTime.MinValue }; RaiseGameSelected(sdkGame); } } else { var gameClient = scope.ServiceProvider.GetRequiredService(); var game = await gameClient.GetAsync(gameItem.Id); if (game != null) RaiseGameSelected(game); else _logger.LogWarning("Game {GameId} not found on server", gameItem.Id); } } catch (Exception ex) { _logger.LogError(ex, "Failed to fetch game {GameId}", gameItem.Id); } } private async Task GetOrDownloadCoverAsync(Media? cover, MediaClient mediaClient) { if (cover == null) return null; try { var localPath = mediaClient.GetLocalPath(cover); if (File.Exists(localPath)) return localPath; if (!IsOfflineMode) { var fileInfo = await mediaClient.DownloadAsync(cover, localPath); if (fileInfo.Exists) return fileInfo.FullName; } } catch (Exception ex) { _logger.LogWarning(ex, "Failed to get or download cover {MediaId}", cover.Id); } return null; } private class GenreComparer : IEqualityComparer { public bool Equals(Genre? x, Genre? y) => x?.Id == y?.Id; public int GetHashCode(Genre obj) => obj.Id.GetHashCode(); } }