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

    var nsVars = window.noesia_admin_vars || {};
    var i18n = (nsVars.i18n && typeof nsVars.i18n === 'object') ? nsVars.i18n : {};
    var modules = window.noesiaSwaAdminModules || {};
    var state = {
        booted: false,
        mounted: false
    };

    function text(key, fallback) {
        return String(i18n[key] || fallback || '');
    }

    function root() {
        return $('[data-noesia-swa-text-dev-tools-root="1"]').first();
    }

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

    function showStatus(message, tone) {
        var $root = root();
        var $status = $root.find('[data-noesia-swa-tdt-status="1"]');
        if (!$status.length) {
            return;
        }
        $status.removeClass('is-success is-error is-neutral');
        $status.addClass(tone === 'error' ? 'is-error' : (tone === 'success' ? 'is-success' : 'is-neutral'));
        $status.text(String(message || ''));
    }

    function setActiveTab(target) {
        var $root = root();
        if (!$root.length) {
            return;
        }

        $root.find('[data-noesia-swa-tdt-tab]').removeClass('is-active').attr('aria-selected', 'false');
        $root.find('[data-noesia-swa-tdt-panel]').removeClass('is-active').attr('hidden', true);

        $root.find('[data-noesia-swa-tdt-tab="' + target + '"]').addClass('is-active').attr('aria-selected', 'true');
        $root.find('[data-noesia-swa-tdt-panel="' + target + '"]').addClass('is-active').removeAttr('hidden');
    }

    function copyText(value) {
        var textValue = String(value || '');

        if (!textValue) {
            showStatus(text('text_dev_tools_copy_error', 'Unable to copy right now.'), 'error');
            return;
        }

        if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
            navigator.clipboard.writeText(textValue).then(function () {
                showStatus(text('text_dev_tools_copy_success', 'Copied to clipboard.'), 'success');
            }).catch(function () {
                fallbackCopy(textValue);
            });
            return;
        }

        fallbackCopy(textValue);
    }

    function fallbackCopy(value) {
        var field = document.createElement('textarea');
        field.value = String(value || '');
        document.body.appendChild(field);
        field.select();
        try {
            document.execCommand('copy');
            showStatus(text('text_dev_tools_copy_success', 'Copied to clipboard.'), 'success');
        } catch (error) {
            showStatus(text('text_dev_tools_copy_error', 'Unable to copy right now.'), 'error');
        }
        document.body.removeChild(field);
    }

    function getCopyValue(target) {
        var $root = root();
        var map = {
            'markdown-html': '[data-noesia-swa-tdt-markdown-html="1"]',
            'json-output': '[data-noesia-swa-tdt-json-output="1"]',
            'shadow-css': '[data-noesia-swa-tdt-shadow-css="1"]',
            'encoder-output': '[data-noesia-swa-tdt-encoder-output="1"]'
        };
        var selector = map[String(target || '')] || '';
        var $field;

        if (!selector) {
            return '';
        }

        $field = $root.find(selector).first();
        return $field.length ? String($field.val() || $field.text() || '') : '';
    }

    function markdownInline(textValue) {
        var value = String(textValue || '');
        value = value.replace(/`([^`]+)`/g, '<code>$1</code>');
        value = value.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
        value = value.replace(/__([^_]+)__/g, '<strong>$1</strong>');
        value = value.replace(/\*([^*]+)\*/g, '<em>$1</em>');
        value = value.replace(/_([^_]+)_/g, '<em>$1</em>');
        value = value.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
        return value;
    }

    function markdownToHtml(source) {
        var input = String(source || '').replace(/\r\n?/g, '\n').trim();
        var safe;
        var lines;
        var html = [];
        var inUl = false;
        var inOl = false;
        var i;
        var line;
        var orderedMatch;

        if (!input) {
            return '<p>' + escapeHtml(text('text_dev_tools_markdown_empty', 'Enter Markdown to generate preview and HTML.')) + '</p>';
        }

        safe = escapeHtml(input);
        lines = safe.split('\n');

        function closeLists() {
            if (inUl) {
                html.push('</ul>');
                inUl = false;
            }
            if (inOl) {
                html.push('</ol>');
                inOl = false;
            }
        }

        for (i = 0; i < lines.length; i += 1) {
            line = String(lines[i] || '');

            if (!line.trim()) {
                closeLists();
                continue;
            }

            if (/^---+$/.test(line.trim())) {
                closeLists();
                html.push('<hr />');
                continue;
            }

            if (/^###\s+/.test(line)) {
                closeLists();
                html.push('<h3>' + markdownInline(line.replace(/^###\s+/, '')) + '</h3>');
                continue;
            }

            if (/^##\s+/.test(line)) {
                closeLists();
                html.push('<h2>' + markdownInline(line.replace(/^##\s+/, '')) + '</h2>');
                continue;
            }

            if (/^#\s+/.test(line)) {
                closeLists();
                html.push('<h1>' + markdownInline(line.replace(/^#\s+/, '')) + '</h1>');
                continue;
            }

            if (/^>\s+/.test(line)) {
                closeLists();
                html.push('<blockquote><p>' + markdownInline(line.replace(/^>\s+/, '')) + '</p></blockquote>');
                continue;
            }

            if (/^[-*]\s+/.test(line)) {
                if (inOl) {
                    html.push('</ol>');
                    inOl = false;
                }
                if (!inUl) {
                    html.push('<ul>');
                    inUl = true;
                }
                html.push('<li>' + markdownInline(line.replace(/^[-*]\s+/, '')) + '</li>');
                continue;
            }

            orderedMatch = line.match(/^\d+\.\s+(.*)$/);
            if (orderedMatch) {
                if (inUl) {
                    html.push('</ul>');
                    inUl = false;
                }
                if (!inOl) {
                    html.push('<ol>');
                    inOl = true;
                }
                html.push('<li>' + markdownInline(orderedMatch[1]) + '</li>');
                continue;
            }

            closeLists();
            html.push('<p>' + markdownInline(line) + '</p>');
        }

        closeLists();
        return html.join('\n');
    }

    function renderMarkdown() {
        var $root = root();
        var input = String($root.find('[data-noesia-swa-tdt-markdown-input="1"]').val() || '');
        var html = markdownToHtml(input);

        $root.find('[data-noesia-swa-tdt-markdown-preview="1"]').html(html);
        $root.find('[data-noesia-swa-tdt-markdown-html="1"]').val(html);

        if (!input.trim()) {
            showStatus(text('text_dev_tools_markdown_empty', 'Enter Markdown to generate preview and HTML.'), 'neutral');
        }
    }

    function formatJson(mode) {
        var $root = root();
        var raw = String($root.find('[data-noesia-swa-tdt-json-input="1"]').val() || '').trim();
        var $output = $root.find('[data-noesia-swa-tdt-json-output="1"]');
        var $feedback = $root.find('[data-noesia-swa-tdt-json-feedback="1"] span');
        var parsed;

        if (!raw) {
            $output.val('');
            $feedback.text(text('text_dev_tools_json_empty', 'Paste JSON first.'));
            showStatus(text('text_dev_tools_json_empty', 'Paste JSON first.'), 'error');
            return;
        }

        try {
            parsed = JSON.parse(raw);
            if (mode === 'minify') {
                $output.val(JSON.stringify(parsed));
            } else if (mode === 'pretty') {
                $output.val(JSON.stringify(parsed, null, 2));
            } else {
                $output.val(JSON.stringify(parsed, null, 2));
            }
            $feedback.text(text('text_dev_tools_json_valid', 'Valid JSON.'));
            showStatus(text('text_dev_tools_json_valid', 'Valid JSON.'), 'success');
        } catch (error) {
            $output.val('');
            $feedback.text(text('text_dev_tools_json_invalid', 'Invalid JSON.') + ' ' + error.message);
            showStatus(text('text_dev_tools_json_invalid', 'Invalid JSON.') + ' ' + error.message, 'error');
        }
    }

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

    function parseHexColor(value) {
        var hex = String(value || '').trim().replace(/^#/, '');

        if (/^[0-9a-fA-F]{3}$/.test(hex)) {
            hex = hex.split('').map(function (part) { return part + part; }).join('');
        }

        if (!/^[0-9a-fA-F]{6}$/.test(hex)) {
            return null;
        }

        return {
            r: parseInt(hex.slice(0, 2), 16),
            g: parseInt(hex.slice(2, 4), 16),
            b: parseInt(hex.slice(4, 6), 16)
        };
    }

    function parseRgbColor(value) {
        var match = String(value || '').trim().match(/^rgba?\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})(?:\s*,\s*(0|1|0?\.\d+))?\s*\)$/i);
        if (!match) {
            return null;
        }
        return {
            r: clamp(parseInt(match[1], 10), 0, 255),
            g: clamp(parseInt(match[2], 10), 0, 255),
            b: clamp(parseInt(match[3], 10), 0, 255)
        };
    }

    function hslToRgb(h, s, l) {
        var hue = (((h % 360) + 360) % 360) / 360;
        var sat = clamp(s, 0, 100) / 100;
        var lig = clamp(l, 0, 100) / 100;
        var q = lig < 0.5 ? lig * (1 + sat) : lig + sat - lig * sat;
        var p = 2 * lig - q;

        function toChannel(t) {
            var temp = t;
            if (temp < 0) { temp += 1; }
            if (temp > 1) { temp -= 1; }
            if (temp < 1 / 6) { return p + (q - p) * 6 * temp; }
            if (temp < 1 / 2) { return q; }
            if (temp < 2 / 3) { return p + (q - p) * (2 / 3 - temp) * 6; }
            return p;
        }

        if (sat === 0) {
            return {
                r: Math.round(lig * 255),
                g: Math.round(lig * 255),
                b: Math.round(lig * 255)
            };
        }

        return {
            r: Math.round(toChannel(hue + 1 / 3) * 255),
            g: Math.round(toChannel(hue) * 255),
            b: Math.round(toChannel(hue - 1 / 3) * 255)
        };
    }

    function parseHslColor(value) {
        var match = String(value || '').trim().match(/^hsla?\(\s*(-?[0-9]{1,3}(?:\.[0-9]+)?)\s*,\s*([0-9]{1,3}(?:\.[0-9]+)?)%\s*,\s*([0-9]{1,3}(?:\.[0-9]+)?)%(?:\s*,\s*(0|1|0?\.\d+))?\s*\)$/i);
        if (!match) {
            return null;
        }
        return hslToRgb(parseFloat(match[1]), parseFloat(match[2]), parseFloat(match[3]));
    }

    function parseColor(value) {
        return parseHexColor(value) || parseRgbColor(value) || parseHslColor(value);
    }

    function rgbToHex(rgb) {
        function part(number) {
            return clamp(number, 0, 255).toString(16).padStart(2, '0');
        }
        return '#' + part(rgb.r) + part(rgb.g) + part(rgb.b);
    }

    function rgbToHsl(rgb) {
        var r = clamp(rgb.r, 0, 255) / 255;
        var g = clamp(rgb.g, 0, 255) / 255;
        var b = clamp(rgb.b, 0, 255) / 255;
        var max = Math.max(r, g, b);
        var min = Math.min(r, g, b);
        var h;
        var s;
        var l = (max + min) / 2;
        var d = max - min;

        if (d === 0) {
            h = 0;
            s = 0;
        } else {
            s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
            switch (max) {
            case r:
                h = ((g - b) / d + (g < b ? 6 : 0));
                break;
            case g:
                h = ((b - r) / d + 2);
                break;
            default:
                h = ((r - g) / d + 4);
                break;
            }
            h /= 6;
        }

        return {
            h: Math.round(h * 360),
            s: Math.round(s * 100),
            l: Math.round(l * 100)
        };
    }

    function relativeLuminance(rgb) {
        function convert(channel) {
            var value = clamp(channel, 0, 255) / 255;
            return value <= 0.03928 ? value / 12.92 : Math.pow((value + 0.055) / 1.055, 2.4);
        }
        return (0.2126 * convert(rgb.r)) + (0.7152 * convert(rgb.g)) + (0.0722 * convert(rgb.b));
    }

    function contrastRatio(rgbA, rgbB) {
        var luminanceA = relativeLuminance(rgbA);
        var luminanceB = relativeLuminance(rgbB);
        var lighter = Math.max(luminanceA, luminanceB);
        var darker = Math.min(luminanceA, luminanceB);
        return (lighter + 0.05) / (darker + 0.05);
    }

    function wcagLabel(ratio) {
        var rounded = ratio.toFixed(2) + ':1';
        var pass = ratio >= 4.5 ? text('text_dev_tools_wcag_pass', 'Pass') : text('text_dev_tools_wcag_fail', 'Fail');
        return rounded + ' · ' + pass + ' AA';
    }

    function mixColors(rgbA, rgbB, amount) {
        var ratio = clamp(amount, 0, 1);
        return {
            r: Math.round(rgbA.r + ((rgbB.r - rgbA.r) * ratio)),
            g: Math.round(rgbA.g + ((rgbB.g - rgbA.g) * ratio)),
            b: Math.round(rgbA.b + ((rgbB.b - rgbA.b) * ratio))
        };
    }

    function renderPalette(baseRgb) {
        var $root = root();
        var $palette = $root.find('[data-noesia-swa-tdt-palette="1"]');
        var colors = [
            { label: text('text_dev_tools_palette_shade', 'Shade') + ' 2', rgb: mixColors(baseRgb, { r: 0, g: 0, b: 0 }, 0.35) },
            { label: text('text_dev_tools_palette_shade', 'Shade') + ' 1', rgb: mixColors(baseRgb, { r: 0, g: 0, b: 0 }, 0.18) },
            { label: text('text_dev_tools_palette_base', 'Base'), rgb: baseRgb },
            { label: text('text_dev_tools_palette_tint', 'Tint') + ' 1', rgb: mixColors(baseRgb, { r: 255, g: 255, b: 255 }, 0.18) },
            { label: text('text_dev_tools_palette_tint', 'Tint') + ' 2', rgb: mixColors(baseRgb, { r: 255, g: 255, b: 255 }, 0.35) }
        ];
        var html = '';

        colors.forEach(function (item) {
            var hex = rgbToHex(item.rgb);
            var bestText = contrastRatio(item.rgb, { r: 255, g: 255, b: 255 }) >= contrastRatio(item.rgb, { r: 0, g: 0, b: 0 }) ? '#ffffff' : '#000000';
            html += '<button type="button" class="noesia-swa-text-dev-tools__swatch" data-noesia-swa-tdt-swatch="' + escapeHtml(hex) + '" style="background:' + escapeHtml(hex) + ';color:' + escapeHtml(bestText) + '">';
            html += '<strong>' + escapeHtml(item.label) + '</strong>';
            html += '<span>' + escapeHtml(hex.toUpperCase()) + '</span>';
            html += '</button>';
        });

        $palette.html(html);
    }

    function convertColor() {
        var $root = root();
        var raw = String($root.find('[data-noesia-swa-tdt-color-input="1"]').val() || '').trim();
        var rgb = parseColor(raw);
        var hex;
        var hsl;
        var whiteRatio;
        var blackRatio;
        var bestText;
        var bestTextValue;

        if (!rgb) {
            showStatus(text('text_dev_tools_color_invalid', 'Enter a valid HEX, RGB, or HSL color.'), 'error');
            return;
        }

        hex = rgbToHex(rgb).toUpperCase();
        hsl = rgbToHsl(rgb);
        whiteRatio = contrastRatio(rgb, { r: 255, g: 255, b: 255 });
        blackRatio = contrastRatio(rgb, { r: 0, g: 0, b: 0 });
        bestTextValue = whiteRatio >= blackRatio ? '#FFFFFF' : '#000000';
        bestText = whiteRatio >= blackRatio ? 'White' : 'Black';

        $root.find('[data-noesia-swa-tdt-color-picker="1"]').val(hex.toLowerCase());
        $root.find('[data-noesia-swa-tdt-color-preview="1"]').css('background', hex).text(hex);
        $root.find('[data-noesia-swa-tdt-color-hex="1"]').text(hex);
        $root.find('[data-noesia-swa-tdt-color-rgb="1"]').text('rgb(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ')');
        $root.find('[data-noesia-swa-tdt-color-hsl="1"]').text('hsl(' + hsl.h + ', ' + hsl.s + '%, ' + hsl.l + '%)');
        $root.find('[data-noesia-swa-tdt-contrast-white="1"]').text(wcagLabel(whiteRatio));
        $root.find('[data-noesia-swa-tdt-contrast-black="1"]').text(wcagLabel(blackRatio));
        $root.find('[data-noesia-swa-tdt-best-text="1"]').text(bestText + ' (' + bestTextValue + ')');

        renderPalette(rgb);
        showStatus('Color converted: ' + hex, 'success');
    }

    function updateShadowGenerator() {
        var $root = root();
        var values = {};
        var opacity;
        var surfaceOpacity;
        var css;

        $root.find('[data-noesia-swa-tdt-shadow-control]').each(function () {
            var $control = $(this);
            values[String($control.data('noesia-swa-tdt-shadow-control') || '')] = parseFloat($control.val() || 0);
        });

        opacity = clamp(values.opacity || 0, 0, 100) / 100;
        surfaceOpacity = clamp(values['surface-opacity'] || 0, 0, 100) / 100;

        $root.find('[data-noesia-swa-tdt-shadow-value="x"]').text((values.x || 0) + 'px');
        $root.find('[data-noesia-swa-tdt-shadow-value="y"]').text((values.y || 0) + 'px');
        $root.find('[data-noesia-swa-tdt-shadow-value="blur"]').text((values.blur || 0) + 'px');
        $root.find('[data-noesia-swa-tdt-shadow-value="spread"]').text((values.spread || 0) + 'px');
        $root.find('[data-noesia-swa-tdt-shadow-value="opacity"]').text(opacity.toFixed(2));
        $root.find('[data-noesia-swa-tdt-shadow-value="glass-blur"]').text((values['glass-blur'] || 0) + 'px');
        $root.find('[data-noesia-swa-tdt-shadow-value="surface-opacity"]').text(surfaceOpacity.toFixed(2));
        $root.find('[data-noesia-swa-tdt-shadow-value="radius"]').text((values.radius || 0) + 'px');

        css = '.glass-card {\n';
        css += '  border-radius: ' + (values.radius || 0) + 'px;\n';
        css += '  background: rgba(255, 255, 255, ' + surfaceOpacity.toFixed(2) + ');\n';
        css += '  border: 1px solid rgba(255, 255, 255, 0.24);\n';
        css += '  box-shadow: ' + (values.x || 0) + 'px ' + (values.y || 0) + 'px ' + (values.blur || 0) + 'px ' + (values.spread || 0) + 'px rgba(15, 23, 42, ' + opacity.toFixed(2) + ');\n';
        css += '  backdrop-filter: blur(' + (values['glass-blur'] || 0) + 'px);\n';
        css += '  -webkit-backdrop-filter: blur(' + (values['glass-blur'] || 0) + 'px);\n';
        css += '}';

        $root.find('[data-noesia-swa-tdt-glass-preview="1"]').css({
            borderRadius: (values.radius || 0) + 'px',
            background: 'rgba(255,255,255,' + surfaceOpacity.toFixed(2) + ')',
            boxShadow: (values.x || 0) + 'px ' + (values.y || 0) + 'px ' + (values.blur || 0) + 'px ' + (values.spread || 0) + 'px rgba(15,23,42,' + opacity.toFixed(2) + ')',
            backdropFilter: 'blur(' + (values['glass-blur'] || 0) + 'px)',
            webkitBackdropFilter: 'blur(' + (values['glass-blur'] || 0) + 'px)'
        });

        $root.find('[data-noesia-swa-tdt-shadow-css="1"]').val(css);
    }

    function normalizeLine(line, options) {
        var value = String(line || '');
        if (options.trim) {
            value = value.trim();
        }
        if (options.caseInsensitive) {
            value = value.toLowerCase();
        }
        return value;
    }

    function computeLineDiff(leftLines, rightLines, options) {
        var leftNormalized = leftLines.map(function (line) { return normalizeLine(line, options); });
        var rightNormalized = rightLines.map(function (line) { return normalizeLine(line, options); });
        var m = leftNormalized.length;
        var n = rightNormalized.length;
        var matrix = [];
        var i;
        var j;
        var result = [];

        for (i = 0; i <= m; i += 1) {
            matrix[i] = [];
            for (j = 0; j <= n; j += 1) {
                matrix[i][j] = 0;
            }
        }

        for (i = m - 1; i >= 0; i -= 1) {
            for (j = n - 1; j >= 0; j -= 1) {
                if (leftNormalized[i] === rightNormalized[j]) {
                    matrix[i][j] = matrix[i + 1][j + 1] + 1;
                } else {
                    matrix[i][j] = Math.max(matrix[i + 1][j], matrix[i][j + 1]);
                }
            }
        }

        i = 0;
        j = 0;
        while (i < m && j < n) {
            if (leftNormalized[i] === rightNormalized[j]) {
                result.push({ type: 'same', left: leftLines[i], right: rightLines[j] });
                i += 1;
                j += 1;
            } else if (matrix[i + 1][j] >= matrix[i][j + 1]) {
                result.push({ type: 'remove', left: leftLines[i], right: '' });
                i += 1;
            } else {
                result.push({ type: 'add', left: '', right: rightLines[j] });
                j += 1;
            }
        }

        while (i < m) {
            result.push({ type: 'remove', left: leftLines[i], right: '' });
            i += 1;
        }

        while (j < n) {
            result.push({ type: 'add', left: '', right: rightLines[j] });
            j += 1;
        }

        return result;
    }

    function renderDiffPane(items, side) {
        var lineNumber = 0;
        var html = '<div class="noesia-swa-text-dev-tools__diff-lines">';

        items.forEach(function (item) {
            var line = String(item[side] || '');
            var type = item.type;
            var modifier = ' is-same';
            if (side === 'left' && type === 'remove') {
                modifier = ' is-remove';
            } else if (side === 'right' && type === 'add') {
                modifier = ' is-add';
            } else if ((side === 'left' && type === 'add') || (side === 'right' && type === 'remove')) {
                modifier = ' is-empty';
            }
            if (line) {
                lineNumber += 1;
            }
            html += '<div class="noesia-swa-text-dev-tools__diff-line' + modifier + '">';
            html += '<span class="noesia-swa-text-dev-tools__diff-number">' + (line ? lineNumber : '·') + '</span>';
            html += '<code>' + (line ? escapeHtml(line) : '&nbsp;') + '</code>';
            html += '</div>';
        });

        html += '</div>';
        return html;
    }

    function compareDiff() {
        var $root = root();
        var left = String($root.find('[data-noesia-swa-tdt-diff-left="1"]').val() || '');
        var right = String($root.find('[data-noesia-swa-tdt-diff-right="1"]').val() || '');
        var options = {
            trim: $root.find('[data-noesia-swa-tdt-diff-option="trim"]').is(':checked'),
            caseInsensitive: $root.find('[data-noesia-swa-tdt-diff-option="case"]').is(':checked')
        };
        var leftLines;
        var rightLines;
        var items;
        var changed;

        if (!left.trim() && !right.trim()) {
            showStatus(text('text_dev_tools_diff_empty', 'Enter text in both panels to compare.'), 'error');
            return;
        }

        leftLines = left.replace(/\r\n?/g, '\n').split('\n');
        rightLines = right.replace(/\r\n?/g, '\n').split('\n');
        items = computeLineDiff(leftLines, rightLines, options);
        changed = items.some(function (item) { return item.type !== 'same'; });

        $root.find('[data-noesia-swa-tdt-diff-left-result="1"]').html(renderDiffPane(items, 'left'));
        $root.find('[data-noesia-swa-tdt-diff-right-result="1"]').html(renderDiffPane(items, 'right'));

        if (!changed) {
            showStatus(text('text_dev_tools_diff_identical', 'No differences found with the current comparison options.'), 'success');
        } else {
            showStatus('Diff comparison updated.', 'success');
        }
    }

    function utf8ToBase64(value) {
        var textValue = String(value || '');
        var bytes;
        var binary = '';
        var i;

        if (window.TextEncoder) {
            bytes = new window.TextEncoder().encode(textValue);
            for (i = 0; i < bytes.length; i += 1) {
                binary += String.fromCharCode(bytes[i]);
            }
            return window.btoa(binary);
        }

        return window.btoa(unescape(encodeURIComponent(textValue)));
    }

    function base64ToUtf8(value) {
        var binary = window.atob(String(value || ''));
        var bytes;
        var i;

        if (window.TextDecoder) {
            bytes = new Uint8Array(binary.length);
            for (i = 0; i < binary.length; i += 1) {
                bytes[i] = binary.charCodeAt(i);
            }
            return new window.TextDecoder().decode(bytes);
        }

        return decodeURIComponent(escape(binary));
    }

    function encodeTools(action) {
        var $root = root();
        var input = String($root.find('[data-noesia-swa-tdt-encoder-input="1"]').val() || '');
        var $output = $root.find('[data-noesia-swa-tdt-encoder-output="1"]');

        try {
            switch (action) {
            case 'base64-encode':
                $output.val(utf8ToBase64(input));
                showStatus('Base64 encoded.', 'success');
                break;
            case 'base64-decode':
                $output.val(base64ToUtf8(input));
                showStatus('Base64 decoded.', 'success');
                break;
            case 'url-encode':
                $output.val(encodeURIComponent(input));
                showStatus('URL encoded.', 'success');
                break;
            case 'url-decode':
                $output.val(decodeURIComponent(input));
                showStatus('URL decoded.', 'success');
                break;
            default:
                break;
            }
        } catch (error) {
            if (action === 'base64-decode') {
                showStatus(text('text_dev_tools_base64_invalid', 'Invalid Base64 input.'), 'error');
            } else if (action === 'url-decode') {
                showStatus(text('text_dev_tools_url_decode_invalid', 'Invalid URL-encoded input.'), 'error');
            }
        }
    }

    function bindEvents() {
        if (state.mounted) {
            return;
        }

        $(document)
            .off('.noesiaSwaTextDevTools')
            .on('click.noesiaSwaTextDevTools', '[data-noesia-swa-tdt-tab]', function (event) {
                event.preventDefault();
                setActiveTab(String($(this).data('noesia-swa-tdt-tab') || 'markdown-json'));
            })
            .on('click.noesiaSwaTextDevTools', '[data-noesia-swa-tdt-copy-target]', function (event) {
                event.preventDefault();
                copyText(getCopyValue($(this).data('noesia-swa-tdt-copy-target')));
            })
            .on('click.noesiaSwaTextDevTools', '[data-noesia-swa-tdt-action]', function (event) {
                var action;
                var $root;
                var left;
                var right;
                event.preventDefault();
                action = String($(this).data('noesia-swa-tdt-action') || '');
                $root = root();

                switch (action) {
                case 'markdown-render':
                    renderMarkdown();
                    break;
                case 'markdown-clear':
                    $root.find('[data-noesia-swa-tdt-markdown-input="1"]').val('');
                    renderMarkdown();
                    break;
                case 'json-pretty':
                    formatJson('pretty');
                    break;
                case 'json-minify':
                    formatJson('minify');
                    break;
                case 'json-validate':
                    formatJson('validate');
                    break;
                case 'json-clear':
                    $root.find('[data-noesia-swa-tdt-json-input="1"], [data-noesia-swa-tdt-json-output="1"]').val('');
                    $root.find('[data-noesia-swa-tdt-json-feedback="1"] span').text('Ready.');
                    break;
                case 'color-convert':
                    convertColor();
                    break;
                case 'diff-compare':
                    compareDiff();
                    break;
                case 'diff-swap':
                    left = $root.find('[data-noesia-swa-tdt-diff-left="1"]').val();
                    right = $root.find('[data-noesia-swa-tdt-diff-right="1"]').val();
                    $root.find('[data-noesia-swa-tdt-diff-left="1"]').val(right);
                    $root.find('[data-noesia-swa-tdt-diff-right="1"]').val(left);
                    compareDiff();
                    break;
                case 'diff-clear':
                    $root.find('[data-noesia-swa-tdt-diff-left="1"], [data-noesia-swa-tdt-diff-right="1"]').val('');
                    $root.find('[data-noesia-swa-tdt-diff-left-result="1"], [data-noesia-swa-tdt-diff-right-result="1"]').empty();
                    break;
                case 'base64-encode':
                case 'base64-decode':
                case 'url-encode':
                case 'url-decode':
                    encodeTools(action);
                    break;
                case 'encoder-clear':
                    $root.find('[data-noesia-swa-tdt-encoder-input="1"], [data-noesia-swa-tdt-encoder-output="1"]').val('');
                    break;
                default:
                    break;
                }
            })
            .on('input.noesiaSwaTextDevTools', '[data-noesia-swa-tdt-markdown-input="1"]', function () {
                renderMarkdown();
            })
            .on('input.noesiaSwaTextDevTools', '[data-noesia-swa-tdt-shadow-control]', function () {
                updateShadowGenerator();
            })
            .on('input.noesiaSwaTextDevTools', '[data-noesia-swa-tdt-color-picker="1"]', function () {
                root().find('[data-noesia-swa-tdt-color-input="1"]').val(String($(this).val() || ''));
                convertColor();
            })
            .on('keydown.noesiaSwaTextDevTools', '[data-noesia-swa-tdt-diff-left="1"], [data-noesia-swa-tdt-diff-right="1"]', function (event) {
                if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
                    event.preventDefault();
                    compareDiff();
                }
            })
            .on('click.noesiaSwaTextDevTools', '[data-noesia-swa-tdt-swatch]', function (event) {
                event.preventDefault();
                root().find('[data-noesia-swa-tdt-color-input="1"]').val(String($(this).data('noesia-swa-tdt-swatch') || ''));
                convertColor();
            });

        state.mounted = true;
    }

    function seedDefaults() {
        renderMarkdown();
        formatJson('pretty');
        updateShadowGenerator();
        convertColor();
        compareDiff();
        showStatus('Text & Dev Tools ready.', 'success');
    }

    function boot() {
        if (state.booted) {
            return true;
        }
        bindEvents();
        seedDefaults();
        state.booted = true;
        return true;
    }

    function onSectionOpen(section) {
        if (String(section || '') !== 'text-dev-tools') {
            return;
        }
        boot();
    }

    modules.textDevTools = {
        boot: boot,
        init: boot,
        mount: boot,
        onSectionOpen: onSectionOpen,
        show: onSectionOpen,
        onShow: onSectionOpen
    };

    window.noesiaSwaAdminModules = modules;
}(window, document, jQuery));
