import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import { logger } from "../monitoring/logger.js";
import {
  isRetryableError,
  getErrorMessage
} from "./index.js";
var CircuitState = /* @__PURE__ */ ((CircuitState2) => {
  CircuitState2["CLOSED"] = "closed";
  CircuitState2["OPEN"] = "open";
  CircuitState2["HALF_OPEN"] = "half_open";
  return CircuitState2;
})(CircuitState || {});
function calculateBackoff(attempt, initialDelay, maxDelay, factor) {
  const exponentialDelay = Math.min(
    initialDelay * Math.pow(factor, attempt - 1),
    maxDelay
  );
  const jitter = exponentialDelay * Math.random() * 0.25;
  return Math.floor(exponentialDelay + jitter);
}
async function retry(fn, options = {}) {
  const {
    maxAttempts = 3,
    initialDelay = 1e3,
    maxDelay = 3e4,
    backoffFactor = 2,
    timeout,
    onRetry
  } = options;
  let lastError;
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      if (timeout) {
        return await Promise.race([
          fn(),
          new Promise(
            (_, reject) => setTimeout(
              () => reject(new Error(`Operation timed out after ${timeout}ms`)),
              timeout
            )
          )
        ]);
      }
      return await fn();
    } catch (error) {
      lastError = error;
      if (!isRetryableError(error) || attempt === maxAttempts) {
        throw error;
      }
      const delay = calculateBackoff(
        attempt,
        initialDelay,
        maxDelay,
        backoffFactor
      );
      logger.warn(`Retry attempt ${attempt}/${maxAttempts} after ${delay}ms`, {
        error: getErrorMessage(error),
        attempt,
        delay
      });
      if (onRetry) {
        onRetry(attempt, error);
      }
      await new Promise((resolve) => setTimeout(resolve, delay));
    }
  }
  throw lastError;
}
class CircuitBreaker {
  constructor(name, options = {}) {
    this.name = name;
    this.options = {
      failureThreshold: options.failureThreshold ?? 5,
      resetTimeout: options.resetTimeout ?? 6e4,
      halfOpenRequests: options.halfOpenRequests ?? 3
    };
  }
  state = "closed" /* CLOSED */;
  failures = 0;
  successCount = 0;
  lastFailTime;
  options;
  async execute(fn) {
    if (this.state === "open" /* OPEN */) {
      const timeSinceLastFailure = this.lastFailTime ? Date.now() - this.lastFailTime.getTime() : 0;
      if (timeSinceLastFailure >= this.options.resetTimeout) {
        this.state = "half_open" /* HALF_OPEN */;
        this.successCount = 0;
        logger.info(`Circuit breaker ${this.name} entering half-open state`);
      } else {
        throw new Error(
          `Circuit breaker ${this.name} is OPEN. Retry after ${this.options.resetTimeout - timeSinceLastFailure}ms`
        );
      }
    }
    try {
      const result = await fn();
      if (this.state === "half_open" /* HALF_OPEN */) {
        this.successCount++;
        if (this.successCount >= this.options.halfOpenRequests) {
          this.state = "closed" /* CLOSED */;
          this.failures = 0;
          logger.info(`Circuit breaker ${this.name} is now CLOSED`);
        }
      } else {
        this.failures = 0;
      }
      return result;
    } catch (error) {
      this.handleFailure(error);
      throw error;
    }
  }
  handleFailure(error) {
    this.failures++;
    this.lastFailTime = /* @__PURE__ */ new Date();
    if (this.state === "half_open" /* HALF_OPEN */) {
      this.state = "open" /* OPEN */;
      logger.error(
        `Circuit breaker ${this.name} reopened due to failure in half-open state`
      );
    } else if (this.state === "closed" /* CLOSED */ && this.failures >= this.options.failureThreshold) {
      this.state = "open" /* OPEN */;
      logger.error(
        `Circuit breaker ${this.name} opened after ${this.failures} failures`
      );
    }
  }
  getState() {
    return this.state;
  }
  reset() {
    this.state = "closed" /* CLOSED */;
    this.failures = 0;
    this.successCount = 0;
    this.lastFailTime = void 0;
    logger.info(`Circuit breaker ${this.name} manually reset`);
  }
}
async function withFallback(primary, fallbacks, context) {
  const errors = [];
  try {
    return await primary();
  } catch (error) {
    errors.push(error);
    logger.warn("Primary operation failed, trying fallbacks", {
      error: getErrorMessage(error),
      context
    });
  }
  for (let i = 0; i < fallbacks.length; i++) {
    try {
      const result = await fallbacks[i]();
      logger.info(`Fallback ${i + 1} succeeded`, { context });
      return result;
    } catch (error) {
      errors.push(error);
      if (i < fallbacks.length - 1) {
        logger.warn(`Fallback ${i + 1} failed, trying next`, {
          error: getErrorMessage(error),
          context
        });
      }
    }
  }
  throw new Error(
    `All attempts failed. Errors: ${errors.map(getErrorMessage).join(", ")}`
  );
}
class Bulkhead {
  constructor(name, maxConcurrent) {
    this.name = name;
    this.maxConcurrent = maxConcurrent;
  }
  running = 0;
  queue = [];
  async execute(fn) {
    if (this.running >= this.maxConcurrent) {
      await new Promise((resolve) => {
        this.queue.push(resolve);
      });
    }
    this.running++;
    try {
      return await fn();
    } finally {
      this.running--;
      const next = this.queue.shift();
      if (next) {
        next();
      }
    }
  }
  getStats() {
    return {
      running: this.running,
      queued: this.queue.length,
      maxConcurrent: this.maxConcurrent
    };
  }
}
async function withTimeout(fn, timeoutMs, timeoutMessage) {
  return Promise.race([
    fn(),
    new Promise(
      (_, reject) => setTimeout(
        () => reject(
          new Error(
            timeoutMessage ?? `Operation timed out after ${timeoutMs}ms`
          )
        ),
        timeoutMs
      )
    )
  ]);
}
async function gracefulDegrade(fn, defaultValue, logContext) {
  try {
    return await fn();
  } catch (error) {
    logger.warn("Operation failed, using default value", {
      error: getErrorMessage(error),
      ...logContext
    });
    return defaultValue;
  }
}
function createResilientOperation(name, options = {}) {
  const circuitBreaker = options.circuitBreaker ? new CircuitBreaker(name, options.circuitBreaker) : null;
  const bulkhead = options.bulkhead ? new Bulkhead(name, options.bulkhead) : null;
  return async (fn) => {
    let currentFn = fn;
    if (bulkhead) {
      const wrapped = currentFn;
      currentFn = () => bulkhead.execute(wrapped);
    }
    if (options.timeout) {
      const wrapped = currentFn;
      currentFn = () => withTimeout(wrapped, options.timeout);
    }
    if (options.retry) {
      const wrapped = currentFn;
      currentFn = () => retry(wrapped, options.retry);
    }
    if (circuitBreaker) {
      const wrapped = currentFn;
      currentFn = () => circuitBreaker.execute(wrapped);
    }
    if (options.fallback) {
      return withFallback(currentFn, [options.fallback]);
    }
    return currentFn();
  };
}
export {
  Bulkhead,
  CircuitBreaker,
  CircuitState,
  calculateBackoff,
  createResilientOperation,
  gracefulDegrade,
  retry,
  withFallback,
  withTimeout
};
//# sourceMappingURL=recovery.js.map
