using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using SVSim.Database;
using SVSim.Database.Repositories.Globals;
using SVSim.Database.Repositories.Viewer;
using SVSim.Database.Services;
using SVSim.EmulatedEntrypoint.Models.Dtos.Common.BasicPuzzle;
using SVSim.EmulatedEntrypoint.Models.Dtos.Requests;
using SVSim.EmulatedEntrypoint.Models.Dtos.Requests.BasicPuzzle;
using SVSim.EmulatedEntrypoint.Models.Dtos.Responses.BasicPuzzle;
using SVSim.EmulatedEntrypoint.Services;
namespace SVSim.EmulatedEntrypoint.Controllers;
///
/// /basic_puzzle/* — solo puzzle subsystem (the "Practice Match" puzzle catalog visible from
/// the home screen). Explicit [Route] override because the base SVSimController's [controller]
/// token would resolve to /puzzle.
///
[Route("basic_puzzle")]
public class PuzzleController : SVSimController
{
private readonly IPuzzleCatalogRepository _catalog;
private readonly IPuzzleClearRepository _clears;
private readonly PuzzleMissionEvaluator _evaluator;
private readonly RewardGrantService _rewards;
private readonly ILogger _logger;
public PuzzleController(
IPuzzleCatalogRepository catalog,
IPuzzleClearRepository clears,
PuzzleMissionEvaluator evaluator,
RewardGrantService rewards,
ILogger logger)
{
_catalog = catalog;
_clears = clears;
_evaluator = evaluator;
_rewards = rewards;
_logger = logger;
}
/// /basic_puzzle/info — full catalog of groups + per-viewer clear flags.
[HttpPost("info")]
public async Task> Info(BaseRequest _)
{
if (!TryGetViewerId(out long viewerId)) viewerId = 0;
var groups = await _catalog.GetAllGroupsWithPuzzles();
var missions = await _catalog.GetAllMissionsOrdered();
var clearedByGroup = await _clears.GetClearedPuzzleIdsByGroup(viewerId);
return ProjectGroups(groups, missions, clearedByGroup);
}
/// /basic_puzzle/open_puzzle_dialog — per-group detail. Unknown puzzle_master_id
/// returns 200 with an empty puzzle_quest array (matches client PuzzleQuestInfo fallback).
[HttpPost("open_puzzle_dialog")]
public async Task OpenPuzzleDialog(OpenPuzzleDialogRequest req)
{
if (!TryGetViewerId(out long viewerId)) viewerId = 0;
var group = await _catalog.GetGroupWithPuzzles(req.PuzzleMasterId);
if (group is null) return new OpenPuzzleDialogResponse();
var cleared = await _clears.GetClearedPuzzleIds(viewerId);
return new OpenPuzzleDialogResponse
{
PuzzleQuest = group.Puzzles
.OrderBy(p => p.Id)
.Select(p => new PuzzleEntryResponse
{
PuzzleId = p.Id,
PuzzleDifficulty = p.PuzzleDifficulty,
IsCleared = cleared.Contains(p.Id),
IsAdditional = p.IsAdditional,
IsPlayable = p.IsPlayable,
ReleaseConditionTextId = p.ReleaseConditionTextId,
})
.ToList(),
PuzzleQuestCharaId = group.PuzzleCharaId,
PuzzleDifficultyNameList = JsonSerializer.Deserialize>(group.DifficultyNameListJson) ?? new(),
IsDisplayBadge = false,
IsDisplayPuzzleNew = false,
};
}
/// /basic_puzzle/start — server is essentially a no-op. Wire data is the literal empty array `[]`.
[HttpPost("start")]
public Task