using Ludarium.Domain; namespace Ludarium.Application; /// /// Everything the library browser can ask of the catalog. /// /// /// Filtering and ordering belong to the query, not to the page the browser happens to hold. The /// library previously sorted the forty-eight games already on screen, which reordered a page rather /// than the collection and quietly disagreed with what the operator asked for. /// public sealed record GameQuery { public string? Search { get; init; } public string? Platform { get; init; } /// Ordering key. Unknown values fall back to title. public string? Sort { get; init; } public bool? Favorite { get; init; } public GamePlayStatus? Status { get; init; } public Guid? CollectionId { get; init; } /// Normalized tag name, as stored by . public string? Tag { get; init; } /// Keep only games rated at least this highly. public int? MinimumRating { get; init; } /// /// Keep only games with a linked, present copy in a format some player accepts. /// /// /// This is a property of the collection, not of the runtime: a sidecar that is switched off does /// not make a game disappear from the library. Whether a specific game can start right now stays /// the capability's answer, shown on the game itself. /// public bool PlayableOnly { get; init; } public int Page { get; init; } = 1; public int PageSize { get; init; } = 50; public static readonly IReadOnlyList SortKeys = ["title", "recent", "updated", "played", "rating"]; public bool HasFilters => !string.IsNullOrWhiteSpace(Search) || !string.IsNullOrWhiteSpace(Platform) || Favorite is not null || Status is not null || CollectionId is not null || !string.IsNullOrWhiteSpace(Tag) || MinimumRating is not null || PlayableOnly; } /// /// The exact platform-and-container pairs some Ludarium player accepts. /// /// /// Derived from the browser matrix and the native registry so the library's "playable copy" filter /// can never claim a format no player takes, and can never drift from the policies that decide it. /// public static class PlayableFormats { /// Each entry is platform|.extension, lower-cased. public static IReadOnlyList Pairs { get; } = BuildPairs(); private static string[] BuildPairs() { var pairs = new SortedSet(StringComparer.Ordinal); foreach (var platform in BrowserPlayPolicy.DescribePlatforms()) foreach (var extension in platform.Extensions.Concat(platform.CandidateExtensions ?? new HashSet())) pairs.Add($"{platform.Platform.ToLowerInvariant()}|{extension.ToLowerInvariant()}"); foreach (var player in NativeRemotePlayerRegistry.All) foreach (var platform in player.Platforms) foreach (var extension in player.Extensions) pairs.Add($"{platform.ToLowerInvariant()}|{extension.ToLowerInvariant()}"); return [.. pairs]; } }