namespace ChessCubing.Server.Social; public sealed class CollaborativeMatchCoordinator { private readonly object _sync = new(); private readonly Dictionary _sessions = new(StringComparer.Ordinal); public void RegisterSession(PlaySessionResponse session) { lock (_sync) { _sessions[session.SessionId] = new CollaborativeSessionState( session.SessionId, new HashSet(StringComparer.Ordinal) { session.WhiteSubject, session.BlackSubject, session.InitiatorSubject, session.RecipientSubject, }, null, 0); } } public bool CanAccessSession(string sessionId, string subject) { lock (_sync) { return _sessions.TryGetValue(sessionId, out var session) && session.ParticipantSubjects.Contains(subject); } } public CollaborativeMatchStateMessage? GetLatestState(string sessionId, string subject) { lock (_sync) { if (!_sessions.TryGetValue(sessionId, out var session) || !session.ParticipantSubjects.Contains(subject)) { throw new SocialValidationException("Cette session de partie est introuvable ou n'est pas accessible."); } return session.LatestState; } } public CollaborativeMatchStateMessage PublishState( string sessionId, string subject, string? matchJson, string route) { lock (_sync) { if (!_sessions.TryGetValue(sessionId, out var session) || !session.ParticipantSubjects.Contains(subject)) { throw new SocialValidationException("Cette session de partie est introuvable ou n'est pas accessible."); } var revision = session.Revision + 1; var message = new CollaborativeMatchStateMessage { SessionId = sessionId, MatchJson = matchJson, Route = string.IsNullOrWhiteSpace(route) ? "/application.html" : route.Trim(), SenderSubject = subject, Revision = revision, UpdatedUtc = DateTime.UtcNow, }; _sessions[sessionId] = session with { Revision = revision, LatestState = message, }; return message; } } private sealed record CollaborativeSessionState( string SessionId, HashSet ParticipantSubjects, CollaborativeMatchStateMessage? LatestState, long Revision); }