/**
 * Exponential-backoff reconnect manager.
 *
 * Starts at 1s delay, doubles each failure (max 30s).
 * On success or manual reset, delay resets to 1s.
 */
export class ReconnectManager {
  private timer: ReturnType<typeof setTimeout> | null = null;
  private retryCount = 0;
  private readonly maxRetries: number;
  private readonly baseDelayMs: number;
  private readonly maxDelayMs: number;
  private callback: (() => void) | null = null;

  constructor(maxRetries = 20, baseDelayMs = 1000, maxDelayMs = 30_000) {
    this.maxRetries = maxRetries;
    this.baseDelayMs = baseDelayMs;
    this.maxDelayMs = maxDelayMs;
  }

  setCallback(cb: () => void): void {
    this.callback = cb;
  }

  get isReconnecting(): boolean {
    return this.timer !== null;
  }

  get state(): { isReconnecting: boolean; retryCount: number } {
    return { isReconnecting: this.isReconnecting, retryCount: this.retryCount };
  }

  /** Start reconnection loop. Resets counter if starting fresh. */
  start(): void {
    this.stop();
    this.schedule();
  }

  /** Called when connection succeeds — resets backoff. */
  succeed(): void {
    this.stop();
    this.retryCount = 0;
  }

  /** Called when a reconnection attempt fails — increments backoff. */
  fail(): void {
    if (this.retryCount >= this.maxRetries) {
      this.stop();
      return;
    }
    // Stop existing timer before scheduling new one to prevent duplicate timers.
    this.stop();
    this.schedule();
  }

  stop(): void {
    if (this.timer) {
      clearTimeout(this.timer);
      this.timer = null;
    }
  }

  /** Reset backoff and start immediately (e.g., tab becomes visible). */
  resetAndStart(): void {
    this.stop();
    this.retryCount = 0;
    if (this.callback) this.callback();
  }

  private schedule(): void {
    const delay = Math.min(
      this.baseDelayMs * Math.pow(2, this.retryCount),
      this.maxDelayMs,
    );
    this.timer = setTimeout(() => {
      this.retryCount++;
      if (this.callback) this.callback();
    }, delay);
  }
}
