/**
 * What to do about a *fatal* hls.js network error.
 *
 * Jellyfin's transcoded HLS streams do not always terminate with an
 * `#EXT-X-ENDLIST`, so a stream that has simply run out looks identical to one
 * that broke: both arrive as a fatal network error. The only thing separating
 * them is how far playback had got, which is why this decision is worth
 * isolating from the player component — read the position wrong and a
 * recoverable stall turns into a skip to the next item.
 *
 * TRACES: UR-004, UR-074 | DR-177 | UT-174
 */

/** Fraction of the runtime past which a fatal error reads as "the stream ended". */
const NEAR_END_FRACTION = 0.9;

/** How many times to ask hls.js to resume before giving up on the stream. */
export const MAX_FATAL_NETWORK_RECOVERIES = 3;

export type FatalNetworkErrorAction = "ended" | "retry" | "giveUp";

export interface FatalNetworkErrorInput {
  /**
   * Absolute position in the media, in seconds — the value the player displays.
   *
   * It is already absolute (`seekOffset + element.currentTime`): do NOT add the
   * transcode seek offset again. After a quality switch or a transcoded seek the
   * offset *is* the resume position, so double-counting it puts an apparent
   * position past the near-end threshold from roughly halfway through, and every
   * transient error then ends playback.
   */
  positionSeconds: number;
  /** Known runtime in seconds; 0 or negative when the runtime isn't known yet. */
  knownDurationSeconds: number;
  /** Recovery attempts already made against this hls.js instance. */
  attempts: number;
}

/** Whether a failure at this position should be read as the stream ending. */
export function isNearEndOfStream(
  positionSeconds: number,
  knownDurationSeconds: number
): boolean {
  if (knownDurationSeconds <= 0 || positionSeconds <= 0) return false;
  return positionSeconds / knownDurationSeconds > NEAR_END_FRACTION;
}

export function fatalNetworkErrorAction({
  positionSeconds,
  knownDurationSeconds,
  attempts,
}: FatalNetworkErrorInput): FatalNetworkErrorAction {
  if (isNearEndOfStream(positionSeconds, knownDurationSeconds)) return "ended";
  return attempts <= MAX_FATAL_NETWORK_RECOVERIES ? "retry" : "giveUp";
}
