(function (window, document) {
	'use strict';

	var config = window.sitynImageOptimizer || {};
	var strings = config.i18n || {};
	var storageKey = 'sitynImageOptimizerSettings';
	var state = loadSettings();
	var avifSupported = true;
	var avifEncodingSupported = false;
	var workerSupported = !!(window.URL && window.File && document.createElement('canvas').toBlob);
	var addFilePatched = false;
	var optimizingBeforeAdd = false;
	var patchTimer = null;
	var networkPatched = false;
	var avifEncoderModulePromise = null;
	var avifEncoderInitPromise = null;
	window.sitynImageOptimizerRuntime = {
		loaded: true,
		version: config.version || '',
		pluploadPatched: false,
		lastAction: 'loaded',
		lastError: ''
	};
	var stats = {
		totalOriginal: 0,
		totalOptimized: 0,
		active: false
	};
	var ui = {
		panels: [],
		fileRows: new Map()
	};

	function loadSettings() {
		var defaults = config.defaults || {};
		var saved = {};

		try {
			saved = JSON.parse(window.localStorage.getItem(storageKey) || '{}');
		} catch (error) {
			saved = {};
		}

		return Object.assign({
			enabled: true,
			format: 'webp',
			quality: 82,
			maxWidth: 1920
		}, defaults, saved);
	}

	function saveSettings() {
		try {
			window.localStorage.setItem(storageKey, JSON.stringify(state));
		} catch (error) {
			// Local storage can be disabled; upload optimization should still work.
		}
	}

	function testAvifSupport() {
		var canvas = document.createElement('canvas');
		var context = canvas.getContext('2d');

		canvas.width = 2;
		canvas.height = 2;

		if (context) {
			context.fillStyle = '#2563eb';
			context.fillRect(0, 0, 2, 2);
		}

		avifSupported = isModernImageBrowser();
		avifEncodingSupported = false;
		refreshPanels();

		if (!canvas.toBlob) {
			return;
		}

		canvas.toBlob(function (blob) {
			avifEncodingSupported = !!(blob && blob.size > 0 && blob.type === 'image/avif');
			avifSupported = avifSupported || avifEncodingSupported;
			refreshPanels();
		}, 'image/avif', 0.82);
	}

	function isModernImageBrowser() {
		var userAgent = window.navigator.userAgent || '';

		return /Chrome|Chromium|Edg|Firefox|Safari/i.test(userAgent) && !/MSIE|Trident/i.test(userAgent);
	}

	function mimeForFormat(format) {
		if (format === 'jpg') {
			return 'image/jpeg';
		}

		return 'image/' + format;
	}

	function formatBytes(bytes) {
		var units = ['B', 'KB', 'MB', 'GB'];
		var size = Math.max(0, bytes);
		var unit = 0;

		while (size >= 1024 && unit < units.length - 1) {
			size = size / 1024;
			unit++;
		}

		return (unit === 0 ? size : size.toFixed(1)) + ' ' + units[unit];
	}

	function isImageFile(file) {
		return file && /^image\/(jpeg|jpg|png|webp|avif)$/i.test(file.type || '');
	}

	function isOptimizedFile(file) {
		return !!(file && file.__sitynOptimized);
	}

	function getNativeFile(file) {
		if (!file) {
			return null;
		}

		if (typeof file.getNative === 'function') {
			return file.getNative();
		}

		return file.getSource ? file.getSource() : null;
	}

	function buildPanel() {
		var panel = document.createElement('section');
		var panelId = 'sityn-optimizer-' + Math.random().toString(16).slice(2);

		panel.className = 'sityn-optimizer-panel';
		panel.innerHTML = [
			'<div class="sityn-optimizer-panel__header">',
				'<div class="sityn-optimizer-brand">',
					'<span class="sityn-optimizer-logo" aria-hidden="true">S</span>',
					'<div>',
						'<strong>' + escapeHtml(strings.panelTitle || 'Sityn Image Optimizer') + '</strong>',
						'<span data-sityn-status>Ready</span>',
					'</div>',
				'</div>',
				'<label class="sityn-optimizer-switch"><input type="checkbox" data-sityn-setting="enabled"><span aria-hidden="true"></span><b>' + escapeHtml(strings.enableOptimizer || 'Optimize uploads') + '</b></label>',
			'</div>',
			'<div class="sityn-optimizer-grid">',
				'<div class="sityn-optimizer-field sityn-optimizer-field--format"><span>' + escapeHtml(strings.format || 'Format') + '</span><div class="sityn-optimizer-segmented" role="radiogroup">',
					'<label><input type="radio" name="' + panelId + '-format" value="webp" data-sityn-setting="format"><span>WebP</span></label>',
					'<label><input type="radio" name="' + panelId + '-format" value="jpg" data-sityn-setting="format"><span>JPG</span></label>',
					'<label><input type="radio" name="' + panelId + '-format" value="png" data-sityn-setting="format"><span>PNG</span></label>',
					'<label data-sityn-avif-wrap><input type="radio" name="' + panelId + '-format" value="avif" data-sityn-setting="format" data-sityn-avif><span>AVIF</span></label>',
				'</div></div>',
				'<label class="sityn-optimizer-field"><span>' + escapeHtml(strings.quality || 'Quality') + '</span><div class="sityn-optimizer-range"><input type="range" min="1" max="100" data-sityn-setting="quality"><output data-sityn-quality></output></div></label>',
				'<label class="sityn-optimizer-field"><span>' + escapeHtml(strings.maxWidth || 'Max Width') + '</span><div class="sityn-optimizer-number"><input type="number" min="0" step="10" data-sityn-setting="maxWidth"><small>' + escapeHtml(strings.pixels || 'px') + '</small></div></label>',
			'</div>',
			'<div class="sityn-optimizer-progress" hidden>',
				'<div class="sityn-optimizer-progress__label"><span>' + escapeHtml(strings.totalProgress || 'Batch Progress') + '</span><b data-sityn-total-percent>0%</b></div>',
				'<div class="sityn-optimizer-bar"><span data-sityn-total-bar></span></div>',
				'<div class="sityn-optimizer-files" data-sityn-files></div>',
				'<div class="sityn-optimizer-savings"><span>' + escapeHtml(strings.totalSaved || 'Total Data Saved') + '</span><strong data-sityn-total-saved>0 B</strong></div>',
			'</div>'
		].join('');

		bindPanel(panel);
		refreshPanel(panel);
		ui.panels.push(panel);

		return panel;
	}

	function bindPanel(panel) {
		panel.addEventListener('input', function (event) {
			var key = event.target.getAttribute('data-sityn-setting');

			if (!key) {
				return;
			}

			if (key === 'enabled') {
				state.enabled = event.target.checked;
			} else if (key === 'quality') {
				state.quality = parseInt(event.target.value, 10) || 82;
			} else if (key === 'maxWidth') {
				state.maxWidth = Math.max(0, parseInt(event.target.value, 10) || 0);
			} else if (key === 'format') {
				state.format = event.target.value;
			}

			saveSettings();
			refreshPanels();
		});
	}

	function refreshPanels() {
		ui.panels.forEach(refreshPanel);
	}

	function refreshPanel(panel) {
		var enabled = panel.querySelector('[data-sityn-setting="enabled"]');
		var formatInputs = panel.querySelectorAll('[data-sityn-setting="format"]');
		var quality = panel.querySelector('[data-sityn-setting="quality"]');
		var qualityOutput = panel.querySelector('[data-sityn-quality]');
		var maxWidth = panel.querySelector('[data-sityn-setting="maxWidth"]');
		var avifOption = panel.querySelector('[data-sityn-avif]');
		var avifWrap = panel.querySelector('[data-sityn-avif-wrap]');
		var status = panel.querySelector('[data-sityn-status]');

		enabled.checked = !!state.enabled;
		formatInputs.forEach(function (input) {
			input.checked = input.value === state.format;
		});
		quality.value = state.quality;
		qualityOutput.textContent = state.quality;
		maxWidth.value = state.maxWidth;

		if (avifOption) {
			avifOption.disabled = !avifSupported;
		}

		if (avifWrap) {
			avifWrap.hidden = !avifSupported;
		}

		if (state.format === 'avif' && !avifSupported) {
			state.format = 'webp';
			formatInputs.forEach(function (input) {
				input.checked = input.value === 'webp';
			});
			saveSettings();
		}

		if (status) {
			if (!workerSupported) {
				status.textContent = 'Unsupported browser';
			} else if (state.enabled && state.format === 'avif' && !avifEncodingSupported) {
				status.textContent = 'AVIF with WebP fallback';
			} else {
				status.textContent = state.enabled ? 'Optimizing uploads' : 'Paused';
			}
		}

		panel.classList.toggle('is-disabled', !state.enabled || !workerSupported);
	}

	function escapeHtml(value) {
		return String(value).replace(/[&<>"']/g, function (character) {
			return {
				'&': '&amp;',
				'<': '&lt;',
				'>': '&gt;',
				'"': '&quot;',
				"'": '&#039;'
			}[character];
		});
	}

	function injectPanels() {
		var uploadTargets = [
			document.querySelector('.media-modal .media-frame-content'),
			document.querySelector('#plupload-upload-ui'),
			document.querySelector('.media-frame.mode-grid .media-toolbar')
		].filter(Boolean);

		uploadTargets.forEach(function (target) {
			if (target.querySelector(':scope > .sityn-optimizer-panel')) {
				return;
			}

			target.insertBefore(buildPanel(), target.firstChild);
		});
	}

	function observeMediaUi() {
		injectPanels();

		var observer = new MutationObserver(function () {
			injectPanels();
		});

		observer.observe(document.body, {
			childList: true,
			subtree: true
		});
	}

	function patchPluploadAddFile() {
		if (!window.plupload || !window.plupload.Uploader || !window.plupload.Uploader.prototype || addFilePatched) {
			return;
		}

		var originalAddFile = window.plupload.Uploader.prototype.addFile;
		var originalStart = window.plupload.Uploader.prototype.start;

		window.plupload.Uploader.prototype.addFile = function (file, fileName) {
			var uploader = this;
			var entries = normalizeAddFileInput(file, fileName);
			var hasConvertible = entries.some(function (entry) {
				return entry.file && isImageFile(entry.file) && !isOptimizedFile(entry.file);
			});

			if (optimizingBeforeAdd || !state.enabled || !workerSupported || !hasConvertible) {
				return originalAddFile.call(uploader, file, fileName);
			}

			window.sitynImageOptimizerRuntime.lastAction = 'intercepted-addFile';
			optimizeBeforeAdd(uploader, entries, originalAddFile);
			return undefined;
		};

		window.plupload.Uploader.prototype.start = function () {
			var uploader = this;
			var queuedEntries = getQueuedConvertibleEntries(uploader);

			if (optimizingBeforeAdd || !state.enabled || !workerSupported || !queuedEntries.length) {
				return originalStart.apply(uploader, arguments);
			}

			window.sitynImageOptimizerRuntime.lastAction = 'intercepted-start';
			optimizeQueuedBeforeStart(uploader, queuedEntries, originalAddFile, originalStart);
			return undefined;
		};

		addFilePatched = true;
		window.sitynImageOptimizerRuntime.pluploadPatched = true;
		window.sitynImageOptimizerRuntime.lastAction = 'plupload-patched';

		if (patchTimer) {
			window.clearInterval(patchTimer);
			patchTimer = null;
		}
	}

	function ensurePluploadPatch() {
		patchPluploadAddFile();

		if (!addFilePatched && !patchTimer) {
			patchTimer = window.setInterval(patchPluploadAddFile, 250);
		}
	}

	function patchNetworkUploads() {
		if (networkPatched || !window.FormData || !window.File) {
			return;
		}

		patchFetchUploads();
		patchXhrUploads();
		networkPatched = true;
		window.sitynImageOptimizerRuntime.networkPatched = true;
	}

	function patchFetchUploads() {
		if (!window.fetch || window.fetch.__sitynPatched) {
			return;
		}

		var originalFetch = window.fetch;

		window.fetch = function (input, init) {
			var requestInit = init || {};
			var body = requestInit.body;

			if (state.enabled && workerSupported && body instanceof FormData) {
				return optimizeFormData(body).then(function (optimizedBody) {
					var nextInit = Object.assign({}, requestInit, { body: optimizedBody });
					window.sitynImageOptimizerRuntime.lastAction = optimizedBody === body ? 'fetch-pass' : 'fetch-optimized';
					return originalFetch.call(window, input, nextInit);
				}).catch(function (error) {
					window.sitynImageOptimizerRuntime.lastError = error && error.message ? error.message : 'Fetch optimization failed.';
					return originalFetch.apply(window, arguments);
				});
			}

			return originalFetch.apply(window, arguments);
		};

		window.fetch.__sitynPatched = true;
	}

	function patchXhrUploads() {
		if (!window.XMLHttpRequest || window.XMLHttpRequest.prototype.send.__sitynPatched) {
			return;
		}

		var originalSend = window.XMLHttpRequest.prototype.send;

		window.XMLHttpRequest.prototype.send = function (body) {
			var xhr = this;
			var args = arguments;

			if (state.enabled && workerSupported && body instanceof FormData) {
				optimizeFormData(body).then(function (optimizedBody) {
					window.sitynImageOptimizerRuntime.lastAction = optimizedBody === body ? 'xhr-pass' : 'xhr-optimized';
					originalSend.call(xhr, optimizedBody);
				}).catch(function (error) {
					window.sitynImageOptimizerRuntime.lastError = error && error.message ? error.message : 'XHR optimization failed.';
					originalSend.apply(xhr, args);
				});

				return undefined;
			}

			return originalSend.apply(xhr, args);
		};

		window.XMLHttpRequest.prototype.send.__sitynPatched = true;
	}

	async function optimizeFormData(formData) {
		var entries;
		var changed = false;
		var optimized = new FormData();
		var settings = {
			mime: mimeForFormat(state.format),
			quality: state.quality,
			maxWidth: state.maxWidth
		};

		if (typeof formData.entries !== 'function') {
			return formData;
		}

		entries = Array.from(formData.entries());

		for (var index = 0; index < entries.length; index++) {
			var key = entries[index][0];
			var value = entries[index][1];

			if (value instanceof File && isImageFile(value) && !isOptimizedFile(value)) {
				var result = await optimizeFile(value, settings, function () {});
				var optimizedFile = new File([result.blob], result.fileName, {
					type: result.mime,
					lastModified: Date.now()
				});

				optimizedFile.__sitynOptimized = true;
				optimized.append(key, optimizedFile, optimizedFile.name);
				changed = true;
			} else {
				optimized.append(key, value);
			}
		}

		return changed ? optimized : formData;
	}

	function normalizeAddFileInput(file, fileName) {
		if (Array.isArray(file)) {
			return file.map(function (item) {
				return normalizeSingleAddFileInput(item);
			});
		}

		if (file && file.files && typeof file.files.length === 'number') {
			return Array.prototype.slice.call(file.files).map(function (item) {
				return normalizeSingleAddFileInput(item);
			});
		}

		return [normalizeSingleAddFileInput(file, fileName)];
	}

	function normalizeSingleAddFileInput(item, fileName) {
		var nativeFile = getNativeFile(item);

		if (!nativeFile && item && typeof item.size === 'number' && typeof item.name === 'string') {
			nativeFile = item;
		}

		return {
			input: item,
			file: nativeFile,
			fileName: fileName || null
		};
	}

	async function optimizeBeforeAdd(uploader, entries, originalAddFile) {
		var outputFiles;

		if (optimizingBeforeAdd) {
			return;
		}

		optimizingBeforeAdd = true;

		try {
			outputFiles = await processQueue(entries);
			window.sitynImageOptimizerRuntime.lastAction = 'optimized-addFile';
			originalAddFile.call(uploader, outputFiles);
		} catch (error) {
			window.sitynImageOptimizerRuntime.lastError = error && error.message ? error.message : 'Optimization failed before add.';
			originalAddFile.call(uploader, entries.map(function (entry) {
				return entry.input;
			}));
		} finally {
			optimizingBeforeAdd = false;
		}
	}

	function getQueuedConvertibleEntries(uploader) {
		if (!uploader || !uploader.files || !uploader.files.length) {
			return [];
		}

		return Array.prototype.slice.call(uploader.files).filter(function (pluploadFile) {
			var nativeFile = getNativeFile(pluploadFile);
			var isQueued = !window.plupload || typeof window.plupload.QUEUED === 'undefined' || pluploadFile.status === window.plupload.QUEUED;

			return isQueued && nativeFile && isImageFile(nativeFile) && !isOptimizedFile(nativeFile) && !pluploadFile.__sitynOptimized;
		}).map(function (pluploadFile) {
			return {
				input: pluploadFile,
				file: getNativeFile(pluploadFile),
				pluploadFile: pluploadFile
			};
		});
	}

	async function optimizeQueuedBeforeStart(uploader, entries, originalAddFile, originalStart) {
		var outputFiles;

		if (optimizingBeforeAdd) {
			return;
		}

		optimizingBeforeAdd = true;
		uploader.stop();

		try {
			outputFiles = await processQueue(entries);
			window.sitynImageOptimizerRuntime.lastAction = 'optimized-start';
			entries.forEach(function (entry) {
				if (entry.pluploadFile) {
					entry.pluploadFile.__sitynOptimized = true;

					if (entry.pluploadFile.attachment && window.wp && wp.Uploader && wp.Uploader.queue) {
						wp.Uploader.queue.remove(entry.pluploadFile.attachment);
						entry.pluploadFile.attachment.destroy();
					}

					removeLegacyQueueRow(entry.pluploadFile);

					try {
						uploader.removeFile(entry.pluploadFile);
					} catch (error) {
						// The queue may already have removed the file after cancellation.
					}
				}
			});

			originalAddFile.call(uploader, outputFiles);
			originalStart.call(uploader);
		} catch (error) {
			window.sitynImageOptimizerRuntime.lastError = error && error.message ? error.message : 'Optimization failed before start.';
			originalStart.call(uploader);
		} finally {
			optimizingBeforeAdd = false;
		}
	}

	async function processQueue(entries) {
		var settings = {
			mime: mimeForFormat(state.format),
			quality: state.quality,
			maxWidth: state.maxWidth
		};
		var outputFiles = [];

		entries.forEach(function (entry, index) {
			if (entry.file) {
				entry.key = entry.file.name + '-' + index;
			}
		});

		stats.totalOriginal = 0;
		stats.totalOptimized = 0;
		stats.active = true;
		ui.fileRows.clear();
		resetProgress(entries.filter(function (entry) {
			return entry.file && isImageFile(entry.file) && !isOptimizedFile(entry.file);
		}));

		for (var index = 0; index < entries.length; index++) {
			var source = entries[index].file;
			var rowKey = entries[index].key;

			if (!source || !isImageFile(source) || isOptimizedFile(source)) {
				outputFiles.push(entries[index].input);
				continue;
			}

			stats.totalOriginal += source.size;
			setFileProgress(rowKey, 1, strings.processing || 'Processing images...');

			try {
				var result = await optimizeFile(source, settings, function (progress) {
					setFileProgress(rowKey, progress);
					setTotalProgress(index, entries.length, progress);
				});
				var optimizedFile = new File([result.blob], result.fileName, {
					type: result.mime,
					lastModified: Date.now()
				});

				optimizedFile.__sitynOptimized = true;
				stats.totalOptimized += result.optimizedSize;
				setFileProgress(rowKey, 100, formatBytes(source.size) + ' -> ' + formatBytes(result.optimizedSize));
				outputFiles.push(optimizedFile);
			} catch (error) {
				source.__sitynOptimized = true;
				stats.totalOptimized += source.size;
				setFileProgress(rowKey, 100, error && error.message ? error.message : 'Optimization failed; uploading original.');
				outputFiles.push(source);
			}
		}

		setTotalProgress(entries.length, entries.length, 100);
		stats.active = false;
		refreshSavings();

		return outputFiles;
	}

	function optimizeFile(file, settings, onProgress) {
		if (settings.mime === 'image/avif') {
			return optimizeFileOnMainThread(file, settings, onProgress);
		}

		if (typeof window.Worker === 'undefined') {
			return optimizeFileOnMainThread(file, settings, onProgress);
		}

		return new Promise(function (resolve, reject) {
			var worker;
			var id = Date.now() + '-' + Math.random().toString(16).slice(2);

			try {
				worker = new Worker(config.workerUrl);
			} catch (error) {
				optimizeFileOnMainThread(file, settings, onProgress).then(resolve).catch(reject);
				return;
			}

			worker.onmessage = function (event) {
				var message = event.data || {};

				if (message.id !== id) {
					return;
				}

				if (message.type === 'progress') {
					onProgress(message.progress || 0);
				}

				if (message.type === 'done') {
					worker.terminate();
					resolve(message);
				}

				if (message.type === 'error') {
					worker.terminate();
					optimizeFileOnMainThread(file, settings, onProgress).then(resolve).catch(function () {
						reject(new Error(message.message || 'Image optimization failed.'));
					});
				}
			};

			worker.onerror = function (error) {
				worker.terminate();
				optimizeFileOnMainThread(file, settings, onProgress).then(resolve).catch(function () {
					reject(new Error(error.message || 'Image optimization failed.'));
				});
			};

			worker.postMessage({
				id: id,
				file: file,
				settings: settings
			});
		});
	}

	function optimizeFileOnMainThread(file, settings, onProgress) {
		return loadImage(file).then(function (loaded) {
			var image = loaded.image;
			var objectUrl = loaded.objectUrl;
			var dimensions = dimensionsFor(image.naturalWidth || image.width, image.naturalHeight || image.height, settings.maxWidth);
			var canvas = document.createElement('canvas');
			var context;

			onProgress(35);
			canvas.width = dimensions.width;
			canvas.height = dimensions.height;
			context = canvas.getContext('2d', { alpha: settings.mime === 'image/png' });
			context.drawImage(image, 0, 0, dimensions.width, dimensions.height);
			onProgress(70);

			return encodeCanvasWithFallback(canvas, settings.mime, settings.quality).then(function (result) {
				onProgress(100);

				if (objectUrl) {
					URL.revokeObjectURL(objectUrl);
				}

				canvas.width = 1;
				canvas.height = 1;

				return {
					blob: result.blob,
					fileName: replaceExtension(file.name, result.mime),
					mime: result.mime,
					originalSize: file.size,
					optimizedSize: result.blob.size,
					width: dimensions.width,
					height: dimensions.height,
					fallback: result.fallback
				};
			}).catch(function (error) {
				if (objectUrl) {
					URL.revokeObjectURL(objectUrl);
				}

				canvas.width = 1;
				canvas.height = 1;
				throw error;
			});
		});
	}

	function loadImage(file) {
		return new Promise(function (resolve, reject) {
			var image = new Image();
			var objectUrl = URL.createObjectURL(file);

			image.onload = function () {
				resolve({
					image: image,
					objectUrl: objectUrl
				});
			};

			image.onerror = function () {
				URL.revokeObjectURL(objectUrl);
				reject(new Error('The browser could not decode this image.'));
			};

			image.src = objectUrl;
		});
	}

	function dimensionsFor(width, height, maxWidth) {
		var targetWidth = width;
		var targetHeight = height;
		var parsedMaxWidth = parseInt(maxWidth, 10) || 0;

		if (parsedMaxWidth > 0 && targetWidth > parsedMaxWidth) {
			targetHeight = Math.round(targetHeight * (parsedMaxWidth / targetWidth));
			targetWidth = parsedMaxWidth;
		}

		return {
			width: Math.max(1, targetWidth),
			height: Math.max(1, targetHeight)
		};
	}

	function encodeCanvasWithFallback(canvas, mime, quality) {
		if (mime === 'image/avif') {
			return encodeCanvasToAvif(canvas, quality).then(function (blob) {
				if (!blob || blob.size < 1) {
					throw new Error('The AVIF encoder returned an empty image.');
				}

				return {
					blob: blob,
					mime: 'image/avif',
					fallback: false
				};
			});
		}

		return canvasToBlob(canvas, mime, quality).then(function (blob) {
			if (!blob || blob.size < 1) {
				throw new Error('The browser returned an empty optimized image.');
			}

			return {
				blob: blob,
				mime: mime,
				fallback: false
			};
		}).catch(function (error) {
			if (mime !== 'image/avif') {
				throw error;
			}

			return canvasToBlob(canvas, 'image/webp', quality).then(function (blob) {
				if (!blob || blob.size < 1) {
					throw new Error('The browser returned an empty optimized image.');
				}

				return {
					blob: blob,
					mime: 'image/webp',
					fallback: true
				};
			});
		});
	}

	function encodeCanvasToAvif(canvas, quality) {
		var context = canvas.getContext('2d');
		var imageData;

		if (!context) {
			return Promise.reject(new Error('Canvas context is unavailable for AVIF encoding.'));
		}

		imageData = context.getImageData(0, 0, canvas.width, canvas.height);

		return getAvifEncoder().then(function (encoderModule) {
			return encoderModule.default(imageData, {
				quality: Math.max(1, Math.min(100, quality)),
				qualityAlpha: -1,
				speed: 6,
				subsample: 1,
				bitDepth: 8
			});
		}).then(function (buffer) {
			return new Blob([buffer], { type: 'image/avif' });
		});
	}

	function getAvifEncoder() {
		if (!config.avifEncoderUrl || !config.avifWasmUrl) {
			return Promise.reject(new Error('AVIF encoder assets are not configured.'));
		}

		if (!avifEncoderModulePromise) {
			avifEncoderModulePromise = import(config.avifEncoderUrl);
		}

		return avifEncoderModulePromise.then(function (encoderModule) {
			if (!avifEncoderInitPromise && typeof encoderModule.init === 'function') {
				avifEncoderInitPromise = encoderModule.init({
					locateFile: function (path) {
						if (path.indexOf('.wasm') !== -1) {
							return config.avifWasmUrl;
						}

						return path;
					}
				});
			}

			return (avifEncoderInitPromise || Promise.resolve()).then(function () {
				return encoderModule;
			});
		});
	}

	function canvasToBlob(canvas, mime, quality) {
		return new Promise(function (resolve, reject) {
			canvas.toBlob(function (blob) {
				if (blob) {
					resolve(blob);
				} else {
					reject(new Error('Canvas encoding failed.'));
				}
			}, mime, Math.min(1, Math.max(0.01, quality / 100)));
		});
	}

	function replaceExtension(name, mime) {
		var extension = mime === 'image/jpeg' ? 'jpg' : mime.replace('image/', '');
		var baseName = name.replace(/\.[^.]+$/, '');

		return baseName + '.' + extension;
	}

	function removeLegacyQueueRow(pluploadFile) {
		var row = document.getElementById('media-item-' + pluploadFile.id);

		if (row && row.parentNode) {
			row.parentNode.removeChild(row);
		}
	}

	function resetProgress(files) {
		ui.fileRows.clear();

		ui.panels.forEach(function (panel) {
			var progress = panel.querySelector('.sityn-optimizer-progress');
			var list = panel.querySelector('[data-sityn-files]');

			progress.hidden = false;
			list.innerHTML = '';

			files.forEach(function (entry) {
				var row = document.createElement('div');
				var rows = ui.fileRows.get(entry.key) || [];

				row.className = 'sityn-optimizer-file';
				row.innerHTML = '<span class="sityn-optimizer-file__name"></span><b>0%</b><div class="sityn-optimizer-bar"><span></span></div><small></small>';
				row.querySelector('.sityn-optimizer-file__name').textContent = entry.file.name;
				list.appendChild(row);
				rows.push(row);
				ui.fileRows.set(entry.key, rows);
			});
		});

		refreshSavings();
	}

	function setFileProgress(fileName, progress, detail) {
		var rows = ui.fileRows.get(fileName) || [];

		if (!rows.length) {
			return;
		}

		rows.forEach(function (row) {
			row.querySelector('b').textContent = Math.round(progress) + '%';
			row.querySelector('.sityn-optimizer-bar span').style.width = Math.max(0, Math.min(100, progress)) + '%';

			if (detail) {
				row.querySelector('small').textContent = detail;
			}
		});
	}

	function setTotalProgress(completedIndex, total, currentProgress) {
		var percent = total > 0 ? ((completedIndex + (currentProgress / 100)) / total) * 100 : 0;

		ui.panels.forEach(function (panel) {
			panel.querySelector('[data-sityn-total-percent]').textContent = Math.round(percent) + '%';
			panel.querySelector('[data-sityn-total-bar]').style.width = Math.max(0, Math.min(100, percent)) + '%';
		});

		refreshSavings();
	}

	function refreshSavings() {
		var saved = Math.max(0, stats.totalOriginal - stats.totalOptimized);

		ui.panels.forEach(function (panel) {
			panel.querySelector('[data-sityn-total-saved]').textContent = formatBytes(saved);
		});
	}

	function init() {
		testAvifSupport();
		ensurePluploadPatch();
		patchNetworkUploads();
		observeMediaUi();
		refreshPanels();
	}

	ensurePluploadPatch();
	patchNetworkUploads();

	if (document.readyState === 'loading') {
		document.addEventListener('DOMContentLoaded', init);
	} else {
		init();
	}
}(window, document));
