import { rewriteUrl } from './cors.mjs';

const json = (url, params) => fetchAsync(url, 'json', params);
const text = (url, params) => fetchAsync(url, 'text', params);
const blob = (url, params) => fetchAsync(url, 'blob', params);

const fetchAsync = async (_url, responseType, _params = {}) => {
	// combine default and provided parameters
	const params = {
		method: 'GET',
		mode: 'cors',
		type: 'GET',
		retryCount: 0,
		..._params,
	};
	// store original number of retries
	params.originalRetries = params.retryCount;

	// build a url, including the rewrite for cors if necessary
	let corsUrl = _url;
	if (params.cors === true) corsUrl = rewriteUrl(_url);
	const url = new URL(corsUrl, `${window.location.origin}/`);
	// match the security protocol when not on localhost
	url.protocol = window.location.hostname === 'localhost' ? url.protocol : window.location.protocol;
	// add parameters if necessary
	if (params.data) {
		Object.keys(params.data).forEach((key) => {
			// get the value
			const value = params.data[key];
			// add to the url
			url.searchParams.append(key, value);
		});
	}

	// make the request
	const response = await doFetch(url, params);

	// check for ok response
	if (!response.ok) throw new Error(`Fetch error ${response.status} ${response.statusText} while fetching ${response.url}`);
	// return the requested response
	switch (responseType) {
	case 'json':
		return response.json();
	case 'text':
		return response.text();
	case 'blob':
		return response.blob();
	default:
		return response;
	}
};

// fetch with retry and back-off
const doFetch = (url, params) => new Promise((resolve, reject) => {
	fetch(url, params).then((response) => {
		if (params.retryCount > 0) {
			// 500 status codes should be retried after a short backoff
			if (response.status >= 500 && response.status <= 599 && params.retryCount > 0) {
				// call the "still waiting" function
				if (typeof params.stillWaiting === 'function' && params.retryCount === params.originalRetries) {
					params.stillWaiting();
				}
				// decrement and retry
				const newParams = {
					...params,
					retryCount: params.retryCount - 1,
				};
				return resolve(delay(retryDelay(params.originalRetries - newParams.retryCount), doFetch, url, newParams));
			}
			// not 500 status
			return resolve(response);
		}
		// out of retries
		return resolve(response);
	})
		.catch((error) => reject(error));
});

const delay = (time, func, ...args) => new Promise((resolve) => {
	setTimeout(() => {
		resolve(func(...args));
	}, time);
});

const retryDelay = (retryNumber) => {
	switch (retryNumber) {
	case 1: return 1000;
	case 2: return 2000;
	case 3: return 5000;
	case 4: return 10_000;
	default: return 30_000;
	}
};

export {
	json,
	text,
	blob,
};
