using Microsoft.EntityFrameworkCore; using SVSim.Database.Models; namespace SVSim.Database.Repositories.Mission; public sealed class MissionCatalogRepository : IMissionCatalogRepository { private readonly SVSimDbContext _db; // Process-level cache for the derived MAX(Level) lookup. Cleared on host restart // (re-bootstrap is the only legitimate way to mutate the catalog at runtime). private static IReadOnlyDictionary? _maxLevelCache; private static readonly SemaphoreSlim _maxLevelLock = new(1, 1); public MissionCatalogRepository(SVSimDbContext db) { _db = db; } public Task> GetByLotTypeAsync(int lotType, CancellationToken ct) => _db.MissionCatalog.AsNoTracking().Where(e => e.LotType == lotType).ToListAsync(ct); public Task> GetByIdsAsync(IReadOnlyCollection ids, CancellationToken ct) => _db.MissionCatalog.AsNoTracking().Where(e => ids.Contains(e.Id)).ToListAsync(ct); public Task GetByIdAsync(int id, CancellationToken ct) => _db.MissionCatalog.AsNoTracking().FirstOrDefaultAsync(e => e.Id == id, ct); public Task> GetByEventTypesAsync(IReadOnlyCollection eventTypes, CancellationToken ct) => _db.MissionCatalog.AsNoTracking() .Where(e => e.EventType != null && eventTypes.Contains(e.EventType)) .ToListAsync(ct); public Task> GetAchievementsByEventTypesAsync(IReadOnlyCollection eventTypes, CancellationToken ct) => _db.AchievementCatalog.AsNoTracking() .Where(e => e.EventType != null && eventTypes.Contains(e.EventType)) .ToListAsync(ct); public Task> GetAllAchievementTypesAsync(CancellationToken ct) => _db.AchievementCatalog.AsNoTracking() .Select(e => e.AchievementType).Distinct() .ToListAsync(ct); public async Task> GetMaxLevelByAchievementTypeAsync(CancellationToken ct) { if (_maxLevelCache is not null) return _maxLevelCache; await _maxLevelLock.WaitAsync(ct); try { if (_maxLevelCache is null) { var pairs = await _db.AchievementCatalog.AsNoTracking() .GroupBy(e => e.AchievementType) .Select(g => new { Type = g.Key, Max = g.Max(e => e.Level) }) .ToListAsync(ct); _maxLevelCache = pairs.ToDictionary(p => p.Type, p => p.Max); } return _maxLevelCache; } finally { _maxLevelLock.Release(); } } public async Task> GetMinLevelByAchievementTypeAsync(CancellationToken ct) { var pairs = await _db.AchievementCatalog.AsNoTracking() .GroupBy(e => e.AchievementType) .Select(g => new { Type = g.Key, Min = g.Min(e => e.Level) }) .ToListAsync(ct); return pairs.ToDictionary(p => p.Type, p => p.Min); } public Task GetAchievementAsync(int achievementType, int level, CancellationToken ct) => _db.AchievementCatalog.AsNoTracking() .FirstOrDefaultAsync(e => e.AchievementType == achievementType && e.Level == level, ct); public Task> GetMonthlyMissionsAsync(int year, int month, CancellationToken ct) => _db.BattlePassMonthlyMissions.AsNoTracking() .Where(e => e.Year == year && e.Month == month) .OrderBy(e => e.OrderNum).ToListAsync(ct); }