using System.Text.Json; using SVSim.BattleNode.Bridge; using SVSim.Database.Enums; using SVSim.Database.Models.Config; using SVSim.Database.Repositories.Deck; using SVSim.Database.Repositories.Viewer; using SVSim.Database.Services; namespace SVSim.EmulatedEntrypoint.Services; public class MatchContextBuilder : IMatchContextBuilder { private readonly IArenaTwoPickRunRepository _runs; private readonly IViewerRepository _viewers; private readonly IDeckRepository _decks; private readonly IGameConfigService _config; public MatchContextBuilder( IArenaTwoPickRunRepository runs, IViewerRepository viewers, IDeckRepository decks, IGameConfigService config) { _runs = runs; _viewers = viewers; _decks = decks; _config = config; } public async Task BuildForTwoPickAsync(long viewerId) { var run = await _runs.GetByViewerIdAsync(viewerId) ?? throw new ArenaTwoPickException("arena_two_pick_no_active_run"); var deck = JsonSerializer.Deserialize>(run.SelectedCardIdsJson) ?? new(); if (deck.Count < 30) throw new ArenaTwoPickException("arena_two_pick_draft_incomplete"); var viewer = await _viewers.LoadForMatchContextAsync(viewerId) ?? throw new ArenaTwoPickException("arena_two_pick_no_active_run"); var challenge = _config.Get(); var defaults = _config.Get(); var emblemId = viewer.Info.SelectedEmblem.Id != 0 ? viewer.Info.SelectedEmblem.Id.ToString() : defaults.EmblemId.ToString(); var degreeId = viewer.Info.SelectedDegree.Id != 0 ? viewer.Info.SelectedDegree.Id.ToString() : defaults.DegreeId.ToString(); var charaId = run.LeaderSkinId != 0 ? run.LeaderSkinId.ToString() : run.ClassId.ToString(); return new MatchContext( SelfDeckCardIds: deck, ClassId: (CardClass)run.ClassId, CharaId: charaId, // Hardcoded v1; see spec §Deferred plumbing. CardMasterName: "card_master_node_10015", CountryCode: viewer.Info.CountryCode ?? string.Empty, UserName: viewer.DisplayName, // TK2-specific cosmetic source; other modes will use the deck row's SleeveId. SleeveId: challenge.TwoPickSleeveId.ToString(), EmblemId: emblemId, DegreeId: degreeId, // Hardcoded v1; needs equipped-MyPageBackground lookup (see spec §Deferred). FieldId: 43, IsOfficial: viewer.Info.IsOfficial ? 1 : 0, BattleModeId: BattleModes.TakeTwo); } public async Task BuildForRankBattleAsync(long viewerId, Format format, int deckNo) { var viewer = await _viewers.LoadForMatchContextAsync(viewerId) ?? throw new InvalidOperationException($"viewer {viewerId} not found"); // IDeckRepository is the right path here — viewer-graph nav refs (DeckCard.Card) // don't auto-load (see project_ef_nav_include_pitfall memory), which would // silently ship card_id=0. var deck = await _decks.GetDeck(viewerId, format, deckNo) ?? throw new InvalidOperationException( $"viewer {viewerId} has no deck #{deckNo} for format {format}"); var defaults = _config.Get(); var emblemId = viewer.Info.SelectedEmblem.Id != 0 ? viewer.Info.SelectedEmblem.Id.ToString() : defaults.EmblemId.ToString(); var degreeId = viewer.Info.SelectedDegree.Id != 0 ? viewer.Info.SelectedDegree.Id.ToString() : defaults.DegreeId.ToString(); var charaId = deck.LeaderSkin.Id != 0 ? deck.LeaderSkin.Id.ToString() : deck.Class.Id.ToString(); var sleeveId = deck.Sleeve.Id != 0 ? deck.Sleeve.Id.ToString() : defaults.SleeveId.ToString(); // DeckCard is count-based (one row per unique card + a Count). The node's deck // is one entry PER PHYSICAL CARD (idx 1..N), so expand each row by its Count — // otherwise a 3-copy card ships as a single in-battle card. var deckCardIds = deck.Cards .SelectMany(c => Enumerable.Repeat(c.Card.Id, c.Count)) .ToList(); return new MatchContext( SelfDeckCardIds: deckCardIds, ClassId: (CardClass)deck.Class.Id, CharaId: charaId, CardMasterName: "card_master_node_10015", CountryCode: viewer.Info.CountryCode ?? string.Empty, UserName: viewer.DisplayName, SleeveId: sleeveId, EmblemId: emblemId, DegreeId: degreeId, FieldId: 43, IsOfficial: viewer.Info.IsOfficial ? 1 : 0, BattleModeId: BattleModes.TakeTwo); } }