import type { key_val } from '$lib/stores/ae_stores';

export let temp_get_blob_percent_completed = 0;
export const get_blob_percent_completed = temp_get_blob_percent_completed;

export const temp_get_object_percent_completed = 0;
export const get_object_percent_completed = temp_get_object_percent_completed;

export const get_object = async function get_object({
	api_cfg = null,
	endpoint = '',
	headers = {},
	params = {},
	data = {},
	timeout = 60000,
	return_meta = false,
	return_blob = false,
	filename = '',
	auto_download = false,
	as_list = false, // Is this still really needed?
	// The task_id value should be a random string that is unique to the task. This is used to identify the task in the message event.
	task_id = crypto.randomUUID(),
	log_lvl = 0,
	retry_count = 5 // Number of retry attempts
}: {
	api_cfg: any;
	endpoint: string;
	headers?: any;
	params?: any;
	data?: any;
	timeout?: number;
	return_meta?: boolean;
	return_blob?: boolean;
	filename?: null | string;
	auto_download?: boolean;
	as_list?: boolean;
	task_id?: string;
	log_lvl?: number;
	retry_count?: number;
}) {
	if (log_lvl) {
		console.log(`*** get_object() *** Endpoint: ${endpoint} AE Task ID: ${task_id}`);
		console.log('Params:', params);
		if (log_lvl > 1) {
			console.log('Data:', data);
		}
	}

	if (!api_cfg) {
		console.log('No API Config was provided. Returning false.');
		return false;
	}

	const url = new URL(endpoint, api_cfg['base_url']);
	Object.keys(params).forEach((key) => url.searchParams.append(key, params[key]));

	const controller = new AbortController();
	const timeoutId = setTimeout(() => controller.abort(), timeout);

	// Remove a header parameter if it is set to null
	if (
		api_cfg['headers'].hasOwnProperty('x-no-account-id') &&
		api_cfg['headers']['x-no-account-id'] === null
	) {
		delete api_cfg['headers']['x-no-account-id'];
	}

	// Clean the headers
	const headers_cleaned: key_val = {};
	for (const prop in headers) {
		const prop_cleaned = prop.replaceAll('_', '-');
		if (typeof headers[prop] != 'string') {
			headers[prop] = JSON.stringify(headers[prop]);
		}
		headers_cleaned[prop_cleaned] = headers[prop];
		if (log_lvl > 1) {
			console.log(`${prop_cleaned}: ${headers_cleaned[prop_cleaned]}`);
		}
	}
	headers = headers_cleaned;
	if (log_lvl > 1) {
		console.log('All headers cleaned:', headers);
	}

	const fetchOptions: RequestInit = {
		method: 'GET',
		headers: {
			...api_cfg['headers'],
			...headers
		},
		signal: controller.signal
	};

	if (log_lvl > 1) {
		console.log('Fetch options:', fetchOptions);
	}

	let fetch_method: any = fetch;
	if (api_cfg.fetch) {
		if (log_lvl > 1) {
			console.log('Using custom fetch function from api_cfg!!!');
		}
		fetch_method = api_cfg.fetch;
	}

	for (let attempt = 1; attempt <= retry_count; attempt++) {
		try {
			const response = await fetch_method(url.toString(), fetchOptions).catch(function (
				error: any
			) {
				console.log(
					'API GET Object *fetch* request was aborted or failed in an unexpected way.',
					error
				);
			});
			clearTimeout(timeoutId);

			if (!response) {
				if (log_lvl > 1) {
					console.log(
						'API GET Object: Something went wrong with *fetch* request. Returning false? Throwing an error!'
					);
				}
				throw new Error(
					`HTTP fetch request was aborted or failed in an unexpected way! URL = ${url.toString()}`
				); // This will allow it to retry
				// return false; // This will stop the retries
			}

			if (log_lvl) {
				console.log(
					`Response: status=${response.status} statusText=${response.statusText} url=${response.url} attempt=${attempt}`
				);
			}
			if (log_lvl > 1) {
				console.log('Response:', response);
			}

			if (!response.ok) {
				if (response.status === 404) {
					if (log_lvl) {
						console.log('The response was a 404 not found "error". Returning null.');
					}
					return null;
				}
				console.log('The response was not ok. Throwing an error!');
				throw new Error(`HTTP error! status: ${response.status}`);
			}

			if (!return_blob) {
				const json = await response.json();
				if (log_lvl > 1) {
					console.log('Response JSON:', json);
				}
				if (!Array.isArray(json.data) && as_list) {
					return [json.data];
				}
				return json.data || json;
			} else {
				const reader = response.body?.getReader();
				const contentLength = +response.headers.get('Content-Length')!;
				let receivedLength = 0;
				const chunks = [];

				while (true) {
					const { done, value } = await reader!.read();
					if (done) break;
					chunks.push(value);
					receivedLength += value.length;

					const percent_completed = Math.round((receivedLength * 100) / contentLength);
					if (log_lvl > 1) {
						console.log(
							'GET Blob Progress:',
							percent_completed,
							'Total:',
							contentLength,
							'Loaded:',
							receivedLength,
							'Percent Completed',
							percent_completed
						);
					}

					temp_get_blob_percent_completed = percent_completed;

					try {
						if (typeof window !== 'undefined') {
							window.postMessage(
								{
									type: 'api_download_blob',
									status: 'downloading',
									task_id: task_id,
									endpoint: endpoint,
									filename: filename,
									size_total: contentLength,
									size_loaded: receivedLength,
									percent_completed: percent_completed
								},
								'*'
							);
						}
					} catch (e) {
						console.error('Error posting message:', e);
					}
				}

				const blob = new Blob(chunks);
				if (auto_download) {
					const downloadUrl = window.URL.createObjectURL(blob);
					const link = document.createElement('a');
					link.href = downloadUrl;
					link.setAttribute('download', filename || 'download');
					document.body.appendChild(link);
					link.click();
					link.remove();
					return true;
				} else {
					return blob;
				}
			}
		} catch (error) {
			console.log(`API GET object request *fetch* error on attempt ${attempt}:`, error);

			if (attempt === retry_count) {
				console.log('Max retry attempts reached. Returning false.');
				return false;
			}

			// Log retry information
			if (log_lvl) {
				console.log(`Retrying... (${attempt}/${retry_count})`);
			}
		}
	}
};
