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

type State = "closed" | "open" | "half-open";

export class CircuitBreaker {
  private readonly failureThreshold: number;
  private readonly openDurationMs: number;
  private failures = 0;
  private lastFailureTime = 0;
  private _state: State = "closed";

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

  get state(): State {
    if (this._state === "open") {
      const elapsed = Date.now() - this.lastFailureTime;
      if (elapsed >= this.openDurationMs) {
        this._state = "half-open";
      }
    }
    return this._state;
  }

  async exec<T>(fn: () => Promise<T>): Promise<T> {
    const currentState = this.state;

    if (currentState === "open") {
      throw new Error("Circuit breaker is open — request rejected");
    }

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

  reset(): void {
    this._state = "closed";
    this.failures = 0;
    this.lastFailureTime = 0;
  }

  private onSuccess(): void {
    this._state = "closed";
    this.failures = 0;
  }

  private onFailure(): void {
    this.failures++;
    this.lastFailureTime = Date.now();
    if (this._state === "half-open" || this.failures >= this.failureThreshold) {
      this._state = "open";
    }
  }
}
