import { NETWORK_UNAVAILABLE_MESSAGE } from './Constants';
import { promiseWithRetry, RetryError } from './promiseWithRetry';
import { SynapseClientError } from './SynapseClientError';
const RATE_LIMIT_MAX_RETRY = 10;
const SERVER_ERROR_MAX_RETRY = 3;
export const SERVER_ERROR_RETRY_CODES = [502, 503, 504];
function getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min) + min);
}
/**
 * Fetches data, retrying if the HTTP status code indicates that it could be retried.
 * To use it in our generated client, this function must NOT consume the response body.
 *
 * @throws SynapseClientError
 */
export async function synapseFetchWithRetry(requestInfo, options, delayMs = 1000 + getRandomInt(0, 100)) {
    let rateLimitRetryCount = 0;
    let transientErrorRetryCount = 0;
    return promiseWithRetry(async () => {
        let response;
        try {
            response = await fetch(requestInfo, options);
        }
        catch (err) {
            transientErrorRetryCount++;
            if (transientErrorRetryCount < SERVER_ERROR_MAX_RETRY) {
                throw new RetryError(err instanceof Error ? err.message : 'Network error', err);
            }
            throw err;
        }
        if (response.status === 429) {
            rateLimitRetryCount++;
            if (rateLimitRetryCount < RATE_LIMIT_MAX_RETRY) {
                throw new RetryError(`HTTP ${response.status}`, response);
            }
        }
        else if (SERVER_ERROR_RETRY_CODES.includes(response.status)) {
            transientErrorRetryCount++;
            if (transientErrorRetryCount < SERVER_ERROR_MAX_RETRY) {
                throw new RetryError(`HTTP ${response.status}`, response);
            }
        }
        // Return response for all other statuses (including errors — middleware handles those)
        return response;
    }, RATE_LIMIT_MAX_RETRY, delayMs, true, // exponential backoff
    10_000);
}
/**
 * Fetches data, retrying if the HTTP status code indicates that it could be retried. Contains custom logic for
 * handling errors returned by the Synapse backend.
 * @throws SynapseClientError
 */
export const synapseClientFetch = async (requestInfo, options) => {
    const url = typeof requestInfo === 'string' ? requestInfo : requestInfo.url;
    let response;
    try {
        response = await synapseFetchWithRetry(requestInfo, options);
    }
    catch (err) {
        console.error(err);
        throw new SynapseClientError(0, NETWORK_UNAVAILABLE_MESSAGE, url);
    }
    const contentType = response.headers.get('Content-Type');
    const responseBody = await response.text();
    let responseObject = responseBody;
    try {
        // try to parse it as json
        if (contentType && contentType.includes('application/json')) {
            responseObject = JSON.parse(responseBody);
        }
    }
    catch (error) {
        console.warn('Failed to parse response as JSON', responseBody);
    }
    if (response.ok) {
        return responseObject;
    }
    else if (responseObject !== null &&
        typeof responseObject === 'object' &&
        'reason' in responseObject) {
        throw new SynapseClientError(response.status, responseObject.reason, url, responseObject);
    }
    else {
        throw new SynapseClientError(response.status, JSON.stringify(responseObject), url);
    }
};
//# sourceMappingURL=synapseClientFetch.js.map