(function() {
	const import_cache = {};

	function fetch_import(id) {
		return new Promise((fulfil, reject) => {
			curl([`https://bundle.run/${id}`]).then(module => {
				import_cache[id] = module;
				fulfil(module);
			}, err => {
				console.error(err.stack);
				reject(new Error(`Error loading ${id} from bundle.run`));
			});
		});
	}

	function fetch_imports(imports, progress_func) {
		const missing_imports = imports.filter(x => !import_cache[x]);
		let pending_imports = missing_imports.length;

		if (missing_imports.length) {
			let promise = Promise.all(
				missing_imports.map(id => fetch_import(id).then(() => {
					pending_imports -= 1;
					if (progress_func) progress_func(pending_imports);
				}))
			);

			return promise;
		} else {
			return Promise.resolve();
		}
	}

	function handle_message(ev) {
		let { action, cmd_id } = ev.data;
		const send_message = (payload) => parent.postMessage( { ...payload }, ev.origin);
		const send_reply = (payload) => send_message({ ...payload, cmd_id });
		const send_ok = () => send_reply({ action: 'cmd_ok' });
		const send_error = (message, stack) => send_reply({ action: 'cmd_error', message, stack });

		if (action === 'eval') {
			try {
				const { script } = ev.data.args;
				eval(script);
				send_ok();
			} catch (e) {
				send_error(e.message, e.stack);
			}
		}

		if (action === 'catch_clicks') {
			try {
				const top_origin = ev.origin;
				document.body.addEventListener('click', event => {
					if (event.which !== 1) return;
					if (event.metaKey || event.ctrlKey || event.shiftKey) return;
					if (event.defaultPrevented) return;

					// ensure target is a link
					let el = event.target;
					while (el && el.nodeName !== 'A') el = el.parentNode;
					if (!el || el.nodeName !== 'A') return;

					if (el.hasAttribute('download') || el.getAttribute('rel') === 'external' || el.target) return;

					event.preventDefault();

					if (el.href.startsWith(top_origin)) {
						const url = new URL(el.href);
						if (url.hash[0] === '#') {
							window.location.hash = url.hash;
							return;
						}
					}

					window.open(el.href, '_blank');
				});
				send_ok();
			} catch(e) {
				send_error(e.message, e.stack);
			}
		}

		if (action === 'fetch_imports') {
			const { imports, import_map } = ev.data.args;
			fetch_imports(imports, (remaining) => {
				send_message({action: 'fetch_progress', args: { remaining }});
			})
			.then(() => {
				imports.forEach(x=> {
					const module = import_cache[x];
					const name = import_map.get(x);
					window[name] = module;
				});
				send_ok();
			})
			.catch(e => {
				send_error(e.message, e.stack);
			});
		}
	}

	window.addEventListener('message', handle_message, false);
})();