using System;
using ytLive.Models;
using ytLive.Services.Encoder;
namespace ytLive.Services;
///
/// TASK 33 slice 2: the session's adaptor between the pump's evidence and the pure policy. One planner
/// per broadcast, because the policy is stateful (budget, cooldown, current tier) and a step must
/// survive across windows.
///
/// It deliberately does not act. It answers "should this session change something, and why"; the
/// view model owns the restart, the toast and the session-report line. Keeping the effects out is what
/// makes the whole ladder testable without an encoder running.
///
public sealed class StepDownPlanner
{
private readonly StepDownPolicy _policy;
private readonly DateTime _startedUtc;
public StepDownPlanner(StepDownPolicy policy, DateTime startedUtc)
{
_policy = policy;
_startedUtc = startedUtc;
}
public StepDownPolicy Policy => _policy;
/// Current tier after any applied steps, in the shape the encoder restart needs.
public (int Width, int Height, int Fps, double BitrateMbps) Current =>
(_policy.Width, _policy.Height, _policy.Fps, _policy.BitrateMbps);
public StepDownDecision Observe(in FramePumpStats stats, DateTime nowUtc)
{
var sample = new StepDownSample(
WindowSeconds: stats.WindowSeconds,
PumpStalls: stats.Stalls,
PumpDrops: 0, // the pump pads with the last frame; FramesRendered is ambiguous
EncoderDrops: stats.Dropped,
AvgRenderMs: stats.AvgRenderMs,
AvgSubmitWaitMs: stats.AvgSubmitMs);
return _policy.Observe(sample, nowUtc - _startedUtc);
}
/// The plain sentence the creator sees, chosen by cause. Never a mechanism string: "ffmpeg
/// exited", "dropped 8", an HTTP code are log findings, not an explanation of what happened to them.
///
public static string Describe(in StepDownDecision decision, (int Width, int Height, int Fps, double BitrateMbps) landed)
=> decision.Action switch
{
StepAction.LowerFrameRate =>
"LlamaCasty couldn't keep up with your scene, so it lowered the frame rate to " +
$"{landed.Fps}. Your connection wasn't the problem.",
StepAction.LowerResolution =>
"LlamaCasty couldn't keep up with your scene, so it lowered the picture to " +
$"{landed.Width}×{landed.Height}. Your connection wasn't the problem.",
StepAction.LowerBitrate =>
$"Your upload couldn't hold the bitrate, so LlamaCasty lowered it to {landed.BitrateMbps:0.0} Mbps " +
"to stop the freezing. It will stay there for this stream.",
StepAction.RaiseFrameRate or StepAction.RaiseResolution or StepAction.RaiseBitrate =>
"Conditions were clean for a while, so LlamaCasty restored one step of quality.",
_ => string.Empty,
};
/// One line for the session report, so a later question is answered from data.
public static string ReportLine(DateTime whenLocal, in StepDownDecision decision) =>
decision.Action switch
{
StepAction.None => string.Empty,
StepAction.LowerFrameRate or StepAction.LowerResolution =>
$"{whenLocal:HH:mm} quality stepped down — encoder ({decision.Why})",
StepAction.LowerBitrate =>
$"{whenLocal:HH:mm} quality stepped down — upload ({decision.Why})",
_ => $"{whenLocal:HH:mm} quality restored ({decision.Why})",
};
}