// {{ display_name }} MCP server — generated by mcpify. Do not hand-edit.

import { CircuitBreakerOpenError } from './errors.js';

type State = 'CLOSED' | 'OPEN' | 'HALF_OPEN';

export interface CircuitBreakerOptions {
  failureThreshold?: number;
  resetTimeoutMs?: number;
}

/**
 * CLOSED -> OPEN -> HALF_OPEN state machine around outbound calls to the
 * target API (REQ-2.3.3). Opens after `failureThreshold` consecutive
 * failures; after `resetTimeoutMs`, lets one trial call through (HALF_OPEN)
 * to decide whether to close again or re-open.
 */
export class CircuitBreaker {
  private state: State = 'CLOSED';
  private failureCount = 0;
  private openedAt = 0;
  private readonly failureThreshold: number;
  private readonly resetTimeoutMs: number;

  constructor(options: CircuitBreakerOptions = {}) {
    this.failureThreshold = options.failureThreshold ?? 5;
    this.resetTimeoutMs = options.resetTimeoutMs ?? 30_000;
  }

  async execute<T>(fn: () => Promise<T>): Promise<T> {
    if (this.state === 'OPEN') {
      if (Date.now() - this.openedAt < this.resetTimeoutMs) {
        throw new CircuitBreakerOpenError();
      }
      this.state = 'HALF_OPEN';
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private onSuccess(): void {
    this.failureCount = 0;
    this.state = 'CLOSED';
  }

  private onFailure(): void {
    this.failureCount += 1;
    if (this.state === 'HALF_OPEN' || this.failureCount >= this.failureThreshold) {
      this.state = 'OPEN';
      this.openedAt = Date.now();
    }
  }

  getState(): State {
    return this.state;
  }
}
