using SVSim.BattleNode.Bridge;
using SVSim.Database.Models;
using SVSim.Database.Repositories.Globals;
namespace SVSim.EmulatedEntrypoint.Matching;
///
/// DB-backed bot roster. Reads BotRoster rows (seeded from
/// seeds/bot-roster.json) and picks one deterministically per
/// . See for the contract.
///
public sealed class BotRoster : IBotRoster
{
private readonly IGlobalsRepository _globals;
public BotRoster(IGlobalsRepository globals)
{
_globals = globals;
}
public async Task PickAsync(MatchContext selfCtx, string battleId, CancellationToken ct = default)
{
var roster = await _globals.GetBotRoster();
if (roster.Count == 0)
{
throw new InvalidOperationException(
"BotRoster is empty. Run SVSim.Bootstrap to import seeds/bot-roster.json.");
}
// Deterministic per battle ID: same pending battle → same bot on retry,
// but different battles get different opponents.
var hash = StringComparer.Ordinal.GetHashCode(battleId);
var index = (int)((uint)hash % roster.Count);
var row = roster[index];
return ToProfile(row);
}
private static AIBotProfile ToProfile(BotRosterEntry row) => new(
AiId: row.AiId,
CountryCode: row.CountryCode,
UserName: row.UserName,
SleeveId: row.SleeveId,
EmblemId: row.EmblemId,
DegreeId: row.DegreeId,
FieldId: row.FieldId,
IsOfficial: row.IsOfficial,
ClassId: row.ClassId,
CharaId: row.CharaId,
Rank: row.Rank,
BattlePoint: row.BattlePoint,
IsMasterRank: row.IsMasterRank,
MasterPoint: row.MasterPoint);
}