import { TimeoutError } from '../httpErrors.js';

export class OperationDeadline {
  /** Absolute deadline, epoch ms — or Infinity when no header was sent. */
  readonly deadlineTimestamp: number;
  /** The AbortSignal that fires at the deadline. Pass THIS to fetch/provider calls. */
  readonly signal: AbortSignal;

  /** @param header deadline timestamp in milliseconds from Epoch, or undefined */
  private constructor(deadlineInMs: number | undefined) {
    if (!deadlineInMs) {
      this.deadlineTimestamp = Infinity;
      this.signal = new AbortController().signal; // never fires
      return;
    }

    this.deadlineTimestamp = deadlineInMs;

    const remaining = this.deadlineTimestamp - Date.now();

    if (remaining <= 0) {
      throw new TimeoutError('Request already timed out upon reception');
    }

    this.signal = AbortSignal.timeout(remaining);
  }

  /** @param header raw X-Unito-Operation-Deadline value (Unix seconds), or undefined */
  static fromHeader(deadlineInSeconds: string | number | undefined): OperationDeadline {
    let timestamp = undefined;
    const seconds = Number(deadlineInSeconds);

    if (seconds) {
      timestamp = seconds * 1000;
    }

    return new OperationDeadline(timestamp);
  }

  static never() {
    return new OperationDeadline(undefined);
  }

  /** @param deadlineInMs absolute deadline timestamp in epoch milliseconds */
  static fromMs(deadlineInMs: number): OperationDeadline {
    return new OperationDeadline(deadlineInMs);
  }

  /** Fresh ms left until the deadline (≤0 = past, Infinity = no deadline). */
  timeRemaining(): number {
    return this.deadlineTimestamp - Date.now();
  }

  /** Computes if the deadline was reached or not */
  hasPassed(): boolean {
    return this.timeRemaining() <= 0;
  }
}
