(function() {
	var is_handling = false;

	/**
	 * Handles the loading of a single script element. If the script element has a `data-type`
	 * attribute, it updates the script type accordingly. If the script element has a `data-src`
	 * attribute, it updates the source and attempts to load it.
	 *
	 * @param {HTMLElement} script_element
	 * @returns {Promise}
	 */
	function handle_script_element(script_element) {
		// If 'data-type' is defined, update the script element's type
		update_script_element_type(script_element);

		// If 'data-src' is defined, update the script's source and load it
		return update_script_element_src(script_element);
	}

	/**
	 * Recursively loads script elements one by one. Once a script is successfully loaded,
	 * the next script in the array is loaded. If all scripts are loaded, the function resolves,
	 * otherwise, it rejects on the first error encountered.
	 *
	 * @param {number} index - The current index of the script element being processed.
	 * @param {NodeListOf<HTMLElementTagNameMap[string]>} script_elements - An array of script elements to be loaded.
	 * @param {function} resolve - A function to call when all scripts have been successfully loaded.
	 * @param {function} reject - A function to call if an error occurs during loading of any script.
	 */
	function load_next_script(index, script_elements, resolve, reject) {

		if (index < script_elements.length) {
			handle_script_element(script_elements[index])
				.then(function() {
					load_next_script(index + 1, script_elements, resolve, reject);
				})
				.catch(function() {
					load_next_script(index + 1, script_elements, resolve, reject);
				});
		} else {
			all_scripts_loaded();
			resolve();
		}

	}

	/**
	 * For the given list of script elements with type="text/plain", replace the current script element
	 * with the appropriate type or replace the src attribute with the data-src value for externally loaded scripts.
	 *
	 * @param {NodeListOf<HTMLElementTagNameMap[string]>} script_elements
	 * @returns {Promise}
	 */
	function load_scripts_sequentially(script_elements) {
		// Wrap everything in a Promise to handle asynchronous loading
		return new Promise(function(resolve, reject) {
			load_next_script(0, script_elements, resolve, reject);
		});
	}

	/**
	 * Replaces the current script element with a new one, setting the type attribute to the value of data-type.
	 *
	 * @param {HTMLElement} script_element
	 */
	function update_script_element_type(script_element) {
		const data_type = script_element.getAttribute('data-type');
		const no_delay_js = script_element.hasAttribute('data-no-delay-js');

		if (!data_type || no_delay_js) return;
		
		script_element.type = data_type;
		script_element.removeAttribute('data-type');

		const new_script = script_element.cloneNode(true);

		// Replace the original script tag with the new one
		// We use replaceChild to exclude script running
		script_element.parentNode.replaceChild(new_script, script_element);
	}

	/**
	 * Updates the `src` attribute of a script element and removes its `data-src` attribute.
	 * Returns a promise that resolves when the script is successfully loaded or rejects if the script fails to load.
	 *
	 * @param {HTMLElement} script_element
	 * @returns {Promise}
	 */
	function update_script_element_src(script_element) {
		return new Promise(function(resolve, reject) {
			const no_delay_js = script_element.hasAttribute('data-no-delay-js');
			const data_src = script_element.getAttribute('data-src');

			// If there's no 'data-src', resolve the promise immediately
			if (!data_src || no_delay_js) {
				return resolve();
			}

			script_element.src = data_src;
			script_element.removeAttribute('data-src');

			// Event listener for script load completion
			script_element.onload = resolve;
			script_element.onerror = reject;
		});
	}


	/**
	 * Called when all delayed scripts have loaded. Triggers DOMContentLoaded and load events to run their handlers in delayed scripts.
	 */
	function all_scripts_loaded() {
		window.wpo_delayed_scripts_loaded = true;

		var event = new Event('DOMContentLoaded');
		document.dispatchEvent(event);
		// Create a new load event
		event = new Event('load');
		// Dispatch the load event on the window
		window.dispatchEvent(event);
	}

	/**
	 * List of events to trigger delayed script loading.
	 *
	 * @return {string[]}
	 */
	function get_event_list() {
		return [
			'scroll',
			'mousemove',
			'mouseover',
			'resize',
			'touchstart',
			'touchmove',
		];
	}

	/**
	 * Adds event listeners to trigger delayed script loading on user interaction.
	 */
	function attach_event_listeners() {
		get_event_list().forEach(function (event) {
			window.addEventListener(event, handle_delay_js);
		});
	}

	/**
	 * Removes event listeners that were added to trigger delayed script loading.
	 */
	function remove_event_listeners() {
		get_event_list().forEach(function (event) {
			window.removeEventListener(event, handle_delay_js);
		});
	}

	/**
	 * Get all script elements and load them if their loading was delayed.
	 *
	 * @return {void}
	 */
	function handle_delay_js() {

		if (is_handling) return;

		is_handling = true;

		// Remove all event listeners after the first call
		remove_event_listeners();

		const script_elements = document.querySelectorAll('script');

		load_scripts_sequentially(script_elements);
	}

	// Attach events on any manipulation with the page
	document.addEventListener('DOMContentLoaded', function () {
		if (!window.wpo_delayed_scripts_loaded) {
			attach_event_listeners();
		}
	});
})();
