namespace ytLive.Models;
/// Owns the session's and the one lifecycle rule that
/// matters: a new session starts from zero.
///
/// Kept separate from the view-model so the rule is testable without a window, and so the
/// scoreboard layer has exactly one place that decides when counting begins.
public sealed class ScoreboardRecorder
{
public ScoreboardState State { get; private set; } = new();
/// Called when a session starts (go-live, test, or a local recording). Every start
/// clears the tallies — "session" is the only scope the numbers promise.
public void StartSession()
{
State = new ScoreboardState();
IsRunning = true;
RaiseChanged();
}
/// Folds a chat event in. Outside a session nothing is counted: a stale tally left over
/// from the last stream would be the "today" lie this design exists to avoid.
public void Record(ChatMessage message)
{
if (!IsRunning) return;
var before = Fingerprint(State);
State.Apply(message);
if (Fingerprint(State) != before) RaiseChanged();
}
/// True while the tallies are live.
public bool IsRunning { get; private set; }
/// Marks the session finished. The tallies stay visible (the End screen may show
/// them) but stop moving.
public void EndSession()
{
IsRunning = false;
RaiseChanged();
}
public event System.Action? Changed;
private void RaiseChanged() => Changed?.Invoke();
/// Cheap change detector: only re-render the strip when a displayed value moved.
private static string Fingerprint(ScoreboardState state) =>
$"{state.NewMembers}|{state.MilestoneCount}|{state.LastMemberName}|{state.LastGifterName}|" +
$"{state.LastGiftCount}|{state.LastSuperChatAuthor}|{state.LastSuperChatDisplay}|" +
$"{state.SessionTotal.Currency}|{state.SessionTotal.Total:0.######}";
}