import { __exportAll } from "../_virtual/_rolldown/runtime.js";
import { getAbortSignalError } from "./signal.js";
import pRetry from "./p-retry/index.js";
import PQueueMod from "p-queue";
//#region src/utils/async_caller.ts
var async_caller_exports = /* @__PURE__ */ __exportAll({
	AsyncCaller: () => AsyncCaller,
	classifyRateLimitError: () => classifyRateLimitError,
	parseRetryAfterMs: () => parseRetryAfterMs
});
const STATUS_NO_RETRY = [
	400,
	401,
	402,
	403,
	404,
	405,
	406,
	407,
	409
];
const RETRY_AFTER_AUTO_RETRY_THRESHOLD_MS = 6e4;
const QUOTA_EXHAUSTED_MESSAGE_PATTERNS = [
	/insufficient[_ -]?quota/i,
	/exceeded (?:your|the current|the available).+quota/i,
	/usage quota/i,
	/quota (?:has been )?exhausted/i,
	/billing/i,
	/credit balance/i,
	/out of credits/i,
	/will reset at/i
];
const RETRY_AFTER_MESSAGE_PATTERN = /(?:try again in|retry after)\s+(\d+(?:\.\d+)?)\s*(milliseconds?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h)\b/i;
function getResponseStatus(error) {
	return typeof error === "object" && error !== null && "response" in error && typeof error.response === "object" && error.response !== null && "status" in error.response && typeof error.response.status === "number" ? error.response.status : void 0;
}
function getDirectStatus(error) {
	if (typeof error !== "object" || error === null) return;
	if ("status" in error && typeof error.status === "number") return error.status;
	if ("statusCode" in error && typeof error.statusCode === "number") return error.statusCode;
}
function getErrorMessage(error) {
	return typeof error === "object" && error !== null && "message" in error && typeof error.message === "string" ? error.message : void 0;
}
function getErrorCode(error) {
	if (typeof error !== "object" || error === null) return;
	if ("code" in error && typeof error.code === "string") return error.code;
	return "error" in error && typeof error.error === "object" && error.error !== null && "code" in error.error && typeof error.error.code === "string" ? error.error.code : void 0;
}
function _getRetryAfterHeader(error) {
	if (error?.headers) {
		if (typeof error.headers.get === "function") return error.headers.get("retry-after");
		return error.headers["retry-after"] ?? error.headers["Retry-After"];
	}
	if (error?.response?.headers) {
		if (typeof error.response.headers.get === "function") return error.response.headers.get("retry-after");
		return error.response.headers["retry-after"] ?? error.response.headers["Retry-After"];
	}
}
function parseRetryAfterFromMessageMs(message) {
	if (message == null) return;
	const match = RETRY_AFTER_MESSAGE_PATTERN.exec(message);
	if (!match) return;
	const rawValue = Number(match[1]);
	const unit = match[2]?.toLowerCase();
	if (Number.isNaN(rawValue) || !unit) return;
	if (unit === "ms" || unit.startsWith("millisecond")) return rawValue;
	if (unit === "m" || unit.startsWith("min")) return rawValue * 6e4;
	if (unit === "h" || unit.startsWith("hr") || unit.startsWith("hour")) return rawValue * 36e5;
	return rawValue * 1e3;
}
function coerceError(error, fallbackMessage) {
	if (error instanceof Error) return error;
	const coerced = new Error(fallbackMessage);
	if (typeof error === "object" && error !== null) Object.assign(coerced, error);
	return coerced;
}
function setRateLimitMetadata(error, classification) {
	if (typeof error !== "object" || error === null) return;
	const mutableError = error;
	mutableError.rateLimitType = classification.action;
	mutableError.rateLimitReason = classification.reason;
	if (classification.retryAfterMs !== void 0) mutableError.retryAfterMs = classification.retryAfterMs;
}
function parseRetryAfterMs(headerValue) {
	if (headerValue == null) return;
	const trimmed = headerValue.trim();
	if (!trimmed) return;
	const seconds = Number(trimmed);
	if (!Number.isNaN(seconds) && seconds >= 0) return seconds * 1e3;
	const date = Date.parse(trimmed);
	if (!Number.isNaN(date)) {
		const delayMs = date - Date.now();
		return delayMs > 0 ? delayMs : 0;
	}
}
function classifyRateLimitError(error) {
	if ((getResponseStatus(error) ?? getDirectStatus(error)) !== 429) return;
	if (getErrorCode(error) === "insufficient_quota") return {
		action: "stop",
		reason: "insufficient_quota"
	};
	const message = getErrorMessage(error);
	if (message && QUOTA_EXHAUSTED_MESSAGE_PATTERNS.some((pattern) => pattern.test(message))) return {
		action: "stop",
		reason: "quota_message"
	};
	const retryAfterMs = parseRetryAfterMs(_getRetryAfterHeader(error)) ?? parseRetryAfterFromMessageMs(message);
	if (retryAfterMs !== void 0) {
		if (retryAfterMs <= RETRY_AFTER_AUTO_RETRY_THRESHOLD_MS) return {
			action: "wait",
			retryAfterMs,
			reason: "retry_after_hint"
		};
		return {
			action: "capacity",
			retryAfterMs,
			reason: "retry_after_too_large"
		};
	}
	return {
		action: "capacity",
		reason: "headerless_429"
	};
}
/**
* The default failed attempt handler for the AsyncCaller.
* @param error - The error to handle.
* @returns void
*/
const defaultFailedAttemptHandler = (error) => {
	if (typeof error !== "object" || error === null) return;
	if ("message" in error && typeof error.message === "string" && (error.message.startsWith("Cancel") || error.message.startsWith("AbortError")) || "name" in error && typeof error.name === "string" && error.name === "AbortError") throw error;
	if ("code" in error && typeof error.code === "string" && error.code === "ECONNABORTED") throw error;
	const status = getResponseStatus(error) ?? getDirectStatus(error);
	if (status && STATUS_NO_RETRY.includes(+status)) throw error;
	if (getErrorCode(error) === "insufficient_quota") {
		const err = coerceError(error, getErrorMessage(error) ?? "Insufficient quota");
		err.name = "InsufficientQuotaError";
		setRateLimitMetadata(err, {
			action: "stop",
			reason: "insufficient_quota"
		});
		throw err;
	}
	const rateLimitClassification = classifyRateLimitError(error);
	if (rateLimitClassification) {
		if (rateLimitClassification.action === "wait") {
			setRateLimitMetadata(error, rateLimitClassification);
			return;
		}
		const err = coerceError(error, getErrorMessage(error) ?? "Rate limit exceeded");
		if (err.name === "Error") err.name = rateLimitClassification.action === "stop" ? "RateLimitQuotaExhaustedError" : "RateLimitCapacityError";
		setRateLimitMetadata(err, rateLimitClassification);
		throw err;
	}
};
/**
* A class that can be used to make async calls with concurrency and retry logic.
*
* This is useful for making calls to any kind of "expensive" external resource,
* be it because it's rate-limited, subject to network issues, etc.
*
* Concurrent calls are limited by the `maxConcurrency` parameter, which defaults
* to `Infinity`. This means that by default, all calls will be made in parallel.
*
* Retries are limited by the `maxRetries` parameter, which defaults to 6. This
* means that by default, each call will be retried up to 6 times, with an
* exponential backoff between each attempt.
*/
var AsyncCaller = class {
	maxConcurrency;
	maxRetries;
	onFailedAttempt;
	queue;
	constructor(params) {
		this.maxConcurrency = params.maxConcurrency ?? Infinity;
		this.maxRetries = params.maxRetries ?? 6;
		this.onFailedAttempt = params.onFailedAttempt ?? defaultFailedAttemptHandler;
		const PQueue = "default" in PQueueMod ? PQueueMod.default : PQueueMod;
		this.queue = new PQueue({ concurrency: this.maxConcurrency });
	}
	async call(callable, ...args) {
		return this.queue.add(() => pRetry(() => callable(...args).catch((error) => {
			if (error instanceof Error) throw error;
			else throw new Error(error);
		}), {
			onFailedAttempt: ({ error }) => this.onFailedAttempt?.(error),
			retries: this.maxRetries,
			randomize: true
		}), { throwOnTimeout: true });
	}
	callWithOptions(options, callable, ...args) {
		if (options.signal) {
			let listener;
			return Promise.race([this.call(callable, ...args), new Promise((_, reject) => {
				listener = () => {
					reject(getAbortSignalError(options.signal));
				};
				options.signal?.addEventListener("abort", listener, { once: true });
			})]).finally(() => {
				if (options.signal && listener) options.signal.removeEventListener("abort", listener);
			});
		}
		return this.call(callable, ...args);
	}
	fetch(...args) {
		return this.call(() => fetch(...args).then((res) => res.ok ? res : Promise.reject(res)));
	}
};
//#endregion
export { AsyncCaller, async_caller_exports, classifyRateLimitError, parseRetryAfterMs };

//# sourceMappingURL=async_caller.js.map