Game/LeaderboardOrder.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Board = Sandbox.Services.Leaderboards.Board2;

namespace BlockParty;

/// <summary>Shared ordering for leaderboard values, including exact finish-time ties.</summary>
public static class LeaderboardOrder
{
	public static int Compare( double valueA, DateTimeOffset timestampA, long steamIdA,
		double valueB, DateTimeOffset timestampB, long steamIdB )
	{
		int result = valueB.CompareTo( valueA );
		if ( result == 0 ) result = timestampA.CompareTo( timestampB );
		// If both the run time and submission instant match, keep every screen deterministic.
		if ( result == 0 ) result = steamIdA.CompareTo( steamIdB );
		return result;
	}

	/// <summary>
	/// Sort the fetched window locally. Only fetch beyond it when a tie involving the top three
	/// reaches the end of the window, so unseen entries could change a medal placement.
	/// Ties at ordinary page boundaries do not justify another backend request.
	/// The factory must create an uncentered board with the same stat, scope and date filters.
	/// </summary>
	public static async Task<List<Board.Entry>> CompletePodiumTiesAsync( List<Board.Entry> entries,
		long totalEntries, Func<Board> createPage, CancellationToken cancellation = default,
		Func<bool> isCurrent = null )
	{
		if ( entries.Count == 0 ) return entries;
		var byRank = entries.OrderBy( e => e.Rank ).ToList();
		var last = byRank[^1];
		// A centered window below rank 1 cannot establish the global podium. For a head window,
		// any value below bronze already proves its podium tie is complete, with no extra probe.
		bool needsMore = byRank[0].Rank == 1 && byRank.Count >= 3
			&& byRank[2].Rank == 3 && last.Value == byRank[2].Value;
		var result = new Dictionary<long, Board.Entry>();
		foreach ( var entry in entries ) result[entry.SteamId] = entry;

		long edge = last.Rank;
		double value = last.Value;
		while ( needsMore && edge < totalEntries )
		{
			cancellation.ThrowIfCancellationRequested();
			if ( isCurrent?.Invoke() == false ) throw new OperationCanceledException();
			var page = createPage();
			page.MaxEntries = (int)Math.Min( 100, totalEntries - edge );
			page.Offset = checked((int)edge);
			await page.Refresh( cancellation );
			cancellation.ThrowIfCancellationRequested();
			if ( isCurrent?.Invoke() == false ) throw new OperationCanceledException();
			var batch = page.Entries?.OrderBy( e => e.Rank ).ToList();
			// Refresh may swallow upstream errors. Never pretend an incomplete tie is resolved.
			if ( batch is null || batch.Count != page.MaxEntries
				|| batch[0].Rank != page.Offset + 1
				|| batch[^1].Rank != page.Offset + batch.Count )
				throw new InvalidOperationException( "Could not complete leaderboard tie." );
			if ( batch.Any( e => e.Value > value ) )
				throw new InvalidOperationException( "Leaderboard changed while completing a tie." );

			foreach ( var entry in batch.Where( e => e.Value == value ) )
			{
				if ( !result.TryAdd( entry.SteamId, entry ) )
					throw new InvalidOperationException( "Leaderboard changed while completing a tie." );
			}
			if ( batch.Any( e => e.Value != value ) ) break;
			edge = batch[^1].Rank;
		}

		return result.Values.OrderByDescending( e => e.Value ).ThenBy( e => e.Timestamp )
			.ThenBy( e => e.SteamId ).ToList();
	}
}