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

    const MAX_BLOCKS = 500;
    const MAX_MARKDOWN_BYTES = Number((window.MARKBLOCKS_EDITOR && window.MARKBLOCKS_EDITOR.maxMarkdownBytes) || 2097152);
    const MAX_MARKDOWN_LINES = Number((window.MARKBLOCKS_EDITOR && window.MARKBLOCKS_EDITOR.maxMarkdownLines) || 50000);
    const MAX_QUOTE_DEPTH = 8;
    const MAX_QUOTE_RECURSION_CHARS = 262144;

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

    function safeUrl(url, forImage) {
        const value = String(url || '').trim();
        if (!value || /[\u0000-\u0020\u007F]/.test(value) || /[<>"']/.test(value) || /%(?:0d|0a)/i.test(value)) return '';

        const isHttp = /^https?:\/\//i.test(value);
        const isContact = !forImage && /^(?:mailto:|tel:)/i.test(value);
        const isFragment = value.startsWith('#');
        const isQuery = value.startsWith('?');
        const isRelative = value.startsWith('./') || value.startsWith('../');
        const isRootRelative = value.startsWith('/') && !value.startsWith('//');

        if (value.includes('\\')) return '';
        return isHttp || isContact || isFragment || isQuery || isRelative || isRootRelative ? value : '';
    }

    function inline(text) {
        let value = String(text || '').replace(/\n/g, ' ');
        const placeholders = [];
        const store = (html) => {
            const key = `\uE100${placeholders.length}\uE101`;
            placeholders.push(html);
            return key;
        };

        value = value.replace(/`([^`]+)`/g, (_m, code) => store(`<code class="markblocks-inline-code">${escapeHtml(code)}</code>`));
        value = value.replace(/!\[([^\]]*)\]\(([^\s\)]+)(?:\s+["']([^"']*)["'])?\)/g, (_m, alt, url, title) => {
            const safe = safeUrl(url, true);
            if (!safe) return escapeHtml(_m);
            const titleAttr = title ? ` title="${escapeHtml(title)}"` : '';
            return store(`<img src="${escapeHtml(safe)}" alt="${escapeHtml(alt)}" loading="lazy" decoding="async"${titleAttr}>`);
        });
        value = value.replace(/\[([^\]]+)\]\(([^\s\)]+)(?:\s+["']([^"']*)["'])?\)/g, (_m, label, url, title) => {
            const safe = safeUrl(url);
            if (!safe) return escapeHtml(_m);
            const titleAttr = title ? ` title="${escapeHtml(title)}"` : '';
            return store(`<a href="${escapeHtml(safe)}"${titleAttr}>${escapeHtml(label)}</a>`);
        });

        value = escapeHtml(value)
            .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
            .replace(/__(.+?)__/g, '<strong>$1</strong>')
            .replace(/~~(.+?)~~/g, '<del>$1</del>')
            .replace(/(^|[^*])\*([^*]+)\*(?!\*)/g, '$1<em>$2</em>')
            .replace(/(^|[^_])_([^_]+)_(?!_)/g, '$1<em>$2</em>');

        placeholders.forEach((html, index) => {
            value = value.replace(`\uE100${index}\uE101`, html);
        });
        return value;
    }

    function byteLength(value) {
        if (window.TextEncoder) return new TextEncoder().encode(String(value)).length;
        return new Blob([String(value)]).size;
    }

    function lineCountWithinLimit(value, limit) {
        let lines = 1;
        const source = String(value || '');

        for (let index = 0; index < source.length; index += 1) {
            const code = source.charCodeAt(index);
            if (code === 13) {
                if (index + 1 < source.length && source.charCodeAt(index + 1) === 10) index += 1;
            } else if (code !== 10) {
                continue;
            }

            lines += 1;
            if (lines > limit) return false;
        }
        return true;
    }

    function normalizeLanguage(language) {
        const normalized = window.MarkBlocksSyntax && window.MarkBlocksSyntax.normalize
            ? window.MarkBlocksSyntax.normalize(language)
            : String(language || '').toLowerCase().replace(/[^a-z0-9_+\-.#]/g, '');
        return String(normalized).slice(0, 64);
    }

    function plainText(value, maxLength) {
        return String(value || '')
            .replace(/[\u0000-\u001F\u007F]/g, ' ')
            .replace(/[<>]/g, '')
            .slice(0, maxLength || 500);
    }

    function splitTableRow(row) {
        const value = String(row).trim().replace(/^\||\|$/g, '');
        const cells = [];
        let current = '';
        let escaped = false;

        for (let index = 0; index < value.length; index += 1) {
            const character = value[index];
            if (character === '|' && !escaped) {
                cells.push(current.trim());
                current = '';
                continue;
            }
            current += character;
            escaped = character === '\\' ? !escaped : false;
        }
        cells.push(current.trim());
        return cells.map((cell) => cell.replace(/\\\|/g, '|'));
    }

    function tableAlignment(separator) {
        const value = separator.trim();
        if (value.startsWith(':') && value.endsWith(':')) return 'center';
        if (value.endsWith(':')) return 'right';
        if (value.startsWith(':')) return 'left';
        return 'left';
    }

    function isTableHeader(lines, index) {
        if (index + 1 >= lines.length || !lines[index].includes('|')) return false;
        return /^\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?$/.test(lines[index + 1].trim());
    }

    function startsBlock(lines, index) {
        const line = lines[index] || '';
        return /^\s*(`{3,}|~{3,})/.test(line) ||
            /^\s{0,3}#{1,6}\s+/.test(line) ||
            /^\s{0,3}((\*\s*){3,}|(-\s*){3,}|(_\s*){3,})$/.test(line) ||
            /^\s*>/.test(line) ||
            /^\s*([-+*]|\d+[.)])\s+/.test(line) ||
            /^\s*!\[[^\]]*\]\([^\)]+\)\s*$/.test(line) ||
            isTableHeader(lines, index);
    }

    function parseList(lines, start) {
        const ordered = /^\s*\d+[.)]\s+/.test(lines[start]);
        const tag = ordered ? 'ol' : 'ul';
        const items = [];
        let i = start;

        while (i < lines.length) {
            const match = lines[i].match(/^(\s*)([-+*]|\d+[.)])\s+(.+)$/);
            if (!match || (/^\d/.test(match[2]) !== ordered)) break;
            let content = match[3];
            const indent = match[1].length;
            i += 1;

            while (i < lines.length && lines[i].trim() !== '' && !/^\s*([-+*]|\d+[.)])\s+/.test(lines[i])) {
                const currentIndent = lines[i].length - lines[i].trimStart().length;
                if (currentIndent <= indent && startsBlock(lines, i)) break;
                content += `\n${lines[i].trim()}`;
                i += 1;
            }

            let prefix = '';
            const task = content.match(/^\[([ xX])\]\s+(.+)$/);
            if (task) {
                prefix = `<input type="checkbox" disabled${task[1].toLowerCase() === 'x' ? ' checked' : ''}> `;
                content = task[2];
            }
            items.push(`<li>${prefix}${inline(content)}</li>`);
        }

        return { html: `<${tag}>${items.join('')}</${tag}>`, next: i };
    }

    function parseTable(lines, start) {
        const headers = splitTableRow(lines[start]);
        const separators = splitTableRow(lines[start + 1]);
        const headerHtml = headers.map((header, index) => {
            const alignment = tableAlignment(separators[index] || '');
            return `<th${alignment ? ` class="markblocks-align-${alignment}"` : ''}>${inline(header)}</th>`;
        }).join('');

        const rows = [];
        let i = start + 2;
        while (i < lines.length && lines[i].trim() !== '' && lines[i].includes('|')) {
            const cells = splitTableRow(lines[i]);
            const row = headers.map((_header, index) => {
                const alignment = tableAlignment(separators[index] || '');
                return `<td${alignment ? ` class="markblocks-align-${alignment}"` : ''}>${inline(cells[index] || '')}</td>`;
            }).join('');
            rows.push(`<tr>${row}</tr>`);
            i += 1;
        }
        return { html: `<div class="markblocks-table-wrap"><table><thead><tr>${headerHtml}</tr></thead><tbody>${rows.join('')}</tbody></table></div>`, next: i };
    }

    function parseMarkdown(markdown, depth = 0) {
        const lines = String(markdown || '').replace(/\r\n?/g, '\n').split('\n');
        const blocks = [];
        let i = 0;

        while (i < lines.length) {
            const line = lines[i];
            if (line.trim() === '') { i += 1; continue; }

            const fence = line.match(/^\s*(`{3,}|~{3,})\s*([^\s]*)[^\n]*$/);
            if (fence) {
                const marker = fence[1];
                const code = [];
                i += 1;
                const close = new RegExp(`^\\s*${marker[0]}{${marker.length},}\\s*$`);
                while (i < lines.length && !close.test(lines[i])) { code.push(lines[i]); i += 1; }
                if (i < lines.length) i += 1;
                blocks.push({ type: 'code', code: code.join('\n'), language: normalizeLanguage(fence[2] || '') });
                continue;
            }

            const heading = line.match(/^\s{0,3}(#{1,6})\s+(.+?)\s*#*\s*$/);
            if (heading) {
                blocks.push({ type: 'heading', level: heading[1].length, html: inline(heading[2]) });
                i += 1;
                continue;
            }

            if (i + 1 < lines.length && line.trim() !== '' && /^\s*(=+|-+)\s*$/.test(lines[i + 1])) {
                blocks.push({ type: 'heading', level: lines[i + 1].trim().startsWith('=') ? 1 : 2, html: inline(line.trim()) });
                i += 2;
                continue;
            }

            if (/^\s{0,3}((\*\s*){3,}|(-\s*){3,}|(_\s*){3,})$/.test(line)) {
                blocks.push({ type: 'divider' });
                i += 1;
                continue;
            }

            if (isTableHeader(lines, i)) {
                const table = parseTable(lines, i);
                blocks.push({ type: 'html', html: table.html });
                i = table.next;
                continue;
            }

            if (/^\s*>/.test(line)) {
                const quote = [];
                while (i < lines.length) {
                    const match = lines[i].match(/^\s*>\s?(.*)$/);
                    if (!match) break;
                    quote.push(match[1]);
                    i += 1;
                }
                const quoteMarkdown = quote.join('\n');
                const inner = depth >= MAX_QUOTE_DEPTH || quoteMarkdown.length > MAX_QUOTE_RECURSION_CHARS
                    ? `<p>${inline(quote.join(' '))}</p>`
                    : parseMarkdown(quoteMarkdown, depth + 1).map((block) => block.type === 'heading'
                        ? `<h${block.level}>${block.html}</h${block.level}>`
                        : block.type === 'html' ? block.html : block.type === 'code' ? `<pre><code>${escapeHtml(block.code)}</code></pre>` : '').join('');
                blocks.push({ type: 'html', html: `<blockquote>${inner || `<p>${inline(quote.join(' '))}</p>`}</blockquote>` });
                continue;
            }

            if (/^\s*([-+*]|\d+[.)])\s+/.test(line)) {
                const list = parseList(lines, i);
                blocks.push({ type: 'html', html: list.html });
                i = list.next;
                continue;
            }

            const image = line.match(/^\s*!\[([^\]]*)\]\(([^\s\)]+)(?:\s+["']([^"']*)["'])?\)\s*$/);
            if (image && safeUrl(image[2], true)) {
                blocks.push({ type: 'image', alt: image[1], url: safeUrl(image[2], true), title: image[3] || '' });
                i += 1;
                continue;
            }

            const paragraph = [line.trim()];
            i += 1;
            while (i < lines.length && lines[i].trim() !== '' && !startsBlock(lines, i)) {
                paragraph.push(lines[i].trim());
                i += 1;
            }
            blocks.push({ type: 'html', html: `<p>${inline(paragraph.join('\n'))}</p>` });
        }

        return blocks;
    }

    function getCurrentContainer() {
        const elementor = window.elementor;
        if (!elementor) return null;

        try {
            const current = typeof elementor.getCurrentElement === 'function' ? elementor.getCurrentElement() : null;
            if (current) {
                if (typeof current.getContainer === 'function') return current.getContainer();
                if (current.container) return current.container;
                if (current.model && current.model.id && typeof elementor.getContainer === 'function') return elementor.getContainer(current.model.id);
            }
        } catch (error) {}

        try {
            const page = elementor.getPanelView && elementor.getPanelView().getCurrentPageView();
            const view = page && page.getOption ? page.getOption('editedElementView') : null;
            if (view) {
                if (typeof view.getContainer === 'function') return view.getContainer();
                if (view.container) return view.container;
                if (view.model && view.model.id) return elementor.getContainer(view.model.id);
            }
        } catch (error) {}
        return null;
    }

    function modelValue(container, key) {
        try {
            if (container.settings && typeof container.settings.get === 'function') return container.settings.get(key);
            const settings = container.model && typeof container.model.get === 'function' ? container.model.get('settings') : null;
            if (settings && typeof settings.get === 'function') return settings.get(key);
            if (settings && Object.prototype.hasOwnProperty.call(settings, key)) return settings[key];
        } catch (error) {}
        return '';
    }

    function widgetType(container) {
        try {
            if (container.model && typeof container.model.get === 'function') return container.model.get('widgetType');
            return container.model && container.model.attributes ? container.model.attributes.widgetType : '';
        } catch (error) { return ''; }
    }

    function parentContainer(container) {
        if (container.parent) return container.parent;
        try {
            const parentModel = container.model && container.model.collection && container.model.collection.parent;
            if (parentModel && parentModel.id && window.elementor.getContainer) return window.elementor.getContainer(parentModel.id);
        } catch (error) {}
        return null;
    }

    function sourceIndex(container) {
        try {
            if (container.model && container.model.collection) return container.model.collection.indexOf(container.model);
        } catch (error) {}
        try {
            if (container.parent && Array.isArray(container.parent.children)) return container.parent.children.indexOf(container);
        } catch (error) {}
        return -1;
    }

    function createdId(result) {
        if (!result) return '';
        return result.id || (result.model && result.model.id) || (result.model && result.model.get && result.model.get('id')) || '';
    }

    function containerId(container) {
        if (!container) return '';
        try {
            return String(container.id || (container.model && container.model.id) ||
                (container.model && container.model.get && container.model.get('id')) || '');
        } catch (error) {
            return '';
        }
    }

    function safeClassToken(value) {
        return String(value || '').replace(/[^a-zA-Z0-9_-]/g, '').slice(0, 80);
    }

    function generatedMarker(source) {
        const sourceId = safeClassToken(containerId(source));
        return sourceId ? `markblocks-generated-markdown-container markblocks-source-${sourceId}` : '';
    }

    function sameContainer(first, second) {
        const firstId = containerId(first);
        const secondId = containerId(second);
        return Boolean(firstId && secondId && firstId === secondId);
    }

    function elementType(container) {
        try {
            return String((container.model && container.model.get && container.model.get('elType')) ||
                (container.model && container.model.attributes && container.model.attributes.elType) || '');
        } catch (error) {
            return '';
        }
    }

    function isOwnedGeneratedContainer(candidate, source, expectedParent) {
        const sourceId = safeClassToken(containerId(source));
        if (!candidate || !sourceId || sameContainer(candidate, source)) return false;

        const classes = String(modelValue(candidate, '_css_classes') || '').split(/\s+/);
        const sourceMarkers = [`markblocks-source-${sourceId}`, `emb-source-${sourceId}`];
        const hasGeneratedClass = classes.includes('markblocks-generated-markdown-container') ||
            classes.includes('emb-generated-markdown-container');
        const hasSourceMarker = sourceMarkers.some((marker) => classes.includes(marker));
        const candidateParent = parentContainer(candidate);
        return elementType(candidate) === 'container' &&
            hasGeneratedClass && hasSourceMarker && sameContainer(candidateParent, expectedParent);
    }

    function createElement(parent, model, at) {
        return window.$e.run('document/elements/create', {
            model,
            container: parent,
            options: { at }
        });
    }

    function updateSourceSettings(source, settings) {
        try {
            window.$e.run('document/elements/settings', { container: source, settings });
            return true;
        } catch (error) {
            if (source.settings && typeof source.settings.set === 'function') {
                Object.keys(settings).forEach((key) => source.settings.set(key, settings[key]));
                if (source.settings.trigger) source.settings.trigger('change');
                return true;
            }
            return false;
        }
    }

    function deleteContainer(container) {
        if (!container) return false;
        try {
            window.$e.run('document/elements/delete', { container });
            return true;
        } catch (error) {
            return false;
        }
    }

    function statusElement(button) {
        const $button = $(button);
        const $scope = $button.closest('.elementor-control-content, .elementor-control, .elementor-panel');
        const $local = $scope.find('.markblocks-convert-status').first();
        return $local.length ? $local : $('.markblocks-convert-status').first();
    }

    function setStatus(button, message, isError) {
        statusElement(button).toggleClass('markblocks-error', Boolean(isError)).text(message || '');
    }

    function convert(button) {
        const source = getCurrentContainer();

        if (!source) {
            setStatus(button, MARKBLOCKS_EDITOR.unsupportedEditor, true);
            return;
        }
        if (widgetType(source) !== MARKBLOCKS_EDITOR.widgetType) {
            setStatus(button, MARKBLOCKS_EDITOR.wrongWidget, true);
            return;
        }

        const markdown = String(modelValue(source, 'markdown') || '');
        if (!markdown.trim()) {
            setStatus(button, MARKBLOCKS_EDITOR.emptyMarkdown, true);
            return;
        }

        if (byteLength(markdown) > MAX_MARKDOWN_BYTES) {
            setStatus(button, MARKBLOCKS_EDITOR.tooLarge, true);
            return;
        }

        if (!lineCountWithinLimit(markdown, MAX_MARKDOWN_LINES)) {
            setStatus(button, MARKBLOCKS_EDITOR.tooManyLines, true);
            return;
        }

        const blocks = parseMarkdown(markdown);
        if (blocks.length > MAX_BLOCKS) {
            setStatus(button, MARKBLOCKS_EDITOR.tooManyBlocks, true);
            return;
        }

        const parent = parentContainer(source);
        if (!parent || !window.$e || typeof window.$e.run !== 'function') {
            setStatus(button, MARKBLOCKS_EDITOR.unsupportedEditor, true);
            return;
        }

        if (button) button.disabled = true;
        setStatus(button, MARKBLOCKS_EDITOR.converting, false);

        let generated = null;
        let committed = false;
        try {
            let replaced = false;
            const previousId = String(modelValue(source, 'generated_container_id') || '');
            const replacePrevious = modelValue(source, 'replace_previous') === 'yes';
            const previousCandidate = replacePrevious && previousId && window.elementor.getContainer
                ? window.elementor.getContainer(previousId)
                : null;
            const previous = isOwnedGeneratedContainer(previousCandidate, source, parent) ? previousCandidate : null;

            const marker = generatedMarker(source);
            if (!marker) throw new Error(MARKBLOCKS_EDITOR.unsupportedEditor);

            const at = Math.max(0, sourceIndex(source) + 1);
            const result = createElement(parent, {
                elType: 'container',
                settings: {
                    _title: MARKBLOCKS_EDITOR.generatedTitle,
                    _css_classes: marker,
                    flex_direction: 'column',
                    content_width: 'full'
                }
            }, at);
            const generatedId = createdId(result);
            generated = generatedId && window.elementor.getContainer ? window.elementor.getContainer(generatedId) : null;
            if (!generated) throw new Error(MARKBLOCKS_EDITOR.createFailed);

            const codeTheme = String(modelValue(source, 'code_theme') || 'midnight');
            const showCopy = modelValue(source, 'show_copy') === 'yes' ? 'yes' : '';

            blocks.forEach((block, index) => {
                let model;
                if (block.type === 'heading') {
                    model = {
                        elType: 'widget',
                        widgetType: 'heading',
                        settings: { title: block.html, header_size: `h${block.level}` }
                    };
                } else if (block.type === 'divider') {
                    model = {
                        elType: 'widget',
                        widgetType: 'divider',
                        settings: { style: 'solid' }
                    };
                } else if (block.type === 'image') {
                    model = {
                        elType: 'widget',
                        widgetType: 'image',
                        settings: {
                            image: { url: block.url, id: '', alt: plainText(block.alt, 500) },
                            image_size: 'full',
                            caption_source: 'none'
                        }
                    };
                } else if (block.type === 'code') {
                    model = {
                        elType: 'widget',
                        widgetType: MARKBLOCKS_EDITOR.codeWidgetType,
                        settings: {
                            code: block.code,
                            language: block.language,
                            filename: '',
                            theme: codeTheme,
                            show_copy: showCopy
                        }
                    };
                } else {
                    model = {
                        elType: 'widget',
                        widgetType: 'text-editor',
                        settings: { editor: block.html }
                    };
                }
                createElement(generated, model, index);
            });

            const sourceUpdated = updateSourceSettings(source, {
                generated_container_id: generatedId,
                show_article: ''
            });
            if (!sourceUpdated) throw new Error(MARKBLOCKS_EDITOR.sourceUpdateFailed);
            committed = true;

            if (previous && !sameContainer(previous, generated)) {
                replaced = deleteContainer(previous);
            }

            setStatus(button, replaced ? `${MARKBLOCKS_EDITOR.converted} ${MARKBLOCKS_EDITOR.updated}` : MARKBLOCKS_EDITOR.converted, false);
        } catch (error) {
            if (generated && !committed) deleteContainer(generated);
            setStatus(button, `${MARKBLOCKS_EDITOR.failed} ${error && error.message ? error.message : error}`, true);
        } finally {
            if (button) button.disabled = false;
        }
    }

    $(document).on('click', '.markblocks-convert-markdown', function (event) {
        event.preventDefault();
        convert(this);
    });
})(jQuery, window, document);
