using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace ytLive.Models;
/// One Super Chat or Super Sticker event, as YouTube reports it.
public sealed record SuperChatEvent(
string Id,
ChatEventKind Kind,
long AmountMicros,
string Currency,
string? DisplayAmount,
DateTime PublishedAt);
///
/// TASK 3·20: what this session's rewards added up to — in memory only.
/// Derived on demand from superChatEvents.list rather than persisted: YouTube is the system
/// of record for money, and keeping a local ledger would contradict the published promise that no
/// viewer, member or donor information is retained after the session ends.
/// Totals are kept per currency and never summed across currencies — a $5 and a €5 are not
/// $10, and a single blended number would be a lie.
///
public sealed record SessionRewardSummary(
int SuperChatCount,
int SuperStickerCount,
IReadOnlyDictionary MicrosByCurrency,
DateTime? FirstEventAt,
DateTime? LastEventAt)
{
public static readonly SessionRewardSummary Empty = new(
0, 0, new Dictionary(), null, null);
public int TotalEvents => SuperChatCount + SuperStickerCount;
public bool HasEvents => TotalEvents > 0;
/// Filters to a session window and rolls the events up. Events outside
/// [, ] are ignored, which is what makes a
/// report "this session" rather than "this channel".
public static SessionRewardSummary From(
IEnumerable events, DateTime sinceUtc, DateTime? untilUtc = null)
{
var until = untilUtc ?? DateTime.MaxValue;
var inWindow = events
.Where(e => e.PublishedAt >= sinceUtc && e.PublishedAt <= until)
.OrderBy(e => e.PublishedAt)
.ToList();
if (inWindow.Count == 0) return Empty;
var byCurrency = new Dictionary(StringComparer.OrdinalIgnoreCase);
foreach (var e in inWindow)
{
var currency = string.IsNullOrWhiteSpace(e.Currency) ? "?" : e.Currency.ToUpperInvariant();
byCurrency.TryGetValue(currency, out var total);
byCurrency[currency] = total + Math.Max(0, e.AmountMicros);
}
return new SessionRewardSummary(
inWindow.Count(e => e.Kind == ChatEventKind.SuperChat),
inWindow.Count(e => e.Kind == ChatEventKind.SuperSticker),
byCurrency,
inWindow[0].PublishedAt,
inWindow[^1].PublishedAt);
}
/// Money lines for display, e.g. "USD $12.34 · EUR €4.00". Empty when there is nothing to
/// show — a creator who never monetized must never be shown a zero.
public IReadOnlyList MoneyLines()
=> MicrosByCurrency
.Where(pair => pair.Value > 0)
.OrderByDescending(pair => pair.Value)
.Select(pair => $"{pair.Key} {Format(pair.Key, pair.Value)}")
.ToList();
private static string Format(string currency, long micros)
{
var amount = micros / 1_000_000m;
var symbol = currency switch
{
"USD" => "$",
"EUR" => "€",
"GBP" => "£",
"CAD" => "CA$",
"AUD" => "A$",
_ => string.Empty,
};
return symbol.Length > 0
? symbol + amount.ToString("0.00", CultureInfo.InvariantCulture)
: amount.ToString("0.00", CultureInfo.InvariantCulture) + " " + currency;
}
}