( function( wp ) {
    const { registerBlockType } = wp.blocks;
    const { useState, useEffect, useCallback, useRef } = wp.element;
    const { TextareaControl, PanelBody, ToggleControl, Placeholder, Spinner, ToolbarGroup, ToolbarButton, ToolbarDropdownMenu, Modal, TextControl, Button, RangeControl } = wp.components;
    const { PlainText } = wp.blockEditor;
    const { InspectorControls, BlockControls, useBlockProps } = wp.blockEditor;
    const { useDispatch } = wp.data;
    const { __ } = wp.i18n;
    const apiFetch = wp.apiFetch;

    // Icons for toolbar
    const icons = {
        bold: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
            wp.element.createElement( 'path', { d: 'M6 4v13h5.5c2.7 0 4.5-1.5 4.5-4 0-1.8-1.1-3.2-2.8-3.6v-.1c1.3-.4 2.3-1.6 2.3-3.1 0-2.1-1.6-3.2-4-3.2H6zm3 5V6.5h1.8c1 0 1.7.5 1.7 1.3s-.7 1.2-1.7 1.2H9zm0 5.5V11h2c1.2 0 1.9.6 1.9 1.5s-.7 1.5-1.9 1.5H9z' } )
        ),
        italic: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
            wp.element.createElement( 'path', { d: 'M10 4v2h2.2l-2.7 11H7v2h7v-2h-2.2l2.7-11H17V4z' } )
        ),
        code: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
            wp.element.createElement( 'path', { d: 'M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z' } )
        ),
        link: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
            wp.element.createElement( 'path', { d: 'M3.9 12c0-1.7 1.4-3.1 3.1-3.1h4V7H7c-2.8 0-5 2.2-5 5s2.2 5 5 5h4v-1.9H7c-1.7 0-3.1-1.4-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.7 0 3.1 1.4 3.1 3.1s-1.4 3.1-3.1 3.1h-4V17h4c2.8 0 5-2.2 5-5s-2.2-5-5-5z' } )
        ),
        image: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
            wp.element.createElement( 'path', { d: 'M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3 3.5-4.5 4.5 6H5l3.5-4.5z' } )
        ),
        heading: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
            wp.element.createElement( 'path', { d: 'M5 4v3h5.5v12h3V7H19V4H5z' } )
        ),
        quote: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
            wp.element.createElement( 'path', { d: 'M6 17h3l2-4V7H5v6h3zm8 0h3l2-4V7h-6v6h3z' } )
        ),
        listUl: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
            wp.element.createElement( 'path', { d: 'M4 10.5c-.8 0-1.5.7-1.5 1.5s.7 1.5 1.5 1.5 1.5-.7 1.5-1.5-.7-1.5-1.5-1.5zm0-6c-.8 0-1.5.7-1.5 1.5S3.2 7.5 4 7.5 5.5 6.8 5.5 6 4.8 4.5 4 4.5zm0 12c-.8 0-1.5.7-1.5 1.5s.7 1.5 1.5 1.5 1.5-.7 1.5-1.5-.7-1.5-1.5-1.5zM7 19h14v-2H7v2zm0-6h14v-2H7v2zm0-8v2h14V5H7z' } )
        ),
        listOl: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
            wp.element.createElement( 'path', { d: 'M2 17h2v.5H3v1h1v.5H2v1h3v-4H2v1zm1-9h1V4H2v1h1v3zm-1 3h1.8L2 13.1v.9h3v-1H3.2L5 10.9V10H2v1zm5-6v2h14V5H7zm0 14h14v-2H7v2zm0-6h14v-2H7v2z' } )
        ),
        codeBlock: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
            wp.element.createElement( 'path', { d: 'M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z' } ),
            wp.element.createElement( 'rect', { x: 2, y: 2, width: 20, height: 20, fill: 'none', stroke: 'currentColor', strokeWidth: 1.5, rx: 2 } )
        ),
        superscript: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
            wp.element.createElement( 'path', { d: 'M16 7.5c0-1.1.9-2 2-2s2 .9 2 2h-1c0-.6-.4-1-1-1s-1 .4-1 1 .4 1 1 1h1v1h-1c-1.1 0-2-.9-2-2zm-8 5L4 19h2.5l2-3.5 2 3.5H13l-4-6.5L13 6h-2.5L8.5 9.5 6.5 6H4l4 6.5z' } )
        ),
        subscript: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
            wp.element.createElement( 'path', { d: 'M16 15.5c0-1.1.9-2 2-2s2 .9 2 2h-1c0-.6-.4-1-1-1s-1 .4-1 1 .4 1 1 1h1v1h-1c-1.1 0-2-.9-2-2zM8 12.5L4 19h2.5l2-3.5 2 3.5H13l-4-6.5L13 6h-2.5L8.5 9.5 6.5 6H4l4 6.5z' } )
        ),
        highlight: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
            wp.element.createElement( 'path', { d: 'M17 5H7c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 12H7V7h10v10z', fill: 'currentColor' } ),
            wp.element.createElement( 'rect', { x: 8, y: 8, width: 8, height: 8, fill: 'yellow', opacity: 0.5 } )
        ),
        insert: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
            wp.element.createElement( 'path', { d: 'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z' } )
        ),
        delete: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
            wp.element.createElement( 'path', { d: 'M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM8 9h8v10H8V9zm7.5-5l-1-1h-5l-1 1H5v2h14V4h-3.5z' } )
        ),
        strikethrough: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
            wp.element.createElement( 'path', { d: 'M10 19h4v-3h-4v3zM5 4v3h5v3h4V7h5V4H5zM3 14h18v-2H3v2z' } )
        ),
        horizontalRule: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
            wp.element.createElement( 'line', { x1: 4, y1: 12, x2: 8, y2: 12, stroke: 'currentColor', strokeWidth: 2 } ),
            wp.element.createElement( 'circle', { cx: 12, cy: 12, r: 1.5, fill: 'currentColor' } ),
            wp.element.createElement( 'line', { x1: 16, y1: 12, x2: 20, y2: 12, stroke: 'currentColor', strokeWidth: 2 } )
        ),
        footnote: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
            wp.element.createElement( 'text', { x: 6, y: 16, fontSize: 12, fontWeight: 'bold' }, 'F' ),
            wp.element.createElement( 'text', { x: 14, y: 12, fontSize: 8 }, '1' )
        ),
        div: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
            wp.element.createElement( 'rect', { x: 3, y: 3, width: 18, height: 18, fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeDasharray: '4 2' } )
        ),
        span: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
            wp.element.createElement( 'path', { d: 'M4 9h16v6H4z', fill: 'none', stroke: 'currentColor', strokeWidth: 2 } )
        ),
        table: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
            wp.element.createElement( 'path', { d: 'M3 3v18h18V3H3zm8 16H5v-6h6v6zm0-8H5V5h6v6zm8 8h-6v-6h6v6zm0-8h-6V5h6v6z' } )
        ),
        formatTable: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
            wp.element.createElement( 'path', { d: 'M3 3v18h18V3H3zm8 16H5v-6h6v6zm0-8H5V5h6v6zm8 8h-6v-6h6v6zm0-8h-6V5h6v6z' } ),
            wp.element.createElement( 'path', { d: 'M17 17l4 4m0-4l-4 4', stroke: 'currentColor', strokeWidth: 2, fill: 'none' } )
        ),
        taskList: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
            wp.element.createElement( 'rect', { x: 3, y: 4, width: 4, height: 4, fill: 'none', stroke: 'currentColor', strokeWidth: 1.5 } ),
            wp.element.createElement( 'path', { d: 'M4 6l1 1 2-2', stroke: 'currentColor', strokeWidth: 1.5, fill: 'none' } ),
            wp.element.createElement( 'line', { x1: 9, y1: 6, x2: 21, y2: 6, stroke: 'currentColor', strokeWidth: 1.5 } ),
            wp.element.createElement( 'rect', { x: 3, y: 10, width: 4, height: 4, fill: 'none', stroke: 'currentColor', strokeWidth: 1.5 } ),
            wp.element.createElement( 'line', { x1: 9, y1: 12, x2: 21, y2: 12, stroke: 'currentColor', strokeWidth: 1.5 } ),
            wp.element.createElement( 'rect', { x: 3, y: 16, width: 4, height: 4, fill: 'none', stroke: 'currentColor', strokeWidth: 1.5 } ),
            wp.element.createElement( 'line', { x1: 9, y1: 18, x2: 21, y2: 18, stroke: 'currentColor', strokeWidth: 1.5 } )
        ),
        video: wp.element.createElement( 'svg', { width: 24, height: 24, viewBox: '0 0 24 24' },
            wp.element.createElement( 'path', { d: 'M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z' } )
        ),
    };

    registerBlockType( 'wpdjot/djot', {
        edit: function( props ) {
            const { attributes, setAttributes } = props;
            const { content } = attributes;
            const { __unstableMarkLastChangeAsPersistent: markUndoBoundary } = useDispatch( 'core/block-editor' );
            const [ preview, setPreview ] = useState( '' );
            // Visual editor mode from settings: 'disabled' | 'enabled' | 'enabled_default'
            const visualEditorSetting = window.wpdjotBlockData?.visualEditorMode || 'disabled';
            const visualEditorEnabled = visualEditorSetting !== 'disabled';
            const initialMode = visualEditorSetting === 'enabled_default' ? 'visual' : 'write';
            const [ editorMode, setEditorMode ] = useState( initialMode ); // 'write' | 'visual' | 'preview'
            const previousModeRef = useRef( initialMode ); // Track mode before preview (ref avoids closure issues)
            const [ isLoading, setIsLoading ] = useState( false );
            const [ visualEditorInstance, setVisualEditorInstance ] = useState( null );
            const [ isVisualLoading, setIsVisualLoading ] = useState( false );
            const visualEditorRef = useRef( null );
            const pendingDjotContent = useRef( null );
            const [ showLinkModal, setShowLinkModal ] = useState( false );
            const [ showImageModal, setShowImageModal ] = useState( false );
            const [ showTableModal, setShowTableModal ] = useState( false );
            const [ linkUrl, setLinkUrl ] = useState( '' );
            const [ linkText, setLinkText ] = useState( '' );
            const [ imageUrl, setImageUrl ] = useState( '' );
            const [ imageAlt, setImageAlt ] = useState( '' );
            const [ tableCols, setTableCols ] = useState( 3 );
            const [ tableRows, setTableRows ] = useState( 2 );
            const [ cursorInTable, setCursorInTable ] = useState( false );
            const [ showImportModal, setShowImportModal ] = useState( false );
            const [ importType, setImportType ] = useState( 'markdown' );
            const [ importInput, setImportInput ] = useState( '' );
            const [ djotPreview, setDjotPreview ] = useState( '' );
            const [ isConverting, setIsConverting ] = useState( false );
            const [ showTaskListModal, setShowTaskListModal ] = useState( false );
            const [ taskListItems, setTaskListItems ] = useState( [ { text: '', checked: false } ] );
            const [ showDefListModal, setShowDefListModal ] = useState( false );
            const [ defListTerms, setDefListTerms ] = useState( [ '' ] );
            const [ defListDefinitions, setDefListDefinitions ] = useState( [ '' ] );
            const [ showVideoModal, setShowVideoModal ] = useState( false );
            const [ videoUrl, setVideoUrl ] = useState( '' );
            const [ videoCaption, setVideoCaption ] = useState( '' );
            const [ videoWidth, setVideoWidth ] = useState( '' );
            const [ showDivModal, setShowDivModal ] = useState( false );
            const [ divClass, setDivClass ] = useState( 'note' );
            const [ showDiffModal, setShowDiffModal ] = useState( false );
            const [ diffData, setDiffData ] = useState( null );
            const pendingVisualEditorInstance = useRef( null );
            const textareaRef = useRef( null );
            const previewRef = useRef( null );
            const [ selectionStart, setSelectionStart ] = useState( 0 );
            const [ selectionEnd, setSelectionEnd ] = useState( 0 );

            const blockProps = useBlockProps( {
                className: 'wpdjot-block',
            } );

            // Helper to get visual editor instance reliably
            // Uses React state if available, falls back to global instance
            function getVisualEditor() {
                if ( visualEditorInstance ) {
                    return visualEditorInstance;
                }
                // Fallback to global instance if React state is stale
                if ( window.WpDjotVisualEditor && window.WpDjotVisualEditor.getEditor() ) {
                    var editor = window.WpDjotVisualEditor.getEditor();
                    // Return a compatible interface
                    return {
                        editor: editor,
                        commands: {
                            bold: function() { return editor.chain().focus().toggleBold().run(); },
                            italic: function() { return editor.chain().focus().toggleItalic().run(); },
                            code: function() { return editor.chain().focus().toggleCode().run(); },
                            highlight: function() { return editor.chain().focus().toggleHighlight().run(); },
                            strikethrough: function() { return editor.chain().focus().toggleStrike().run(); },
                            superscript: function() { return editor.chain().focus().toggleSuperscript().run(); },
                            subscript: function() { return editor.chain().focus().toggleSubscript().run(); },
                            djotInsert: function() { return editor.chain().focus().toggleDjotInsert().run(); },
                            djotDelete: function() { return editor.chain().focus().toggleDjotDelete().run(); },
                            heading: function( level ) { return editor.chain().focus().toggleHeading( { level: level } ).run(); },
                            blockquote: function() { return editor.chain().focus().toggleBlockquote().run(); },
                            bulletList: function() { return editor.chain().focus().toggleBulletList().run(); },
                            orderedList: function() { return editor.chain().focus().toggleOrderedList().run(); },
                            taskList: function() { return editor.chain().focus().toggleTaskList().run(); },
                            codeBlock: function() { return editor.chain().focus().toggleCodeBlock().run(); },
                            horizontalRule: function() { return editor.chain().focus().setHorizontalRule().run(); },
                            link: function( href ) { return href ? editor.chain().focus().setLink( { href: href } ).run() : editor.chain().focus().unsetLink().run(); },
                            image: function( src, alt ) { return editor.chain().focus().setImage( { src: src, alt: alt } ).run(); },
                            table: function( rows, cols ) { return editor.chain().focus().insertTable( { rows: rows, cols: cols, withHeaderRow: true } ).run(); },
                            djotDiv: function( className ) { return editor.chain().focus().setDjotDiv( { class: className } ).run(); },
                            djotSpan: function( className ) { return editor.chain().focus().setDjotSpan( { class: className } ).run(); },
                            djotFootnote: function( label ) { return editor.chain().focus().insertDjotFootnote( { label: label } ).run(); },
                        },
                    };
                }
                return null;
            }

            // Track selection in textarea and check if in table
            function updateSelection() {
                if ( textareaRef.current ) {
                    const textarea = textareaRef.current.querySelector( 'textarea' );
                    if ( textarea ) {
                        setSelectionStart( textarea.selectionStart );
                        setSelectionEnd( textarea.selectionEnd );

                        // Check if cursor is in a table
                        var text = content || '';
                        var cursorPos = textarea.selectionStart;
                        var lineStart = cursorPos;
                        while ( lineStart > 0 && text[ lineStart - 1 ] !== '\n' ) {
                            lineStart--;
                        }
                        var lineEnd = cursorPos;
                        while ( lineEnd < text.length && text[ lineEnd ] !== '\n' ) {
                            lineEnd++;
                        }
                        var currentLine = text.substring( lineStart, lineEnd ).trim();
                        setCursorInTable( currentLine.startsWith( '|' ) && currentLine.endsWith( '|' ) );
                    }
                }
            }

            // Track previous content for undo/redo cursor positioning
            const previousContent = useRef( content || '' );
            const isInternalChange = useRef( false );

            // Find position where two strings first differ
            function findDiffPosition( oldStr, newStr ) {
                oldStr = oldStr || '';
                newStr = newStr || '';
                var minLen = Math.min( oldStr.length, newStr.length );
                for ( var i = 0; i < minLen; i++ ) {
                    if ( oldStr[ i ] !== newStr[ i ] ) {
                        return i;
                    }
                }
                // Strings are identical up to minLen, diff is at the end of shorter string
                return minLen;
            }

            // Compute line-by-line diff between two strings
            function computeLineDiff( original, modified ) {
                var oldLines = ( original || '' ).split( '\n' );
                var newLines = ( modified || '' ).split( '\n' );
                var contextLines = 3; // Lines of context before/after changes

                // Simple LCS-based diff
                var m = oldLines.length;
                var n = newLines.length;

                // Build LCS table
                var lcs = [];
                for ( var i = 0; i <= m; i++ ) {
                    lcs[ i ] = [];
                    for ( var j = 0; j <= n; j++ ) {
                        if ( i === 0 || j === 0 ) {
                            lcs[ i ][ j ] = 0;
                        } else if ( oldLines[ i - 1 ] === newLines[ j - 1 ] ) {
                            lcs[ i ][ j ] = lcs[ i - 1 ][ j - 1 ] + 1;
                        } else {
                            lcs[ i ][ j ] = Math.max( lcs[ i - 1 ][ j ], lcs[ i ][ j - 1 ] );
                        }
                    }
                }

                // Backtrack to build full diff
                var fullDiff = [];
                i = m;
                var j = n;
                while ( i > 0 || j > 0 ) {
                    if ( i > 0 && j > 0 && oldLines[ i - 1 ] === newLines[ j - 1 ] ) {
                        fullDiff.unshift( { type: 'equal', line: oldLines[ i - 1 ] } );
                        i--;
                        j--;
                    } else if ( j > 0 && ( i === 0 || lcs[ i ][ j - 1 ] >= lcs[ i - 1 ][ j ] ) ) {
                        fullDiff.unshift( { type: 'add', line: newLines[ j - 1 ] } );
                        j--;
                    } else {
                        fullDiff.unshift( { type: 'remove', line: oldLines[ i - 1 ] } );
                        i--;
                    }
                }

                // Find indices of changed lines
                var changeIndices = [];
                for ( i = 0; i < fullDiff.length; i++ ) {
                    if ( fullDiff[ i ].type !== 'equal' ) {
                        changeIndices.push( i );
                    }
                }

                if ( changeIndices.length === 0 ) {
                    return [];
                }

                // Build condensed diff with context
                var result = [];
                var lastIncludedIndex = -1;

                for ( var ci = 0; ci < changeIndices.length; ci++ ) {
                    var changeIdx = changeIndices[ ci ];
                    var startContext = Math.max( 0, changeIdx - contextLines );
                    var endContext = Math.min( fullDiff.length - 1, changeIdx + contextLines );

                    // Extend to include consecutive changes
                    while ( ci + 1 < changeIndices.length && changeIndices[ ci + 1 ] <= endContext + contextLines ) {
                        ci++;
                        endContext = Math.min( fullDiff.length - 1, changeIndices[ ci ] + contextLines );
                    }

                    // Add separator if there's a gap
                    if ( lastIncludedIndex >= 0 && startContext > lastIncludedIndex + 1 ) {
                        result.push( { type: 'separator', line: '...' } );
                    }

                    // Add lines in this hunk
                    for ( var k = Math.max( startContext, lastIncludedIndex + 1 ); k <= endContext; k++ ) {
                        result.push( fullDiff[ k ] );
                    }

                    lastIncludedIndex = endContext;
                }

                return result;
            }

            // Handle external content changes (undo/redo) - position cursor at change location
            useEffect( function() {
                if ( isInternalChange.current ) {
                    isInternalChange.current = false;
                    previousContent.current = content || '';
                    return;
                }

                // External content change (undo/redo)
                if ( editorMode === 'write' ) {
                    var textarea = textareaRef.current ? textareaRef.current.querySelector( 'textarea' ) : null;
                    if ( textarea ) {
                        var oldContent = previousContent.current;
                        var newContent = content || '';
                        var diffPos = findDiffPosition( oldContent, newContent );

                        // Position cursor at the change location
                        requestAnimationFrame( function() {
                            textarea.focus( { preventScroll: true } );
                            textarea.setSelectionRange( diffPos, diffPos );
                            // Scroll textarea to make cursor visible
                            textarea.blur();
                            textarea.focus();
                        } );
                    }
                }

                previousContent.current = content || '';
            }, [ content ] );

            // Restore focus and scroll position after React re-render
            function restoreFocus( textarea, cursorPos ) {
                const scrollY = window.scrollY;
                const scrollX = window.scrollX;
                isInternalChange.current = true;

                requestAnimationFrame( function() {
                    if ( textarea ) {
                        textarea.focus( { preventScroll: true } );
                        textarea.setSelectionRange( cursorPos, cursorPos );
                    }
                    window.scrollTo( scrollX, scrollY );
                } );
            }

            // Insert text at cursor or wrap selection
            function insertMarkup( before, after ) {
                after = after || '';

                const textarea = textareaRef.current ? textareaRef.current.querySelector( 'textarea' ) : null;
                if ( ! textarea ) return;

                // Create undo boundary so this change is a separate undo step
                if ( markUndoBoundary ) {
                    markUndoBoundary();
                }

                const start = textarea.selectionStart;
                const end = textarea.selectionEnd;
                const text = content || '';
                const selectedText = text.substring( start, end );

                let newText;
                let newCursorPos;

                if ( selectedText ) {
                    // Wrap selection, cursor at end
                    newText = text.substring( 0, start ) + before + selectedText + after + text.substring( end );
                    newCursorPos = start + before.length + selectedText.length + after.length;
                } else {
                    // Insert markers only, cursor between them
                    newText = text.substring( 0, start ) + before + after + text.substring( end );
                    newCursorPos = start + before.length;
                }

                setAttributes( { content: newText } );
                restoreFocus( textarea, newCursorPos );
            }

            // Insert block-level markup at cursor position
            function insertBlockMarkup( markup, placeholder ) {
                const textarea = textareaRef.current ? textareaRef.current.querySelector( 'textarea' ) : null;
                if ( ! textarea ) return;

                // Create undo boundary so this change is a separate undo step
                if ( markUndoBoundary ) {
                    markUndoBoundary();
                }

                const start = textarea.selectionStart;
                const end = textarea.selectionEnd;
                const text = content || '';
                const selectedText = text.substring( start, end );

                // Use selected text or placeholder
                const blockText = selectedText || placeholder;

                // Check how many newlines needed before
                let prefix = '';
                if ( start === 0 ) {
                    // At beginning, no newlines needed
                } else if ( start >= 2 && text[ start - 1 ] === '\n' && text[ start - 2 ] === '\n' ) {
                    // Already have blank line, no extra needed
                } else if ( text[ start - 1 ] === '\n' ) {
                    // On a newline, need one more for blank line
                    prefix = '\n';
                } else {
                    // In middle of line, need two newlines for blank line
                    prefix = '\n\n';
                }

                // Always add two newlines after for blank line separation
                let suffix = '\n\n';
                if ( end < text.length - 1 && text[ end ] === '\n' && text[ end + 1 ] === '\n' ) {
                    // Already have blank line after
                    suffix = '';
                } else if ( end < text.length && text[ end ] === '\n' ) {
                    // Have one newline, add one more
                    suffix = '\n';
                }

                const newText = text.substring( 0, start ) + prefix + markup + blockText + suffix + text.substring( end );
                const newCursorPos = start + prefix.length + markup.length + blockText.length + suffix.length;

                setAttributes( { content: newText } );
                restoreFocus( textarea, newCursorPos );
            }

            // Insert multi-line block (like code blocks, divs)
            function insertMultiLineBlock( startTag, endTag, placeholder ) {
                const textarea = textareaRef.current ? textareaRef.current.querySelector( 'textarea' ) : null;
                if ( ! textarea ) return;

                // Create undo boundary so this change is a separate undo step
                if ( markUndoBoundary ) {
                    markUndoBoundary();
                }

                const start = textarea.selectionStart;
                const end = textarea.selectionEnd;
                const text = content || '';
                const selectedText = text.substring( start, end );

                // Check newlines before cursor
                let prefixNewlines = '';
                if ( start === 0 ) {
                    // At beginning, no newlines needed
                } else if ( start >= 2 && text[ start - 1 ] === '\n' && text[ start - 2 ] === '\n' ) {
                    // Already have blank line, no extra needed
                } else if ( text[ start - 1 ] === '\n' ) {
                    // On a newline, need one more for blank line
                    prefixNewlines = '\n';
                } else {
                    // In middle of line, need two newlines
                    prefixNewlines = '\n\n';
                }

                // Check newlines after cursor - always want blank line after block
                let suffixNewlines = '\n\n';
                if ( end < text.length - 1 && text[ end ] === '\n' && text[ end + 1 ] === '\n' ) {
                    // Already have blank line after
                    suffixNewlines = '';
                } else if ( end < text.length && text[ end ] === '\n' ) {
                    // Have one newline, add one more
                    suffixNewlines = '\n';
                }

                const contentToWrap = selectedText || placeholder;
                let blockContent;
                let newCursorPos;

                if ( ! contentToWrap && ! endTag ) {
                    // Simple block like horizontal rule - cursor goes to blank line after
                    blockContent = prefixNewlines + startTag + suffixNewlines;
                    // Position cursor after first newline (on the blank line)
                    newCursorPos = start + prefixNewlines.length + startTag.length + 1;
                } else {
                    // Multi-line block with content
                    blockContent = prefixNewlines + startTag + '\n' + contentToWrap + '\n' + endTag + suffixNewlines;
                    newCursorPos = start + prefixNewlines.length + startTag.length + 1 + contentToWrap.length;
                }

                const newText = text.substring( 0, start ) + blockContent + text.substring( end );

                setAttributes( { content: newText } );
                restoreFocus( textarea, newCursorPos );
            }

            // Toolbar button handlers
            function onBold() {
                var visualEditor = editorMode === 'visual' ? getVisualEditor() : null;
                if ( visualEditor ) {
                    visualEditor.commands.bold();
                } else {
                    insertMarkup( '*', '*' );
                }
            }
            function onItalic() {
                var visualEditor = editorMode === 'visual' ? getVisualEditor() : null;
                if ( visualEditor ) {
                    visualEditor.commands.italic();
                } else {
                    insertMarkup( '_', '_' );
                }
            }
            function onCode() {
                var visualEditor = editorMode === 'visual' ? getVisualEditor() : null;
                if ( visualEditor ) {
                    visualEditor.commands.code();
                } else {
                    insertMarkup( '`', '`' );
                }
            }
            function onSuperscript() {
                var visualEditor = editorMode === 'visual' ? getVisualEditor() : null;
                if ( visualEditor ) {
                    visualEditor.commands.superscript();
                } else {
                    insertMarkup( '^', '^' );
                }
            }
            function onSubscript() {
                var visualEditor = editorMode === 'visual' ? getVisualEditor() : null;
                if ( visualEditor ) {
                    visualEditor.commands.subscript();
                } else {
                    insertMarkup( '~', '~' );
                }
            }
            function onHighlight() {
                var visualEditor = editorMode === 'visual' ? getVisualEditor() : null;
                if ( visualEditor ) {
                    visualEditor.commands.highlight();
                } else {
                    insertMarkup( '{=', '=}' );
                }
            }
            function onInsert() {
                var visualEditor = editorMode === 'visual' ? getVisualEditor() : null;
                if ( visualEditor ) {
                    visualEditor.commands.djotInsert();
                } else {
                    insertMarkup( '{+', '+}' );
                }
            }
            function onDelete() {
                var visualEditor = editorMode === 'visual' ? getVisualEditor() : null;
                if ( visualEditor ) {
                    visualEditor.commands.djotDelete();
                } else {
                    insertMarkup( '{-', '-}' );
                }
            }
            function onStrikethrough() {
                var visualEditor = editorMode === 'visual' ? getVisualEditor() : null;
                if ( visualEditor ) {
                    visualEditor.commands.strikethrough();
                } else {
                    insertMarkup( '{~', '~}' );
                }
            }
            function onSpan() {
                var visualEditor = editorMode === 'visual' ? getVisualEditor() : null;
                if ( visualEditor ) {
                    visualEditor.commands.djotSpan( 'class' );
                } else {
                    insertMarkup( '[', ']{.class}' );
                }
            }

            function onLink() {
                const textarea = textareaRef.current ? textareaRef.current.querySelector( 'textarea' ) : null;
                if ( textarea ) {
                    const selectedText = ( content || '' ).substring( textarea.selectionStart, textarea.selectionEnd );
                    setLinkText( selectedText || '' );
                    setLinkUrl( 'https://' );
                }
                setShowLinkModal( true );
            }

            function onInsertLink() {
                // Visual mode: use Tiptap link command
                var visualEditor = editorMode === 'visual' ? getVisualEditor() : null;
                if ( visualEditor ) {
                    visualEditor.commands.link( linkUrl );
                    setShowLinkModal( false );
                    setLinkUrl( '' );
                    setLinkText( '' );
                    return;
                }

                const textarea = textareaRef.current ? textareaRef.current.querySelector( 'textarea' ) : null;
                if ( ! textarea ) return;

                // Create undo boundary so this change is a separate undo step
                if ( markUndoBoundary ) {
                    markUndoBoundary();
                }

                const start = textarea.selectionStart;
                const end = textarea.selectionEnd;
                const text = content || '';

                let linkMarkup;
                if ( linkText.trim() ) {
                    linkMarkup = '[' + linkText + '](' + linkUrl + ')';
                } else {
                    // Use autolink syntax when no text provided
                    linkMarkup = '<' + linkUrl + '>';
                }

                // Replace selection (or insert at cursor) with the complete link
                const newText = text.substring( 0, start ) + linkMarkup + text.substring( end );
                const newCursorPos = start + linkMarkup.length;

                setAttributes( { content: newText } );
                restoreFocus( textarea, newCursorPos );

                setShowLinkModal( false );
                setLinkUrl( '' );
                setLinkText( '' );
            }

            function onImage() {
                setImageUrl( 'https://' );
                setImageAlt( '' );
                setShowImageModal( true );
            }

            function onInsertImage() {
                // Visual mode: use Tiptap image command
                var visualEditor = editorMode === 'visual' ? getVisualEditor() : null;
                if ( visualEditor ) {
                    visualEditor.commands.image( imageUrl, imageAlt );
                    setShowImageModal( false );
                    setImageUrl( '' );
                    setImageAlt( '' );
                    return;
                }

                const textarea = textareaRef.current ? textareaRef.current.querySelector( 'textarea' ) : null;
                if ( ! textarea ) return;

                // Create undo boundary so this change is a separate undo step
                if ( markUndoBoundary ) {
                    markUndoBoundary();
                }

                const start = textarea.selectionStart;
                const end = textarea.selectionEnd;
                const text = content || '';

                const imageMarkup = '![' + imageAlt + '](' + imageUrl + ')';

                // Replace selection (or insert at cursor) with the complete image
                const newText = text.substring( 0, start ) + imageMarkup + text.substring( end );
                const newCursorPos = start + imageMarkup.length;

                setAttributes( { content: newText } );
                restoreFocus( textarea, newCursorPos );

                setShowImageModal( false );
                setImageUrl( '' );
                setImageAlt( '' );
            }

            function onHeading( level ) {
                var visualEditor = editorMode === 'visual' ? getVisualEditor() : null;
                if ( visualEditor ) {
                    visualEditor.commands.heading( level );
                    return;
                }

                const textarea = textareaRef.current ? textareaRef.current.querySelector( 'textarea' ) : null;
                if ( ! textarea ) return;

                // Create undo boundary so this change is a separate undo step
                if ( markUndoBoundary ) {
                    markUndoBoundary();
                }

                const text = content || '';
                const cursorPos = textarea.selectionStart;

                // Find the start of the current line
                let lineStart = cursorPos;
                while ( lineStart > 0 && text[ lineStart - 1 ] !== '\n' ) {
                    lineStart--;
                }

                // Find the end of the current line
                let lineEnd = cursorPos;
                while ( lineEnd < text.length && text[ lineEnd ] !== '\n' ) {
                    lineEnd++;
                }

                const currentLine = text.substring( lineStart, lineEnd );

                // Check if current line has heading markup
                const headingMatch = currentLine.match( /^(#{1,6})\s+(.*)$/ );

                if ( headingMatch ) {
                    // Replace existing heading with new level
                    const headingContent = headingMatch[ 2 ];
                    const newHashes = '#'.repeat( level ) + ' ';
                    const newLine = newHashes + headingContent;
                    const newText = text.substring( 0, lineStart ) + newLine + text.substring( lineEnd );
                    const newCursorPos = lineStart + newLine.length;

                    setAttributes( { content: newText } );
                    restoreFocus( textarea, newCursorPos );
                } else {
                    // No existing heading on line, insert heading prefix
                    const hashes = '#'.repeat( level ) + ' ';
                    insertBlockMarkup( hashes, '' );
                }
            }

            function onBlockquote() {
                var visualEditor = editorMode === 'visual' ? getVisualEditor() : null;
                if ( visualEditor ) {
                    visualEditor.commands.blockquote();
                } else {
                    insertBlockMarkup( '> ', '' );
                }
            }
            function onListUl() {
                var visualEditor = editorMode === 'visual' ? getVisualEditor() : null;
                if ( visualEditor ) {
                    visualEditor.commands.bulletList();
                } else {
                    insertBlockMarkup( '- ', '' );
                }
            }
            function onListOl() {
                var visualEditor = editorMode === 'visual' ? getVisualEditor() : null;
                if ( visualEditor ) {
                    visualEditor.commands.orderedList();
                } else {
                    insertBlockMarkup( '1. ', '' );
                }
            }
            function onHorizontalRule() {
                var visualEditor = editorMode === 'visual' ? getVisualEditor() : null;
                if ( visualEditor ) {
                    visualEditor.commands.horizontalRule();
                } else {
                    insertMultiLineBlock( '---', '', '' );
                }
            }
            function onCodeBlock() {
                var visualEditor = editorMode === 'visual' ? getVisualEditor() : null;
                if ( visualEditor ) {
                    visualEditor.commands.codeBlock();
                } else {
                    insertMultiLineBlock( '```', '```', '' );
                }
            }
            function onDiv() {
                console.log( 'onDiv called, showing modal' );
                setDivClass( 'note' );
                setShowDivModal( true );
            }
            function onInsertDiv() {
                var className = divClass.trim() || 'note';
                var visualEditor = editorMode === 'visual' ? getVisualEditor() : null;
                if ( visualEditor ) {
                    visualEditor.commands.djotDiv( className );
                } else {
                    insertMultiLineBlock( '::: ' + className, ':::', '' );
                }
                setShowDivModal( false );
            }
            function onFootnote() {
                var visualEditor = editorMode === 'visual' ? getVisualEditor() : null;
                if ( visualEditor ) {
                    visualEditor.commands.djotFootnote( 'note' );
                } else {
                    insertMarkup( '[^', ']' );
                }
            }

            function onTable() {
                setTableCols( 3 );
                setTableRows( 2 );
                setShowTableModal( true );
            }

            function onInsertTable() {
                var cols = Math.max( 1, Math.min( 10, tableCols ) );
                var rows = Math.max( 1, Math.min( 20, tableRows ) );

                // Visual mode: use Tiptap table command
                var visualEditor = editorMode === 'visual' ? getVisualEditor() : null;
                if ( visualEditor ) {
                    visualEditor.commands.table( rows, cols );
                    setShowTableModal( false );
                    return;
                }

                // Build header row
                var headerCells = [];
                var separatorCells = [];
                for ( var c = 1; c <= cols; c++ ) {
                    headerCells.push( ' Column ' );
                    separatorCells.push( '--------' );
                }
                var header = '|' + headerCells.join( '|' ) + '|';
                var separator = '|' + separatorCells.join( '|' ) + '|';

                // Build data rows
                var dataRows = [];
                for ( var r = 1; r <= rows; r++ ) {
                    var rowCells = [];
                    for ( var c = 1; c <= cols; c++ ) {
                        rowCells.push( '     ' );
                    }
                    dataRows.push( '|' + rowCells.join( '|' ) + '|' );
                }

                var tableTemplate = header + '\n' + separator + '\n' + dataRows.join( '\n' ) + '\n';
                insertMultiLineBlock( '', '', tableTemplate );
                setShowTableModal( false );
            }

            function onTaskList() {
                // Visual mode: toggle task list directly
                var visualEditor = editorMode === 'visual' ? getVisualEditor() : null;
                if ( visualEditor ) {
                    visualEditor.commands.taskList();
                    return;
                }
                setTaskListItems( [ { text: '', checked: false } ] );
                setShowTaskListModal( true );
            }

            function onInsertTaskList() {
                var items = taskListItems.filter( function( item ) { return item.text.trim() !== ''; } );
                if ( items.length === 0 ) {
                    setShowTaskListModal( false );
                    return;
                }

                var taskListText = items.map( function( item ) {
                    return ( item.checked ? '- [x] ' : '- [ ] ' ) + item.text;
                } ).join( '\n' ) + '\n';

                insertMultiLineBlock( '', '', taskListText );
                setShowTaskListModal( false );
            }

            function updateTaskItem( index, field, value ) {
                var newItems = taskListItems.slice();
                newItems[ index ] = Object.assign( {}, newItems[ index ], ( function() { var o = {}; o[ field ] = value; return o; } )() );
                setTaskListItems( newItems );
            }

            function addTaskItem() {
                setTaskListItems( taskListItems.concat( [ { text: '', checked: false } ] ) );
            }

            function removeTaskItem( index ) {
                if ( taskListItems.length <= 1 ) return;
                var newItems = taskListItems.slice();
                newItems.splice( index, 1 );
                setTaskListItems( newItems );
            }

            function onDefList() {
                setDefListTerms( [ '' ] );
                setDefListDefinitions( [ '' ] );
                setShowDefListModal( true );
            }

            function onInsertDefList() {
                var terms = defListTerms.filter( function( t ) { return t.trim() !== ''; } );
                var definitions = defListDefinitions.filter( function( d ) { return d.trim() !== ''; } );
                if ( terms.length === 0 && definitions.length === 0 ) {
                    setShowDefListModal( false );
                    return;
                }

                // Djot spec syntax: `: term` lines, then blank line, then indented definition
                // Multiple dd elements use `: +` continuation marker
                var termsText = terms.map( function( t ) { return ': ' + t; } ).join( '\n' );

                var defsText = definitions.map( function( def, index ) {
                    var defLines = def.split( '\n' );
                    var indentedDef = defLines.map( function( line ) {
                        return '  ' + line;
                    } ).join( '\n' );

                    // First definition follows terms, subsequent ones use `: +` marker
                    if ( index === 0 ) {
                        return indentedDef;
                    }
                    return ': +\n\n' + indentedDef;
                } ).join( '\n\n' );

                var defListText = termsText + '\n\n' + defsText;

                // Visual mode: convert to HTML and insert
                if ( editorMode === 'visual' ) {
                    apiFetch( {
                        path: '/wpdjot/v1/render',
                        method: 'POST',
                        data: { content: defListText },
                    } ).then( function( response ) {
                        var visualEditor = getVisualEditor();
                        if ( visualEditor && visualEditor.editor ) {
                            visualEditor.editor.chain().focus().insertContent( response.html || '' ).run();
                        }
                        setShowDefListModal( false );
                        setDefListTerms( [ '' ] );
                        setDefListDefinitions( [ '' ] );
                    } ).catch( function() {
                        alert( __( 'Error inserting definition list', 'djot-markup' ) );
                    } );
                    return;
                }

                // Write mode: insert Djot syntax
                insertMultiLineBlock( '', '', defListText + '\n' );
                setShowDefListModal( false );
            }

            function updateDefTerm( index, value ) {
                var newTerms = defListTerms.slice();
                newTerms[ index ] = value;
                setDefListTerms( newTerms );
            }

            function addDefTerm() {
                setDefListTerms( defListTerms.concat( [ '' ] ) );
            }

            function removeDefTerm( index ) {
                if ( defListTerms.length <= 1 ) return;
                var newTerms = defListTerms.slice();
                newTerms.splice( index, 1 );
                setDefListTerms( newTerms );
            }

            function updateDefDefinition( index, value ) {
                var newDefs = defListDefinitions.slice();
                newDefs[ index ] = value;
                setDefListDefinitions( newDefs );
            }

            function addDefDefinition() {
                setDefListDefinitions( defListDefinitions.concat( [ '' ] ) );
            }

            function removeDefDefinition( index ) {
                if ( defListDefinitions.length <= 1 ) return;
                var newDefs = defListDefinitions.slice();
                newDefs.splice( index, 1 );
                setDefListDefinitions( newDefs );
            }

            function onVideo() {
                setVideoUrl( '' );
                setVideoCaption( '' );
                setVideoWidth( '' );
                setShowVideoModal( true );
            }

            function onInsertVideo() {
                if ( ! videoUrl.trim() ) {
                    setShowVideoModal( false );
                    return;
                }

                // Build the Djot syntax: ![caption](url){video width=X}
                var attrs = 'video';
                if ( videoWidth.trim() ) {
                    attrs += ' width=' + videoWidth.trim();
                }
                var videoText = '![' + videoCaption + '](' + videoUrl.trim() + '){' + attrs + '}';

                // Visual mode: convert to HTML and insert
                if ( editorMode === 'visual' ) {
                    apiFetch( {
                        path: '/wpdjot/v1/render',
                        method: 'POST',
                        data: { content: videoText },
                    } ).then( function( response ) {
                        var visualEditor = getVisualEditor();
                        if ( visualEditor && visualEditor.editor ) {
                            visualEditor.editor.chain().focus().insertContent( response.html || '' ).run();
                        }
                        setShowVideoModal( false );
                        setVideoUrl( '' );
                        setVideoCaption( '' );
                        setVideoWidth( '' );
                    } ).catch( function() {
                        alert( __( 'Error inserting video', 'djot-markup' ) );
                    } );
                    return;
                }

                // Write mode: insert Djot syntax
                insertMultiLineBlock( '', '', videoText + '\n' );
                setShowVideoModal( false );
            }

            // Format table at cursor position
            function onFormatTable() {
                var textarea = textareaRef.current ? textareaRef.current.querySelector( 'textarea' ) : null;
                if ( ! textarea ) return;

                // Create undo boundary so this change is a separate undo step
                if ( markUndoBoundary ) {
                    markUndoBoundary();
                }

                var text = content || '';
                var cursorPos = textarea.selectionStart;

                // Find table boundaries
                var lines = text.split( '\n' );
                var lineIndex = 0;
                var charCount = 0;
                for ( var i = 0; i < lines.length; i++ ) {
                    if ( charCount + lines[ i ].length >= cursorPos ) {
                        lineIndex = i;
                        break;
                    }
                    charCount += lines[ i ].length + 1; // +1 for newline
                }

                // Find table start (go up until non-table line)
                var tableStart = lineIndex;
                while ( tableStart > 0 && lines[ tableStart - 1 ].trim().startsWith( '|' ) ) {
                    tableStart--;
                }

                // Find table end (go down until non-table line)
                var tableEnd = lineIndex;
                while ( tableEnd < lines.length - 1 && lines[ tableEnd + 1 ].trim().startsWith( '|' ) ) {
                    tableEnd++;
                }

                // Extract table lines
                var tableLines = lines.slice( tableStart, tableEnd + 1 );
                if ( tableLines.length < 2 ) return; // Need at least header + separator

                // Parse cells
                var parsedRows = tableLines.map( function( line ) {
                    // Remove leading/trailing pipes and split
                    var trimmed = line.trim();
                    if ( trimmed.startsWith( '|' ) ) trimmed = trimmed.substring( 1 );
                    if ( trimmed.endsWith( '|' ) ) trimmed = trimmed.substring( 0, trimmed.length - 1 );
                    return trimmed.split( '|' ).map( function( cell ) {
                        return cell.trim();
                    } );
                } );

                // Find max width per column
                var colWidths = [];
                parsedRows.forEach( function( row, rowIdx ) {
                    row.forEach( function( cell, colIdx ) {
                        // Skip separator row for width calculation (use dashes count)
                        var width = cell.length;
                        if ( rowIdx === 1 && /^[-:]+$/.test( cell ) ) {
                            width = 3; // minimum for separator
                        }
                        if ( ! colWidths[ colIdx ] || width > colWidths[ colIdx ] ) {
                            colWidths[ colIdx ] = Math.max( width, 3 );
                        }
                    } );
                } );

                // Rebuild table with padding
                var formattedLines = parsedRows.map( function( row, rowIdx ) {
                    var cells = row.map( function( cell, colIdx ) {
                        var width = colWidths[ colIdx ] || 3;
                        if ( rowIdx === 1 && /^[-:]+$/.test( cell ) ) {
                            // Separator row - preserve alignment markers
                            var leftAlign = cell.startsWith( ':' );
                            var rightAlign = cell.endsWith( ':' );
                            var dashes = '-'.repeat( width );
                            if ( leftAlign && rightAlign ) {
                                return ':' + '-'.repeat( width - 2 ) + ':';
                            } else if ( leftAlign ) {
                                return ':' + '-'.repeat( width - 1 );
                            } else if ( rightAlign ) {
                                return '-'.repeat( width - 1 ) + ':';
                            }
                            return dashes;
                        }
                        // Pad cell with spaces
                        return cell + ' '.repeat( width - cell.length );
                    } );
                    return '| ' + cells.join( ' | ' ) + ' |';
                } );

                // Replace table in content
                var newLines = lines.slice( 0, tableStart ).concat( formattedLines ).concat( lines.slice( tableEnd + 1 ) );
                var newText = newLines.join( '\n' );

                // Calculate new cursor position (keep it roughly in same place)
                var newCursorPos = 0;
                for ( var i = 0; i < tableStart; i++ ) {
                    newCursorPos += newLines[ i ].length + 1;
                }
                newCursorPos += formattedLines[ 0 ].length; // Put cursor at end of first table line

                setAttributes( { content: newText } );
                restoreFocus( textarea, newCursorPos );
            }

            // Convert Markdown to Djot
            function convertMarkdownToDjot( md ) {
                var result = md;

                // Protect code blocks first (store and replace with placeholders)
                var codeBlocks = [];
                result = result.replace( /```[\s\S]*?```/g, function( match ) {
                    codeBlocks.push( match );
                    return '%%CODEBLOCK' + ( codeBlocks.length - 1 ) + '%%';
                } );

                // Protect inline code
                var inlineCode = [];
                result = result.replace( /`[^`]+`/g, function( match ) {
                    inlineCode.push( match );
                    return '%%INLINECODE' + ( inlineCode.length - 1 ) + '%%';
                } );

                // Convert indented code blocks to fenced (4 spaces or 1 tab)
                result = result.replace( /^((?:(?:    |\t).+\n?)+)/gm, function( match ) {
                    var code = match.replace( /^(    |\t)/gm, '' ).trimEnd();
                    return '```\n' + code + '\n```\n';
                } );

                // Bold: **text** or __text__ → *text*
                // Use placeholder to prevent italic conversion from affecting these
                result = result.replace( /\*\*([^*]+)\*\*/g, '%%DJOTBOLD%%$1%%DJOTBOLDEND%%' );
                result = result.replace( /__([^_]+)__/g, '%%DJOTBOLD%%$1%%DJOTBOLDEND%%' );

                // Italic: *text* or _text_ → _text_ (but not inside words)
                // Only convert *text* that's not already bold
                result = result.replace( /(?<!\*)\*([^*]+)\*(?!\*)/g, '_$1_' );

                // Restore bold markers
                result = result.replace( /%%DJOTBOLD%%/g, '*' );
                result = result.replace( /%%DJOTBOLDEND%%/g, '*' );

                // Strikethrough: ~~text~~ → {~text~}
                result = result.replace( /~~([^~]+)~~/g, '{~$1~}' );

                // Highlight: ==text== → {=text=}
                result = result.replace( /==([^=]+)==/g, '{=$1=}' );

                // Headers: ensure space after # (Djot requires it)
                result = result.replace( /^(#{1,6})([^ #\n])/gm, '$1 $2' );

                // Remove trailing # from headers (Djot treats them as content)
                result = result.replace( /^(#{1,6} .+?)\s*#+\s*$/gm, '$1' );

                // Setext-style headers: convert to ATX style
                // H1: text followed by line of ===
                result = result.replace( /^(.+)\n=+$/gm, '# $1' );
                // H2: text followed by line of ---
                result = result.replace( /^(.+)\n-+$/gm, '## $1' );

                // Ensure blank line after headers (Djot requires it)
                result = result.replace( /^(#{1,6} .+)$(\n?)(?!\n)/gm, '$1\n\n' );

                // Link titles: [text](url "title") → [text](url){title="title"}
                result = result.replace( /\[([^\]]+)\]\(([^)"]+)\s+"([^"]+)"\)/g, '[$1]($2){title="$3"}' );
                result = result.replace( /\[([^\]]+)\]\(([^)']+)\s+'([^']+)'\)/g, "[$1]($2){title='$3'}" );

                // Hard line breaks: trailing two spaces → backslash
                result = result.replace( /  $/gm, '\\' );

                // Blockquotes: ensure space after > (unless followed by newline)
                result = result.replace( /^>([^ \n>])/gm, '> $1' );

                // Ensure blank line before blockquotes
                result = result.replace( /([^\n])\n(>)/gm, '$1\n\n$2' );

                // Ensure blank line before lists
                result = result.replace( /([^\n])\n([-*+] |\d+\. )/gm, '$1\n\n$2' );

                // Raw HTML: wrap in djot raw syntax
                result = result.replace( /<([a-z][a-z0-9]*)([ >])/gi, function( match, tag, after ) {
                    // Skip common safe tags that might be intentional
                    var safeTags = [ 'http', 'https' ];
                    if ( safeTags.indexOf( tag.toLowerCase() ) >= 0 ) {
                        return match;
                    }
                    return '`<' + tag + after.trimEnd() + '`{=html}';
                } );

                // Restore inline code
                inlineCode.forEach( function( code, idx ) {
                    result = result.replace( '%%INLINECODE' + idx + '%%', code );
                } );

                // Restore code blocks
                codeBlocks.forEach( function( block, idx ) {
                    result = result.replace( '%%CODEBLOCK' + idx + '%%', block );
                } );

                // Clean up excessive blank lines (more than 2 consecutive)
                result = result.replace( /\n{3,}/g, '\n\n' );

                return result;
            }

            // Open import modal
            function onImport( type ) {
                setImportType( type );
                setImportInput( '' );
                setDjotPreview( '' );
                setShowImportModal( true );
            }

            // Update preview when import input changes (using server-side converter)
            const debouncedConvert = useCallback(
                debounce( function( input, type ) {
                    if ( ! input.trim() ) {
                        setDjotPreview( '' );
                        setIsConverting( false );
                        return;
                    }

                    var endpoint = type === 'html' ? '/wpdjot/v1/convert-html' : '/wpdjot/v1/convert-markdown';

                    apiFetch( {
                        path: endpoint,
                        method: 'POST',
                        data: { content: input },
                    } )
                        .then( function( response ) {
                            setDjotPreview( response.djot || '' );
                            setIsConverting( false );
                        } )
                        .catch( function() {
                            // Fall back to client-side conversion on error (markdown only)
                            if ( type === 'markdown' ) {
                                setDjotPreview( convertMarkdownToDjot( input ) );
                            }
                            setIsConverting( false );
                        } );
                }, 300 ),
                []
            );

            function onImportInputChange( value ) {
                setImportInput( value );
                setIsConverting( true );
                debouncedConvert( value, importType );
            }

            // Insert converted djot at cursor position
            function onInsertImported() {
                if ( ! djotPreview.trim() ) {
                    setShowImportModal( false );
                    return;
                }

                // Visual mode: convert Djot to HTML and insert into editor
                if ( editorMode === 'visual' ) {
                    apiFetch( {
                        path: '/wpdjot/v1/render',
                        method: 'POST',
                        data: { content: djotPreview },
                    } ).then( function( response ) {
                        var visualEditor = getVisualEditor();
                        if ( visualEditor && visualEditor.editor ) {
                            visualEditor.editor.chain().focus().insertContent( response.html || '' ).run();
                        }
                        setShowImportModal( false );
                        setImportInput( '' );
                        setDjotPreview( '' );
                    } ).catch( function() {
                        alert( __( 'Error converting content', 'djot-markup' ) );
                    } );
                    return;
                }

                // Write mode: insert Djot text at cursor
                // Create undo boundary so this change is a separate undo step
                if ( markUndoBoundary ) {
                    markUndoBoundary();
                }

                var textarea = textareaRef.current ? textareaRef.current.querySelector( 'textarea' ) : null;
                var text = content || '';
                var start = textarea ? textarea.selectionStart : text.length;

                // Add newlines if needed
                var prefix = '';
                if ( start > 0 && text[ start - 1 ] !== '\n' ) {
                    prefix = '\n\n';
                } else if ( start > 1 && text[ start - 2 ] !== '\n' ) {
                    prefix = '\n';
                }

                var suffix = '\n\n';
                if ( start < text.length - 1 && text[ start ] === '\n' && text[ start + 1 ] === '\n' ) {
                    suffix = '';
                } else if ( start < text.length && text[ start ] === '\n' ) {
                    suffix = '\n';
                }

                var newText = text.substring( 0, start ) + prefix + djotPreview + suffix + text.substring( start );
                var newCursorPos = start + prefix.length + djotPreview.length;

                setAttributes( { content: newText } );
                setShowImportModal( false );
                setImportInput( '' );
                setDjotPreview( '' );

                if ( textarea ) {
                    restoreFocus( textarea, newCursorPos );
                }
            }

            // Indent/outdent handlers
            function indentLines() {
                var textarea = textareaRef.current ? textareaRef.current.querySelector( 'textarea' ) : null;
                if ( ! textarea ) return;

                // Create undo boundary so this change is a separate undo step
                if ( markUndoBoundary ) {
                    markUndoBoundary();
                }

                var text = content || '';
                var start = textarea.selectionStart;
                var end = textarea.selectionEnd;

                // Find line boundaries
                var lineStart = start;
                while ( lineStart > 0 && text[ lineStart - 1 ] !== '\n' ) {
                    lineStart--;
                }
                var lineEnd = end;
                while ( lineEnd < text.length && text[ lineEnd ] !== '\n' ) {
                    lineEnd++;
                }

                // Get selected lines and indent each
                var selectedText = text.substring( lineStart, lineEnd );
                var indentedText = selectedText.split( '\n' ).map( function( line ) {
                    return '    ' + line;
                } ).join( '\n' );

                var newText = text.substring( 0, lineStart ) + indentedText + text.substring( lineEnd );
                var lineCount = selectedText.split( '\n' ).length;
                var newStart = start + 4;
                var newEnd = end + ( lineCount * 4 );

                setAttributes( { content: newText } );
                requestAnimationFrame( function() {
                    textarea.focus( { preventScroll: true } );
                    textarea.setSelectionRange( newStart, newEnd );
                } );
            }

            function outdentLines() {
                var textarea = textareaRef.current ? textareaRef.current.querySelector( 'textarea' ) : null;
                if ( ! textarea ) return;

                // Create undo boundary so this change is a separate undo step
                if ( markUndoBoundary ) {
                    markUndoBoundary();
                }

                var text = content || '';
                var start = textarea.selectionStart;
                var end = textarea.selectionEnd;

                // Find line boundaries
                var lineStart = start;
                while ( lineStart > 0 && text[ lineStart - 1 ] !== '\n' ) {
                    lineStart--;
                }
                var lineEnd = end;
                while ( lineEnd < text.length && text[ lineEnd ] !== '\n' ) {
                    lineEnd++;
                }

                // Get selected lines and outdent each
                var selectedText = text.substring( lineStart, lineEnd );
                var removedTotal = 0;
                var removedFirst = 0;
                var isFirst = true;
                var outdentedText = selectedText.split( '\n' ).map( function( line ) {
                    var removed = 0;
                    // Remove up to 4 spaces or 1 tab
                    if ( line.startsWith( '    ' ) ) {
                        line = line.substring( 4 );
                        removed = 4;
                    } else if ( line.startsWith( '\t' ) ) {
                        line = line.substring( 1 );
                        removed = 1;
                    } else if ( line.startsWith( '   ' ) ) {
                        line = line.substring( 3 );
                        removed = 3;
                    } else if ( line.startsWith( '  ' ) ) {
                        line = line.substring( 2 );
                        removed = 2;
                    } else if ( line.startsWith( ' ' ) ) {
                        line = line.substring( 1 );
                        removed = 1;
                    }
                    if ( isFirst ) {
                        removedFirst = removed;
                        isFirst = false;
                    }
                    removedTotal += removed;
                    return line;
                } ).join( '\n' );

                var newText = text.substring( 0, lineStart ) + outdentedText + text.substring( lineEnd );
                var newStart = Math.max( lineStart, start - removedFirst );
                var newEnd = Math.max( newStart, end - removedTotal );

                setAttributes( { content: newText } );
                requestAnimationFrame( function() {
                    textarea.focus( { preventScroll: true } );
                    textarea.setSelectionRange( newStart, newEnd );
                } );
            }

            // Keyboard shortcut handler for textarea
            function handleTextareaKeyDown( e ) {
                // Handle Tab/Shift+Tab for indent/outdent
                if ( e.key === 'Tab' ) {
                    e.preventDefault();
                    if ( e.shiftKey ) {
                        outdentLines();
                    } else {
                        indentLines();
                    }
                    return;
                }

                const isMod = e.ctrlKey || e.metaKey;
                if ( ! isMod ) return;

                var handled = false;

                if ( e.shiftKey ) {
                    switch ( e.key.toLowerCase() ) {
                        case 'x': onStrikethrough(); handled = true; break;
                        case 'e': onCodeBlock(); handled = true; break;
                        case 'h': onHighlight(); handled = true; break;
                        case 'i': onImage(); handled = true; break;
                        case '.': onBlockquote(); handled = true; break;
                        case '8': onListUl(); handled = true; break;
                        case '7': onListOl(); handled = true; break;
                    }
                } else {
                    switch ( e.key.toLowerCase() ) {
                        case 'b': onBold(); handled = true; break;
                        case 'i': onItalic(); handled = true; break;
                        case 'e': onCode(); handled = true; break;
                        case 'k': onLink(); handled = true; break;
                        case '.': onSuperscript(); handled = true; break;
                        case ',': onSubscript(); handled = true; break;
                        case '1': onHeading( 1 ); handled = true; break;
                        case '2': onHeading( 2 ); handled = true; break;
                        case '3': onHeading( 3 ); handled = true; break;
                        case '4': onHeading( 4 ); handled = true; break;
                        case '5': onHeading( 5 ); handled = true; break;
                        case '6': onHeading( 6 ); handled = true; break;
                    }
                }

                if ( handled ) {
                    e.preventDefault();
                    e.stopPropagation();
                }
            }

            // Debounced preview fetch
            const fetchPreview = useCallback(
                debounce( function( djotContent ) {
                    if ( ! djotContent.trim() ) {
                        setPreview( '' );
                        return;
                    }

                    setIsLoading( true );
                    apiFetch( {
                        path: '/wpdjot/v1/render',
                        method: 'POST',
                        data: { content: djotContent },
                    } )
                        .then( function( response ) {
                            setPreview( response.html || '' );
                            setIsLoading( false );
                        } )
                        .catch( function() {
                            setPreview( '<p style="color:red;">Error rendering Djot</p>' );
                            setIsLoading( false );
                        } );
                }, 500 ),
                []
            );

            useEffect( function() {
                if ( editorMode === 'preview' && content ) {
                    fetchPreview( content );
                }
            }, [ content, editorMode ] );

            // Re-initialize mermaid diagrams after preview updates
            useEffect( function() {
                if ( editorMode === 'preview' && preview && typeof window.mermaid !== 'undefined' ) {
                    // Delay to ensure DOM is fully rendered and painted
                    var timer = setTimeout( function() {
                        var container = previewRef.current;
                        if ( ! container ) return;

                        // Find unprocessed mermaid elements
                        var mermaidElements = container.querySelectorAll( 'pre.mermaid:not([data-processed])' );
                        if ( mermaidElements.length === 0 ) return;

                        // Ensure mermaid is initialized
                        window.mermaid.initialize( { startOnLoad: false, theme: 'default' } );

                        // Process each mermaid element individually using render API
                        mermaidElements.forEach( function( el, index ) {
                            var id = 'mermaid-preview-' + Date.now() + '-' + index;
                            var code = el.textContent || '';
                            if ( ! code.trim() ) return;

                            window.mermaid.render( id, code ).then( function( result ) {
                                el.innerHTML = result.svg;
                                el.setAttribute( 'data-processed', 'true' );
                            } ).catch( function() {
                                // Silently ignore syntax errors
                            } );
                        } );
                    }, 300 );
                    return function() {
                        clearTimeout( timer );
                    };
                }
            }, [ preview, editorMode ] );

            // ESC key exits preview mode back to previous mode, or visual mode back to write
            useEffect( function() {
                if ( editorMode === 'write' ) return;

                function handleKeyDown( e ) {
                    if ( e.key === 'Escape' ) {
                        e.preventDefault();
                        if ( editorMode === 'preview' ) {
                            // Return to previous mode (read from ref to get current value)
                            if ( previousModeRef.current === 'visual' ) {
                                setEditorMode( 'visual' );
                                setIsVisualLoading( true );
                            } else {
                                setEditorMode( 'write' );
                            }
                        } else {
                            // From visual mode, go to write
                            setEditorMode( 'write' );
                        }
                    }
                }

                document.addEventListener( 'keydown', handleKeyDown );
                return function() {
                    document.removeEventListener( 'keydown', handleKeyDown );
                };
            }, [ editorMode ] );

            // Keyboard shortcuts for visual mode
            // Tiptap has different default shortcuts, so we add custom ones to match write mode
            useEffect( function() {
                if ( editorMode !== 'visual' ) return;

                function handleVisualKeyDown( e ) {
                    var visualEditor = getVisualEditor();
                    if ( ! visualEditor ) return;
                    if ( ! visualEditor.commands ) return;

                    var isMod = e.ctrlKey || e.metaKey;
                    if ( ! isMod ) return;

                    var handled = false;

                    if ( e.shiftKey ) {
                        switch ( e.key.toLowerCase() ) {
                            case 'x': visualEditor.commands.strikethrough(); handled = true; break;
                            case 'e': visualEditor.commands.codeBlock(); handled = true; break;
                            case 'h': visualEditor.commands.highlight(); handled = true; break;
                            case 'i': onImage(); handled = true; break;
                            case '.': visualEditor.commands.blockquote(); handled = true; break;
                            case '8': visualEditor.commands.bulletList(); handled = true; break;
                            case '7': visualEditor.commands.orderedList(); handled = true; break;
                        }
                    } else {
                        switch ( e.key.toLowerCase() ) {
                            case 'k': onLink(); handled = true; break;
                            case '.': visualEditor.commands.superscript(); handled = true; break;
                            case ',': visualEditor.commands.subscript(); handled = true; break;
                            case '1': visualEditor.commands.heading( 1 ); handled = true; break;
                            case '2': visualEditor.commands.heading( 2 ); handled = true; break;
                            case '3': visualEditor.commands.heading( 3 ); handled = true; break;
                            case '4': visualEditor.commands.heading( 4 ); handled = true; break;
                            case '5': visualEditor.commands.heading( 5 ); handled = true; break;
                            case '6': visualEditor.commands.heading( 6 ); handled = true; break;
                            // Bold, Italic, Code handled by Tiptap natively (Mod-b, Mod-i, Mod-e)
                        }
                    }

                    if ( handled ) {
                        e.preventDefault();
                        e.stopPropagation();
                    }
                }

                document.addEventListener( 'keydown', handleVisualKeyDown );
                return function() {
                    document.removeEventListener( 'keydown', handleVisualKeyDown );
                };
            }, [ editorMode ] );

            // Switch to write mode, syncing content from visual editor if needed
            function switchToWriteMode() {
                if ( editorMode === 'visual' && visualEditorInstance ) {
                    // Get Djot content from visual editor before switching
                    var djotContent = visualEditorInstance.getDjot();
                    if ( djotContent !== content ) {
                        setAttributes( { content: djotContent } );
                    }
                }
                setEditorMode( 'write' );
            }

            // Switch to visual mode
            function switchToVisualMode() {
                setEditorMode( 'visual' );
                setIsVisualLoading( true );
            }

            // Switch to preview mode
            function switchToPreviewMode() {
                // If coming from visual mode, sync content first
                if ( editorMode === 'visual' && visualEditorInstance ) {
                    var djotContent = visualEditorInstance.getDjot();
                    if ( djotContent !== content ) {
                        setAttributes( { content: djotContent } );
                    }
                }
                // Save current mode before switching to preview
                if ( editorMode !== 'preview' ) {
                    previousModeRef.current = editorMode;
                }
                setEditorMode( 'preview' );
            }

            // Exit preview mode, return to previous mode (write or visual)
            function exitPreviewMode() {
                if ( previousModeRef.current === 'visual' ) {
                    switchToVisualMode();
                } else {
                    switchToWriteMode();
                }
            }

            // Initialize visual editor when switching to visual mode
            useEffect( function() {
                if ( editorMode !== 'visual' ) return;
                if ( ! visualEditorRef.current ) return;

                // Load the visual editor module
                async function loadVisualEditor() {
                    try {
                        // First, render the content as HTML via the API
                        var htmlContent = '<p></p>';
                        if ( content ) {
                            var response = await apiFetch( {
                                path: '/wpdjot/v1/render',
                                method: 'POST',
                                data: { content: content, context: 'editor' },
                            } );
                            htmlContent = response.html || '<p></p>';
                        }

                        // Load the visual editor module (cache-bust with version)
                        var baseUrl = window.wpdjotBlockData ? window.wpdjotBlockData.assetsUrl : '/wp-content/plugins/djot-markup/assets/';
                        var cacheBust = '?v=' + ( window.wpdjotBlockData?.version || Date.now() );
                        var module = await import( baseUrl + 'js/tiptap/visual-editor.js' + cacheBust );

                        // Initialize the editor
                        var instance = await module.initVisualEditor(
                            visualEditorRef.current,
                            htmlContent,
                            function( djotContent ) {
                                // Store pending content update (debounced sync)
                                pendingDjotContent.current = djotContent;
                            }
                        );

                        // Check for round-trip data loss before proceeding
                        if ( content ) {
                            var roundTrippedContent = instance.getDjot();

                            // Normalize both for comparison (ignore whitespace differences)
                            function normalizeForComparison( djot ) {
                                if ( ! djot ) return '';
                                var normalized = djot
                                    .trim()
                                    .replace( /\r\n/g, '\n' )
                                    .replace( /[ \t]+$/gm, '' )      // trailing whitespace
                                    .replace( /\n{3,}/g, '\n\n' );   // multiple blank lines
                                normalized = normalizeFenceInfoSpacing( normalized );
                                normalized = normalizeTableSeparators( normalized );
                                normalized = normalizeParagraphWrapping( normalized );
                                return normalized;
                            }

                            function normalizeFenceInfoSpacing( djot ) {
                                return djot.replace( /^(`{3,}|~{3,})[ \t]+([^`~\s].*)$/gm, '$1$2' );
                            }

                            function normalizeTableSeparators( djot ) {
                                return djot.replace( /^\|(?:[ \t]*:?-{3,}:?[ \t]*\|)+[ \t]*$/gm, function( line ) {
                                    var cells = line.split( '|' ).length - 2;
                                    return '| ' + Array( cells ).fill( '---' ).join( ' | ' ) + ' |';
                                } );
                            }

                            function normalizeParagraphWrapping( djot ) {
                                var lines = djot.split( '\n' );
                                var result = [];
                                var paragraph = [];
                                var inFence = false;
                                var fenceMarker = null;
                                var inFrontmatter = false;

                                function flushParagraph() {
                                    if ( paragraph.length ) {
                                        result.push( paragraph.join( ' ' ) );
                                        paragraph = [];
                                    }
                                }

                                function isPlainParagraphLine( line ) {
                                    if ( ! line.trim() ) return false;
                                    if ( /^(#{1,6}\s|>|-{3,}\s*$|={3,}\s*$|:{3,}|`{3,}|~{3,})/.test( line ) ) return false;
                                    if ( /^(\s*)([-+*]|\d+[.)])\s+/.test( line ) ) return false;
                                    if ( /^\s*\|.*\|\s*$/.test( line ) ) return false;
                                    if ( /^\s*\[[^\]]+\]:/.test( line ) ) return false;
                                    if ( /^\s*\{[.#%]/.test( line ) ) return false;
                                    return true;
                                }

                                function isFenceOpener( line ) {
                                    return line.match( /^\s*(`{3,}|~{3,})/ );
                                }

                                function isFenceCloser( line, marker ) {
                                    if ( ! marker ) return false;
                                    var pattern = marker.charAt( 0 ) === '`' ? '`' : '~';
                                    return new RegExp( '^\\s*' + pattern + '{' + marker.length + ',}\\s*$' ).test( line );
                                }

                                lines.forEach( function( line, index ) {
                                    if ( index === 0 && /^---\w*\s*$/.test( line ) ) {
                                        flushParagraph();
                                        inFrontmatter = true;
                                        result.push( line );
                                        return;
                                    }

                                    if ( inFrontmatter ) {
                                        result.push( line );
                                        if ( /^---\s*$/.test( line ) ) {
                                            inFrontmatter = false;
                                        }
                                        return;
                                    }

                                    if ( inFence ) {
                                        result.push( line );
                                        if ( isFenceCloser( line, fenceMarker ) ) {
                                            inFence = false;
                                            fenceMarker = null;
                                        }
                                        return;
                                    }

                                    var fenceMatch = isFenceOpener( line );
                                    if ( fenceMatch ) {
                                        flushParagraph();
                                        inFence = true;
                                        fenceMarker = fenceMatch[1];
                                        result.push( line );
                                        return;
                                    }

                                    if ( isPlainParagraphLine( line ) ) {
                                        paragraph.push( line );
                                        return;
                                    }

                                    flushParagraph();
                                    result.push( line );
                                } );

                                flushParagraph();
                                return result.join( '\n' );
                            }

                            var originalNormalized = normalizeForComparison( content );
                            var roundTrippedNormalized = normalizeForComparison( roundTrippedContent );

                            if ( originalNormalized !== roundTrippedNormalized ) {
                                // Compute diff and show modal
                                var diff = computeLineDiff( originalNormalized, roundTrippedNormalized );
                                pendingVisualEditorInstance.current = instance;
                                setDiffData( diff );
                                setShowDiffModal( true );
                                // Don't set loading to false yet - modal will handle it
                                return;
                            }
                        }

                        setVisualEditorInstance( instance );
                        setIsVisualLoading( false );
                    } catch ( error ) {
                        console.error( 'Failed to load visual editor:', error );
                        setIsVisualLoading( false );
                        setEditorMode( 'write' ); // Fall back to write mode
                    }
                }

                loadVisualEditor();

                return function() {
                    // Cleanup visual editor when leaving visual mode
                    if ( visualEditorInstance ) {
                        visualEditorInstance.destroy();
                        setVisualEditorInstance( null );
                    }
                };
            }, [ editorMode ] );

            function onChangeContent( newContent ) {
                isInternalChange.current = true;
                setAttributes( { content: newContent } );
            }

            // Scroll preview to match cursor position in editor
            function syncPreviewScroll() {
                var textarea = textareaRef.current ? textareaRef.current.querySelector( 'textarea' ) : null;
                var previewEl = previewRef.current;
                if ( ! textarea || ! previewEl ) return;

                var text = content || '';
                var cursorPos = textarea.selectionStart;

                // Calculate percentage through the document based on cursor position
                var percentage = text.length > 0 ? cursorPos / text.length : 0;

                // Apply to preview scroll with a small delay to ensure preview is rendered
                setTimeout( function() {
                    var scrollHeight = previewEl.scrollHeight - previewEl.clientHeight;
                    if ( scrollHeight > 0 ) {
                        previewEl.scrollTop = scrollHeight * percentage;
                    }
                }, 100 );
            }

            // Sync scroll when entering preview mode
            useEffect( function() {
                if ( editorMode === 'preview' && preview && ! isLoading ) {
                    syncPreviewScroll();
                }
            }, [ editorMode, preview, isLoading ] );

            // Heading dropdown controls
            const headingControls = [
                { title: __( 'Heading 1', 'djot-markup' ), onClick: function() { onHeading( 1 ); } },
                { title: __( 'Heading 2', 'djot-markup' ), onClick: function() { onHeading( 2 ); } },
                { title: __( 'Heading 3', 'djot-markup' ), onClick: function() { onHeading( 3 ); } },
                { title: __( 'Heading 4', 'djot-markup' ), onClick: function() { onHeading( 4 ); } },
                { title: __( 'Heading 5', 'djot-markup' ), onClick: function() { onHeading( 5 ); } },
                { title: __( 'Heading 6', 'djot-markup' ), onClick: function() { onHeading( 6 ); } },
            ];

            return wp.element.createElement(
                'div',
                blockProps,
                // Block Controls (Toolbar) - only show for write/visual modes, not preview
                editorMode !== 'preview' && wp.element.createElement(
                    BlockControls,
                    null,
                    // Inline formatting group
                    wp.element.createElement(
                        ToolbarGroup,
                        null,
                        wp.element.createElement( ToolbarButton, {
                            icon: icons.bold,
                            label: __( 'Bold (*text*)', 'djot-markup' ),
                            onClick: onBold,
                        } ),
                        wp.element.createElement( ToolbarButton, {
                            icon: icons.italic,
                            label: __( 'Italic (_text_)', 'djot-markup' ),
                            onClick: onItalic,
                        } ),
                        wp.element.createElement( ToolbarButton, {
                            icon: icons.code,
                            label: __( 'Inline Code (`code`)', 'djot-markup' ),
                            onClick: onCode,
                        } ),
                        wp.element.createElement( ToolbarButton, {
                            icon: icons.strikethrough,
                            label: __( 'Strikethrough ({~text~})', 'djot-markup' ),
                            onClick: onStrikethrough,
                        } )
                    ),
                    // Headings dropdown
                    wp.element.createElement(
                        ToolbarGroup,
                        null,
                        wp.element.createElement( ToolbarDropdownMenu, {
                            icon: icons.heading,
                            label: __( 'Headings', 'djot-markup' ),
                            controls: headingControls,
                        } )
                    ),
                    // Links and media group
                    wp.element.createElement(
                        ToolbarGroup,
                        null,
                        wp.element.createElement( ToolbarButton, {
                            icon: icons.link,
                            label: __( 'Link ([text](url))', 'djot-markup' ),
                            onClick: onLink,
                        } ),
                        wp.element.createElement( ToolbarButton, {
                            icon: icons.image,
                            label: __( 'Image (![alt](url))', 'djot-markup' ),
                            onClick: onImage,
                        } )
                    ),
                    // Block elements group
                    wp.element.createElement(
                        ToolbarGroup,
                        null,
                        wp.element.createElement( ToolbarButton, {
                            icon: icons.quote,
                            label: __( 'Blockquote (> quote)', 'djot-markup' ),
                            onClick: onBlockquote,
                        } ),
                        wp.element.createElement( ToolbarButton, {
                            icon: icons.listUl,
                            label: __( 'Unordered List (- item)', 'djot-markup' ),
                            onClick: onListUl,
                        } ),
                        wp.element.createElement( ToolbarButton, {
                            icon: icons.listOl,
                            label: __( 'Ordered List (1. item)', 'djot-markup' ),
                            onClick: onListOl,
                        } ),
                        wp.element.createElement( ToolbarButton, {
                            icon: icons.codeBlock,
                            label: __( 'Code Block (```)', 'djot-markup' ),
                            onClick: onCodeBlock,
                        } )
                    ),
                    // Djot-specific formatting
                    wp.element.createElement(
                        ToolbarGroup,
                        null,
                        wp.element.createElement( ToolbarButton, {
                            icon: icons.superscript,
                            label: __( 'Superscript (^text^)', 'djot-markup' ),
                            onClick: onSuperscript,
                        } ),
                        wp.element.createElement( ToolbarButton, {
                            icon: icons.subscript,
                            label: __( 'Subscript (~text~)', 'djot-markup' ),
                            onClick: onSubscript,
                        } ),
                        wp.element.createElement( ToolbarButton, {
                            icon: icons.highlight,
                            label: __( 'Highlight ({=text=})', 'djot-markup' ),
                            onClick: onHighlight,
                        } )
                    ),
                    // Insert/Delete/More
                    wp.element.createElement(
                        ToolbarGroup,
                        null,
                        wp.element.createElement( ToolbarButton, {
                            icon: icons.insert,
                            label: __( 'Insert ({+text+})', 'djot-markup' ),
                            onClick: onInsert,
                        } ),
                        wp.element.createElement( ToolbarButton, {
                            icon: icons.delete,
                            label: __( 'Delete ({-text-})', 'djot-markup' ),
                            onClick: onDelete,
                        } ),
                        wp.element.createElement( ToolbarButton, {
                            icon: icons.horizontalRule,
                            label: __( 'Horizontal Rule (---)', 'djot-markup' ),
                            onClick: onHorizontalRule,
                        } )
                    ),
                    // Advanced group
                    wp.element.createElement(
                        ToolbarGroup,
                        null,
                        wp.element.createElement( ToolbarButton, {
                            icon: icons.table,
                            label: __( 'Table', 'djot-markup' ),
                            onClick: onTable,
                        } ),
                        cursorInTable && wp.element.createElement( ToolbarButton, {
                            icon: icons.formatTable,
                            label: __( 'Format Table', 'djot-markup' ),
                            onClick: onFormatTable,
                        } ),
                        wp.element.createElement( ToolbarButton, {
                            icon: icons.div,
                            label: __( 'Div Block (::: class)', 'djot-markup' ),
                            onClick: onDiv,
                        } ),
                        wp.element.createElement( ToolbarButton, {
                            icon: icons.span,
                            label: __( 'Span with Class ([text]{.class})', 'djot-markup' ),
                            onClick: onSpan,
                        } ),
                        wp.element.createElement( ToolbarButton, {
                            icon: icons.footnote,
                            label: __( 'Footnote ([^note])', 'djot-markup' ),
                            onClick: onFootnote,
                        } )
                    )
                ),
                // Inspector Controls (Sidebar)
                wp.element.createElement(
                    InspectorControls,
                    null,
                    wp.element.createElement(
                        PanelBody,
                        { title: __( 'Djot Settings', 'djot-markup' ) },
                        wp.element.createElement( 'div', { style: { marginBottom: '16px' } },
                            wp.element.createElement( 'label', { style: { display: 'block', marginBottom: '8px', fontWeight: 500 } },
                                __( 'Editor Mode', 'djot-markup' )
                            ),
                            wp.element.createElement( 'div', { style: { display: 'flex', gap: '8px' } },
                                wp.element.createElement( Button, {
                                    variant: editorMode === 'write' ? 'primary' : 'secondary',
                                    onClick: switchToWriteMode,
                                    style: { flex: 1 },
                                }, __( 'Write', 'djot-markup' ) ),
                                wp.element.createElement( Button, {
                                    variant: editorMode === 'visual' ? 'primary' : 'secondary',
                                    onClick: switchToVisualMode,
                                    style: { flex: 1 },
                                }, __( 'Visual', 'djot-markup' ) ),
                                wp.element.createElement( Button, {
                                    variant: editorMode === 'preview' ? 'primary' : 'secondary',
                                    onClick: switchToPreviewMode,
                                    style: { flex: 1 },
                                }, __( 'Preview', 'djot-markup' ) )
                            )
                        )
                    ),
                    wp.element.createElement(
                        PanelBody,
                        { title: __( 'Syntax Help', 'djot-markup' ), initialOpen: false },
                        wp.element.createElement( 'div', { className: 'wpdjot-syntax-help' },
                            wp.element.createElement( 'p', { style: { marginTop: 0, marginBottom: '2px' } }, wp.element.createElement( 'strong', null, 'Inline:' ) ),
                            wp.element.createElement( 'div', { style: { marginBottom: '12px' } },
                                wp.element.createElement( 'code', null, '*bold*' ), ' ',
                                wp.element.createElement( 'code', null, '_italic_' ), ' ',
                                wp.element.createElement( 'code', null, '`code`' )
                            ),
                            wp.element.createElement( 'p', { style: { marginTop: 0, marginBottom: '2px' } }, wp.element.createElement( 'strong', null, 'Links/Images:' ) ),
                            wp.element.createElement( 'div', { style: { marginBottom: '12px' } },
                                wp.element.createElement( 'code', null, '[text](url)' ), wp.element.createElement( 'br' ),
                                wp.element.createElement( 'code', null, '![alt](src)' )
                            ),
                            wp.element.createElement( 'p', { style: { marginTop: 0, marginBottom: '2px' } }, wp.element.createElement( 'strong', null, 'Djot-specific:' ) ),
                            wp.element.createElement( 'div', { style: { marginBottom: '12px' } },
                                wp.element.createElement( 'code', null, '^super^' ), ' ',
                                wp.element.createElement( 'code', null, '~sub~' ), ' ',
                                wp.element.createElement( 'code', null, '{=highlight=}' )
                            ),
                            wp.element.createElement( 'p', { style: { marginTop: 0, marginBottom: '2px' } }, wp.element.createElement( 'strong', null, 'Semantic:' ) ),
                            wp.element.createElement( 'div', { style: { marginBottom: '12px' } },
                                wp.element.createElement( 'code', null, '[CSS]{abbr="title"}' ), wp.element.createElement( 'br' ),
                                wp.element.createElement( 'code', null, '[Ctrl+C]{kbd}' ), wp.element.createElement( 'br' ),
                                wp.element.createElement( 'code', null, '[term]{dfn}' )
                            ),
                            wp.element.createElement( 'p', { style: { marginTop: 0, marginBottom: 0 } },
                                wp.element.createElement( 'a', { href: 'https://djot.net/', target: '_blank' }, __( 'Full Djot Documentation →', 'djot-markup' ) )
                            )
                        )
                    ),
                    wp.element.createElement(
                        PanelBody,
                        { title: __( 'Keyboard Shortcuts', 'djot-markup' ), initialOpen: false },
                        wp.element.createElement( 'div', { className: 'wpdjot-shortcuts-help', style: { fontSize: '12px' } },
                            wp.element.createElement( 'p', { style: { marginBottom: '8px' } },
                                wp.element.createElement( 'strong', null, 'Formatting:' )
                            ),
                            wp.element.createElement( 'div', null, wp.element.createElement( 'kbd', null, 'Ctrl+B' ), ' Bold' ),
                            wp.element.createElement( 'div', null, wp.element.createElement( 'kbd', null, 'Ctrl+I' ), ' Italic' ),
                            wp.element.createElement( 'div', null, wp.element.createElement( 'kbd', null, 'Ctrl+E' ), ' Inline code' ),
                            wp.element.createElement( 'div', null, wp.element.createElement( 'kbd', null, 'Ctrl+K' ), ' Link' ),
                            wp.element.createElement( 'p', { style: { marginTop: '12px', marginBottom: '8px' } },
                                wp.element.createElement( 'strong', null, 'Headings:' )
                            ),
                            wp.element.createElement( 'div', null, wp.element.createElement( 'kbd', null, 'Ctrl+[1-6]' ), ' H1-H6' ),
                            wp.element.createElement( 'p', { style: { marginTop: '12px', marginBottom: '8px' } },
                                wp.element.createElement( 'strong', null, 'Blocks (Ctrl+Shift):' )
                            ),
                            wp.element.createElement( 'div', null, wp.element.createElement( 'kbd', null, 'Ctrl+Shift+E' ), ' Code block' ),
                            wp.element.createElement( 'div', null, wp.element.createElement( 'kbd', null, 'Ctrl+Shift+.' ), ' Blockquote' ),
                            wp.element.createElement( 'div', null, wp.element.createElement( 'kbd', null, 'Ctrl+Shift+8' ), ' Bullet list' ),
                            wp.element.createElement( 'div', null, wp.element.createElement( 'kbd', null, 'Ctrl+Shift+7' ), ' Numbered list' ),
                            wp.element.createElement( 'p', { style: { marginTop: '12px', marginBottom: '8px' } },
                                wp.element.createElement( 'strong', null, 'Other:' )
                            ),
                            wp.element.createElement( 'div', null, wp.element.createElement( 'kbd', null, 'Ctrl+.' ), ' Superscript' ),
                            wp.element.createElement( 'div', null, wp.element.createElement( 'kbd', null, 'Ctrl+,' ), ' Subscript' ),
                            wp.element.createElement( 'div', null, wp.element.createElement( 'kbd', null, 'Ctrl+Shift+H' ), ' Highlight' ),
                            wp.element.createElement( 'div', null, wp.element.createElement( 'kbd', null, 'Ctrl+Shift+X' ), ' Strikethrough' ),
                            wp.element.createElement( 'div', null, wp.element.createElement( 'kbd', null, 'Tab' ), ' Indent' ),
                            wp.element.createElement( 'div', null, wp.element.createElement( 'kbd', null, 'Shift+Tab' ), ' Outdent' ),
                            wp.element.createElement( 'div', null, wp.element.createElement( 'kbd', null, 'ESC' ), ' Exit preview' )
                        )
                    ),
                    wp.element.createElement(
                        PanelBody,
                        { title: __( 'Tools', 'djot-markup' ), initialOpen: false },
                        wp.element.createElement( 'div', { style: { display: 'flex', flexDirection: 'column', gap: '8px' } },
                            wp.element.createElement( Button, {
                                variant: 'secondary',
                                icon: icons.taskList,
                                onClick: onTaskList,
                                style: { width: '100%', justifyContent: 'center' },
                            }, __( 'Insert Task List', 'djot-markup' ) ),
                            wp.element.createElement( Button, {
                                variant: 'secondary',
                                onClick: onDefList,
                                style: { width: '100%', justifyContent: 'center' },
                            }, __( 'Insert Definition List', 'djot-markup' ) ),
                            wp.element.createElement( Button, {
                                variant: 'secondary',
                                onClick: function() { onImport( 'markdown' ); },
                                style: { width: '100%', justifyContent: 'center' },
                            }, __( 'Import Markdown', 'djot-markup' ) ),
                            wp.element.createElement( Button, {
                                variant: 'secondary',
                                onClick: function() { onImport( 'html' ); },
                                style: { width: '100%', justifyContent: 'center' },
                            }, __( 'Import HTML', 'djot-markup' ) ),
                            wp.element.createElement( Button, {
                                variant: 'secondary',
                                icon: icons.video,
                                onClick: onVideo,
                                style: { width: '100%', justifyContent: 'center' },
                            }, __( 'Insert Video', 'djot-markup' ) )
                        )
                    )
                ),
                // Link Modal
                showLinkModal && wp.element.createElement(
                    Modal,
                    {
                        title: __( 'Insert Link', 'djot-markup' ),
                        onRequestClose: function() { setShowLinkModal( false ); },
                    },
                    wp.element.createElement( TextControl, {
                        label: __( 'URL', 'djot-markup' ),
                        value: linkUrl,
                        onChange: setLinkUrl,
                        type: 'url',
                    } ),
                    wp.element.createElement( TextControl, {
                        label: __( 'Link Text (optional, leave empty for autolink)', 'djot-markup' ),
                        value: linkText,
                        onChange: setLinkText,
                    } ),
                    wp.element.createElement(
                        'div',
                        { style: { marginTop: '16px' } },
                        wp.element.createElement( Button, {
                            variant: 'primary',
                            onClick: onInsertLink,
                        }, __( 'Insert Link', 'djot-markup' ) ),
                        wp.element.createElement( Button, {
                            variant: 'secondary',
                            onClick: function() { setShowLinkModal( false ); },
                            style: { marginLeft: '8px' },
                        }, __( 'Cancel', 'djot-markup' ) )
                    )
                ),
                // Image Modal
                showImageModal && wp.element.createElement(
                    Modal,
                    {
                        title: __( 'Insert Image', 'djot-markup' ),
                        onRequestClose: function() { setShowImageModal( false ); },
                    },
                    wp.element.createElement( TextControl, {
                        label: __( 'Image URL', 'djot-markup' ),
                        value: imageUrl,
                        onChange: setImageUrl,
                        type: 'url',
                    } ),
                    wp.element.createElement( TextControl, {
                        label: __( 'Alt Text (optional)', 'djot-markup' ),
                        value: imageAlt,
                        onChange: setImageAlt,
                    } ),
                    wp.element.createElement(
                        'div',
                        { style: { marginTop: '16px' } },
                        wp.element.createElement( Button, {
                            variant: 'primary',
                            onClick: onInsertImage,
                        }, __( 'Insert Image', 'djot-markup' ) ),
                        wp.element.createElement( Button, {
                            variant: 'secondary',
                            onClick: function() { setShowImageModal( false ); },
                            style: { marginLeft: '8px' },
                        }, __( 'Cancel', 'djot-markup' ) )
                    )
                ),
                // Table Modal
                showTableModal && wp.element.createElement(
                    Modal,
                    {
                        title: __( 'Insert Table', 'djot-markup' ),
                        onRequestClose: function() { setShowTableModal( false ); },
                    },
                    wp.element.createElement( RangeControl, {
                        label: __( 'Columns', 'djot-markup' ),
                        value: tableCols,
                        onChange: setTableCols,
                        min: 1,
                        max: 10,
                    } ),
                    wp.element.createElement( RangeControl, {
                        label: __( 'Rows (excluding header)', 'djot-markup' ),
                        value: tableRows,
                        onChange: setTableRows,
                        min: 1,
                        max: 20,
                    } ),
                    wp.element.createElement(
                        'div',
                        { style: { marginTop: '16px' } },
                        wp.element.createElement( Button, {
                            variant: 'primary',
                            onClick: onInsertTable,
                        }, __( 'Insert Table', 'djot-markup' ) ),
                        wp.element.createElement( Button, {
                            variant: 'secondary',
                            onClick: function() { setShowTableModal( false ); },
                            style: { marginLeft: '8px' },
                        }, __( 'Cancel', 'djot-markup' ) )
                    )
                ),
                // Import Modal (Markdown/HTML)
                showImportModal && wp.element.createElement(
                    Modal,
                    {
                        title: importType === 'html' ? __( 'Import HTML', 'djot-markup' ) : __( 'Import Markdown', 'djot-markup' ),
                        onRequestClose: function() { setShowImportModal( false ); },
                        style: { width: '600px', maxWidth: '90vw' },
                    },
                    wp.element.createElement( 'div', { style: { display: 'flex', gap: '16px' } },
                        wp.element.createElement( 'div', { style: { flex: 1 } },
                            wp.element.createElement( 'label', { style: { display: 'block', marginBottom: '8px', fontWeight: 600 } },
                                importType === 'html' ? __( 'HTML Input', 'djot-markup' ) : __( 'Markdown Input', 'djot-markup' )
                            ),
                            wp.element.createElement( 'textarea', {
                                value: importInput,
                                onChange: function( e ) { onImportInputChange( e.target.value ); },
                                style: { width: '100%', height: '200px', fontFamily: 'monospace', fontSize: '13px', padding: '8px' },
                                placeholder: importType === 'html' ? __( 'Paste your HTML here...', 'djot-markup' ) : __( 'Paste your Markdown here...', 'djot-markup' ),
                            } )
                        ),
                        wp.element.createElement( 'div', { style: { flex: 1, position: 'relative' } },
                            wp.element.createElement( 'label', { style: { display: 'block', marginBottom: '8px', fontWeight: 600 } },
                                __( 'Djot Preview', 'djot-markup' ),
                                isConverting && wp.element.createElement( 'span', { style: { marginLeft: '8px', fontSize: '11px', color: '#999' } }, __( 'Converting...', 'djot-markup' ) )
                            ),
                            wp.element.createElement( 'textarea', {
                                value: djotPreview,
                                readOnly: true,
                                style: { width: '100%', height: '200px', fontFamily: 'monospace', fontSize: '13px', padding: '8px', background: '#f9f9f9' },
                                placeholder: __( 'Converted Djot will appear here...', 'djot-markup' ),
                            } )
                        )
                    ),
                    importType === 'markdown' && wp.element.createElement( 'p', { style: { marginTop: '12px', fontSize: '12px', color: '#666' } },
                        // translators: Translate only the leading "Converts:" label. The Markdown/Djot syntax tokens (e.g. **bold**, _italic_, {=highlight=}) and the → arrows must stay unchanged.
                        __( 'Converts: **bold** → *bold*, *italic* → _italic_, ~~strike~~ → {~strike~}, ==highlight== → {=highlight=}', 'djot-markup' )
                    ),
                    wp.element.createElement(
                        'div',
                        { style: { marginTop: '16px' } },
                        wp.element.createElement( Button, {
                            variant: 'primary',
                            onClick: onInsertImported,
                            disabled: isConverting || ! djotPreview.trim(),
                        }, __( 'Insert', 'djot-markup' ) ),
                        wp.element.createElement( Button, {
                            variant: 'secondary',
                            onClick: function() { setShowImportModal( false ); },
                            style: { marginLeft: '8px' },
                        }, __( 'Cancel', 'djot-markup' ) )
                    )
                ),
                // Task List Modal
                showTaskListModal && wp.element.createElement(
                    Modal,
                    {
                        title: __( 'Insert Task List', 'djot-markup' ),
                        onRequestClose: function() { setShowTaskListModal( false ); },
                        style: { width: '400px', maxWidth: '90vw' },
                    },
                    wp.element.createElement( 'div', { style: { marginBottom: '16px' } },
                        taskListItems.map( function( item, index ) {
                            return wp.element.createElement( 'div', {
                                key: index,
                                style: { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' },
                            },
                                wp.element.createElement( 'input', {
                                    type: 'checkbox',
                                    checked: item.checked,
                                    onChange: function( e ) { updateTaskItem( index, 'checked', e.target.checked ); },
                                    style: { width: '18px', height: '18px', cursor: 'pointer' },
                                } ),
                                wp.element.createElement( 'input', {
                                    type: 'text',
                                    value: item.text,
                                    onChange: function( e ) { updateTaskItem( index, 'text', e.target.value ); },
                                    placeholder: __( 'Task item...', 'djot-markup' ),
                                    style: { flex: 1, padding: '6px 8px', border: '1px solid #ccc', borderRadius: '4px' },
                                    onKeyDown: function( e ) {
                                        if ( e.key === 'Enter' ) {
                                            e.preventDefault();
                                            addTaskItem();
                                        }
                                    },
                                } ),
                                taskListItems.length > 1 && wp.element.createElement( Button, {
                                    variant: 'tertiary',
                                    isDestructive: true,
                                    onClick: function() { removeTaskItem( index ); },
                                    style: { padding: '4px' },
                                }, '✕' )
                            );
                        } )
                    ),
                    wp.element.createElement( Button, {
                        variant: 'secondary',
                        onClick: addTaskItem,
                        style: { marginBottom: '16px' },
                    }, __( '+ Add Item', 'djot-markup' ) ),
                    wp.element.createElement(
                        'div',
                        { style: { marginTop: '16px', borderTop: '1px solid #ddd', paddingTop: '16px' } },
                        wp.element.createElement( Button, {
                            variant: 'primary',
                            onClick: onInsertTaskList,
                        }, __( 'Insert Task List', 'djot-markup' ) ),
                        wp.element.createElement( Button, {
                            variant: 'secondary',
                            onClick: function() { setShowTaskListModal( false ); },
                            style: { marginLeft: '8px' },
                        }, __( 'Cancel', 'djot-markup' ) )
                    )
                ),
                // Definition List Modal
                showDefListModal && wp.element.createElement(
                    Modal,
                    {
                        title: __( 'Insert Definition List', 'djot-markup' ),
                        onRequestClose: function() { setShowDefListModal( false ); },
                        style: { width: '500px', maxWidth: '90vw' },
                    },
                    // Terms section
                    wp.element.createElement( 'div', { style: { marginBottom: '16px' } },
                        wp.element.createElement( 'label', { style: { display: 'block', fontWeight: 600, marginBottom: '8px' } },
                            __( 'Terms (dt):', 'djot-markup' )
                        ),
                        defListTerms.map( function( term, index ) {
                            return wp.element.createElement( 'div', {
                                key: index,
                                style: { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' },
                            },
                                wp.element.createElement( 'input', {
                                    type: 'text',
                                    value: term,
                                    onChange: function( e ) { updateDefTerm( index, e.target.value ); },
                                    placeholder: __( 'Term...', 'djot-markup' ),
                                    style: { flex: 1, padding: '6px 8px', border: '1px solid #ccc', borderRadius: '4px' },
                                    onKeyDown: function( e ) {
                                        if ( e.key === 'Enter' ) {
                                            e.preventDefault();
                                            addDefTerm();
                                        }
                                    },
                                } ),
                                defListTerms.length > 1 && wp.element.createElement( Button, {
                                    variant: 'tertiary',
                                    isDestructive: true,
                                    onClick: function() { removeDefTerm( index ); },
                                    style: { padding: '4px' },
                                }, '✕' )
                            );
                        } ),
                        wp.element.createElement( Button, {
                            variant: 'secondary',
                            onClick: addDefTerm,
                            style: { marginTop: '4px' },
                            isSmall: true,
                        }, __( '+ Add Term', 'djot-markup' ) )
                    ),
                    // Definitions section
                    wp.element.createElement( 'div', { style: { marginBottom: '16px' } },
                        wp.element.createElement( 'label', { style: { display: 'block', fontWeight: 600, marginBottom: '8px' } },
                            __( 'Definitions (dd):', 'djot-markup' )
                        ),
                        defListDefinitions.map( function( definition, index ) {
                            return wp.element.createElement( 'div', {
                                key: index,
                                style: { display: 'flex', alignItems: 'flex-start', gap: '8px', marginBottom: '8px' },
                            },
                                wp.element.createElement( 'textarea', {
                                    value: definition,
                                    onChange: function( e ) { updateDefDefinition( index, e.target.value ); },
                                    placeholder: __( 'Definition... (use blank lines for multiple paragraphs)', 'djot-markup' ),
                                    style: { flex: 1, padding: '8px', border: '1px solid #ccc', borderRadius: '4px', minHeight: '60px', resize: 'vertical', boxSizing: 'border-box' },
                                } ),
                                defListDefinitions.length > 1 && wp.element.createElement( Button, {
                                    variant: 'tertiary',
                                    isDestructive: true,
                                    onClick: function() { removeDefDefinition( index ); },
                                    style: { padding: '4px', marginTop: '4px' },
                                }, '✕' )
                            );
                        } ),
                        wp.element.createElement( Button, {
                            variant: 'secondary',
                            onClick: addDefDefinition,
                            style: { marginTop: '4px' },
                            isSmall: true,
                        }, __( '+ Add Definition', 'djot-markup' ) )
                    ),
                    wp.element.createElement( 'p', { style: { fontSize: '12px', color: '#666', marginTop: '0' } },
                        __( 'Tip: Press Enter in term field to add another term', 'djot-markup' )
                    ),
                    wp.element.createElement(
                        'div',
                        { style: { marginTop: '16px', borderTop: '1px solid #ddd', paddingTop: '16px' } },
                        wp.element.createElement( Button, {
                            variant: 'primary',
                            onClick: onInsertDefList,
                        }, __( 'Insert', 'djot-markup' ) ),
                        wp.element.createElement( Button, {
                            variant: 'secondary',
                            onClick: function() { setShowDefListModal( false ); },
                            style: { marginLeft: '8px' },
                        }, __( 'Cancel', 'djot-markup' ) )
                    )
                ),
                // Video Modal
                showVideoModal && wp.element.createElement(
                    Modal,
                    {
                        title: __( 'Insert Video', 'djot-markup' ),
                        onRequestClose: function() { setShowVideoModal( false ); },
                    },
                    wp.element.createElement( TextControl, {
                        label: __( 'Video URL', 'djot-markup' ),
                        value: videoUrl,
                        onChange: setVideoUrl,
                        type: 'url',
                        placeholder: 'https://www.youtube.com/watch?v=...',
                        help: __( 'YouTube, Vimeo, TikTok, Twitter, and other oEmbed-supported URLs', 'djot-markup' ),
                    } ),
                    wp.element.createElement( TextControl, {
                        label: __( 'Caption (optional)', 'djot-markup' ),
                        value: videoCaption,
                        onChange: setVideoCaption,
                    } ),
                    wp.element.createElement( TextControl, {
                        label: __( 'Width (optional)', 'djot-markup' ),
                        value: videoWidth,
                        onChange: setVideoWidth,
                        type: 'number',
                        placeholder: '650',
                        help: __( 'Width in pixels', 'djot-markup' ),
                    } ),
                    wp.element.createElement(
                        'div',
                        { style: { marginTop: '16px' } },
                        wp.element.createElement( Button, {
                            variant: 'primary',
                            onClick: onInsertVideo,
                        }, __( 'Insert Video', 'djot-markup' ) ),
                        wp.element.createElement( Button, {
                            variant: 'secondary',
                            onClick: function() { setShowVideoModal( false ); },
                            style: { marginLeft: '8px' },
                        }, __( 'Cancel', 'djot-markup' ) )
                    )
                ),
                // Div container modal
                showDivModal && wp.element.createElement(
                    Modal,
                    {
                        title: __( 'Insert Container', 'djot-markup' ),
                        onRequestClose: function() { setShowDivModal( false ); },
                    },
                    wp.element.createElement( 'div', { className: 'wpdjot-container-options', style: { marginBottom: '16px' } },
                        wp.element.createElement( 'p', { style: { marginBottom: '8px', fontWeight: '600' } }, __( 'Container Type', 'djot-markup' ) ),
                        [ 'note', 'tip', 'warning', 'danger', 'info' ].map( function( type ) {
                            var labels = { note: 'Note (blue)', tip: 'Tip (green)', warning: 'Warning (yellow)', danger: 'Danger (red)', info: 'Info (gray)' };
                            return wp.element.createElement( 'label', { key: type, style: { display: 'block', marginBottom: '6px', cursor: 'pointer' } },
                                wp.element.createElement( 'input', {
                                    type: 'radio',
                                    name: 'divClass',
                                    value: type,
                                    checked: divClass === type,
                                    onChange: function() { setDivClass( type ); },
                                    style: { marginRight: '8px' },
                                } ),
                                labels[ type ]
                            );
                        } )
                    ),
                    wp.element.createElement( TextControl, {
                        label: __( 'Or custom class', 'djot-markup' ),
                        value: divClass,
                        onChange: setDivClass,
                        placeholder: 'custom-class',
                    } ),
                    wp.element.createElement(
                        'div',
                        { style: { marginTop: '16px' } },
                        wp.element.createElement( Button, {
                            variant: 'primary',
                            onClick: onInsertDiv,
                        }, __( 'Insert Container', 'djot-markup' ) ),
                        wp.element.createElement( Button, {
                            variant: 'secondary',
                            onClick: function() { setShowDivModal( false ); },
                            style: { marginLeft: '8px' },
                        }, __( 'Cancel', 'djot-markup' ) )
                    )
                ),
                // Round-trip diff warning modal
                showDiffModal && wp.element.createElement(
                    Modal,
                    {
                        title: __( 'Visual Editor Warning', 'djot-markup' ),
                        onRequestClose: function() {
                            // Cancel - destroy pending instance and go back to write mode
                            if ( pendingVisualEditorInstance.current ) {
                                pendingVisualEditorInstance.current.destroy();
                                pendingVisualEditorInstance.current = null;
                            }
                            setShowDiffModal( false );
                            setDiffData( null );
                            setIsVisualLoading( false );
                            setEditorMode( 'write' );
                        },
                        className: 'wpdjot-diff-modal',
                    },
                    wp.element.createElement( 'p', { style: { marginBottom: '12px' } },
                        __( 'The visual editor may not preserve all formatting. The following changes will occur:', 'djot-markup' )
                    ),
                    wp.element.createElement( 'div', {
                        className: 'wpdjot-diff-view',
                        style: {
                            maxHeight: '300px',
                            overflow: 'auto',
                            fontFamily: 'monospace',
                            fontSize: '12px',
                            border: '1px solid #ddd',
                            borderRadius: '4px',
                            padding: '8px',
                            backgroundColor: '#f9f9f9',
                            marginBottom: '16px',
                        },
                    },
                        diffData && diffData.map( function( item, index ) {
                            var style = {
                                padding: '1px 4px',
                                whiteSpace: 'pre',
                                display: 'block',
                            };
                            var prefix = '  ';
                            if ( item.type === 'remove' ) {
                                style.backgroundColor = '#ffecec';
                                style.color = '#c00';
                                prefix = '- ';
                            } else if ( item.type === 'add' ) {
                                style.backgroundColor = '#eaffea';
                                style.color = '#060';
                                prefix = '+ ';
                            } else if ( item.type === 'separator' ) {
                                style.backgroundColor = '#f0f0f0';
                                style.color = '#666';
                                style.textAlign = 'center';
                                prefix = '';
                            }
                            return wp.element.createElement( 'span', { key: index, style: style },
                                prefix + ( item.line || '' )
                            );
                        } )
                    ),
                    wp.element.createElement(
                        'div',
                        { style: { display: 'flex', gap: '8px' } },
                        wp.element.createElement( Button, {
                            variant: 'primary',
                            onClick: function() {
                                // Proceed - activate the pending editor
                                if ( pendingVisualEditorInstance.current ) {
                                    setVisualEditorInstance( pendingVisualEditorInstance.current );
                                    pendingVisualEditorInstance.current = null;
                                }
                                setShowDiffModal( false );
                                setDiffData( null );
                                setIsVisualLoading( false );
                            },
                        }, __( 'Continue Anyway', 'djot-markup' ) ),
                        wp.element.createElement( Button, {
                            variant: 'secondary',
                            onClick: function() {
                                // Cancel - destroy pending instance and go back to write mode
                                if ( pendingVisualEditorInstance.current ) {
                                    pendingVisualEditorInstance.current.destroy();
                                    pendingVisualEditorInstance.current = null;
                                }
                                setShowDiffModal( false );
                                setDiffData( null );
                                setIsVisualLoading( false );
                                setEditorMode( 'write' );
                            },
                        }, __( 'Cancel', 'djot-markup' ) )
                    )
                ),
                // Mode tabs
                wp.element.createElement(
                    'div',
                    { className: 'wpdjot-mode-tabs' },
                    wp.element.createElement( 'button', {
                        className: editorMode === 'write' ? 'active' : '',
                        onClick: switchToWriteMode,
                    }, __( 'Write', 'djot-markup' ) ),
                    // Visual tab only shown when enabled in settings
                    visualEditorEnabled && wp.element.createElement( 'button', {
                        className: editorMode === 'visual' ? 'active' : '',
                        onClick: switchToVisualMode,
                    }, __( 'Visual', 'djot-markup' ) ),
                    wp.element.createElement( 'button', {
                        className: editorMode === 'preview' ? 'active' : '',
                        onClick: switchToPreviewMode,
                    }, __( 'Preview', 'djot-markup' ) )
                ),
                // Main content area
                wp.element.createElement(
                    'div',
                    { className: 'wpdjot-block-wrapper' },
                    // Write mode (textarea)
                    editorMode === 'write' && wp.element.createElement(
                        'div',
                        { ref: textareaRef },
                        wp.element.createElement( PlainText, {
                            value: content,
                            onChange: onChangeContent,
                            onSelect: updateSelection,
                            onClick: updateSelection,
                            onKeyUp: updateSelection,
                            onKeyDown: handleTextareaKeyDown,
                            className: 'wpdjot-editor',
                            placeholder: __( 'Write your Djot markup here...\n\n# Heading\n\nThis is _emphasized_ and *strong* text.\n\n- List item 1\n- List item 2', 'djot-markup' ),
                        } )
                    ),
                    // Visual mode (Tiptap editor) - only when enabled in settings
                    visualEditorEnabled && editorMode === 'visual' && wp.element.createElement(
                        'div',
                        { className: 'wpdjot-visual-wrapper' },
                        // Always render the container so the ref is available for useEffect
                        wp.element.createElement( 'div', {
                            ref: visualEditorRef,
                            className: 'wpdjot-visual-container',
                            style: isVisualLoading ? { display: 'none' } : {},
                        } ),
                        // Show loading indicator while loading
                        isVisualLoading && wp.element.createElement( 'div', { className: 'wpdjot-visual-loading' },
                            __( 'Loading visual editor...', 'djot-markup' )
                        )
                    ),
                    // Preview mode
                    editorMode === 'preview' && wp.element.createElement(
                        'div',
                        { className: 'wpdjot-preview-wrapper' },
                        wp.element.createElement(
                            'div',
                            { className: 'wpdjot-preview-header' },
                            wp.element.createElement( 'span', null, __( 'Preview', 'djot-markup' ) ),
                            wp.element.createElement(
                                'button',
                                {
                                    className: 'wpdjot-edit-button',
                                    onClick: exitPreviewMode,
                                    title: __( 'Press ESC to exit preview', 'djot-markup' ),
                                },
                                __( 'Edit (ESC)', 'djot-markup' )
                            )
                        ),
                        isLoading
                            ? wp.element.createElement( Spinner, null )
                            : wp.element.createElement( 'div', {
                                  ref: previewRef,
                                  className: 'wpdjot-preview djot-content',
                                  dangerouslySetInnerHTML: { __html: preview },
                              } )
                    )
                )
            );
        },

        save: function() {
            // Dynamic block - rendered on server
            return null;
        },
    } );

    // Simple debounce helper
    function debounce( func, wait ) {
        var timeout;
        return function() {
            var context = this;
            var args = arguments;
            clearTimeout( timeout );
            timeout = setTimeout( function() {
                func.apply( context, args );
            }, wait );
        };
    }
} )( window.wp );
