using Microsoft.AspNetCore.Mvc; using SVSim.Database.Enums; using SVSim.Database.Models; using SVSim.Database.Repositories.Viewer; using SVSim.EmulatedEntrypoint.Constants; using SVSim.EmulatedEntrypoint.Models.Dtos; using SVSim.EmulatedEntrypoint.Models.Dtos.Requests; using SVSim.EmulatedEntrypoint.Models.Dtos.Requests.Common; using SVSim.EmulatedEntrypoint.Models.Dtos.Requests.Practice; using SVSim.EmulatedEntrypoint.Models.Dtos.Responses.Practice; namespace SVSim.EmulatedEntrypoint.Controllers; public class PracticeController : SVSimController { // Hand-curated AI opponents (audit B14 pattern). Replace with master data when the // practice subsystem is built out. private static readonly List StubOpponents = new() { new PracticeOpponent { PracticeId = 1, TextId = "Practice_001", ClassId = 1, CharaId = 1, DegreeId = 0, AiDeckLevel = 1, AiLogicLevel = 1, AiMaxLife = 20, Battle3dFieldId = "1", IsCampaignPractice = false }, new PracticeOpponent { PracticeId = 2, TextId = "Practice_002", ClassId = 2, CharaId = 2, DegreeId = 0, AiDeckLevel = 2, AiLogicLevel = 2, AiMaxLife = 20, Battle3dFieldId = "1", IsCampaignPractice = false }, new PracticeOpponent { PracticeId = 3, TextId = "Practice_003", ClassId = 3, CharaId = 3, DegreeId = 0, AiDeckLevel = 3, AiLogicLevel = 3, AiMaxLife = 25, Battle3dFieldId = "1", IsCampaignPractice = false } }; private readonly IViewerRepository _viewerRepository; public PracticeController(IViewerRepository viewerRepository) { _viewerRepository = viewerRepository; } /// /// /practice/info — returns the AI opponent catalog. Response data is a JSON array /// directly (not wrapped in an object), per spec. /// [HttpPost("info")] public Task> Info(BaseRequest request) { return Task.FromResult(StubOpponents); } /// /// /practice/deck_list — returns viewer's decks scoped by format (always Format.All /// per spec, server can ignore the request field). /// [HttpPost("deck_list")] public async Task> DeckList(DeckFormatRequest request) { var shortUdidClaim = User.Claims.FirstOrDefault(c => c.Type == ShadowverseClaimTypes.ShortUdidClaim)?.Value; if (shortUdidClaim is null || !long.TryParse(shortUdidClaim, out long shortUdid)) { return Unauthorized(); } Viewer? viewer = await _viewerRepository.GetViewerByShortUdid(shortUdid); if (viewer is null) { return NotFound(); } return new PracticeDeckListResponse { MaintenanceCardList = new List(), UserDeckRotation = viewer.Decks.Where(d => d.Format == Format.Rotation) .Select(d => new UserDeck(d)).ToList(), UserDeckUnlimited = viewer.Decks.Where(d => d.Format == Format.Unlimited) .Select(d => new UserDeck(d)).ToList() }; } /// /// /practice/start — server is essentially a no-op for practice. Spec: empty body /// response is fine; client tolerates missing mission_parameter. /// [HttpPost("start")] public Task Start(BaseRequest request) { return Task.FromResult(new PracticeStartResponse()); } /// /// /practice/finish — accept the recovery_data blob without validation; return zero /// XP / no rewards. Class XP bookkeeping is deferred until a per-class XP store exists. /// [HttpPost("finish")] public Task Finish(PracticeFinishRequest request) { return Task.FromResult(new PracticeFinishResponse { GetClassExperience = 0, ClassExperience = 0, ClassLevel = 1, AchievedInfo = new Dictionary(), RewardList = new List() }); } }