import { getLogger } from "../utils/logger.js";
import { DEFAULT_RETRYABLE_CODES, getPostgresErrorCode, isRetryableError } from "./retryable-errors.js";

//#region src/retry/withRetry.ts
const logger = getLogger("retry");
async function withRetry(fn, options = {}) {
	const { maxRetries = 3, baseDelayMs = 50, maxDelayMs = 5e3, retryableCodes = DEFAULT_RETRYABLE_CODES, context = "db-operation", noRetry = false } = options;
	if (noRetry) return fn();
	for (let attempt = 0; attempt <= maxRetries; attempt++) try {
		return await fn();
	} catch (error) {
		const isLastAttempt = attempt === maxRetries;
		const retryable = isRetryableError(error, retryableCodes);
		if (isLastAttempt || !retryable) throw error;
		const expDelay = Math.min(maxDelayMs, baseDelayMs * 2 ** attempt);
		const jitteredDelay = Math.random() * expDelay;
		logger.warn({
			context,
			attempt: attempt + 1,
			maxRetries,
			delayMs: Math.round(jitteredDelay),
			errorCode: getPostgresErrorCode(error) ?? error?.code ?? error?.cause?.code,
			errorMessage: error?.message ?? String(error)
		}, "Retrying transient database error");
		await new Promise((resolve) => setTimeout(resolve, jitteredDelay));
	}
	throw new Error("unreachable");
}
async function withRetryTransaction(db, txFn, options = {}) {
	return withRetry(() => db.transaction(txFn), {
		...options,
		context: options.context ?? "transaction"
	});
}

//#endregion
export { withRetry, withRetryTransaction };