//#region src/infra/retry.ts
const DEFAULT_RETRY_CONFIG = {
	attempts: 3,
	minDelayMs: 300,
	maxDelayMs: 3e4,
	jitter: .1
};
/**
* Sleep utility
*/
function sleep(ms) {
	return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Apply jitter to delay
*/
function applyJitter(delayMs, jitter) {
	if (jitter <= 0) return delayMs;
	const offset = (Math.random() * 2 - 1) * jitter;
	return Math.max(0, Math.round(delayMs * (1 + offset)));
}
/**
* Resolve retry configuration with defaults
*/
function resolveRetryConfig(defaults = DEFAULT_RETRY_CONFIG, overrides) {
	return {
		attempts: Math.max(1, overrides?.attempts ?? defaults.attempts),
		minDelayMs: Math.max(0, overrides?.minDelayMs ?? defaults.minDelayMs),
		maxDelayMs: Math.max(overrides?.minDelayMs ?? defaults.minDelayMs, overrides?.maxDelayMs ?? defaults.maxDelayMs),
		jitter: Math.max(0, Math.min(1, overrides?.jitter ?? defaults.jitter))
	};
}
/**
* Execute a function with retry logic
*/
async function withRetry(fn, options = {}) {
	const config = resolveRetryConfig(DEFAULT_RETRY_CONFIG, options);
	const shouldRetry = options.shouldRetry ?? (() => true);
	let lastError;
	for (let attempt = 1; attempt <= config.attempts; attempt++) try {
		return await fn();
	} catch (error) {
		lastError = error;
		if (attempt >= config.attempts) break;
		if (!shouldRetry(error, attempt)) throw error;
		let delayMs;
		const retryAfter = options.retryAfterMs?.(error);
		if (typeof retryAfter === "number" && Number.isFinite(retryAfter)) delayMs = Math.max(retryAfter, config.minDelayMs);
		else delayMs = config.minDelayMs * Math.pow(2, attempt - 1);
		delayMs = Math.min(delayMs, config.maxDelayMs);
		delayMs = applyJitter(delayMs, config.jitter);
		options.onRetry?.({
			attempt,
			maxAttempts: config.attempts,
			delayMs,
			error,
			label: options.label
		});
		await sleep(delayMs);
	}
	throw lastError ?? /* @__PURE__ */ new Error("Retry failed");
}
/**
* Telegram-specific recoverable error codes
*/
const RECOVERABLE_ERROR_CODES = new Set([
	"ECONNRESET",
	"ECONNREFUSED",
	"EPIPE",
	"ETIMEDOUT",
	"ESOCKETTIMEDOUT",
	"ENETUNREACH",
	"EHOSTUNREACH",
	"ENOTFOUND",
	"EAI_AGAIN",
	"UND_ERR_CONNECT_TIMEOUT",
	"UND_ERR_HEADERS_TIMEOUT",
	"UND_ERR_BODY_TIMEOUT",
	"UND_ERR_SOCKET",
	"UND_ERR_ABORTED",
	"ECONNABORTED",
	"ERR_NETWORK"
]);
/**
* Telegram-specific recoverable error names
*/
const RECOVERABLE_ERROR_NAMES = new Set([
	"AbortError",
	"TimeoutError",
	"ConnectTimeoutError",
	"HeadersTimeoutError",
	"BodyTimeoutError"
]);
/**
* Check if an error is a recoverable network error
*/
function isRecoverableNetworkError(error, context = "unknown") {
	if (!error) return false;
	const getErrorCode = (err) => {
		if (!err || typeof err !== "object") return void 0;
		const code = err.code;
		if (typeof code === "string") return code;
		if (typeof code === "number") return String(code);
		const errno = err.errno;
		if (typeof errno === "string") return errno;
		if (typeof errno === "number") return String(errno);
	};
	const getErrorName = (err) => {
		if (!err || typeof err !== "object") return "";
		return String(err.name ?? "");
	};
	const errors = [];
	let current = error;
	const seen = /* @__PURE__ */ new WeakSet();
	while (current && typeof current === "object" && !seen.has(current)) {
		seen.add(current);
		errors.push(current);
		const cause = current.cause;
		if (cause) {
			current = cause;
			continue;
		}
		if (getErrorName(current) === "HttpError") {
			const wrapped = current.error;
			if (wrapped) {
				current = wrapped;
				continue;
			}
		}
		break;
	}
	for (const err of errors) {
		const code = getErrorCode(err);
		if (code && RECOVERABLE_ERROR_CODES.has(code.toUpperCase())) return true;
		const name = getErrorName(err);
		if (RECOVERABLE_ERROR_NAMES.has(name)) return true;
		if (context !== "send") {
			const message = String(err.message ?? "").toLowerCase();
			if ([
				"fetch failed",
				"network error",
				"socket hang up",
				"getaddrinfo",
				"timeout",
				"timed out"
			].some((p) => message.includes(p))) return true;
		}
	}
	return false;
}
/**
* Create a retry runner with Telegram-specific defaults
*/
function createRetryRunner(options = {}) {
	resolveRetryConfig(DEFAULT_RETRY_CONFIG, options);
	return async (fn, label) => {
		return withRetry(fn, {
			...options,
			label: label ?? options.label,
			shouldRetry: (err, attempt) => {
				if (options.shouldRetry) return options.shouldRetry(err, attempt);
				return isRecoverableNetworkError(err, "send");
			}
		});
	};
}
//#endregion
export { RECOVERABLE_ERROR_CODES, RECOVERABLE_ERROR_NAMES, createRetryRunner, isRecoverableNetworkError, resolveRetryConfig, sleep, withRetry };

//# sourceMappingURL=retry.js.map