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

    var root = null;
    var adminVars = window.noesia_admin_vars || {};
    var ajaxUrl = String(adminVars.ajax_url || '');
    var nonce = String(adminVars.nonce || '');
    var i18n = (adminVars.i18n && typeof adminVars.i18n === 'object') ? adminVars.i18n : {};
    var siteLocale = 'en-US';
    var ratesPayload = {};
    var observer = null;

    function parseRootPayload(targetRoot) {
        siteLocale = targetRoot.getAttribute('data-site-locale') || 'en-US';
        try {
            ratesPayload = JSON.parse(targetRoot.getAttribute('data-rates') || '{}');
        } catch (error) {
            ratesPayload = {};
        }
    }

    function t(key, fallback) {
        return i18n[key] || fallback;
    }

    function qs(selector, context) {
        return (context || root).querySelector(selector);
    }

    function qsa(selector, context) {
        return Array.prototype.slice.call((context || root).querySelectorAll(selector));
    }

    function setText(node, value) {
        if (node) {
            node.textContent = value;
        }
    }

    function humanDate(value) {
        if (!value) {
            return t('widgets_date_unavailable', 'Update time unavailable');
        }
        var parts = String(value).split('-');
        if (parts.length !== 3) {
            return value;
        }
        var date = new Date(parts[0], parts[1] - 1, parts[2]);
        try {
            return new Intl.DateTimeFormat(siteLocale, { year: 'numeric', month: 'long', day: 'numeric' }).format(date);
        } catch (error) {
            return value;
        }
    }

    function ajax(action, method, payload) {
        var options = {
            method: method || 'POST',
            credentials: 'same-origin'
        };
        var data = payload instanceof URLSearchParams ? payload : new URLSearchParams(payload || {});
        data.set('action', action);
        data.set('nonce', nonce);
        options.body = data;

        return window.fetch(ajaxUrl, options).then(function (response) {
            return response.json();
        });
    }

    function initTabs() {
        var tabs = qsa('[data-widgets-tab]');
        var panels = qsa('[data-widgets-panel]');
        tabs.forEach(function (tab) {
            tab.addEventListener('click', function () {
                var target = tab.getAttribute('data-widgets-tab');
                tabs.forEach(function (button) {
                    button.classList.toggle('is-active', button === tab);
                });
                panels.forEach(function (panel) {
                    panel.classList.toggle('is-active', panel.getAttribute('data-widgets-panel') === target);
                });
            });
        });
    }

    function formatNumber(value, digits) {
        var precision = typeof digits === 'number' ? digits : 2;
        var numeric = Number(value);
        if (!Number.isFinite(numeric)) {
            return '0';
        }
        try {
            return new Intl.NumberFormat(siteLocale, { minimumFractionDigits: 0, maximumFractionDigits: precision }).format(numeric);
        } catch (error) {
            return numeric.toFixed(precision);
        }
    }

    function initCurrencyRates() {
        var statusNode = qs('[data-rates-status]');
        var fromSelect = qs('[data-rates-from]');
        var toSelect = qs('[data-rates-to]');
        var amountInput = qs('[data-rates-amount]');
        var resultNode = qs('[data-rates-result]');
        var updatedNode = qs('[data-rates-updated]');
        var favoritesNode = qs('[data-rates-favorites]');
        var refreshButton = qs('[data-action="refresh-rates"]');

        function populate() {
            var rates = ratesPayload.rates || {};
            var currencies = Object.keys(rates).sort();
            fromSelect.innerHTML = '';
            toSelect.innerHTML = '';
            currencies.forEach(function (currency) {
                var optionFrom = document.createElement('option');
                optionFrom.value = currency;
                optionFrom.textContent = currency;
                fromSelect.appendChild(optionFrom);

                var optionTo = document.createElement('option');
                optionTo.value = currency;
                optionTo.textContent = currency;
                toSelect.appendChild(optionTo);
            });
            fromSelect.value = ratesPayload.base || 'EUR';
            toSelect.value = 'USD' in rates ? 'USD' : (currencies[1] || currencies[0] || 'EUR');
            renderFavorites();
            renderResult();
        }

        function renderFavorites() {
            favoritesNode.innerHTML = '';
            (ratesPayload.favorites || []).forEach(function (currency) {
                if (!(currency in (ratesPayload.rates || {}))) {
                    return;
                }
                var button = document.createElement('button');
                button.type = 'button';
                button.className = 'noesia-swa-widgets__chip';
                button.textContent = currency;
                button.addEventListener('click', function () {
                    toSelect.value = currency;
                    renderResult();
                });
                favoritesNode.appendChild(button);
            });
        }

        function renderResult() {
            var rates = ratesPayload.rates || {};
            var from = fromSelect.value;
            var to = toSelect.value;
            var amount = parseFloat(amountInput.value || '0');
            var base = ratesPayload.base || 'EUR';
            if (!rates[from] || !rates[to] || !Number.isFinite(amount)) {
                setText(resultNode, '—');
                return;
            }
            var inBase = from === base ? amount : amount / rates[from];
            var converted = to === base ? inBase : inBase * rates[to];
            setText(resultNode, formatNumber(amount, 2) + ' ' + from + ' = ' + formatNumber(converted, 4) + ' ' + to);
            setText(updatedNode, humanDate(ratesPayload.updated_at) + ' • ' + (ratesPayload.is_cached ? t('widgets_currency_stale', 'Showing cached rates.') : t('widgets_currency_live', 'Live provider data loaded.')));
        }

        [fromSelect, toSelect, amountInput].forEach(function (field) {
            field.addEventListener('input', renderResult);
            field.addEventListener('change', renderResult);
        });

        refreshButton.addEventListener('click', function () {
            setText(statusNode, t('widgets_refreshing', 'Refreshing…'));
            ajax('noesia_swa_widgets_refresh_rates', 'POST', {})
                .then(function (response) {
                    if (!response || !response.success || !response.data || !response.data.rates_payload) {
                        throw new Error('refresh_failed');
                    }
                    ratesPayload = response.data.rates_payload;
                    populate();
                    setText(statusNode, t('widgets_refreshed', 'Rates refreshed.'));
                })
                .catch(function () {
                    setText(statusNode, t('widgets_refresh_failed', 'Unable to refresh rates right now.'));
                });
        });

        populate();
    }

    function initUnitConverters() {
        var config = {
            length: {
                label: 'Length',
                units: { m: 1, km: 1000, cm: 0.01, mm: 0.001, ft: 0.3048, in: 0.0254, yd: 0.9144, px: 0.0254 / 96 }
            },
            weight: {
                label: 'Weight',
                units: { kg: 1, g: 0.001, lb: 0.45359237, oz: 0.0283495231 }
            },
            volume: {
                label: 'Volume',
                units: { l: 1, ml: 0.001, cup: 0.2365882365, tbsp: 0.0147867648, tsp: 0.0049289216 }
            },
            speed: {
                label: 'Speed',
                units: { 'm/s': 1, 'km/h': 0.2777777778, mph: 0.44704, knot: 0.514444 }
            },
            area: {
                label: 'Area',
                units: { 'm²': 1, 'km²': 1000000, 'ft²': 0.092903, acre: 4046.8564224 }
            },
            data: {
                label: 'Data Sizes',
                units: { B: 1, KB: 1024, MB: 1048576, GB: 1073741824, TB: 1099511627776 }
            }
        };
        var categorySelect = qs('[data-unit-category]');
        var fromSelect = qs('[data-unit-from]');
        var toSelect = qs('[data-unit-to]');
        var valueInput = qs('[data-unit-value]');
        var resultNode = qs('[data-unit-result]');

        function populateCategories() {
            Object.keys(config).forEach(function (key) {
                var option = document.createElement('option');
                option.value = key;
                option.textContent = config[key].label;
                categorySelect.appendChild(option);
            });
        }

        function populateUnits() {
            var category = categorySelect.value;
            var units = config[category].units;
            fromSelect.innerHTML = '';
            toSelect.innerHTML = '';
            Object.keys(units).forEach(function (unit, index) {
                var optionA = document.createElement('option');
                optionA.value = unit;
                optionA.textContent = unit;
                fromSelect.appendChild(optionA);
                var optionB = document.createElement('option');
                optionB.value = unit;
                optionB.textContent = unit;
                toSelect.appendChild(optionB);
                if (index === 0) {
                    fromSelect.value = unit;
                }
            });
            toSelect.selectedIndex = Math.min(1, toSelect.options.length - 1);
            render();
        }

        function render() {
            var category = categorySelect.value;
            var input = parseFloat(valueInput.value || '0');
            if (!Number.isFinite(input)) {
                setText(resultNode, '—');
                return;
            }
            if (category === 'temperature') {
                return;
            }
            var units = config[category].units;
            var base = input * units[fromSelect.value];
            var output = base / units[toSelect.value];
            setText(resultNode, formatNumber(input, 4) + ' ' + fromSelect.value + ' = ' + formatNumber(output, 4) + ' ' + toSelect.value);
        }

        populateCategories();
        categorySelect.addEventListener('change', populateUnits);
        [fromSelect, toSelect, valueInput].forEach(function (field) {
            field.addEventListener('input', render);
            field.addEventListener('change', render);
        });
        populateUnits();

        // Temperature converter.
        config.temperature = { label: 'Temperature', units: { C: 'C', F: 'F', K: 'K' } };
        var tempOption = document.createElement('option');
        tempOption.value = 'temperature';
        tempOption.textContent = config.temperature.label;
        categorySelect.appendChild(tempOption);

        function convertTemperature(value, from, to) {
            var celsius = value;
            if (from === 'F') {
                celsius = (value - 32) * 5 / 9;
            } else if (from === 'K') {
                celsius = value - 273.15;
            }
            if (to === 'F') {
                return (celsius * 9 / 5) + 32;
            }
            if (to === 'K') {
                return celsius + 273.15;
            }
            return celsius;
        }

        categorySelect.addEventListener('change', function () {
            if (categorySelect.value !== 'temperature') {
                return;
            }
            fromSelect.innerHTML = '';
            toSelect.innerHTML = '';
            ['C', 'F', 'K'].forEach(function (unit, index) {
                var a = document.createElement('option');
                a.value = unit;
                a.textContent = unit;
                fromSelect.appendChild(a);
                var b = document.createElement('option');
                b.value = unit;
                b.textContent = unit;
                toSelect.appendChild(b);
                if (index === 0) {
                    fromSelect.value = unit;
                }
            });
            toSelect.value = 'F';
            renderTemperature();
        });

        function renderTemperature() {
            if (categorySelect.value !== 'temperature') {
                return;
            }
            var input = parseFloat(valueInput.value || '0');
            if (!Number.isFinite(input)) {
                setText(resultNode, '—');
                return;
            }
            var output = convertTemperature(input, fromSelect.value, toSelect.value);
            setText(resultNode, formatNumber(input, 2) + '°' + fromSelect.value + ' = ' + formatNumber(output, 2) + '°' + toSelect.value);
        }

        [fromSelect, toSelect, valueInput].forEach(function (field) {
            field.addEventListener('input', renderTemperature);
            field.addEventListener('change', renderTemperature);
        });

        // Kitchen converter.
        var kitchenUnits = { g: 1, kg: 1000, oz: 28.3495231, lb: 453.59237, ml: 1, l: 1000, cup: 236.5882365, tbsp: 14.7867648, tsp: 4.9289216 };
        var kitchenFrom = qs('[data-kitchen-from]');
        var kitchenTo = qs('[data-kitchen-to]');
        var kitchenValue = qs('[data-kitchen-value]');
        var kitchenResult = qs('[data-kitchen-result]');

        Object.keys(kitchenUnits).forEach(function (unit, index) {
            var opt1 = document.createElement('option');
            opt1.value = unit;
            opt1.textContent = unit;
            kitchenFrom.appendChild(opt1);
            var opt2 = document.createElement('option');
            opt2.value = unit;
            opt2.textContent = unit;
            kitchenTo.appendChild(opt2);
            if (index === 0) {
                kitchenFrom.value = unit;
            }
        });
        kitchenTo.value = 'cup';

        function renderKitchen() {
            var input = parseFloat(kitchenValue.value || '0');
            if (!Number.isFinite(input)) {
                setText(kitchenResult, '—');
                return;
            }
            var base = input * kitchenUnits[kitchenFrom.value];
            var output = base / kitchenUnits[kitchenTo.value];
            setText(kitchenResult, formatNumber(input, 4) + ' ' + kitchenFrom.value + ' = ' + formatNumber(output, 4) + ' ' + kitchenTo.value);
        }

        [kitchenFrom, kitchenTo, kitchenValue].forEach(function (field) {
            field.addEventListener('input', renderKitchen);
            field.addEventListener('change', renderKitchen);
        });
        renderKitchen();
    }

    function initVatCalculator() {
        var mode = qs('[data-vat-mode]');
        var amount = qs('[data-vat-amount]');
        var rate = qs('[data-vat-rate]');
        var result = qs('[data-vat-result]');

        function render() {
            var value = parseFloat(amount.value || '0');
            var percent = parseFloat(rate.value || '0');
            if (!Number.isFinite(value) || !Number.isFinite(percent)) {
                result.innerHTML = '';
                return;
            }
            var html = '';
            if (mode.value === 'add') {
                var taxValue = value * (percent / 100);
                var gross = value + taxValue;
                html += '<div><strong>Net</strong><span>' + formatNumber(value, 2) + '</span></div>';
                html += '<div><strong>Tax</strong><span>' + formatNumber(taxValue, 2) + '</span></div>';
                html += '<div><strong>Gross</strong><span>' + formatNumber(gross, 2) + '</span></div>';
            } else {
                var net = value / (1 + percent / 100);
                var extracted = value - net;
                html += '<div><strong>Gross</strong><span>' + formatNumber(value, 2) + '</span></div>';
                html += '<div><strong>Tax</strong><span>' + formatNumber(extracted, 2) + '</span></div>';
                html += '<div><strong>Net</strong><span>' + formatNumber(net, 2) + '</span></div>';
            }
            result.innerHTML = html;
        }

        [mode, amount, rate].forEach(function (field) {
            field.addEventListener('input', render);
            field.addEventListener('change', render);
        });
        render();
    }

    function initTextTools() {
        var source = qs('[data-text-source]');
        var wordStats = qs('[data-word-stats]');
        var jsonSource = qs('[data-json-source]');
        var jsonStatus = qs('[data-json-status]');
        var base64Source = qs('[data-base64-source]');
        var hashSource = qs('[data-hash-source]');
        var hashAlgorithm = qs('[data-hash-algorithm]');
        var hashResult = qs('[data-hash-result]');
        var colorPicker = qs('[data-color-picker]');
        var colorHex = qs('[data-color-hex]');
        var colorRgb = qs('[data-color-rgb]');
        var colorSwatch = qs('[data-color-swatch]');
        var emailSource = qs('[data-email-source]');
        var emailStatus = qs('[data-email-status]');
        var emailResult = qs('[data-email-result]');

        function titleCase(text) {
            return text.toLowerCase().replace(/\b\w/g, function (char) { return char.toUpperCase(); });
        }

        function sentenceCase(text) {
            return text.toLowerCase().replace(/(^\s*\w|[.!?]\s+\w)/g, function (char) { return char.toUpperCase(); });
        }

        qsa('[data-case]').forEach(function (button) {
            button.addEventListener('click', function () {
                var text = source.value || '';
                switch (button.getAttribute('data-case')) {
                    case 'upper': source.value = text.toUpperCase(); break;
                    case 'lower': source.value = text.toLowerCase(); break;
                    case 'title': source.value = titleCase(text); break;
                    case 'sentence': source.value = sentenceCase(text); break;
                }
                updateCounts();
            });
        });

        function updateCounts() {
            var text = source.value || '';
            var words = text.trim() ? text.trim().split(/\s+/).length : 0;
            var chars = text.length;
            var charsNoSpaces = text.replace(/\s+/g, '').length;
            wordStats.innerHTML = '' +
                '<div><strong>Words</strong><span>' + formatNumber(words, 0) + '</span></div>' +
                '<div><strong>Characters</strong><span>' + formatNumber(chars, 0) + '</span></div>' +
                '<div><strong>No Spaces</strong><span>' + formatNumber(charsNoSpaces, 0) + '</span></div>';
        }
        source.addEventListener('input', updateCounts);
        updateCounts();

        qsa('[data-json-action]').forEach(function (button) {
            button.addEventListener('click', function () {
                try {
                    var parsed = JSON.parse(jsonSource.value || '{}');
                    if (button.getAttribute('data-json-action') === 'format') {
                        jsonSource.value = JSON.stringify(parsed, null, 2);
                    } else {
                        jsonSource.value = JSON.stringify(parsed);
                    }
                    setText(jsonStatus, t('widgets_json_valid', 'Valid JSON'));
                } catch (error) {
                    setText(jsonStatus, t('widgets_json_invalid', 'Invalid JSON') + ': ' + error.message);
                }
            });
        });

        qsa('[data-base64-action]').forEach(function (button) {
            button.addEventListener('click', function () {
                try {
                    if (button.getAttribute('data-base64-action') === 'encode') {
                        base64Source.value = window.btoa(unescape(encodeURIComponent(base64Source.value || '')));
                    } else {
                        base64Source.value = decodeURIComponent(escape(window.atob(base64Source.value || '')));
                    }
                } catch (error) {
                    base64Source.value = '';
                }
            });
        });

        qs('[data-hash-action="generate"]').addEventListener('click', function () {
            if (!window.crypto || !window.crypto.subtle) {
                setText(hashResult, t('widgets_hash_unsupported', 'Hashing is not supported in this browser.'));
                return;
            }
            var algo = hashAlgorithm.value;
            var data = new TextEncoder().encode(hashSource.value || '');
            window.crypto.subtle.digest(algo, data).then(function (buffer) {
                var hex = Array.prototype.map.call(new Uint8Array(buffer), function (byte) {
                    return ('00' + byte.toString(16)).slice(-2);
                }).join('');
                hashResult.value = hex;
            });
        });

        function hexToRgb(hex) {
            var normalized = hex.replace('#', '');
            if (normalized.length === 3) {
                normalized = normalized.split('').map(function (char) { return char + char; }).join('');
            }
            if (!/^[0-9a-fA-F]{6}$/.test(normalized)) {
                return null;
            }
            var intValue = parseInt(normalized, 16);
            return {
                r: (intValue >> 16) & 255,
                g: (intValue >> 8) & 255,
                b: intValue & 255
            };
        }

        function updateColorFromHex() {
            var rgb = hexToRgb(colorHex.value || '');
            if (!rgb) {
                return;
            }
            colorPicker.value = '#' + (colorHex.value || '').replace('#', '').slice(0, 6);
            colorRgb.value = 'rgb(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ')';
            colorSwatch.style.background = colorPicker.value;
        }

        colorPicker.addEventListener('input', function () {
            colorHex.value = colorPicker.value;
            updateColorFromHex();
        });
        colorHex.addEventListener('input', updateColorFromHex);
        updateColorFromHex();

        qs('[data-email-action="extract"]').addEventListener('click', function () {
            var text = emailSource.value || '';
            var matches = text.match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi) || [];
            var unique = Array.from(new Set(matches.map(function (item) { return item.toLowerCase(); })));
            emailResult.value = unique.join('\n');
            setText(emailStatus, unique.length ? (unique.length + ' unique email(s)') : t('widgets_no_emails', 'No email addresses found.'));
        });

        qsa('[data-copy-target]').forEach(function (button) {
            button.addEventListener('click', function () {
                var target = qs(button.getAttribute('data-copy-target'));
                if (!target) {
                    return;
                }
                var value = target.value || target.textContent || '';
                navigator.clipboard.writeText(value).then(function () {
                    button.textContent = t('widgets_copied', 'Copied.');
                    window.setTimeout(function () {
                        button.textContent = t('widgets_copy', 'Copy');
                    }, 1200);
                });
            });
        });
    }

    function initImageTools() {
        var fileInput = qs('[data-image-file]');
        var formatSelect = qs('[data-image-format]');
        var qualityInput = qs('[data-image-quality]');
        var widthInput = qs('[data-image-width]');
        var heightInput = qs('[data-image-height]');
        var processButton = qs('[data-image-action="process"]');
        var downloadLink = qs('[data-image-download]');
        var statusNode = qs('[data-image-status]');
        var originalImage = qs('[data-image-original]');
        var processedImage = qs('[data-image-processed]');
        var originalMeta = qs('[data-image-original-meta]');
        var processedMeta = qs('[data-image-processed-meta]');
        var currentFile = null;

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

        if (!supportsImageType('image/avif')) {
            qsa('option', formatSelect).forEach(function (option) {
                if (option.value === 'image/avif') {
                    option.disabled = true;
                    option.textContent += ' (' + t('widgets_avif_unsupported', 'Unsupported') + ')';
                }
            });
        }

        fileInput.addEventListener('change', function () {
            var file = fileInput.files && fileInput.files[0] ? fileInput.files[0] : null;
            if (!file) {
                currentFile = null;
                return;
            }
            currentFile = file;
            var reader = new FileReader();
            reader.onload = function (event) {
                originalImage.src = event.target.result;
                setText(originalMeta, file.name + ' • ' + formatNumber(file.size / 1024, 1) + ' KB');
                setText(statusNode, t('widgets_image_ready', 'Image ready.'));
            };
            reader.readAsDataURL(file);
        });

        processButton.addEventListener('click', function () {
            if (!currentFile) {
                setText(statusNode, t('widgets_image_missing', 'Please choose an image first.'));
                return;
            }
            if (formatSelect.value === 'image/avif' && !supportsImageType('image/avif')) {
                setText(statusNode, t('widgets_avif_unsupported', 'AVIF export is not supported in this browser.'));
                return;
            }
            var image = new Image();
            image.onload = function () {
                var maxWidth = Math.max(1, parseInt(widthInput.value || image.width, 10));
                var maxHeight = Math.max(1, parseInt(heightInput.value || image.height, 10));
                var ratio = Math.min(maxWidth / image.width, maxHeight / image.height, 1);
                var width = Math.round(image.width * ratio);
                var height = Math.round(image.height * ratio);
                var canvas = document.createElement('canvas');
                canvas.width = width;
                canvas.height = height;
                var ctx = canvas.getContext('2d');
                ctx.drawImage(image, 0, 0, width, height);
                canvas.toBlob(function (blob) {
                    if (!blob) {
                        setText(statusNode, 'Unable to process image.');
                        return;
                    }
                    var objectUrl = URL.createObjectURL(blob);
                    processedImage.src = objectUrl;
                    processedMeta.textContent = width + '×' + height + ' • ' + formatNumber(blob.size / 1024, 1) + ' KB';
                    downloadLink.hidden = false;
                    downloadLink.href = objectUrl;
                    var extension = formatSelect.value.replace('image/', '').replace('jpeg', 'jpg');
                    downloadLink.download = 'noesia-image.' + extension;
                    setText(statusNode, t('widgets_image_ready', 'Image ready.'));
                }, formatSelect.value, parseFloat(qualityInput.value || '0.85'));
            };
            image.src = originalImage.src;
        });
    }

    function initRuler() {
        var overlay = qs('[data-ruler-overlay]');
        var horizontal = qs('[data-ruler-horizontal]');
        var vertical = qs('[data-ruler-vertical]');
        var toggle = qs('[data-ruler-toggle]');
        var orientation = qs('[data-ruler-orientation]');
        var opacity = qs('[data-ruler-opacity]');
        var scale = qs('[data-ruler-scale]');
        var scaleReadout = qs('[data-ruler-scale-readout]');
        var lengthReadout = qs('[data-ruler-length-readout]');
        var horizontalCm = qs('[data-ruler-horizontal-cm]');
        var verticalCm = qs('[data-ruler-vertical-cm]');
        var reset = qs('[data-ruler-reset]');
        var storageKey = 'noesiaSwaWidgetsRulerState';
        var defaultState = {
            visible: false,
            orientation: 'both',
            opacity: 0.85,
            scale: 37.8,
            horizontalTop: 84,
            horizontalLeft: 32,
            verticalTop: 132,
            verticalLeft: 32,
            horizontalCm: 20,
            verticalCm: 20
        };
        var state = defaultState;

        try {
            state = Object.assign({}, defaultState, JSON.parse(window.localStorage.getItem(storageKey) || '{}'));
        } catch (error) {
            state = Object.assign({}, defaultState);
        }

        function saveState() {
            try {
                window.localStorage.setItem(storageKey, JSON.stringify(state));
            } catch (error) {
                // Ignore storage issues.
            }
        }

        function clamp(value, min, max) {
            return Math.max(min, Math.min(max, value));
        }

        function renderLabels(bar, axis, size, scalePxCm) {
            if (!bar) {
                return;
            }
            bar.innerHTML = '';
            bar.style.setProperty('--ruler-cm', scalePxCm + 'px');
            bar.style.setProperty('--ruler-mm', (scalePxCm / 10) + 'px');

            var count = Math.floor(size / scalePxCm);
            for (var i = 0; i <= count; i += 1) {
                var label = document.createElement('span');
                label.className = 'noesia-swa-widgets__ruler-label noesia-swa-widgets__ruler-label--' + axis;
                if (axis === 'x') {
                    label.style.left = (i * scalePxCm) + 'px';
                } else {
                    label.style.top = (i * scalePxCm) + 'px';
                }
                label.textContent = i + ' cm';
                bar.appendChild(label);
            }
        }

        function render() {
            state.visible = !!state.visible;
            state.orientation = orientation.value || state.orientation || 'both';
            state.opacity = parseFloat(opacity.value || state.opacity || '0.85');
            state.scale = parseFloat(scale.value || state.scale || '37.8');
            state.horizontalCm = parseFloat(horizontalCm.value || state.horizontalCm || '20');
            state.verticalCm = parseFloat(verticalCm.value || state.verticalCm || '20');

            overlay.hidden = !state.visible;
            toggle.textContent = state.visible ? t('widgets_ruler_hide', 'Hide Ruler') : t('widgets_ruler_show', 'Show Ruler');
            overlay.style.opacity = String(state.opacity);

            horizontal.hidden = !state.visible || (state.orientation === 'vertical');
            vertical.hidden = !state.visible || (state.orientation === 'horizontal');

            horizontal.style.top = state.horizontalTop + 'px';
            horizontal.style.left = state.horizontalLeft + 'px';
            horizontal.style.width = Math.max(5, state.horizontalCm) * state.scale + 'px';
            vertical.style.top = state.verticalTop + 'px';
            vertical.style.left = state.verticalLeft + 'px';
            vertical.style.height = Math.max(5, state.verticalCm) * state.scale + 'px';

            renderLabels(horizontal, 'x', horizontal.offsetWidth || 0, state.scale);
            renderLabels(vertical, 'y', vertical.offsetHeight || 0, state.scale);

            setText(scaleReadout, t('widgets_ruler_calibrated_for_screen', 'Calibrated for this screen:') + ' 1 cm = ' + formatNumber(state.scale, 2) + ' px');
            setText(lengthReadout, t('widgets_ruler_length', 'Length') + ': H ' + formatNumber(state.horizontalCm, 0) + ' cm · V ' + formatNumber(state.verticalCm, 0) + ' cm');
            saveState();
        }

        function makeDraggable(node, axisPrefix) {
            if (!node) {
                return;
            }
            var drag = null;

            function onMove(event) {
                if (!drag) {
                    return;
                }
                var viewportWidth = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
                var viewportHeight = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
                var nextLeft = clamp(drag.startLeft + (event.clientX - drag.x), 8, viewportWidth - node.offsetWidth - 8);
                var nextTop = clamp(drag.startTop + (event.clientY - drag.y), 8, viewportHeight - node.offsetHeight - 8);
                state[axisPrefix + 'Left'] = nextLeft;
                state[axisPrefix + 'Top'] = nextTop;
                node.style.left = nextLeft + 'px';
                node.style.top = nextTop + 'px';
            }

            function onUp() {
                if (!drag) {
                    return;
                }
                drag = null;
                document.removeEventListener('pointermove', onMove);
                document.removeEventListener('pointerup', onUp);
                saveState();
            }

            node.addEventListener('pointerdown', function (event) {
                if (!state.visible) {
                    return;
                }
                drag = {
                    x: event.clientX,
                    y: event.clientY,
                    startLeft: state[axisPrefix + 'Left'],
                    startTop: state[axisPrefix + 'Top']
                };
                node.setPointerCapture(event.pointerId);
                document.addEventListener('pointermove', onMove);
                document.addEventListener('pointerup', onUp);
            });
        }

        toggle.addEventListener('click', function () {
            state.visible = !state.visible;
            render();
        });
        orientation.value = state.orientation;
        opacity.value = String(state.opacity);
        scale.value = String(state.scale);
        horizontalCm.value = String(state.horizontalCm || 20);
        verticalCm.value = String(state.verticalCm || 20);
        orientation.addEventListener('change', render);
        opacity.addEventListener('input', render);
        scale.addEventListener('input', render);
        horizontalCm.addEventListener('input', render);
        verticalCm.addEventListener('input', render);
        reset.addEventListener('click', function () {
            state.horizontalTop = defaultState.horizontalTop;
            state.horizontalLeft = defaultState.horizontalLeft;
            state.verticalTop = defaultState.verticalTop;
            state.verticalLeft = defaultState.verticalLeft;
            render();
        });
        window.addEventListener('resize', render);
        makeDraggable(horizontal, 'horizontal');
        makeDraggable(vertical, 'vertical');
        render();
    }

    function initConnectionTest() {
        var button = qs('[data-diagnostics-run]');
        var copyButton = qs('[data-diagnostics-copy]');
        var statusNode = qs('[data-diagnostics-status]');
        var resultsNode = qs('[data-diagnostics-results]');
        var restRoot = root ? String(root.getAttribute('data-rest-root') || '') : '';
        var lastReportText = '';

        if (!button || !statusNode || !resultsNode) {
            return;
        }

        function notReportedLabel() {
            return t('widgets_not_reported', 'Not reported');
        }

        function unavailableLabel() {
            return t('widgets_unavailable', 'Unavailable');
        }

        function sortNumeric(list) {
            return list.slice().sort(function (a, b) {
                return a - b;
            });
        }

        function average(list) {
            if (!list.length) {
                return 0;
            }
            return list.reduce(function (sum, item) {
                return sum + item;
            }, 0) / list.length;
        }

        function median(list) {
            if (!list.length) {
                return 0;
            }
            var sorted = sortNumeric(list);
            var middle = Math.floor(sorted.length / 2);
            if (sorted.length % 2) {
                return sorted[middle];
            }
            return (sorted[middle - 1] + sorted[middle]) / 2;
        }

        function getJitter(list) {
            if (list.length < 2) {
                return 0;
            }
            var deltas = [];
            for (var i = 1; i < list.length; i += 1) {
                deltas.push(Math.abs(list[i] - list[i - 1]));
            }
            return average(deltas);
        }

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

        function formatSignedSeconds(milliseconds) {
            var seconds = (Number(milliseconds) || 0) / 1000;
            var sign = seconds > 0 ? '+' : '';
            return sign + formatNumber(seconds, 2) + ' s';
        }

        function getLatencyLabel(milliseconds) {
            if (milliseconds < 450) {
                return 'Fast';
            }
            if (milliseconds < 850) {
                return 'Normal';
            }
            return 'Slow';
        }

        function getJitterLabel(milliseconds) {
            if (milliseconds < 120) {
                return 'Stable';
            }
            if (milliseconds < 250) {
                return 'Variable';
            }
            return 'Unstable';
        }

        function getClockStatus(driftMs, latencyMs) {
            var absoluteDrift = Math.abs(Number(driftMs) || 0);
            var uncertainty = Math.max((Number(latencyMs) || 0) / 2, 250);
            if (absoluteDrift <= (3000 + uncertainty)) {
                return 'OK';
            }
            if (absoluteDrift <= (30000 + uncertainty)) {
                return 'Warning';
            }
            return 'Critical';
        }

        function getReachabilityLabel(result) {
            if (!result) {
                return unavailableLabel();
            }
            if (result.ok) {
                return 'OK · HTTP ' + result.statusCode + ' · ' + formatNumber(result.durationMs, 1) + ' ms';
            }
            if (result.statusCode) {
                return 'Failed · HTTP ' + result.statusCode;
            }
            return 'Failed';
        }

        function getSecureContextLabel() {
            if (window.isSecureContext || window.location.protocol === 'https:') {
                return 'Secure (HTTPS)';
            }
            return 'Not secure';
        }

        function getBrowserNetworkLabel() {
            var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
            if (!connection) {
                return notReportedLabel();
            }
            if (connection.effectiveType) {
                return String(connection.effectiveType);
            }
            if (typeof connection.type === 'string' && connection.type) {
                return String(connection.type);
            }
            return notReportedLabel();
        }

        function getTimezoneLabel() {
            try {
                var timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
                return timezone || notReportedLabel();
            } catch (error) {
                return notReportedLabel();
            }
        }

        function getViewportLabel() {
            return String(Math.max(0, window.innerWidth || 0)) + ' × ' + String(Math.max(0, window.innerHeight || 0));
        }

        function runAjaxSample() {
            var startedPerf = performance.now();
            var startedEpoch = Date.now();

            return ajax('noesia_swa_widgets_speed_ping', 'POST', {})
                .then(function (response) {
                    var endedPerf = performance.now();
                    var endedEpoch = Date.now();
                    var payload = response && response.success && response.data ? response.data : null;
                    var serverEpoch = payload && payload.server_time_unix_ms ? Number(payload.server_time_unix_ms) : 0;
                    if (!serverEpoch) {
                        throw new Error('invalid_ping_payload');
                    }
                    return {
                        ok: true,
                        durationMs: endedPerf - startedPerf,
                        serverEpochMs: serverEpoch,
                        serverIsoUtc: String(payload.server_time_iso_utc || ''),
                        midpointEpochMs: (startedEpoch + endedEpoch) / 2,
                        approxDriftMs: serverEpoch - ((startedEpoch + endedEpoch) / 2)
                    };
                })
                .catch(function () {
                    return {
                        ok: false
                    };
                });
        }

        function runAjaxSamples(count) {
            var samples = [];
            var sequence = Promise.resolve();

            function queueSample() {
                return runAjaxSample().then(function (sample) {
                    samples.push(sample);
                });
            }

            for (var i = 0; i < count; i += 1) {
                sequence = sequence.then(queueSample);
            }

            return sequence.then(function () {
                return samples;
            });
        }

        function runReachability(url) {
            if (!url) {
                return Promise.resolve({ ok: false, statusCode: 0, durationMs: 0 });
            }
            var startedPerf = performance.now();
            var requestUrl = String(url);
            requestUrl += (requestUrl.indexOf('?') === -1 ? '?' : '&') + '_ns_diag=' + Date.now();

            return window.fetch(requestUrl, {
                method: 'GET',
                credentials: 'same-origin',
                cache: 'no-store',
                headers: {
                    'Cache-Control': 'no-store, no-cache, must-revalidate',
                    'Pragma': 'no-cache'
                }
            }).then(function (response) {
                return {
                    ok: !!response.ok,
                    statusCode: Number(response.status || 0),
                    durationMs: performance.now() - startedPerf
                };
            }).catch(function () {
                return {
                    ok: false,
                    statusCode: 0,
                    durationMs: 0
                };
            });
        }

        function copyTextValue(value) {
            if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
                return navigator.clipboard.writeText(value);
            }
            return new Promise(function (resolve, reject) {
                try {
                    var helper = document.createElement('textarea');
                    helper.value = value;
                    helper.setAttribute('readonly', 'readonly');
                    helper.style.position = 'fixed';
                    helper.style.opacity = '0';
                    helper.style.pointerEvents = 'none';
                    document.body.appendChild(helper);
                    helper.focus();
                    helper.select();
                    if (!document.execCommand('copy')) {
                        throw new Error('copy_failed');
                    }
                    document.body.removeChild(helper);
                    resolve();
                } catch (error) {
                    reject(error);
                }
            });
        }

        function buildReport(samples, restResult) {
            var successfulSamples = samples.filter(function (sample) {
                return sample && sample.ok;
            });
            if (!successfulSamples.length) {
                throw new Error('no_successful_samples');
            }

            var durations = successfulSamples.map(function (sample) {
                return sample.durationMs;
            });
            var drifts = successfulSamples.map(function (sample) {
                return sample.approxDriftMs;
            });
            var serverIsoUtc = successfulSamples[successfulSamples.length - 1].serverIsoUtc || '';
            var latencyMedianMs = median(durations);
            var jitterMs = getJitter(durations);
            var clockDriftMs = median(drifts);
            var ajaxLabel = successfulSamples.length === samples.length
                ? 'OK · ' + successfulSamples.length + '/' + samples.length + ' samples'
                : 'Degraded · ' + successfulSamples.length + '/' + samples.length + ' samples';
            var report = {
                latencyMedianMs: latencyMedianMs,
                latencyLabel: getLatencyLabel(latencyMedianMs),
                jitterMs: jitterMs,
                jitterLabel: getJitterLabel(jitterMs),
                clockDriftMs: clockDriftMs,
                clockStatus: getClockStatus(clockDriftMs, latencyMedianMs),
                ajaxLabel: ajaxLabel,
                restLabel: getReachabilityLabel(restResult),
                secureContext: getSecureContextLabel(),
                browserNetwork: getBrowserNetworkLabel(),
                timezone: getTimezoneLabel(),
                locale: navigator.language || notReportedLabel(),
                viewport: getViewportLabel(),
                userAgent: navigator.userAgent || notReportedLabel(),
                serverIsoUtc: serverIsoUtc || unavailableLabel(),
                sampleQuality: successfulSamples.length + '/' + samples.length
            };
            return report;
        }

        function buildCopyText(report) {
            return [
                'Noesia System & Network Diagnostics',
                'WordPress Backend Latency: ' + formatNumber(report.latencyMedianMs, 1) + ' ms (' + report.latencyLabel + ')',
                'Connection Stability: ' + formatNumber(report.jitterMs, 1) + ' ms (' + report.jitterLabel + ')',
                'Clock Synchronization: ' + report.clockStatus + ' (Approx. drift ' + formatSignedSeconds(report.clockDriftMs) + ')',
                'AJAX Reachability: ' + report.ajaxLabel,
                'REST API Reachability: ' + report.restLabel,
                'Secure Context: ' + report.secureContext,
                'Browser Network Hint: ' + report.browserNetwork,
                'Timezone: ' + report.timezone,
                'Locale: ' + report.locale,
                'Viewport: ' + report.viewport,
                'Server UTC Time: ' + report.serverIsoUtc,
                'User Agent: ' + report.userAgent
            ].join('\n');
        }

        function renderReport(report) {
            lastReportText = buildCopyText(report);
            if (copyButton) {
                copyButton.disabled = false;
            }

            resultsNode.innerHTML = '' +
                '<div><strong>WordPress Backend Latency</strong><span>' + escapeHtml(formatNumber(report.latencyMedianMs, 1) + ' ms · ' + report.latencyLabel) + '</span></div>' +
                '<div><strong>Connection Stability</strong><span>' + escapeHtml(formatNumber(report.jitterMs, 1) + ' ms · ' + report.jitterLabel) + '</span></div>' +
                '<div><strong>Clock Synchronization</strong><span>' + escapeHtml(report.clockStatus + ' · Approx. drift ' + formatSignedSeconds(report.clockDriftMs)) + '</span></div>' +
                '<div><strong>AJAX Reachability</strong><span>' + escapeHtml(report.ajaxLabel) + '</span></div>' +
                '<div><strong>REST API Reachability</strong><span>' + escapeHtml(report.restLabel) + '</span></div>' +
                '<div><strong>Secure Context</strong><span>' + escapeHtml(report.secureContext) + '</span></div>' +
                '<div><strong>Browser Network Hint</strong><span>' + escapeHtml(report.browserNetwork) + '</span></div>' +
                '<div><strong>Timezone</strong><span>' + escapeHtml(report.timezone) + '</span></div>' +
                '<div><strong>Locale</strong><span>' + escapeHtml(report.locale) + '</span></div>' +
                '<div><strong>Viewport</strong><span>' + escapeHtml(report.viewport) + '</span></div>' +
                '<div class="noesia-swa-widgets__mini-stats-wide"><strong>Server UTC Time</strong><span>' + escapeHtml(report.serverIsoUtc) + '</span></div>' +
                '<div class="noesia-swa-widgets__mini-stats-note"><strong>User Agent</strong><span>' + escapeHtml(report.userAgent) + '</span></div>';
        }

        if (copyButton) {
            copyButton.addEventListener('click', function () {
                if (!lastReportText) {
                    return;
                }
                copyTextValue(lastReportText)
                    .then(function () {
                        setText(statusNode, t('widgets_diagnostics_copy_ready', 'Diagnostics copied.'));
                    })
                    .catch(function () {
                        setText(statusNode, t('widgets_diagnostics_copy_failed', 'Unable to copy diagnostics right now.'));
                    });
            });
        }

        button.addEventListener('click', function () {
            if (copyButton) {
                copyButton.disabled = true;
            }
            lastReportText = '';
            resultsNode.innerHTML = '';
            setText(statusNode, t('widgets_diagnostics_running', 'Running system diagnostics…'));

            Promise.all([
                runAjaxSamples(5),
                runReachability(restRoot)
            ]).then(function (payload) {
                var samples = payload[0] || [];
                var restResult = payload[1] || null;
                var report = buildReport(samples, restResult);
                renderReport(report);
                if (report.sampleQuality === '5/5') {
                    setText(statusNode, t('widgets_diagnostics_done', 'System diagnostics completed.'));
                } else {
                    setText(statusNode, t('widgets_diagnostics_partial', 'System diagnostics completed with limited samples.'));
                }
            }).catch(function () {
                if (copyButton) {
                    copyButton.disabled = true;
                }
                resultsNode.innerHTML = '';
                setText(statusNode, t('widgets_diagnostics_failed', 'System diagnostics failed.'));
            });
        });
    }

    function initSafely(callback) {
        try {
            callback();
        } catch (error) {
            // Keep one failing widget from blocking the rest of the module.
        }
    }

    function bootstrap(targetRoot) {
        if (!targetRoot || targetRoot.getAttribute('data-widgets-initialized') === '1') {
            return;
        }

        root = targetRoot;
        parseRootPayload(targetRoot);
        targetRoot.setAttribute('data-widgets-initialized', '1');

        initSafely(initTabs);
        initSafely(initCurrencyRates);
        initSafely(initUnitConverters);
        initSafely(initVatCalculator);
        initSafely(initTextTools);
        initSafely(initImageTools);
        initSafely(initRuler);
        initSafely(initConnectionTest);
    }

    function findRoot(context) {
        if (!context || !context.querySelector) {
            return null;
        }

        return context.querySelector('.noesia-swa-widgets');
    }

    function scanAndBootstrap(context) {
        var foundRoot = findRoot(context || document);
        if (foundRoot) {
            bootstrap(foundRoot);
        }
    }

    function startObserver() {
        if (observer || !document.body || !window.MutationObserver) {
            return;
        }

        observer = new window.MutationObserver(function (mutations) {
            mutations.forEach(function (mutation) {
                Array.prototype.forEach.call(mutation.addedNodes || [], function (node) {
                    if (!node || node.nodeType !== 1) {
                        return;
                    }

                    if (node.matches && node.matches('.noesia-swa-widgets')) {
                        bootstrap(node);
                        return;
                    }

                    scanAndBootstrap(node);
                });
            });
        });

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

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