/**
 * Watermark-based flow control for terminal output.
 *
 * SSH/PTY output has no back-pressure by default: the source streams as fast as
 * it can, the main process forwards it over IPC, and the renderer queues every
 * chunk into xterm. When output outpaces rendering (e.g. `cat` of a big file, a
 * noisy build, `tail -f`, `yes`), the renderer-side backlog and xterm's internal
 * buffer grow without bound — memory climbs and the whole UI, typing included,
 * janks.
 *
 * This tracks bytes that have been received but not yet acknowledged by xterm's
 * write callback. When the backlog crosses `highWaterMark` it asks the caller to
 * pause the source; once it drains back to `lowWaterMark` it asks to resume. The
 * hysteresis gap avoids rapid pause/resume flapping. During interactive use the
 * backlog hovers near zero, so this never engages.
 */
export interface OutputFlowController {
  /** Account bytes handed to xterm (call when a chunk is received). */
  received(bytes: number): void;
  /** Account bytes whose xterm write callback has fired. */
  written(bytes: number): void;
  /** Clear all state (e.g. on a fresh session attach). Fires no callbacks. */
  reset(): void;
  pendingBytes(): number;
  isPaused(): boolean;
}

export interface OutputFlowControllerOptions {
  highWaterMark: number;
  lowWaterMark: number;
  /** Asked to pause the source when the backlog crosses the high watermark. */
  onPause: () => void;
  /** Asked to resume the source when the backlog drains to the low watermark. */
  onResume: () => void;
}

export function createOutputFlowController(
  options: OutputFlowControllerOptions,
): OutputFlowController {
  const { highWaterMark, lowWaterMark, onPause, onResume } = options;
  let pending = 0;
  let paused = false;

  return {
    received(bytes: number): void {
      if (bytes <= 0) return;
      pending += bytes;
      if (!paused && pending >= highWaterMark) {
        paused = true;
        onPause();
      }
    },
    written(bytes: number): void {
      if (bytes <= 0) return;
      pending -= bytes;
      if (pending < 0) pending = 0;
      if (paused && pending <= lowWaterMark) {
        paused = false;
        onResume();
      }
    },
    reset(): void {
      pending = 0;
      paused = false;
    },
    pendingBytes(): number {
      return pending;
    },
    isPaused(): boolean {
      return paused;
    },
  };
}
