using System; using System.Collections.Generic; using System.Linq; namespace ytLive.Models; /// The session's scoreboard tallies — the facts a creator puts on screen to prove the /// channel is moving. /// /// Scope is the RUNNING SESSION (creator ruling 2026-10-30): the counts start at zero when a /// session starts and die with it. No persistence, nothing to reconcile, and every number is true /// for exactly what this session saw — a stored "today" that survives a Windows restart would be /// right only for the hours the app happened to be running, which is a lie the creator cannot /// check. /// /// The two polled tiles (subscribers, watch hours) are NOT here: they are channel state, not /// session events, and live on the polled side of the scoreboard. public sealed class ScoreboardState { /// Super Chat + Super Sticker money, per currency. Adding across currencies would be /// the one arithmetic nobody forgives, so totals are keyed and only the dominant one is shown. private readonly Dictionary _moneyByCurrency = new(StringComparer.OrdinalIgnoreCase); public string? LastMemberName { get; private set; } public string? LastSuperChatAuthor { get; private set; } public string? LastSuperChatDisplay { get; private set; } public double LastSuperChatAmount { get; private set; } public string? LastSuperChatCurrency { get; private set; } public string? LastGifterName { get; private set; } public int LastGiftCount { get; private set; } /// Members gained this session: paid memberships plus gifted ones (counted once, from /// the GIFT PURCHASE — see ). public int NewMembers { get; private set; } public int MilestoneCount { get; private set; } public IReadOnlyDictionary MoneyByCurrency => _moneyByCurrency; /// The session's dominant currency (most money) and its total, or ("", 0) when nothing /// has come in. Chosen by VALUE so a single EUR tip beside ten USD cents cannot flip the display /// to the smaller currency. public (string Currency, double Total) SessionTotal => _moneyByCurrency.Count == 0 ? (string.Empty, 0) : _moneyByCurrency.OrderByDescending(pair => pair.Value).First() is var top ? (top.Key, top.Value) : (string.Empty, 0); public bool HasMixedCurrencies => _moneyByCurrency.Count > 1; /// Folds one chat message into the tallies. Every event kind the poll decodes is /// handled; plain chat is ignored. /// /// giftMembershipReceivedEvent deliberately does nothing. It is the RECIPIENT's view /// of a purchase already counted by membershipGiftingEvent, so counting it would double both the /// members and the money (creator ruling 2026-10-30: "come on"). public void Apply(ChatMessage message) { switch (message.Kind) { case ChatEventKind.NewMember: NewMembers++; LastMemberName = message.AuthorName; break; case ChatEventKind.MemberGift: // The purchase: one event carrying how many memberships were bought. var count = Math.Max(1, message.GiftNewMembersCount); NewMembers += count; LastGiftCount = count; LastGifterName = message.AuthorName; break; case ChatEventKind.GiftReceived: break; // recipient side of the event above — never counted case ChatEventKind.MemberMilestone: MilestoneCount++; break; case ChatEventKind.SuperChat: case ChatEventKind.SuperSticker: LastSuperChatAuthor = message.AuthorName; LastSuperChatAmount = message.SuperChatAmount; LastSuperChatCurrency = string.IsNullOrWhiteSpace(message.SuperChatCurrency) ? "USD" : message.SuperChatCurrency!; LastSuperChatDisplay = message.SuperChatDisplayString ?? FormatMoney(LastSuperChatAmount, LastSuperChatCurrency); if (message.SuperChatAmount > 0) { _moneyByCurrency.TryGetValue(LastSuperChatCurrency!, out var running); _moneyByCurrency[LastSuperChatCurrency!] = running + message.SuperChatAmount; } break; } } /// A new session starts from zero. public void Reset() => CopyFrom(new ScoreboardState()); private void CopyFrom(ScoreboardState other) { _moneyByCurrency.Clear(); foreach (var pair in other._moneyByCurrency) _moneyByCurrency[pair.Key] = pair.Value; LastMemberName = other.LastMemberName; LastSuperChatAuthor = other.LastSuperChatAuthor; LastSuperChatDisplay = other.LastSuperChatDisplay; LastSuperChatAmount = other.LastSuperChatAmount; LastSuperChatCurrency = other.LastSuperChatCurrency; LastGifterName = other.LastGifterName; LastGiftCount = other.LastGiftCount; NewMembers = other.NewMembers; MilestoneCount = other.MilestoneCount; } private static string FormatMoney(double amount, string? currency) => string.IsNullOrEmpty(currency) ? amount.ToString("0.00") : $"{currency} {amount:0.00}"; }