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

    if (window.NoesiaSwaUniversalLocalConverter) {
        return;
    }

    var dependencyChecks = {
        jszip: function () {
            return !!window.JSZip;
        },
        pdf: function () {
            return !!window.pdfjsLib;
        },
        heic: function () {
            return typeof window.heic2any === 'function';
        },
        xlsx: function () {
            return !!window.XLSX;
        },
        jspdf: function () {
            return !!(window.jspdf && window.jspdf.jsPDF);
        },
        dompurify: function () {
            return !!(window.DOMPurify && typeof window.DOMPurify.sanitize === 'function');
        },
        rtf: function () {
            return !!window.RTFJS;
        },
        utif: function () {
            return !!window.UTIF;
        },
        gif: function () {
            return !!window.NoesiaSwaGifuct;
        }
    };

    var XLSX_MIME = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';

function ensureDependencies(names, config) {
	var requested = Array.isArray(names) ? names : [];
	var waitForPdf = requested.indexOf('pdf') !== -1 &&
		window.noesiaSwaPdfJsReadyPromise &&
		!(window.pdfjsLib && typeof window.pdfjsLib.getDocument === 'function');

	return (waitForPdf ? window.noesiaSwaPdfJsReadyPromise : Promise.resolve()).then(function () {
		var missing = [];

		requested.forEach(function (name) {
			if (!dependencyChecks[name] || !dependencyChecks[name]()) {
				missing.push(name);
			}
		});

		if (missing.length) {
			return Promise.reject(new Error('Missing registered browser-local dependencies: ' + missing.join(', ')));
		}

		if (requested.indexOf('pdf') !== -1 && window.pdfjsLib && window.pdfjsLib.GlobalWorkerOptions) {
			var globalConfig = window.noesiaSwaImageToolsConverterConfig || {};
			window.pdfjsLib.GlobalWorkerOptions.workerSrc = globalConfig.pdfWorkerSrc || (config.vendorBaseUrl + 'pdf.worker.min.js');
		}

		return true;
	});
}




    function ensureIndependentEncoders(config) {
        if (config.independentEncoderPromise) {
            return config.independentEncoderPromise;
        }

        config.independentEncoderPromise = Promise.resolve().then(function () {
            if (window.NoesiaSwaIndependentEncoders && typeof window.NoesiaSwaIndependentEncoders.probe === 'function') {
                return window.NoesiaSwaIndependentEncoders;
            }

            if (window.NoesiaSwaIndependentEncodersReady && typeof window.NoesiaSwaIndependentEncodersReady.then === 'function') {
                return window.NoesiaSwaIndependentEncodersReady;
            }

            throw new Error('The self-hosted WEBP / AVIF encoder stack is not available on this admin screen.');
        }).then(function (encoders) {
            config.independentEncoders = encoders;
            return encoders.probe();
        }).then(function (probe) {
            config.independentEncoderError = null;
            return {
                webp: !!(probe && probe.webp),
                avif: !!(probe && probe.avif)
            };
        }).catch(function (error) {
            config.independentEncoders = null;
            config.independentEncoderError = error;
            return {
                webp: false,
                avif: false
            };
        });

        return config.independentEncoderPromise;
    }


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

    function readArrayBuffer(file) {
        return new Promise(function (resolve, reject) {
            var reader = new FileReader();
            reader.onload = function () {
                resolve(reader.result);
            };
            reader.onerror = function () {
                reject(new Error('Unable to read the selected file.'));
            };
            reader.readAsArrayBuffer(file);
        });
    }

    function readText(file) {
        return new Promise(function (resolve, reject) {
            var reader = new FileReader();
            reader.onload = function () {
                resolve(typeof reader.result === 'string' ? reader.result : '');
            };
            reader.onerror = function () {
                reject(new Error('Unable to read the selected file.'));
            };
            reader.readAsText(file);
        });
    }

    function slugifyBaseName(name) {
        return String(name || 'file')
            .replace(/\.[^.]+$/, '')
            .trim()
            .replace(/[^a-zA-Z0-9_-]+/g, '-')
            .replace(/-+/g, '-')
            .replace(/^-|-$/g, '') || 'file';
    }

    function fileExtension(name) {
        var parts = String(name || '').toLowerCase().split('.');
        return parts.length > 1 ? parts.pop() : '';
    }

    function getFileKind(file) {
        var extension = fileExtension(file && file.name ? file.name : '');
        var type = String(file && file.type ? file.type : '').toLowerCase();

        if (extension === 'heic' || extension === 'heif' || type === 'image/heic' || type === 'image/heif') {
            return 'heic';
        }
        if (extension === 'svg' || type === 'image/svg+xml') {
            return 'svg';
        }
        if (extension === 'tif' || extension === 'tiff' || type === 'image/tiff') {
            return 'tiff';
        }
        if (extension === 'gif' || type === 'image/gif') {
            return 'gif';
        }
        if (type.indexOf('image/') === 0 || ['png', 'jpg', 'jpeg', 'webp', 'avif', 'bmp'].indexOf(extension) !== -1) {
            return 'image';
        }
        if (extension === 'pdf' || type === 'application/pdf') {
            return 'pdf';
        }
        if (extension === 'txt' || type.indexOf('text/plain') === 0) {
            return 'txt';
        }
        if (extension === 'csv' || type.indexOf('text/csv') === 0 || type === 'application/csv') {
            return 'csv';
        }
        if (extension === 'json' || type === 'application/json') {
            return 'json';
        }
        if (['xlsx', 'xlsm', 'xlsb', 'xls'].indexOf(extension) !== -1 || type.indexOf('spreadsheetml') !== -1 || type.indexOf('ms-excel') !== -1) {
            return 'xlsx';
        }
        if (extension === 'docx' || type.indexOf('wordprocessingml') !== -1) {
            return 'docx';
        }
        if (extension === 'doc' || type === 'application/msword') {
            return 'doc';
        }
        if (extension === 'rtf' || type === 'application/rtf' || type === 'text/rtf') {
            return 'rtf';
        }
        return 'unknown';
    }

    function isImageKind(kind) {
        return kind === 'image' || kind === 'heic' || kind === 'svg';
    }

    var exportSupportCache = {};

    function dataUrlSupportsMime(type) {
        try {
            var canvas = document.createElement('canvas');
            return canvas.toDataURL(type).indexOf('data:' + type) === 0;
        } catch (error) {
            return false;
        }
    }

    function probeCanvasExportSupport(type) {
        if (Object.prototype.hasOwnProperty.call(exportSupportCache, type)) {
            return exportSupportCache[type];
        }

        exportSupportCache[type] = new Promise(function (resolve) {
            var canvas;
            var context;
            var resolved = false;

            function finish(value) {
                if (!resolved) {
                    resolved = true;
                    resolve(!!value);
                }
            }

            if (type === 'image/png') {
                finish(true);
                return;
            }

            if (!dataUrlSupportsMime(type)) {
                finish(false);
                return;
            }

            canvas = document.createElement('canvas');
            canvas.width = 2;
            canvas.height = 2;
            context = canvas.getContext('2d');

            if (!context) {
                finish(false);
                return;
            }

            context.fillStyle = '#0f172a';
            context.fillRect(0, 0, 2, 2);

            if (typeof canvas.toBlob !== 'function') {
                finish(dataUrlSupportsMime(type));
                return;
            }

            try {
                canvas.toBlob(function (blob) {
                    if (!blob) {
                        finish(false);
                        return;
                    }
                    if (blob.type === type) {
                        finish(true);
                        return;
                    }
                    finish(!blob.type && dataUrlSupportsMime(type));
                }, type, 0.92);
                window.setTimeout(function () {
                    finish(false);
                }, 800);
            } catch (error) {
                finish(false);
            }
        });

        return exportSupportCache[type];
    }

    function detectCanvasExportSupport(type) {
        return dataUrlSupportsMime(type);
    }

    function imageMimeToExtension(type) {
        if (type === 'image/jpeg') {
            return 'jpg';
        }
        if (type === 'image/png') {
            return 'png';
        }
        if (type === 'image/webp') {
            return 'webp';
        }
        if (type === 'image/avif') {
            return 'avif';
        }
        return 'bin';
    }

    function routeLabel(route) {
        switch (route) {
        case 'image/jpeg':
            return 'JPG';
        case 'image/png':
            return 'PNG';
        case 'image/webp':
            return 'WEBP';
        case 'image/avif':
            return 'AVIF';
        case 'application/pdf':
            return 'PDF';
        case 'application/json':
            return 'JSON';
        case 'text/csv':
            return 'CSV';
        case 'text/plain':
            return 'TXT';
        case 'text/html':
            return 'HTML';
        case XLSX_MIME:
            return 'XLSX';
        default:
            return route;
        }
    }

    function isRasterImageRoute(route) {
        return ['image/jpeg', 'image/png', 'image/webp', 'image/avif'].indexOf(route) !== -1;
    }

    function isLossyImageRoute(route) {
        return ['image/jpeg', 'image/webp', 'image/avif'].indexOf(route) !== -1;
    }

    function parseQualityValue(value) {
        var quality = parseFloat(value || 0.92);
        if (!window.isFinite(quality)) {
            quality = 0.92;
        }
        return Math.max(0.4, Math.min(1, quality));
    }

    function parseMaxEdgeValue(value) {
        var maxEdge = parseInt(value || '0', 10);
        if (!window.isFinite(maxEdge) || maxEdge < 0) {
            maxEdge = 0;
        }
        return maxEdge;
    }

    function downloadBlob(blob, fileName) {
        var url = window.URL.createObjectURL(blob);
        var anchor = document.createElement('a');
        anchor.href = url;
        anchor.download = fileName;
        anchor.rel = 'noopener';
        document.body.appendChild(anchor);
        anchor.click();
        document.body.removeChild(anchor);
        window.setTimeout(function () {
            window.URL.revokeObjectURL(url);
        }, 2000);
    }

    function blobToObjectUrl(blob) {
        return window.URL.createObjectURL(blob);
    }

    function revokeUrl(url) {
        if (url) {
            window.URL.revokeObjectURL(url);
        }
    }

    function arrayBufferToBlob(buffer, type) {
        return new Blob([buffer], { type: type || 'application/octet-stream' });
    }

    function loadImageFromBlob(blob) {
        return new Promise(function (resolve, reject) {
            var url = blobToObjectUrl(blob);
            var image = new Image();
            image.onload = function () {
                revokeUrl(url);
                resolve(image);
            };
            image.onerror = function () {
                revokeUrl(url);
                reject(new Error('Unable to decode this image in the current browser.'));
            };
            image.src = url;
        });
    }

    function resolveCanvasDimensions(width, height, maxEdge) {
        var normalizedWidth = Math.max(1, Math.round(width || 1));
        var normalizedHeight = Math.max(1, Math.round(height || 1));
        var longestEdge = Math.max(normalizedWidth, normalizedHeight);

        if (!maxEdge || longestEdge <= maxEdge) {
            return {
                width: normalizedWidth,
                height: normalizedHeight,
                scaled: false
            };
        }

        var ratio = maxEdge / longestEdge;
        return {
            width: Math.max(1, Math.round(normalizedWidth * ratio)),
            height: Math.max(1, Math.round(normalizedHeight * ratio)),
            scaled: true
        };
    }

    function drawImageToCanvas(image, targetMime, options) {
        var settings = options || {};
        var maxEdge = parseMaxEdgeValue(settings.maxEdge);
        var sourceWidth = image.naturalWidth || image.width || 1;
        var sourceHeight = image.naturalHeight || image.height || 1;
        var dimensions = resolveCanvasDimensions(sourceWidth, sourceHeight, maxEdge);
        var canvas = document.createElement('canvas');
        var context = canvas.getContext('2d');
        if (!context) {
            throw new Error('Canvas is not available in this browser.');
        }
        canvas.width = dimensions.width;
        canvas.height = dimensions.height;
        if (isLossyImageRoute(targetMime)) {
            context.fillStyle = '#ffffff';
            context.fillRect(0, 0, canvas.width, canvas.height);
        }
        context.drawImage(image, 0, 0, canvas.width, canvas.height);
        return canvas;
    }

    function resizeCanvasIfNeeded(canvas, maxEdge, backgroundColor) {
        var dimensions = resolveCanvasDimensions(canvas.width, canvas.height, parseMaxEdgeValue(maxEdge));
        var resized;
        var context;
        if (!dimensions.scaled) {
            return canvas;
        }
        resized = document.createElement('canvas');
        resized.width = dimensions.width;
        resized.height = dimensions.height;
        context = resized.getContext('2d');
        if (!context) {
            throw new Error('Canvas rendering is not available in this browser.');
        }
        if (backgroundColor) {
            context.fillStyle = backgroundColor;
            context.fillRect(0, 0, resized.width, resized.height);
        }
        context.drawImage(canvas, 0, 0, resized.width, resized.height);
        return resized;
    }

    function dataUrlToBlob(dataUrl) {
        var parts = String(dataUrl || '').split(',');
        var header = parts[0] || '';
        var mimeMatch = header.match(/data:([^;]+)/i);
        var mimeType = mimeMatch ? mimeMatch[1] : 'application/octet-stream';
        var binary = window.atob(parts[1] || '');
        var length = binary.length;
        var bytes = new Uint8Array(length);
        var index;

        for (index = 0; index < length; index += 1) {
            bytes[index] = binary.charCodeAt(index);
        }

        return new Blob([bytes], { type: mimeType });
    }

    function canvasToBlob(canvas, mimeType, quality) {
        var normalizedQuality = parseQualityValue(quality);
        return new Promise(function (resolve, reject) {
            function fallbackToDataUrl() {
                var dataUrl;
                try {
                    dataUrl = canvas.toDataURL(mimeType, mimeType === 'image/png' ? undefined : normalizedQuality);
                } catch (error) {
                    reject(new Error('Unable to export the selected format in the current browser.'));
                    return;
                }
                if (dataUrl.indexOf('data:' + mimeType) !== 0) {
                    reject(new Error('The current browser did not return the requested output format.'));
                    return;
                }
                resolve(dataUrlToBlob(dataUrl));
            }

            if (typeof canvas.toBlob !== 'function') {
                fallbackToDataUrl();
                return;
            }

            canvas.toBlob(function (blob) {
                if (!blob) {
                    fallbackToDataUrl();
                    return;
                }
                if (mimeType !== 'image/png' && blob.type && blob.type !== mimeType) {
                    fallbackToDataUrl();
                    return;
                }
                resolve(blob);
            }, mimeType, mimeType === 'image/png' ? undefined : normalizedQuality);
        });
    }

    function blobToDataUrl(blob) {
        return new Promise(function (resolve, reject) {
            var reader = new FileReader();
            reader.onload = function () {
                resolve(reader.result);
            };
            reader.onerror = function () {
                reject(new Error('Unable to preview this output.'));
            };
            reader.readAsDataURL(blob);
        });
    }

    function humanBytes(bytes) {
        var value = Number(bytes || 0);
        var units = ['B', 'KB', 'MB', 'GB'];
        var index = 0;
        while (value >= 1024 && index < units.length - 1) {
            value /= 1024;
            index += 1;
        }
        return value.toFixed(value >= 100 || index === 0 ? 0 : 1) + ' ' + units[index];
    }

    function parsePageRange(value, totalPages) {
        var pages = [];
        var tokens;
        var normalizedValue = String(value || '').trim().toLowerCase();
        if (!normalizedValue || normalizedValue === 'all' || normalizedValue === '*') {
            for (var all = 1; all <= totalPages; all += 1) {
                pages.push(all);
            }
            return pages;
        }
        tokens = normalizedValue.split(',');
        tokens.forEach(function (token) {
            var trimmed = token.trim();
            var rangeParts;
            var start;
            var end;
            var step;
            if (!trimmed) {
                return;
            }
            if (trimmed.indexOf('-') !== -1) {
                rangeParts = trimmed.split('-');
                start = rangeParts[0] === '' ? 1 : parseInt(rangeParts[0], 10);
                end = rangeParts[1] === '' ? totalPages : parseInt(rangeParts[1], 10);
                if (!window.isFinite(start) || !window.isFinite(end)) {
                    return;
                }
                step = start <= end ? 1 : -1;
                for (var page = start; step > 0 ? page <= end : page >= end; page += step) {
                    if (page >= 1 && page <= totalPages && pages.indexOf(page) === -1) {
                        pages.push(page);
                    }
                }
                return;
            }
            start = parseInt(trimmed, 10);
            if (window.isFinite(start) && start >= 1 && start <= totalPages && pages.indexOf(start) === -1) {
                pages.push(start);
            }
        });
        pages.sort(function (left, right) {
            return left - right;
        });
        return pages.length ? pages : [];
    }

    function sanitizeHtml(html) {
        if (window.DOMPurify && typeof window.DOMPurify.sanitize === 'function') {
            return window.DOMPurify.sanitize(html, {
                USE_PROFILES: { html: true }
            });
        }
        return html;
    }

    function normalizeHeicResult(result) {
        if (Array.isArray(result)) {
            return result[0] || null;
        }
        return result || null;
    }

    async function decodeHeicToDisplayBlob(file) {
        var result = await window.heic2any({
            blob: file,
            toType: 'image/png',
            quality: 1
        });
        result = normalizeHeicResult(result);
        if (!result) {
            throw new Error('Unable to convert the HEIC file in the current browser.');
        }
        return result;
    }

    function getCanvasContext2d(canvas) {
        var context = canvas.getContext('2d');
        if (!context) {
            throw new Error('Canvas rendering is not available in this browser.');
        }
        return context;
    }

    function getPngPaletteSize(quality) {
        var normalizedQuality = parseQualityValue(quality);
        if (normalizedQuality >= 0.97) {
            return 256;
        }
        if (normalizedQuality >= 0.93) {
            return 192;
        }
        if (normalizedQuality >= 0.88) {
            return 160;
        }
        if (normalizedQuality >= 0.82) {
            return 128;
        }
        if (normalizedQuality >= 0.74) {
            return 96;
        }
        if (normalizedQuality >= 0.66) {
            return 64;
        }
        if (normalizedQuality >= 0.56) {
            return 48;
        }
        if (normalizedQuality >= 0.48) {
            return 32;
        }
        return 16;
    }

    function bufferToArrayBuffer(buffer) {
        if (buffer instanceof ArrayBuffer) {
            return buffer;
        }
        if (ArrayBuffer.isView(buffer)) {
            return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
        }
        return null;
    }

    async function encodePngQuantized(canvas, quality) {
        var upng = window.UPNG;
        var context;
        var imageData;
        var encoded;
        var paletteSize;
        var arrayBuffer;

        if (!upng || typeof upng.encode !== 'function') {
            return null;
        }

        context = getCanvasContext2d(canvas);
        imageData = context.getImageData(0, 0, canvas.width, canvas.height);
        paletteSize = getPngPaletteSize(quality);

        try {
            encoded = upng.encode([imageData.data.buffer], canvas.width, canvas.height, paletteSize);
            arrayBuffer = bufferToArrayBuffer(encoded);
            if (!arrayBuffer) {
                return null;
            }
            return new Blob([arrayBuffer], { type: 'image/png' });
        } catch (error) {
            return null;
        }
    }

    async function canvasToRasterBlob(canvas, mimeType, quality, config) {
        var normalizedQuality = parseQualityValue(quality);
        var context;
        var imageData;
        var encodedBuffer;
        var canvasSupported;
        var pngBlob;

        if (mimeType === 'image/png') {
            pngBlob = await encodePngQuantized(canvas, normalizedQuality);
            if (pngBlob && pngBlob.size) {
                return pngBlob;
            }
            return canvasToBlob(canvas, mimeType, normalizedQuality);
        }

        if (mimeType === 'image/webp' || mimeType === 'image/avif') {
            await ensureIndependentEncoders(config);

            if (config.independentEncoders) {
                try {
                    context = getCanvasContext2d(canvas);
                    imageData = context.getImageData(0, 0, canvas.width, canvas.height);
                    if (mimeType === 'image/webp') {
                        encodedBuffer = await config.independentEncoders.encodeWebP(imageData, { quality: normalizedQuality });
                    } else {
                        encodedBuffer = await config.independentEncoders.encodeAVIF(imageData, { quality: normalizedQuality });
                    }
                    return arrayBufferToBlob(encodedBuffer, mimeType);
                } catch (error) {
                    config.independentEncoderError = error;
                }
            }

            canvasSupported = await probeCanvasExportSupport(mimeType);
            if (canvasSupported) {
                return canvasToBlob(canvas, mimeType, normalizedQuality);
            }

            throw new Error('The requested raster export format is not available in the current browser.');
        }

        return canvasToBlob(canvas, mimeType, normalizedQuality);
    }

    async function canvasToPreviewBlob(canvas) {
        return canvasToBlob(canvas, 'image/png');
    }

    async function convertDecodedImageBlob(blob, targetMime, options, config) {
        var image = await loadImageFromBlob(blob);
        var canvas = drawImageToCanvas(image, targetMime, options || {});
        return canvasToRasterBlob(canvas, targetMime, options && options.quality, config);
    }

    async function convertHeicToRoute(file, targetMime, options, config) {
        var decoded = await decodeHeicToDisplayBlob(file);
        if (targetMime === 'image/png' && !parseMaxEdgeValue(options && options.maxEdge)) {
            return decoded;
        }
        return convertDecodedImageBlob(decoded, targetMime, options || {}, config);
    }

    async function convertStandardImage(file, targetMime, options, config) {
        return convertDecodedImageBlob(file, targetMime, options || {}, config);
    }

    async function makePdfFromImageBlobs(imageBlobs, outputName) {
        var jsPDF = window.jspdf && window.jspdf.jsPDF ? window.jspdf.jsPDF : null;
        var doc = null;
        var index;

        if (!jsPDF) {
            throw new Error('The local PDF generator is not available.');
        }

        for (index = 0; index < imageBlobs.length; index += 1) {
            var blob = imageBlobs[index];
            var image = await loadImageFromBlob(blob);
            var canvas = drawImageToCanvas(image, 'image/png');
            var dataUrl = canvas.toDataURL('image/png');
            var pageWidth = Math.max(1, Math.round(canvas.width * 0.75));
            var pageHeight = Math.max(1, Math.round(canvas.height * 0.75));
            var orientation = pageWidth >= pageHeight ? 'landscape' : 'portrait';

            if (!doc) {
                doc = new jsPDF({
                    orientation: orientation,
                    unit: 'pt',
                    format: [pageWidth, pageHeight],
                    compress: true
                });
            } else {
                doc.addPage([pageWidth, pageHeight], orientation);
            }

            doc.addImage(dataUrl, 'PNG', 0, 0, pageWidth, pageHeight, undefined, 'FAST');
        }

        if (!doc) {
            throw new Error('No image pages were available for PDF export.');
        }

        return {
            blob: doc.output('blob'),
            filename: outputName,
            previewType: 'pdf',
            previewText: 'PDF export is ready to download.',
            outputSummary: imageBlobs.length > 1 ? (imageBlobs.length + ' images were merged into one PDF.') : 'The image was exported as PDF.'
        };
    }

    async function convertMultipleImages(files, targetMime, options, config) {
        var zip = new window.JSZip();
        var total = 0;
        var extension = imageMimeToExtension(targetMime);
        var quality = parseQualityValue(options && options.quality);
        var maxEdge = parseMaxEdgeValue(options && options.maxEdge);

        for (var index = 0; index < files.length; index += 1) {
            var entry = files[index];
            var kind = getFileKind(entry);
            var blob;
            if (kind === 'heic') {
                blob = await convertHeicToRoute(entry, targetMime, { quality: quality, maxEdge: maxEdge }, config);
            } else if (kind === 'svg') {
                blob = await convertSvgToRoute(entry, targetMime, { quality: quality, maxEdge: maxEdge }, config);
            } else {
                blob = await convertStandardImage(entry, targetMime, { quality: quality, maxEdge: maxEdge }, config);
            }
            zip.file(slugifyBaseName(entry.name) + '.' + extension, blob);
            total += 1;
        }

        return {
            blob: await zip.generateAsync({ type: 'blob' }),
            filename: 'converted-images-' + extension + '.zip',
            previewType: 'zip',
            outputSummary: total + ' images were converted to ' + routeLabel(targetMime) + ' and zipped together.'
        };
    }

    function canvasFromRgbaPixels(rgba, width, height, backgroundColor) {
        var canvas = document.createElement('canvas');
        canvas.width = Math.max(1, Math.round(width || 1));
        canvas.height = Math.max(1, Math.round(height || 1));
        var context = getCanvasContext2d(canvas);
        if (backgroundColor) {
            context.fillStyle = backgroundColor;
            context.fillRect(0, 0, canvas.width, canvas.height);
        }
        context.putImageData(new ImageData(new Uint8ClampedArray(rgba), canvas.width, canvas.height), 0, 0);
        return canvas;
    }

    function cloneCanvas(canvas) {
        var clone = document.createElement('canvas');
        clone.width = canvas.width;
        clone.height = canvas.height;
        getCanvasContext2d(clone).drawImage(canvas, 0, 0);
        return clone;
    }

    async function exportCanvasSequenceToRoute(canvases, targetMime, baseName, options, config, unitLabel) {
        var extension = imageMimeToExtension(targetMime);
        var quality = parseQualityValue(options && options.quality);
        var maxEdge = parseMaxEdgeValue(options && options.maxEdge);
        var outputs = [];
        var zip;
        var finalCanvas;
        var blob;
        var previewBlob;
        var label = unitLabel || 'pages';

        if (!canvases.length) {
            throw new Error('No raster surfaces were available for export.');
        }

        if (targetMime === 'application/pdf') {
            return makePdfFromCanvases(canvases, baseName + '.pdf', label);
        }

        for (var index = 0; index < canvases.length; index += 1) {
            finalCanvas = resizeCanvasIfNeeded(canvases[index], maxEdge, isLossyImageRoute(targetMime) ? '#ffffff' : '');
            blob = await canvasToRasterBlob(finalCanvas, targetMime, quality, config);
            previewBlob = await canvasToPreviewBlob(finalCanvas);
            outputs.push({
                blob: blob,
                previewBlob: previewBlob,
                filename: baseName + '-' + label.slice(0, -1) + '-' + (index + 1) + '.' + extension,
                index: index + 1
            });
        }

        if (outputs.length === 1) {
            return {
                blob: outputs[0].blob,
                previewBlob: outputs[0].previewBlob,
                filename: outputs[0].filename,
                previewType: 'image',
                outputSummary: label.slice(0, -1).charAt(0).toUpperCase() + label.slice(1, -1) + ' ' + outputs[0].index + ' was exported as ' + routeLabel(targetMime) + '.'
            };
        }

        zip = new window.JSZip();
        outputs.forEach(function (entry) {
            zip.file(entry.filename, entry.blob);
        });

        return {
            blob: await zip.generateAsync({ type: 'blob' }),
            filename: baseName + '-' + extension + '-' + label + '.zip',
            previewType: 'zip',
            outputSummary: outputs.length + ' ' + label + ' were exported as ' + routeLabel(targetMime) + ' and zipped together.'
        };
    }

    async function makePdfFromCanvases(canvases, outputName, unitLabel) {
        var jsPDF = window.jspdf && window.jspdf.jsPDF ? window.jspdf.jsPDF : null;
        var doc = null;
        var label = unitLabel || 'pages';

        if (!jsPDF) {
            throw new Error('The local PDF generator is not available.');
        }

        for (var index = 0; index < canvases.length; index += 1) {
            var canvas = canvases[index];
            var pageWidth = Math.max(1, Math.round(canvas.width * 0.75));
            var pageHeight = Math.max(1, Math.round(canvas.height * 0.75));
            var orientation = pageWidth >= pageHeight ? 'landscape' : 'portrait';
            var dataUrl = canvas.toDataURL('image/png');

            if (!doc) {
                doc = new jsPDF({ orientation: orientation, unit: 'pt', format: [pageWidth, pageHeight], compress: true });
            } else {
                doc.addPage([pageWidth, pageHeight], orientation);
            }

            doc.addImage(dataUrl, 'PNG', 0, 0, pageWidth, pageHeight, undefined, 'FAST');
        }

        return {
            blob: doc.output('blob'),
            filename: outputName,
            previewType: 'pdf',
            previewText: 'PDF export is ready to download.',
            outputSummary: canvases.length > 1 ? (canvases.length + ' ' + label + ' were exported into one PDF.') : 'The file was exported as PDF.'
        };
    }

    async function decodeSvgToCanvas(file, targetMime, options) {
        var image = await loadImageFromBlob(file);
        return drawImageToCanvas(image, targetMime || 'image/png', options || {});
    }

    async function convertSvgToRoute(file, targetMime, options, config) {
        var canvas = await decodeSvgToCanvas(file, targetMime, options);
        return canvasToRasterBlob(canvas, targetMime, options && options.quality, config);
    }

    async function readTiffMetadata(file) {
        var buffer = await readArrayBuffer(file);
        var ifds = window.UTIF.decode(buffer) || [];
        return {
            pageCount: ifds.length || 0
        };
    }

    async function decodeTiffCanvases(file) {
        var buffer = await readArrayBuffer(file);
        var ifds = window.UTIF.decode(buffer) || [];
        var canvases = [];

        if (!ifds.length) {
            throw new Error('No TIFF pages were found in the selected file.');
        }

        ifds.forEach(function (ifd) {
            window.UTIF.decodeImage(buffer, ifd);
            var rgba = window.UTIF.toRGBA8(ifd);
            canvases.push(canvasFromRgbaPixels(rgba, ifd.width || ifd.t256 || 1, ifd.height || ifd.t257 || 1));
        });

        return canvases;
    }

    async function convertTiffToRoute(file, targetMime, pageRange, options, config) {
        var canvases = await decodeTiffCanvases(file);
        var pages = parsePageRange(pageRange, canvases.length);
        var selected = (pages.length ? pages : canvases.map(function (_, index) { return index + 1; })).map(function (pageNumber) {
            return canvases[pageNumber - 1];
        });
        return exportCanvasSequenceToRoute(selected, targetMime, slugifyBaseName(file.name), options, config, 'pages');
    }

    async function readGifMetadata(file) {
        var buffer = await readArrayBuffer(file);
        var parsed = window.NoesiaSwaGifuct.parseGIF(buffer);
        var frames = window.NoesiaSwaGifuct.decompressFrames(parsed, true) || [];
        return {
            frameCount: frames.length || 0,
            width: parsed && parsed.lsd ? parsed.lsd.width : 0,
            height: parsed && parsed.lsd ? parsed.lsd.height : 0
        };
    }

    async function decodeGifCanvases(file) {
        var buffer = await readArrayBuffer(file);
        var parsed = window.NoesiaSwaGifuct.parseGIF(buffer);
        var frames = window.NoesiaSwaGifuct.decompressFrames(parsed, true) || [];
        var width = parsed && parsed.lsd ? parsed.lsd.width : 0;
        var height = parsed && parsed.lsd ? parsed.lsd.height : 0;
        var stage = document.createElement('canvas');
        var context;
        var results = [];
        var previousFrame = null;
        var previousSnapshot = null;

        if (!frames.length || !width || !height) {
            throw new Error('No GIF frames were available for export.');
        }

        stage.width = width;
        stage.height = height;
        context = getCanvasContext2d(stage);
        context.clearRect(0, 0, width, height);

        frames.forEach(function (frame) {
            if (previousFrame) {
                if (previousFrame.disposalType === 2) {
                    context.clearRect(previousFrame.dims.left, previousFrame.dims.top, previousFrame.dims.width, previousFrame.dims.height);
                } else if (previousFrame.disposalType === 3 && previousSnapshot) {
                    context.putImageData(previousSnapshot, 0, 0);
                }
            }

            previousSnapshot = frame.disposalType === 3 ? context.getImageData(0, 0, width, height) : null;
            context.putImageData(new ImageData(frame.patch, frame.dims.width, frame.dims.height), frame.dims.left, frame.dims.top);
            results.push(cloneCanvas(stage));
            previousFrame = frame;
        });

        return results;
    }

    async function convertGifToRoute(file, targetMime, pageRange, options, config) {
        var canvases = await decodeGifCanvases(file);
        var frames = parsePageRange(pageRange, canvases.length);
        var selected = (frames.length ? frames : canvases.map(function (_, index) { return index + 1; })).map(function (frameNumber) {
            return canvases[frameNumber - 1];
        });
        return exportCanvasSequenceToRoute(selected, targetMime, slugifyBaseName(file.name), options, config, 'frames');
    }


    async function readPdfDocument(file) {
        var arrayBuffer = await readArrayBuffer(file);
        return window.pdfjsLib.getDocument({ data: arrayBuffer }).promise;
    }

    async function readPdfMetadata(file) {
        var pdf = await readPdfDocument(file);
        return {
            pageCount: pdf.numPages || 0
        };
    }

    async function extractPdfText(file, pageRange) {
        var pdf = await readPdfDocument(file);
        var pages = parsePageRange(pageRange, pdf.numPages);
        var chunks = [];

        if (!pages.length) {
            throw new Error('The selected page range did not match any pages.');
        }

        for (var index = 0; index < pages.length; index += 1) {
            var pageNumber = pages[index];
            var page = await pdf.getPage(pageNumber);
            var content = await page.getTextContent();
            var line = content.items.map(function (item) {
                return item && item.str ? item.str : '';
            }).join(' ').replace(/\s+/g, ' ').trim();
                        chunks.push(line ? ('Page ' + pageNumber + '\n' + line) : ('Page ' + pageNumber));
        }

                return chunks.join('\n\n').trim();
    }

    async function convertPdfToImageOutputs(file, targetMime, pageRange, renderScale, options, config) {
        var pdf = await readPdfDocument(file);
        var pages = parsePageRange(pageRange, pdf.numPages);
        var scale = parseFloat(renderScale || 2);
        var baseName = slugifyBaseName(file.name);
        var extension = imageMimeToExtension(targetMime);
        var outputs = [];
        var zip;
        var zipBlob;
        var settings = options || {};
        var imageQuality = parseQualityValue(settings.quality);
        var maxEdge = parseMaxEdgeValue(settings.maxEdge);
        if (!window.isFinite(scale) || scale <= 0) {
            scale = 2;
        }

        if (!pages.length) {
            throw new Error('The selected page range did not match any pages.');
        }

        for (var index = 0; index < pages.length; index += 1) {
            var pageNumber = pages[index];
            var page = await pdf.getPage(pageNumber);
            var viewport = page.getViewport({ scale: scale });
            var canvas = document.createElement('canvas');
            var context = getCanvasContext2d(canvas);
            if (!context) {
                throw new Error('Canvas rendering is not available in this browser.');
            }
            canvas.width = viewport.width;
            canvas.height = viewport.height;
            if (targetMime === 'image/jpeg' || targetMime === 'image/webp' || targetMime === 'image/avif') {
                context.fillStyle = '#ffffff';
                context.fillRect(0, 0, canvas.width, canvas.height);
            }
            await page.render({ canvasContext: context, viewport: viewport }).promise;
            canvas = resizeCanvasIfNeeded(canvas, maxEdge, isLossyImageRoute(targetMime) ? '#ffffff' : '');
            var blob = await canvasToRasterBlob(canvas, targetMime, imageQuality, config);
            var previewBlob = await canvasToPreviewBlob(canvas);
            outputs.push({
                blob: blob,
                previewBlob: previewBlob,
                filename: baseName + '-page-' + pageNumber + '.' + extension,
                pageNumber: pageNumber
            });
        }

        if (outputs.length === 1) {
            return {
                blob: outputs[0].blob,
                previewBlob: outputs[0].previewBlob,
                filename: outputs[0].filename,
                previewType: 'image',
                outputSummary: 'Page ' + outputs[0].pageNumber + ' was exported as ' + routeLabel(targetMime) + ' at ' + scale + '× render quality.',
                sourceCount: 1
            };
        }

        zip = new window.JSZip();
        outputs.forEach(function (entry) {
            zip.file(entry.filename, entry.blob);
        });
        zipBlob = await zip.generateAsync({ type: 'blob' });

        return {
            blob: zipBlob,
            filename: baseName + '-' + extension + '-pages.zip',
            previewType: 'zip',
            outputSummary: outputs.length + ' pages were exported and zipped together at ' + scale + '× render quality.',
            sourceCount: outputs.length
        };
    }

    function normalizeLineEndings(text) {
        return String(text || '').replace(/\r\n?/g, '\n');
    }

    function buildParagraphHtml(text) {
        var normalized = normalizeLineEndings(text);
        var chunks = normalized.split(/\n{2,}/);
        var paragraphs = chunks.filter(function (chunk) {
            return chunk !== '';
        }).map(function (chunk) {
            return '<p>' + escapeHtml(chunk).replace(/\n/g, '<br>') + '</p>';
        });

        if (!paragraphs.length) {
            return '<p>&nbsp;</p>';
        }

        return paragraphs.join('');
    }

    function createCsvLine(values) {
        return values.map(function (value) {
            var stringValue = String(value == null ? '' : value);
            if (/[",\n]/.test(stringValue)) {
                return '"' + stringValue.replace(/"/g, '""') + '"';
            }
            return stringValue;
        }).join(',');
    }

    function getTextStats(text) {
        var normalized = normalizeLineEndings(text);
        var lines = normalized === '' ? [] : normalized.split('\n');
        return {
            normalized: normalized,
            lines: lines,
            characterCount: normalized.length,
            lineCount: lines.length
        };
    }

    function createPdfFromText(text, fileName, summaryText) {
        var jsPDF = window.jspdf && window.jspdf.jsPDF ? window.jspdf.jsPDF : null;
        var doc;
        var pageWidth;
        var lines;
        var cursorY = 44;

        if (!jsPDF) {
            throw new Error('The local PDF generator is not available.');
        }

        doc = new jsPDF({ unit: 'pt', format: 'a4', compress: true });
        doc.setFont('helvetica', 'normal');
        doc.setFontSize(11);
        pageWidth = doc.internal.pageSize.getWidth() - 80;
        lines = doc.splitTextToSize(normalizeLineEndings(text) || '', pageWidth);

        if (!lines.length) {
            lines = [''];
        }

        lines.forEach(function (line) {
            if (cursorY > doc.internal.pageSize.getHeight() - 40) {
                doc.addPage();
                cursorY = 44;
            }
            doc.text(line, 40, cursorY);
            cursorY += 16;
        });

        return {
            blob: doc.output('blob'),
            filename: fileName,
            previewType: 'pdf',
            previewText: summaryText,
            outputSummary: summaryText
        };
    }

    function buildHtmlDocument(title, bodyHtml) {
        return '<!DOCTYPE html><html><head><meta charset="utf-8"><title>' + escapeHtml(title || 'Export') + '</title>' +
            '<style>body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Arial,sans-serif;padding:24px;line-height:1.55;color:#111;background:#fff;}' +
            'table{width:100%;border-collapse:collapse;margin:0 0 16px;}th,td{border:1px solid #d0d7de;padding:8px;vertical-align:top;text-align:left;}' +
            'th{background:#f6f8fa;}pre{white-space:pre-wrap;word-break:break-word;}</style></head><body>' + bodyHtml + '</body></html>';
    }

    function matrixToText(matrix) {
        if (!Array.isArray(matrix) || !matrix.length) {
            return '';
        }
        return matrix.map(function (row) {
            return (Array.isArray(row) ? row : []).map(function (value) {
                return String(value == null ? '' : value);
            }).join('\t').replace(/[ \t]+$/g, '');
        }).join('\n').replace(/\n{3,}/g, '\n\n').trim();
    }

    function matrixToHtml(matrix) {
        if (!Array.isArray(matrix) || !matrix.length) {
            return '<p>&nbsp;</p>';
        }
        var body = ['<table>'];
        matrix.forEach(function (row, rowIndex) {
            body.push('<tr>');
            (Array.isArray(row) ? row : []).forEach(function (value) {
                var tag = rowIndex === 0 ? 'th' : 'td';
                body.push('<' + tag + '>' + escapeHtml(String(value == null ? '' : value)) + '</' + tag + '>');
            });
            if (!row || !row.length) {
                body.push(rowIndex === 0 ? '<th>&nbsp;</th>' : '<td>&nbsp;</td>');
            }
            body.push('</tr>');
        });
        body.push('</table>');
        return body.join('');
    }

    function worksheetToMatrix(sheet) {
        return window.XLSX.utils.sheet_to_json(sheet, {
            header: 1,
            defval: ''
        });
    }

    function createStructuredJsonBlob(payload, fileName, summaryText) {
        var jsonText = JSON.stringify(payload, null, 2);
        return {
            blob: new Blob([jsonText], { type: 'application/json;charset=utf-8' }),
            filename: fileName,
            previewType: 'text',
            previewText: jsonText,
            outputSummary: summaryText
        };
    }

    function createHtmlExport(bodyHtml, fileName, summaryText) {
        var safeBody = sanitizeHtml(bodyHtml);
        return {
            blob: new Blob([buildHtmlDocument(fileName, safeBody)], { type: 'text/html;charset=utf-8' }),
            filename: fileName,
            previewType: 'html',
            previewHtml: safeBody,
            outputSummary: summaryText
        };
    }

    function createPlainTextExport(text, fileName, summaryText) {
        return {
            blob: new Blob([text], { type: 'text/plain;charset=utf-8' }),
            filename: fileName,
            previewType: 'text',
            previewText: text,
            outputSummary: summaryText
        };
    }

    function createLineCsvExport(text, fileName, summaryText) {
        var stats = getTextStats(text);
        var rows = [createCsvLine(['line', 'text'])];
        (stats.lines.length ? stats.lines : ['']).forEach(function (line, index) {
            rows.push(createCsvLine([index + 1, line]));
        });
        rows = rows.join('\n');

        return {
            blob: new Blob([rows], { type: 'text/csv;charset=utf-8' }),
            filename: fileName,
            previewType: 'text',
            previewText: rows,
            outputSummary: summaryText
        };
    }

    function workbookToBlob(workbook) {
        var array = window.XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
        return new Blob([array], { type: XLSX_MIME });
    }

    function createWorkbookBlobFromMatrix(matrix, fileName, summaryText, sheetName) {
        var workbook;
        var worksheet;

        if (!window.XLSX || !window.XLSX.utils) {
            throw new Error('The local workbook generator is not available.');
        }

        workbook = window.XLSX.utils.book_new();
        worksheet = window.XLSX.utils.aoa_to_sheet(Array.isArray(matrix) && matrix.length ? matrix : [['']]);
        window.XLSX.utils.book_append_sheet(workbook, worksheet, String(sheetName || 'Export').slice(0, 31));

        return {
            blob: workbookToBlob(workbook),
            filename: fileName,
            previewType: 'text',
            previewText: matrixToText(matrix),
            outputSummary: summaryText
        };
    }

    function createWorkbookBlobFromLines(text, fileName, summaryText, sheetName) {
        var stats = getTextStats(text);
        var matrix = [['Line', 'Text']];

        if (!stats.lines.length) {
            matrix.push([1, '']);
        } else {
            stats.lines.forEach(function (line, index) {
                matrix.push([index + 1, line]);
            });
        }

        return createWorkbookBlobFromMatrix(matrix, fileName, summaryText, sheetName || 'Lines');
    }

    async function convertTextFile(file, route) {
        var text = await readText(file);
        var stats = getTextStats(text);
        var html;
        var jsonText;
        var csv;

        if (route === 'application/pdf') {
            return createPdfFromText(stats.normalized, slugifyBaseName(file.name) + '.pdf', 'Plain text was exported as PDF.');
        }

        if (route === 'text/html') {
            html = buildParagraphHtml(stats.normalized);
            return {
                blob: new Blob([html], { type: 'text/html;charset=utf-8' }),
                filename: slugifyBaseName(file.name) + '.html',
                previewType: 'html',
                previewHtml: html,
                outputSummary: 'Plain text was exported as semantic HTML.'
            };
        }

        if (route === 'application/json') {
            jsonText = JSON.stringify({
                text: stats.normalized,
                line_count: stats.lineCount,
                character_count: stats.characterCount,
                lines: stats.lines
            }, null, 2);
            return {
                blob: new Blob([jsonText], { type: 'application/json;charset=utf-8' }),
                filename: slugifyBaseName(file.name) + '.json',
                previewType: 'text',
                previewText: jsonText,
                outputSummary: 'Plain text was exported as structured JSON.'
            };
        }

        if (route === 'text/csv') {
            csv = [createCsvLine(['line', 'text'])];
            if (!stats.lines.length) {
                csv.push(createCsvLine([1, '']));
            } else {
                stats.lines.forEach(function (line, index) {
                    csv.push(createCsvLine([index + 1, line]));
                });
            }
            csv = csv.join('\n');
            return createLineCsvExport(stats.normalized, slugifyBaseName(file.name) + '.csv', 'Plain text was exported as CSV rows.');
        }

        if (route === XLSX_MIME) {
            return createWorkbookBlobFromLines(stats.normalized, slugifyBaseName(file.name) + '.xlsx', 'Plain text was exported as XLSX rows.', 'Text');
        }

        throw new Error('The selected text output route is not available.');
    }

    function convertWorksheetByRoute(sheet, route, baseName, label) {
        var selectedSheetName = label || 'Sheet1';
        var matrix = worksheetToMatrix(sheet);
        var csv = window.XLSX.utils.sheet_to_csv(sheet);
        var jsonRows = window.XLSX.utils.sheet_to_json(sheet, { defval: '' });
        var text = matrixToText(matrix);
        var html = matrixToHtml(matrix);

        if (route === 'text/csv') {
            return {
                blob: new Blob([csv], { type: 'text/csv;charset=utf-8' }),
                filename: baseName + '.csv',
                previewType: 'text',
                previewText: csv,
                outputSummary: 'Worksheet “' + selectedSheetName + '” was exported as CSV.'
            };
        }

        if (route === 'application/json') {
            return createStructuredJsonBlob(jsonRows, baseName + '.json', 'Worksheet “' + selectedSheetName + '” was exported as JSON.');
        }

        if (route === 'text/plain') {
            return createPlainTextExport(text, baseName + '.txt', 'Worksheet “' + selectedSheetName + '” was exported as tab-separated text.');
        }

        if (route === 'application/pdf') {
            return createPdfFromText(text, baseName + '.pdf', 'Worksheet “' + selectedSheetName + '” was exported as PDF.');
        }

        return createHtmlExport(html, baseName + '.html', 'Worksheet “' + selectedSheetName + '” was exported as HTML.');
    }

    async function convertCsv(file, route) {
        var text = await readText(file);
        var workbook = window.XLSX.read(text, { type: 'string' });
        var firstSheetName = workbook.SheetNames[0] || 'Sheet1';
        var sheet = workbook.Sheets[firstSheetName];

        if (route === XLSX_MIME) {
            return {
                blob: workbookToBlob(workbook),
                filename: slugifyBaseName(file.name) + '.xlsx',
                previewType: 'text',
                previewText: text,
                outputSummary: 'CSV content was exported as XLSX.'
            };
        }

        return convertWorksheetByRoute(sheet, route, slugifyBaseName(file.name), firstSheetName);
    }

    async function convertJson(file, route) {
        var text = await readText(file);
        var parsed = JSON.parse(text);
        var rows = Array.isArray(parsed) ? parsed : [parsed];
        var worksheet = window.XLSX.utils.json_to_sheet(rows);
        var workbook;

        if (route === XLSX_MIME) {
            workbook = window.XLSX.utils.book_new();
            window.XLSX.utils.book_append_sheet(workbook, worksheet, 'JSON');
            return {
                blob: workbookToBlob(workbook),
                filename: slugifyBaseName(file.name) + '.xlsx',
                previewType: 'text',
                previewText: text,
                outputSummary: 'JSON content was exported as XLSX.'
            };
        }

        return convertWorksheetByRoute(worksheet, route, slugifyBaseName(file.name), 'JSON');
    }

    async function readWorkbook(file) {
        var arrayBuffer = await readArrayBuffer(file);
        return window.XLSX.read(arrayBuffer, { type: 'array' });
    }

    async function convertWorkbook(file, route, sheetName) {
        var workbook = await readWorkbook(file);
        var selectedSheetName = sheetName && workbook.Sheets[sheetName] ? sheetName : workbook.SheetNames[0];
        var sheet = workbook.Sheets[selectedSheetName];

        if (!sheet) {
            throw new Error('No worksheet was available in this workbook.');
        }

        return convertWorksheetByRoute(
            sheet,
            route,
            slugifyBaseName(file.name) + '-' + slugifyBaseName(selectedSheetName),
            selectedSheetName
        );
    }

    function xmlChildrenByLocalName(node, localName) {
        var result = [];
        var index;
        if (!node || !node.childNodes) {
            return result;
        }
        for (index = 0; index < node.childNodes.length; index += 1) {
            var child = node.childNodes[index];
            if (child.nodeType === 1 && child.localName === localName) {
                result.push(child);
            }
        }
        return result;
    }

    function xmlFirstChildByLocalName(node, localName) {
        var children = xmlChildrenByLocalName(node, localName);
        return children.length ? children[0] : null;
    }

    function xmlAttributeByLocalName(node, localName) {
        var index;
        if (!node || !node.attributes) {
            return '';
        }
        for (index = 0; index < node.attributes.length; index += 1) {
            if (node.attributes[index].localName === localName) {
                return node.attributes[index].value;
            }
        }
        return '';
    }

    function paragraphTagForStyle(styleId, styleName) {
        var source = (String(styleId || '') + ' ' + String(styleName || '')).toLowerCase();
        var headingMatch = source.match(/heading\s*([1-6])/);
        if (headingMatch) {
            return 'h' + headingMatch[1];
        }
        if (source.indexOf('title') !== -1) {
            return 'h1';
        }
        if (source.indexOf('subtitle') !== -1) {
            return 'h2';
        }
        return 'p';
    }

    function wrapRunHtml(text, props) {
        var html = escapeHtml(text).replace(/\n/g, '<br>');
        if (!html) {
            return '';
        }
        if (props.superscript) {
            html = '<sup>' + html + '</sup>';
        }
        if (props.subscript) {
            html = '<sub>' + html + '</sub>';
        }
        if (props.strike) {
            html = '<s>' + html + '</s>';
        }
        if (props.underline) {
            html = '<u>' + html + '</u>';
        }
        if (props.italic) {
            html = '<em>' + html + '</em>';
        }
        if (props.bold) {
            html = '<strong>' + html + '</strong>';
        }
        return html;
    }

    function extractRunContent(runNode) {
        var htmlParts = [];
        var textParts = [];
        var propsNode = xmlFirstChildByLocalName(runNode, 'rPr');
        var props = {
            bold: !!xmlFirstChildByLocalName(propsNode, 'b'),
            italic: !!xmlFirstChildByLocalName(propsNode, 'i'),
            underline: !!xmlFirstChildByLocalName(propsNode, 'u'),
            strike: !!xmlFirstChildByLocalName(propsNode, 'strike') || !!xmlFirstChildByLocalName(propsNode, 'dstrike'),
            superscript: false,
            subscript: false
        };
        var vertAlign = xmlFirstChildByLocalName(propsNode, 'vertAlign');
        var value = xmlAttributeByLocalName(vertAlign, 'val');
        var index;

        if (value === 'superscript') {
            props.superscript = true;
        }
        if (value === 'subscript') {
            props.subscript = true;
        }

        for (index = 0; index < runNode.childNodes.length; index += 1) {
            var child = runNode.childNodes[index];
            if (child.nodeType !== 1) {
                continue;
            }
            if (child.localName === 't') {
                htmlParts.push(wrapRunHtml(child.textContent || '', props));
                textParts.push(child.textContent || '');
            } else if (child.localName === 'tab') {
                htmlParts.push('\t');
                textParts.push('\t');
            } else if (child.localName === 'br' || child.localName === 'cr') {
                htmlParts.push('<br>');
                textParts.push('\n');
            }
        }

        return {
            html: htmlParts.join(''),
            text: textParts.join('')
        };
    }

    function extractParagraphContent(paragraphNode, styleNames, relationships) {
        var htmlParts = [];
        var textParts = [];
        var propsNode = xmlFirstChildByLocalName(paragraphNode, 'pPr');
        var styleNode = xmlFirstChildByLocalName(propsNode, 'pStyle');
        var styleId = xmlAttributeByLocalName(styleNode, 'val');
        var styleName = styleNames[styleId] || styleId;
        var numProps = xmlFirstChildByLocalName(propsNode, 'numPr');
        var isListItem = !!numProps;
        var tag = isListItem ? 'li' : paragraphTagForStyle(styleId, styleName);
        var index;
        var textValue;

        for (index = 0; index < paragraphNode.childNodes.length; index += 1) {
            var child = paragraphNode.childNodes[index];
            if (child.nodeType !== 1) {
                continue;
            }
            if (child.localName === 'r') {
                var run = extractRunContent(child);
                htmlParts.push(run.html);
                textParts.push(run.text);
            } else if (child.localName === 'hyperlink') {
                var hyperlinkHtml = [];
                var hyperlinkText = [];
                var runIndex;
                var relationshipId = xmlAttributeByLocalName(child, 'id');
                var href = relationshipId && relationships[relationshipId] ? relationships[relationshipId] : '';
                for (runIndex = 0; runIndex < child.childNodes.length; runIndex += 1) {
                    if (child.childNodes[runIndex].nodeType === 1 && child.childNodes[runIndex].localName === 'r') {
                        var hyperlinkRun = extractRunContent(child.childNodes[runIndex]);
                        hyperlinkHtml.push(hyperlinkRun.html);
                        hyperlinkText.push(hyperlinkRun.text);
                    }
                }
                var hyperlinkMarkup = hyperlinkHtml.join('');
                if (href && hyperlinkMarkup) {
                    hyperlinkMarkup = '<a href="' + escapeHtml(href) + '" target="_blank" rel="noopener noreferrer">' + hyperlinkMarkup + '</a>';
                }
                htmlParts.push(hyperlinkMarkup);
                textParts.push(hyperlinkText.join(''));
            }
        }

        textValue = textParts.join('').replace(/[ \t]+\n/g, '\n').trim();
        if (isListItem && textValue) {
            textValue = '• ' + textValue;
        }

        return {
            html: '<' + tag + '>' + (htmlParts.join('') || '&nbsp;') + '</' + tag + '>',
            text: textValue,
            isListItem: isListItem
        };
    }

    function extractTableContent(tableNode, styleNames, relationships) {
        var rowNodes = xmlChildrenByLocalName(tableNode, 'tr');
        var htmlRows = [];
        var textRows = [];

        rowNodes.forEach(function (rowNode) {
            var cells = xmlChildrenByLocalName(rowNode, 'tc');
            var htmlCells = [];
            var textCells = [];
            cells.forEach(function (cellNode) {
                var paragraphs = xmlChildrenByLocalName(cellNode, 'p');
                var htmlParagraphs = [];
                var textParagraphs = [];
                paragraphs.forEach(function (paragraphNode) {
                    var content = extractParagraphContent(paragraphNode, styleNames, relationships);
                    htmlParagraphs.push(content.html);
                    if (content.text) {
                        textParagraphs.push(content.text);
                    }
                });
                htmlCells.push('<td>' + (htmlParagraphs.join('') || '&nbsp;') + '</td>');
                textCells.push(textParagraphs.join(' ').trim());
            });
            htmlRows.push('<tr>' + htmlCells.join('') + '</tr>');
            textRows.push(textCells.join('\t').trim());
        });

        return {
            html: '<table>' + htmlRows.join('') + '</table>',
            text: textRows.join('\n')
        };
    }

    async function parseDocxRelationships(zip, parser) {
        var relationshipsEntry = zip.file('word/_rels/document.xml.rels');
        var relationships = {};

        if (!relationshipsEntry) {
            return relationships;
        }

        var relationshipsXml = await relationshipsEntry.async('string');
        var relationshipsDoc = parser.parseFromString(relationshipsXml, 'application/xml');
        Array.prototype.slice.call(relationshipsDoc.documentElement ? relationshipsDoc.documentElement.childNodes : []).forEach(function (node) {
            if (node.nodeType !== 1 || node.localName !== 'Relationship') {
                return;
            }
            var id = xmlAttributeByLocalName(node, 'Id');
            var target = xmlAttributeByLocalName(node, 'Target');
            var targetMode = xmlAttributeByLocalName(node, 'TargetMode');
            if (!id || !target) {
                return;
            }
            if (targetMode === 'External' || /^https?:/i.test(target) || /^mailto:/i.test(target)) {
                relationships[id] = target;
            }
        });

        return relationships;
    }

    async function parseDocx(file) {
        var arrayBuffer = await readArrayBuffer(file);
        var zip = await window.JSZip.loadAsync(arrayBuffer);
        var documentEntry = zip.file('word/document.xml');
        var stylesEntry = zip.file('word/styles.xml');
        var parser = new DOMParser();
        var styleNames = {};
        var htmlParts = [];
        var textParts = [];
        var relationships = await parseDocxRelationships(zip, parser);
        var listOpen = false;

        if (!documentEntry) {
            throw new Error('This DOCX file does not contain word/document.xml.');
        }

        if (stylesEntry) {
            var stylesXml = await stylesEntry.async('string');
            var stylesDoc = parser.parseFromString(stylesXml, 'application/xml');
            xmlChildrenByLocalName(stylesDoc.documentElement, 'style').forEach(function (styleNode) {
                var styleId = xmlAttributeByLocalName(styleNode, 'styleId');
                var nameNode = xmlFirstChildByLocalName(styleNode, 'name');
                var styleName = xmlAttributeByLocalName(nameNode, 'val');
                if (styleId) {
                    styleNames[styleId] = styleName || styleId;
                }
            });
        }

        var documentXml = await documentEntry.async('string');
        var documentDoc = parser.parseFromString(documentXml, 'application/xml');
        var body = xmlFirstChildByLocalName(documentDoc.documentElement, 'body');
        if (!body) {
            throw new Error('The DOCX document body could not be read.');
        }

        Array.prototype.slice.call(body.childNodes).forEach(function (child) {
            if (child.nodeType !== 1) {
                return;
            }
            if (child.localName === 'p') {
                var paragraphContent = extractParagraphContent(child, styleNames, relationships);
                if (paragraphContent.isListItem) {
                    if (!listOpen) {
                        htmlParts.push('<ul>');
                        listOpen = true;
                    }
                    htmlParts.push(paragraphContent.html);
                } else {
                    if (listOpen) {
                        htmlParts.push('</ul>');
                        listOpen = false;
                    }
                    htmlParts.push(paragraphContent.html);
                }
                textParts.push(paragraphContent.text || '');
            } else if (child.localName === 'tbl') {
                if (listOpen) {
                    htmlParts.push('</ul>');
                    listOpen = false;
                }
                var tableContent = extractTableContent(child, styleNames, relationships);
                htmlParts.push(tableContent.html);
                if (tableContent.text) {
                    textParts.push(tableContent.text);
                }
            }
        });

        if (listOpen) {
            htmlParts.push('</ul>');
        }

        return {
            html: sanitizeHtml(htmlParts.join('')),
            text: textParts.join('\n\n').replace(/\n{3,}/g, '\n\n').trim()
        };
    }

    async function convertDocx(file, route) {
        var parsed = await parseDocx(file);
        var baseName = slugifyBaseName(file.name);
        var stats;
        var csv;
        if (route === 'text/plain') {
            return createPlainTextExport(parsed.text, baseName + '.txt', 'DOCX content was extracted as plain text.');
        }
        if (route === 'application/pdf') {
            return createPdfFromText(parsed.text, baseName + '.pdf', 'DOCX content was exported as PDF.');
        }
        if (route === 'application/json') {
            return createStructuredJsonBlob({
                text: parsed.text,
                html: parsed.html
            }, baseName + '.json', 'DOCX content was exported as structured JSON.');
        }
        if (route === 'text/csv') {
            return createLineCsvExport(parsed.text, baseName + '.csv', 'DOCX content was exported as CSV rows.');
        }
        if (route === XLSX_MIME) {
            return createWorkbookBlobFromLines(parsed.text, baseName + '.xlsx', 'DOCX content was exported as XLSX rows.', 'DOCX');
        }
        return createHtmlExport(parsed.html, baseName + '.html', 'DOCX content was extracted as semantic HTML.');
    }

    function extractLegacyDocTextFromBytes(arrayBuffer) {
        var bytes = new Uint8Array(arrayBuffer);
        var ascii = [];
        var asciiSegments = [];
        var unicode = [];
        var unicodeSegments = [];
        var index;

        function flushAscii() {
            if (ascii.length >= 4) {
                asciiSegments.push(ascii.join(''));
            }
            ascii = [];
        }

        function flushUnicode() {
            if (unicode.length >= 3) {
                unicodeSegments.push(unicode.join(''));
            }
            unicode = [];
        }

        for (index = 0; index < bytes.length; index += 1) {
            var value = bytes[index];
            if (value === 9 || value === 10 || value === 13 || (value >= 32 && value <= 126)) {
                ascii.push(String.fromCharCode(value));
            } else {
                flushAscii();
            }
        }
        flushAscii();

        for (index = 0; index + 1 < bytes.length; index += 2) {
            var low = bytes[index];
            var high = bytes[index + 1];
            if (high === 0 && (low === 9 || low === 10 || low === 13 || (low >= 32 && low <= 126))) {
                unicode.push(String.fromCharCode(low));
            } else {
                flushUnicode();
            }
        }
        flushUnicode();

        return normalizeLineEndings(asciiSegments.concat(unicodeSegments).join('\n'))
            .replace(/\u0000/g, '')
            .replace(/[ \t]+\n/g, '\n')
            .replace(/\n{3,}/g, '\n\n')
            .trim();
    }

    async function convertLegacyDoc(file, route) {
        var arrayBuffer = await readArrayBuffer(file);
        var text = extractLegacyDocTextFromBytes(arrayBuffer);
        var baseName = slugifyBaseName(file.name);

        if (!text) {
            throw new Error('The legacy DOC file did not expose readable text in the current browser path.');
        }

        if (route === 'text/plain') {
            return createPlainTextExport(text, baseName + '.txt', 'Legacy DOC content was extracted as plain text.');
        }

        if (route === 'application/pdf') {
            return createPdfFromText(text, baseName + '.pdf', 'Legacy DOC content was exported as PDF.');
        }

        if (route === 'application/json') {
            return createStructuredJsonBlob({
                text: text,
                lines: getTextStats(text).lines
            }, baseName + '.json', 'Legacy DOC content was exported as structured JSON.');
        }

        if (route === 'text/csv') {
            return createLineCsvExport(text, baseName + '.csv', 'Legacy DOC content was exported as CSV rows.');
        }

        if (route === XLSX_MIME) {
            return createWorkbookBlobFromLines(text, baseName + '.xlsx', 'Legacy DOC content was exported as XLSX rows.', 'DOC');
        }

        return createHtmlExport(buildParagraphHtml(text), baseName + '.html', 'Legacy DOC content was extracted as semantic HTML.');
    }

    function decodeRtfToText(source) {
        var rtf = String(source || '');
        var output = [];
        var stack = [];
        var state = { ignorable: false };
        var index = 0;
        var destinations = {
            fonttbl: true,
            colortbl: true,
            stylesheet: true,
            info: true,
            pict: true,
            object: true,
            header: true,
            headerl: true,
            headerr: true,
            footer: true,
            footerl: true,
            footerr: true,
            footnote: true,
            annotation: true,
            listtable: true,
            listoverridetable: true,
            xmlnstbl: true,
            datastore: true,
            generator: true,
            fldinst: true,
            rsidtbl: true,
            nonshppict: true,
            shppict: true
        };
        var specialChars = {
            par: '\n',
            line: '\n',
            tab: '\t',
            emdash: '—',
            endash: '–',
            bullet: '•',
            lquote: '‘',
            rquote: '’',
            ldblquote: '“',
            rdblquote: '”'
        };

        function append(value) {
            if (!state.ignorable && value) {
                output.push(value);
            }
        }

        while (index < rtf.length) {
            var character = rtf.charAt(index);

            if (character === '{') {
                stack.push({ ignorable: state.ignorable });
                index += 1;
                continue;
            }

            if (character === '}') {
                state = stack.pop() || { ignorable: false };
                index += 1;
                continue;
            }

            if (character !== '\\') {
                append(character);
                index += 1;
                continue;
            }

            index += 1;
            if (index >= rtf.length) {
                break;
            }

            character = rtf.charAt(index);

            if (character === '\\' || character === '{' || character === '}') {
                append(character);
                index += 1;
                continue;
            }

            if (character === "'") {
                var hex = rtf.substr(index + 1, 2);
                if (/^[0-9a-fA-F]{2}$/.test(hex)) {
                    append(String.fromCharCode(parseInt(hex, 16)));
                    index += 3;
                    continue;
                }
            }

            if (character === '~') {
                append(' ');
                index += 1;
                continue;
            }

            if (character === '_') {
                append('-');
                index += 1;
                continue;
            }

            if (character === '-') {
                index += 1;
                continue;
            }

            if (character === '*') {
                state.ignorable = true;
                index += 1;
                continue;
            }

            if (!/[a-zA-Z]/.test(character)) {
                index += 1;
                continue;
            }

            var wordStart = index;
            while (index < rtf.length && /[a-zA-Z]/.test(rtf.charAt(index))) {
                index += 1;
            }
            var word = rtf.slice(wordStart, index);
            var number = '';
            if (rtf.charAt(index) === '-' || /[0-9]/.test(rtf.charAt(index))) {
                number += rtf.charAt(index);
                index += 1;
                while (index < rtf.length && /[0-9]/.test(rtf.charAt(index))) {
                    number += rtf.charAt(index);
                    index += 1;
                }
            }
            if (rtf.charAt(index) === ' ') {
                index += 1;
            }

            if (destinations[word]) {
                state.ignorable = true;
                continue;
            }

            if (word === 'u') {
                var codepoint = parseInt(number, 10);
                if (window.isFinite(codepoint)) {
                    if (codepoint < 0) {
                        codepoint += 65536;
                    }
                    append(String.fromCharCode(codepoint));
                    if (rtf.charAt(index) === '?') {
                        index += 1;
                    }
                }
                continue;
            }

            if (Object.prototype.hasOwnProperty.call(specialChars, word)) {
                append(specialChars[word]);
            }
        }

        return normalizeLineEndings(output.join(''))
            .replace(/\u0000/g, '')
            .replace(/[ \t]+\n/g, '\n')
            .replace(/\n{3,}/g, '\n\n')
            .trim();
    }

    async function convertRtf(file, route) {
        var source = await readText(file);
        var text = decodeRtfToText(source);
        var html = buildParagraphHtml(text);
        var baseName = slugifyBaseName(file.name);
        var stats;
        var csv;

        if (route === 'text/plain') {
            return createPlainTextExport(text, baseName + '.txt', 'RTF content was exported as plain text.');
        }

        if (route === 'application/pdf') {
            return createPdfFromText(text, baseName + '.pdf', 'RTF content was exported as PDF.');
        }

        if (route === 'application/json') {
            return createStructuredJsonBlob({
                text: text,
                html: html
            }, baseName + '.json', 'RTF content was exported as structured JSON.');
        }

        if (route === 'text/csv') {
            return createLineCsvExport(text, baseName + '.csv', 'RTF content was exported as CSV rows.');
        }

        if (route === XLSX_MIME) {
            return createWorkbookBlobFromLines(text, baseName + '.xlsx', 'RTF content was exported as XLSX rows.', 'RTF');
        }

        return createHtmlExport(html, baseName + '.html', 'RTF content was exported as semantic HTML.');
    }

    function buildStandardImageRoutes(imageSupport) {
        var routes = ['image/jpeg', 'image/png'];
        if (imageSupport.webp) {
            routes.push('image/webp');
        }
        if (imageSupport.avif) {
            routes.push('image/avif');
        }
        return routes;
    }

    function describeRasterSupport(imageSupport) {
        var supported = ['JPG', 'PNG'];
        var unavailable = [];

        if (imageSupport.webp) {
            supported.push('WEBP');
        } else {
            unavailable.push('WEBP');
        }

        if (imageSupport.avif) {
            supported.push('AVIF');
        } else {
            unavailable.push('AVIF');
        }

        return {
            supported: supported,
            unavailable: unavailable
        };
    }

    function routesForState(files, imageSupport) {
        var kinds = files.map(getFileKind);
        var uniqueKinds = kinds.filter(function (value, index) {
            return kinds.indexOf(value) === index;
        });
        var standardImageRoutes = buildStandardImageRoutes(imageSupport);

        if (!files.length) {
            return { kind: 'empty', routes: [] };
        }

        if (files.length > 1) {
            if (uniqueKinds.every(isImageKind)) {
                return { kind: 'multi-image', routes: standardImageRoutes.concat(['application/pdf']) };
            }
            return { kind: 'mixed', routes: [] };
        }

        switch (uniqueKinds[0]) {
        case 'image':
            return {
                kind: 'image',
                routes: standardImageRoutes.concat(['application/pdf']).filter(function (route) {
                    var mimeType = String(files[0].type || '').toLowerCase();
                    return route === 'application/pdf' || route !== mimeType;
                })
            };
        case 'svg':
            return { kind: 'svg', routes: standardImageRoutes.concat(['application/pdf']) };
        case 'tiff':
            return { kind: 'tiff', routes: standardImageRoutes.concat(['application/pdf']) };
        case 'gif':
            return { kind: 'gif', routes: standardImageRoutes.concat(['application/pdf']) };
        case 'heic':
            return { kind: 'heic', routes: standardImageRoutes.concat(['application/pdf']) };
        case 'pdf':
            return { kind: 'pdf', routes: buildStandardImageRoutes(imageSupport).concat(['text/plain', 'text/html', 'application/json', 'text/csv', XLSX_MIME]) };
        case 'txt':
            return { kind: 'txt', routes: ['application/pdf', 'text/html', 'application/json', 'text/csv', XLSX_MIME] };
        case 'csv':
            return { kind: 'csv', routes: ['application/json', 'text/plain', 'text/html', 'application/pdf', XLSX_MIME] };
        case 'json':
            return { kind: 'json', routes: ['text/csv', 'text/plain', 'text/html', 'application/pdf', XLSX_MIME] };
        case 'xlsx':
            return { kind: 'xlsx', routes: ['text/csv', 'application/json', 'text/html', 'text/plain', 'application/pdf'] };
        case 'docx':
            return { kind: 'docx', routes: ['text/plain', 'text/html', 'application/pdf', 'application/json', 'text/csv', XLSX_MIME] };
        case 'doc':
            return { kind: 'doc', routes: ['text/plain', 'text/html', 'application/pdf', 'application/json', 'text/csv', XLSX_MIME] };
        case 'rtf':
            return { kind: 'rtf', routes: ['text/plain', 'text/html', 'application/pdf', 'application/json', 'text/csv', XLSX_MIME] };
        default:
            return { kind: 'unknown', routes: [] };
        }
    }

    function updateCodecSupportNote(elements, imageSupport) {
        var support;
        if (!elements.codecNote) {
            return;
        }
        support = describeRasterSupport(imageSupport);
        elements.codecNote.textContent = 'Self-hosted raster export support: ' + support.supported.join(', ') + '.' +
            (support.unavailable.length ? ' Disabled here: ' + support.unavailable.join(', ') + '.' : '');
    }

    function ensureImageCodecSupport(config, elements) {
        if (config.imageSupport.probePromise) {
            return config.imageSupport.probePromise;
        }

        config.imageSupport.probePromise = ensureIndependentEncoders(config).then(function (independentSupport) {
            return Promise.all([
                independentSupport && independentSupport.webp ? true : probeCanvasExportSupport('image/webp'),
                independentSupport && independentSupport.avif ? true : probeCanvasExportSupport('image/avif')
            ]).then(function (results) {
                config.imageSupport.webp = !!(independentSupport && independentSupport.webp) || !!results[0];
                config.imageSupport.avif = !!(independentSupport && independentSupport.avif) || !!results[1];
                updateCodecSupportNote(elements, config.imageSupport);
                return config.imageSupport;
            });
        }).catch(function () {
            config.imageSupport.webp = false;
            config.imageSupport.avif = false;
            updateCodecSupportNote(elements, config.imageSupport);
            return config.imageSupport;
        });

        return config.imageSupport.probePromise;
    }

    function createPreview(result, elements) {
        var previewBody = elements.previewBody;
        previewBody.innerHTML = '';

        if (!result) {
            previewBody.innerHTML = '<p class="noesia-swa-image-tools__converter-empty">No output preview yet.</p>';
            return;
        }

        if (result.previewType === 'text') {
            previewBody.innerHTML = '<pre class="noesia-swa-image-tools__converter-code">' + escapeHtml(result.previewText || '') + '</pre>';
            return;
        }

        if (result.previewType === 'html') {
            previewBody.innerHTML = '<div class="noesia-swa-image-tools__converter-html">' + (result.previewHtml || '') + '</div>';
            return;
        }

        if (result.previewType === 'image' && (result.previewBlob || result.blob)) {
            var imageBlob = result.previewBlob || result.blob;
            var url = blobToObjectUrl(imageBlob);
            var image = document.createElement('img');
            image.className = 'noesia-swa-image-tools__converter-preview-image';
            image.src = url;
            image.alt = 'Converted image preview';
            image.onload = function () {
                revokeUrl(url);
            };
            image.onerror = function () {
                revokeUrl(url);
                previewBody.innerHTML = '<div class="noesia-swa-image-tools__converter-result-note"><strong>Preview unavailable.</strong><span>' + escapeHtml(result.outputSummary || 'The converted file is ready to download.') + '</span></div>';
            };
            previewBody.appendChild(image);
            return;
        }

        if (result.previewType === 'pdf') {
            previewBody.innerHTML = '<div class="noesia-swa-image-tools__converter-result-note"><strong>PDF ready.</strong><span>' + escapeHtml(result.previewText || 'The PDF file is ready to download.') + '</span></div>';
            return;
        }

        if (result.previewType === 'zip') {
            previewBody.innerHTML = '<div class="noesia-swa-image-tools__converter-result-note"><strong>ZIP ready.</strong><span>' + escapeHtml(result.outputSummary || 'The exported files were zipped together.') + '</span></div>';
            return;
        }

        previewBody.innerHTML = '<div class="noesia-swa-image-tools__converter-result-note"><strong>Output ready.</strong><span>' + escapeHtml(result.outputSummary || 'The converted file is ready to download.') + '</span></div>';
    }

    function renderFileOrder(state, elements, updateSelection) {
        var list = elements.orderList;
        list.innerHTML = '';
        if (state.files.length <= 1 || state.kind !== 'multi-image') {
            elements.orderWrap.hidden = true;
            return;
        }
        elements.orderWrap.hidden = false;
        state.files.forEach(function (file, index) {
            var item = document.createElement('div');
            item.className = 'noesia-swa-image-tools__converter-order-item';
            item.innerHTML = '<span class="noesia-swa-image-tools__converter-order-title">' + escapeHtml(file.name) + '</span>' +
                '<div class="noesia-swa-image-tools__converter-order-actions">' +
                '<button type="button" class="button noesia-swa-image-tools__button" data-move-up>↑</button>' +
                '<button type="button" class="button noesia-swa-image-tools__button" data-move-down>↓</button>' +
                '<button type="button" class="button noesia-swa-image-tools__button" data-remove>×</button>' +
                '</div>';
            item.querySelector('[data-move-up]').disabled = index === 0;
            item.querySelector('[data-move-down]').disabled = index === state.files.length - 1;
            item.querySelector('[data-move-up]').addEventListener('click', function () {
                if (index > 0) {
                    var swap = state.files[index - 1];
                    state.files[index - 1] = state.files[index];
                    state.files[index] = swap;
                    updateSelection();
                }
            });
            item.querySelector('[data-move-down]').addEventListener('click', function () {
                if (index < state.files.length - 1) {
                    var swap = state.files[index + 1];
                    state.files[index + 1] = state.files[index];
                    state.files[index] = swap;
                    updateSelection();
                }
            });
            item.querySelector('[data-remove]').addEventListener('click', function () {
                state.files.splice(index, 1);
                updateSelection();
            });
            list.appendChild(item);
        });
    }

    function bootstrap(panel, payload) {
        if (!panel || panel.getAttribute('data-image-converter-booted') === '1') {
            return;
        }

        panel.setAttribute('data-image-converter-booted', '1');

        var config = {
            vendorBaseUrl: panel.getAttribute('data-image-converter-vendor-base') || '',
            independentEncoders: null,
            independentEncoderPromise: null,
            imageSupport: {
                jpeg: true,
                png: true,
                webp: false,
                avif: false,
                probePromise: null
            }
        };
        var state = {
            files: [],
            kind: 'empty',
            routes: [],
            workbookSheetNames: [],
            pdfMetadata: null,
            sequenceMetadata: null,
            output: null,
            analyzing: false,
            converting: false
        };
        var elements = {
            dropzone: panel.querySelector('[data-image-converter-dropzone]'),
            input: panel.querySelector('[data-image-converter-input]'),
            browse: panel.querySelector('[data-image-converter-browse]'),
            clear: panel.querySelector('[data-image-converter-clear]'),
            convert: panel.querySelector('[data-image-converter-convert]'),
            routeSelect: panel.querySelector('[data-image-converter-route]'),
            status: panel.querySelector('[data-image-converter-status]'),
            summary: panel.querySelector('[data-image-converter-summary]'),
            outputSummary: panel.querySelector('[data-image-converter-output-summary]'),
            previewBody: panel.querySelector('[data-image-converter-preview-body]'),
            pageRangeWrap: panel.querySelector('[data-image-converter-page-range-wrap]'),
            pageRange: panel.querySelector('[data-image-converter-page-range]'),
            pageRangeNote: panel.querySelector('[data-image-converter-page-range-note]'),
            pdfScaleWrap: panel.querySelector('[data-image-converter-pdf-scale-wrap]'),
            pdfScale: panel.querySelector('[data-image-converter-pdf-scale]'),
            sheetWrap: panel.querySelector('[data-image-converter-sheet-wrap]'),
            sheetSelect: panel.querySelector('[data-image-converter-sheet]'),
            qualityWrap: panel.querySelector('[data-image-converter-quality-wrap]'),
            quality: panel.querySelector('[data-image-converter-quality]'),
            maxEdgeWrap: panel.querySelector('[data-image-converter-max-edge-wrap]'),
            maxEdge: panel.querySelector('[data-image-converter-max-edge]'),
            orderWrap: panel.querySelector('[data-image-converter-order-wrap]'),
            orderList: panel.querySelector('[data-image-converter-order-list]'),
            codecNote: panel.querySelector('[data-image-converter-codec-note]')
        };

        function setStatus(message, isError) {
            elements.status.textContent = message;
            elements.status.classList.toggle('is-error', !!isError);
        }

        function fillRouteOptions(routes) {
            elements.routeSelect.innerHTML = '';
            routes.forEach(function (route) {
                var option = document.createElement('option');
                option.value = route;
                option.textContent = routeLabel(route);
                elements.routeSelect.appendChild(option);
            });
        }

        function updateRouteDependentFields() {
            var route = elements.routeSelect.value;
            var imageRouteActive = isRasterImageRoute(route);
            var pdfImageRouteActive = state.kind === 'pdf' && imageRouteActive;
            var imageQueueActive = state.kind === 'image' || state.kind === 'heic' || state.kind === 'multi-image' || state.kind === 'svg' || state.kind === 'tiff' || state.kind === 'gif';
            var rangedSequenceActive = state.kind === 'pdf' || state.kind === 'tiff' || state.kind === 'gif';

            elements.pageRangeWrap.hidden = !rangedSequenceActive;
            elements.pdfScaleWrap.hidden = !pdfImageRouteActive;
            elements.sheetWrap.hidden = state.kind !== 'xlsx';
            elements.qualityWrap.hidden = !(imageRouteActive && (imageQueueActive || state.kind === 'pdf'));
            elements.maxEdgeWrap.hidden = !(imageRouteActive && (imageQueueActive || state.kind === 'pdf'));
        }

        async function analyzeSelection() {
            await ensureImageCodecSupport(config, elements);
            var routeInfo = routesForState(state.files, config.imageSupport);
            state.kind = routeInfo.kind;
            state.routes = routeInfo.routes.slice();
            state.output = null;
            elements.outputSummary.textContent = '';
            createPreview(null, elements);
            renderFileOrder(state, elements, updateSelection);
            state.pdfMetadata = null;
            state.sequenceMetadata = null;
            updateRouteDependentFields();

            if (!state.files.length) {
                elements.summary.textContent = 'Drop a file to start the local conversion flow.';
                if (elements.pageRangeNote) {
                    elements.pageRangeNote.textContent = 'Leave this empty to export every page or frame.';
                }
                if (elements.pageRange) {
                    elements.pageRange.placeholder = '1-3,5 or all';
                }
                fillRouteOptions([]);
                updateRouteDependentFields();
                elements.convert.disabled = true;
                setStatus('No file selected yet.', false);
                return;
            }

            if (!state.routes.length) {
                elements.summary.textContent = 'This selection is not supported by the current local converter route set.';
                if (elements.pageRangeNote) {
                    elements.pageRangeNote.textContent = 'Leave this empty to export every page or frame.';
                }
                if (elements.pageRange) {
                    elements.pageRange.placeholder = '1-3,5 or all';
                }
                fillRouteOptions([]);
                updateRouteDependentFields();
                elements.convert.disabled = true;
                setStatus('Unsupported selection. Use one supported file or multiple images for a merged PDF.', true);
                return;
            }

            fillRouteOptions(state.routes);
            updateRouteDependentFields();
            elements.convert.disabled = false;
            elements.summary.textContent = state.files.length === 1
                ? state.files[0].name + ' · ' + humanBytes(state.files[0].size)
                : state.files.length + ' files selected in the current local queue. Use PDF to merge them, or export raster formats as a ZIP package.';
            setStatus('Ready. Choose the output route and convert locally in this browser.', false);

            if (state.kind === 'pdf') {
                try {
                    await ensureDependencies(['pdf'], config);
                    state.pdfMetadata = await readPdfMetadata(state.files[0]);
                    elements.pageRange.placeholder = state.pdfMetadata.pageCount > 1 ? '1-' + state.pdfMetadata.pageCount : '1';
                    if (elements.pageRangeNote) {
                        elements.pageRangeNote.textContent = state.pdfMetadata.pageCount > 1
                            ? 'Detected ' + state.pdfMetadata.pageCount + ' pages. Leave this empty to export every page, or use values like 1-3,5,-4,7-.'
                            : 'Detected 1 page. Leave this empty to export the single page.';
                    }
                    elements.summary.textContent = state.files[0].name + ' · ' + humanBytes(state.files[0].size) + ' · ' + state.pdfMetadata.pageCount + ' pages detected.';
                } catch (error) {
                    elements.convert.disabled = true;
                    setStatus(error.message, true);
                    return;
                }
            }

            if (state.kind === 'tiff') {
                try {
                    await ensureDependencies(['utif'], config);
                    state.sequenceMetadata = await readTiffMetadata(state.files[0]);
                    elements.pageRange.placeholder = state.sequenceMetadata.pageCount > 1 ? '1-' + state.sequenceMetadata.pageCount : '1';
                    if (elements.pageRangeNote) {
                        elements.pageRangeNote.textContent = state.sequenceMetadata.pageCount > 1
                            ? 'Detected ' + state.sequenceMetadata.pageCount + ' TIFF pages. Leave this empty to export every page, or use values like 1-3,5,-4,7-.'
                            : 'Detected 1 TIFF page. Leave this empty to export the single page.';
                    }
                    elements.summary.textContent = state.files[0].name + ' · ' + humanBytes(state.files[0].size) + ' · ' + state.sequenceMetadata.pageCount + ' TIFF pages detected.';
                } catch (error) {
                    elements.convert.disabled = true;
                    setStatus(error.message, true);
                    return;
                }
            }

            if (state.kind === 'gif') {
                try {
                    await ensureDependencies(['gif'], config);
                    state.sequenceMetadata = await readGifMetadata(state.files[0]);
                    elements.pageRange.placeholder = state.sequenceMetadata.frameCount > 1 ? '1-' + state.sequenceMetadata.frameCount : '1';
                    if (elements.pageRangeNote) {
                        elements.pageRangeNote.textContent = state.sequenceMetadata.frameCount > 1
                            ? 'Detected ' + state.sequenceMetadata.frameCount + ' GIF frames. Leave this empty to export every frame, or use values like 1-3,5,-4,7-.'
                            : 'Detected 1 GIF frame. Leave this empty to export the single frame.';
                    }
                    elements.summary.textContent = state.files[0].name + ' · ' + humanBytes(state.files[0].size) + ' · ' + state.sequenceMetadata.frameCount + ' GIF frames detected.';
                } catch (error) {
                    elements.convert.disabled = true;
                    setStatus(error.message, true);
                    return;
                }
            }

            if (state.kind === 'xlsx') {
                try {
                    await ensureDependencies(['xlsx'], config);
                    var workbook = await readWorkbook(state.files[0]);
                    state.workbookSheetNames = workbook.SheetNames.slice();
                    elements.sheetSelect.innerHTML = '';
                    state.workbookSheetNames.forEach(function (sheetName) {
                        var option = document.createElement('option');
                        option.value = sheetName;
                        option.textContent = sheetName;
                        elements.sheetSelect.appendChild(option);
                    });
                } catch (error) {
                    elements.convert.disabled = true;
                    setStatus(error.message, true);
                }
            }
        }

        function updateSelection(newFiles) {
            if (newFiles) {
                state.files = Array.prototype.slice.call(newFiles);
            }
            analyzeSelection().catch(function (error) {
                elements.convert.disabled = true;
                setStatus(error.message, true);
            });
        }

        async function performConversion() {
            var route = elements.routeSelect.value;
            var file = state.files[0];
            var result;
            var imageBlobs = [];
            var index;

            if (!route || !state.files.length) {
                setStatus('Select at least one supported file first.', true);
                return;
            }

            state.converting = true;
            elements.convert.disabled = true;
            setStatus('Converting locally…', false);
            elements.outputSummary.textContent = '';

            try {
                switch (state.kind) {
                case 'image':
                    if (route === 'application/pdf') {
                        await ensureDependencies(['jspdf'], config);
                        imageBlobs.push(file);
                        result = await makePdfFromImageBlobs(imageBlobs, slugifyBaseName(file.name) + '.pdf');
                    } else {
                        result = {
                            blob: await convertStandardImage(file, route, { quality: elements.quality ? elements.quality.value : 0.92, maxEdge: elements.maxEdge ? elements.maxEdge.value : 0 }, config),
                            filename: slugifyBaseName(file.name) + '.' + imageMimeToExtension(route),
                            previewType: 'image',
                            outputSummary: 'Image exported as ' + routeLabel(route) + '.'
                        };
                    }
                    break;
                case 'svg':
                    if (route === 'application/pdf') {
                        await ensureDependencies(['jspdf'], config);
                        imageBlobs.push(await convertSvgToRoute(file, 'image/png', { maxEdge: elements.maxEdge ? elements.maxEdge.value : 0 }, config));
                        result = await makePdfFromImageBlobs(imageBlobs, slugifyBaseName(file.name) + '.pdf');
                    } else {
                        result = {
                            blob: await convertSvgToRoute(file, route, { quality: elements.quality ? elements.quality.value : 0.92, maxEdge: elements.maxEdge ? elements.maxEdge.value : 0 }, config),
                            filename: slugifyBaseName(file.name) + '.' + imageMimeToExtension(route),
                            previewType: 'image',
                            outputSummary: 'SVG content exported as ' + routeLabel(route) + '.'
                        };
                    }
                    break;
                case 'heic':
                    await ensureDependencies(['heic'], config);
                    if (route === 'application/pdf') {
                        await ensureDependencies(['jspdf'], config);
                        imageBlobs.push(await decodeHeicToDisplayBlob(file));
                        result = await makePdfFromImageBlobs(imageBlobs, slugifyBaseName(file.name) + '.pdf');
                    } else {
                        result = {
                            blob: await convertHeicToRoute(file, route, { quality: elements.quality ? elements.quality.value : 0.92, maxEdge: elements.maxEdge ? elements.maxEdge.value : 0 }, config),
                            filename: slugifyBaseName(file.name) + '.' + imageMimeToExtension(route),
                            previewType: 'image',
                            outputSummary: 'HEIC content exported as ' + routeLabel(route) + '.'
                        };
                    }
                    break;
                case 'tiff':
                    await ensureDependencies(['utif', 'jszip'], config);
                    if (route === 'application/pdf') {
                        await ensureDependencies(['jspdf'], config);
                    }
                    result = await convertTiffToRoute(file, route, elements.pageRange.value, { quality: elements.quality ? elements.quality.value : 0.92, maxEdge: elements.maxEdge ? elements.maxEdge.value : 0 }, config);
                    break;
                case 'gif':
                    await ensureDependencies(['gif', 'jszip'], config);
                    if (route === 'application/pdf') {
                        await ensureDependencies(['jspdf'], config);
                    }
                    result = await convertGifToRoute(file, route, elements.pageRange.value, { quality: elements.quality ? elements.quality.value : 0.92, maxEdge: elements.maxEdge ? elements.maxEdge.value : 0 }, config);
                    break;
                case 'multi-image':
                    if (route === 'application/pdf') {
                        await ensureDependencies(['jspdf', 'heic'], config);
                        for (index = 0; index < state.files.length; index += 1) {
                            var currentFile = state.files[index];
                            var currentKind = getFileKind(currentFile);
                            if (currentKind === 'heic') {
                                imageBlobs.push(await decodeHeicToDisplayBlob(currentFile));
                            } else if (currentKind === 'svg') {
                                imageBlobs.push(await convertSvgToRoute(currentFile, 'image/png', { maxEdge: elements.maxEdge ? elements.maxEdge.value : 0 }, config));
                            } else {
                                imageBlobs.push(currentFile);
                            }
                        }
                        result = await makePdfFromImageBlobs(imageBlobs, 'merged-images.pdf');
                    } else {
                        await ensureDependencies(['jszip', 'heic'], config);
                        result = await convertMultipleImages(state.files, route, {
                            quality: elements.quality ? elements.quality.value : 0.92,
                            maxEdge: elements.maxEdge ? elements.maxEdge.value : 0
                        }, config);
                    }
                    break;
                case 'pdf':
                    await ensureDependencies(['pdf', 'jszip'], config);
                    if (route === 'text/plain' || route === 'text/html' || route === 'application/json' || route === 'text/csv' || route === XLSX_MIME) {
                        var pdfText = await extractPdfText(file, elements.pageRange.value);
                        if (route === 'text/plain') {
                            result = createPlainTextExport(pdfText, slugifyBaseName(file.name) + '.txt', 'PDF text was extracted as plain text.');
                        } else if (route === 'text/html') {
                            result = createHtmlExport(buildParagraphHtml(pdfText), slugifyBaseName(file.name) + '.html', 'PDF text was extracted as semantic HTML.');
                        } else if (route === 'application/json') {
                            result = createStructuredJsonBlob({ text: pdfText, lines: getTextStats(pdfText).lines }, slugifyBaseName(file.name) + '.json', 'PDF text was exported as structured JSON.');
                        } else if (route === 'text/csv') {
                            result = createLineCsvExport(pdfText, slugifyBaseName(file.name) + '.csv', 'PDF text was exported as CSV rows.');
                        } else {
                            await ensureDependencies(['xlsx'], config);
                            result = createWorkbookBlobFromLines(pdfText, slugifyBaseName(file.name) + '.xlsx', 'PDF text was exported as XLSX rows.', 'PDF');
                        }
                    } else {
                        result = await convertPdfToImageOutputs(file, route, elements.pageRange.value, elements.pdfScale ? elements.pdfScale.value : '2', { quality: elements.quality ? elements.quality.value : 0.92, maxEdge: elements.maxEdge ? elements.maxEdge.value : 0 }, config);
                    }
                    break;
                case 'txt':
                    if (route === 'application/pdf') {
                        await ensureDependencies(['jspdf'], config);
                    }
                    if (route === XLSX_MIME) {
                        await ensureDependencies(['xlsx'], config);
                    }
                    result = await convertTextFile(file, route);
                    break;
                case 'csv':
                    await ensureDependencies(['xlsx', 'dompurify'], config);
                    if (route === 'application/pdf') {
                        await ensureDependencies(['jspdf'], config);
                    }
                    result = await convertCsv(file, route);
                    break;
                case 'json':
                    await ensureDependencies(['xlsx', 'dompurify'], config);
                    if (route === 'application/pdf') {
                        await ensureDependencies(['jspdf'], config);
                    }
                    result = await convertJson(file, route);
                    break;
                case 'xlsx':
                    await ensureDependencies(['xlsx', 'dompurify'], config);
                    if (route === 'application/pdf') {
                        await ensureDependencies(['jspdf'], config);
                    }
                    result = await convertWorkbook(file, route, elements.sheetSelect.value);
                    break;
                case 'docx':
                    await ensureDependencies(['jszip', 'dompurify'], config);
                    if (route === 'application/pdf') {
                        await ensureDependencies(['jspdf'], config);
                    }
                    if (route === XLSX_MIME) {
                        await ensureDependencies(['xlsx'], config);
                    }
                    result = await convertDocx(file, route);
                    break;
                case 'doc':
                    if (route === 'application/pdf') {
                        await ensureDependencies(['jspdf'], config);
                    }
                    if (route === XLSX_MIME) {
                        await ensureDependencies(['xlsx'], config);
                    }
                    result = await convertLegacyDoc(file, route);
                    break;
                case 'rtf':
                    if (route === 'application/pdf') {
                        await ensureDependencies(['jspdf'], config);
                    }
                    if (route === XLSX_MIME) {
                        await ensureDependencies(['xlsx'], config);
                    }
                    result = await convertRtf(file, route);
                    break;
                default:
                    throw new Error('This route is not available for the current selection.');
                }

                if (!result || !result.blob) {
                    throw new Error('No converted output was generated.');
                }

                state.output = result;
                createPreview(result, elements);
                elements.outputSummary.textContent = result.outputSummary || 'The converted file is ready.';
                downloadBlob(result.blob, result.filename);
                setStatus('Done. The converted file was created locally and downloaded.', false);
            } catch (error) {
                setStatus(error && error.message ? error.message : 'The local conversion failed.', true);
            } finally {
                state.converting = false;
                elements.convert.disabled = !state.routes.length;
            }
        }

        elements.dropzone.addEventListener('click', function (event) {
            if (event.target === elements.dropzone) {
                elements.input.click();
            }
        });

        ['dragenter', 'dragover'].forEach(function (type) {
            elements.dropzone.addEventListener(type, function (event) {
                event.preventDefault();
                elements.dropzone.classList.add('is-dragover');
            });
        });

        ['dragleave', 'drop'].forEach(function (type) {
            elements.dropzone.addEventListener(type, function (event) {
                event.preventDefault();
                if (type === 'drop') {
                    updateSelection(event.dataTransfer.files);
                }
                elements.dropzone.classList.remove('is-dragover');
            });
        });

        elements.input.addEventListener('change', function () {
            updateSelection(elements.input.files);
        });

        elements.browse.addEventListener('click', function () {
            elements.input.click();
        });

        elements.routeSelect.addEventListener('change', updateRouteDependentFields);

        elements.clear.addEventListener('click', function () {
            state.files = [];
            state.routes = [];
            elements.input.value = '';
            updateSelection();
        });

        elements.convert.addEventListener('click', function () {
            if (!state.converting) {
                performConversion();
            }
        });

        createPreview(null, elements);
        updateCodecSupportNote(elements, config.imageSupport);
        ensureImageCodecSupport(config, elements).catch(function () {
            updateCodecSupportNote(elements, config.imageSupport);
        });
        updateRouteDependentFields();
        updateSelection();
    }

    window.NoesiaSwaUniversalLocalConverter = {
        bootstrap: bootstrap,
        ensureDependencies: ensureDependencies
    };
}(window, document));
