using SVSim.Database.Models; using SVSim.Database.Services.Inventory; using SVSim.EmulatedEntrypoint.Models.Dtos; namespace SVSim.EmulatedEntrypoint.Services; public interface IGachaPointService { /// /// Build the gacha-point exchange catalog for one pack, with per-viewer is_received /// resolved. Returns an empty list if the pack has no gacha-point config or no eligible /// cards in its pool — callers should treat the empty result as a valid response, not /// an error. Order: standard legendaries first (class_id ASC, card_id ASC), then leader /// cards (class_id ASC, card_id ASC). /// Task> GetRewardsAsync(int packId, long viewerId); /// /// Increment the viewer's balance for by /// child.OverrideIncreaseGachaPoint > 0 ? child.OverrideIncreaseGachaPoint : pack.GachaPointConfig.IncreaseGachaPoint /// times . No-op when the pack lacks a GachaPointConfig. /// Caller is responsible for SaveChangesAsync. /// void Accrue(Viewer viewer, PackConfigEntry pack, PackChildGachaEntry child, int packNumber); /// /// Validate + execute an exchange using the provided inventory transaction (which must /// have GachaPointBalances and GachaPointReceived loaded on tx.Viewer /// via extra includes). Grants the card via /// the tx. Returns the grant outcome on success (reward_list entries already converted to /// ), or a failure result describing why. Caller commits /// the tx on success. /// Task TryExchangeAsync(IInventoryTransaction tx, int packId, long cardId); } public sealed record ExchangeOutcome(bool Success, string? Error, IReadOnlyList RewardList) { public static ExchangeOutcome Fail(string error) => new(false, error, Array.Empty()); public static ExchangeOutcome Ok(IReadOnlyList rewards) => new(true, null, rewards); }