Tricks/TrickScoreHolder.cs
namespace Skateboard.Tricks;

public sealed class TrickScoreHolder : Component
{
	public delegate void TrickScoreDelegate();

	/// <summary>How many tricks to show in the UI string.</summary>
	[Property] public int MaxTricks { get; set; } = 5;

	public TrickScoreDelegate OnComboFinish;
	public TrickScoreDelegate OnComboFailed;

	/// <summary>Client UI hook (local pawn only).</summary>
	public static TrickScoreDelegate OnLocalTrickScoreUpdate;

	// Replicated state
	[Sync( SyncFlags.FromHost )] private bool FailedInternal { get; set; }
	[Sync( SyncFlags.FromHost )] private bool FinishedInternal { get; set; }

	[Sync( SyncFlags.FromHost )] public int TotalScore { get; set; }

	/// <summary>All trick entries in the current combo.</summary>
	[Sync( SyncFlags.FromHost )] public NetList<TrickScoreEntry> Entries { get; set; } = new();

	[Sync( SyncFlags.FromHost )] public int Multiplier { get; private set; }
	[Sync( SyncFlags.FromHost )] public int Score { get; private set; }

	/// <summary>Concise display string of the last few trick names.</summary>
	[Sync( SyncFlags.FromHost )] public string String { get; private set; } = "";

	public bool Failed
	{
		get => FailedInternal;
		set
		{
			if ( FinishedInternal )
				return;

			if ( value )
				OnComboFailed?.Invoke();

			FinishedInternal = value;
			FailedInternal = value;
		}
	}

	public bool Finished
	{
		get => FinishedInternal;
		set
		{
			if ( value && !FinishedInternal )
			{
				TotalScore += Score * Multiplier;

				if ( !VisuallyEmpty )
					OnComboFinish?.Invoke();
			}

			FinishedInternal = value;

			if ( !FinishedInternal )
				FailedInternal = false;
		}
	}

	public bool Empty => Entries.Count == 0;
	public bool VisuallyEmpty => Empty || string.IsNullOrEmpty( String );

	public void Reset()
	{
		Finished = false;
		Entries.Clear();
		Refresh();
	}

	public void Refresh()
	{
		var display = new List<TrickScoreEntry>( MaxTricks );

		for ( int i = Entries.Count - 1; i >= 0 && display.Count < MaxTricks; i-- )
		{
			var e = Entries[i];
			if ( !string.IsNullOrEmpty( e?.Name ) )
				display.Insert( 0, e );
		}

		String = string.Join( " + ", display.ConvertAll( x => x.Name ) );

		var score = 0;
		var mult = 0;
		var entryCount = 0;

		foreach ( var e in Entries )
		{
			if ( e == null ) continue;
			score += e.Score;
			mult += e.Multiplier;
			entryCount++;
		}

		mult += entryCount;
		Score = score;
		Multiplier = mult;
	}

	public void Add( TrickScoreEntry entry )
	{
		if ( !Networking.IsHost )
			return;

		if ( FinishedInternal )
			Reset();

		FinishedInternal = false;

		Entries.Add( entry );
		Refresh();
	}
}