( function( blocks, element, blockEditor, components, i18n, apiFetch ) {
    'use strict';

    var el               = element.createElement;
    var useBlockProps     = blockEditor.useBlockProps;
    var InspectorControls = blockEditor.InspectorControls;
    var PanelBody         = components.PanelBody;
    var SelectControl     = components.SelectControl;
    var TextControl       = components.TextControl;
    var ToggleControl     = components.ToggleControl;
    var TextareaControl   = components.TextareaControl;
    var Button            = components.Button;
    var Modal             = components.Modal;
    var Popover           = components.Popover;
    var Fragment          = element.Fragment;
    var createBlock       = blocks.createBlock;
    var __                = i18n.__;
    var sprintf           = i18n.sprintf;

    var languageOptions = [
        { label: 'Auto Detect', value: '' },
        { label: 'Bash / Shell', value: 'bash' },
        { label: 'C', value: 'c' },
        { label: 'C++', value: 'cpp' },
        { label: 'C#', value: 'csharp' },
        { label: 'CSS', value: 'css' },
        { label: 'Dockerfile', value: 'dockerfile' },
        { label: 'Go', value: 'go' },
        { label: 'GraphQL', value: 'graphql' },
        { label: 'HCL / Terraform', value: 'hcl' },
        { label: 'HTML / XML', value: 'xml' },
        { label: 'Java', value: 'java' },
        { label: 'JavaScript', value: 'javascript' },
        { label: 'JSON', value: 'json' },
        { label: 'Kotlin', value: 'kotlin' },
        { label: 'Lua', value: 'lua' },
        { label: 'Makefile', value: 'makefile' },
        { label: 'Markdown', value: 'markdown' },
        { label: 'Perl', value: 'perl' },
        { label: 'PHP', value: 'php' },
        { label: 'Python', value: 'python' },
        { label: 'R', value: 'r' },
        { label: 'Ruby', value: 'ruby' },
        { label: 'Rust', value: 'rust' },
        { label: 'SCSS', value: 'scss' },
        { label: 'SQL', value: 'sql' },
        { label: 'Swift', value: 'swift' },
        { label: 'TOML', value: 'toml' },
        { label: 'TypeScript', value: 'typescript' },
        { label: 'YAML', value: 'yaml' }
    ];

    var themeOptions = [
        { label: 'Default (from settings)', value: '' },
        { label: 'Dark', value: 'dark' },
        { label: 'Light', value: 'light' }
    ];

    // Decode HTML entities in stored content (e.g. &gt; → >, from core/code migrations).
    function decodeHtmlEntities( text ) {
        if ( ! text || ! /&[a-z#0-9]+;/i.test( text ) ) return text;
        var t = document.createElement( 'textarea' );
        t.innerHTML = text;
        return t.value;
    }

    // ── Language helpers (module scope, used by transforms AND edit fn) ────────

    var LANG_ALIASES = {
        'sh': 'bash', 'shell': 'bash', 'zsh': 'bash',
        'py': 'python', 'py3': 'python',
        'js': 'javascript', 'mjs': 'javascript',
        'ts': 'typescript',
        'rb': 'ruby',
        'rs': 'rust',
        'yml': 'yaml',
        'md': 'markdown',
        'tf': 'hcl'
    };

    // Detect a markdown/GitHub fenced block and return { lang, code } or null.
    function detectFence( text ) {
        var m = text.match( /^[ \t]*(?:`{3,}|~{3,})([a-zA-Z0-9_+#.-]*)[^\n]*\r?\n([\s\S]*?)(?:\r?\n[ \t]*(?:`{3,}|~{3,})[ \t]*)?$/ );
        if ( ! m ) return null;
        var lang = ( m[1] || '' ).trim().toLowerCase();
        lang = LANG_ALIASES[ lang ] || lang;
        var code = m[2].replace( /\r\n/g, '\n' ).replace( /\n$/, '' );
        return { lang: lang, code: code };
    }

    // Extract text from a DOM node, converting <br> to newlines and stripping tags.
    function nodeToText( node ) {
        var html = node.innerHTML || '';
        html = html.replace( /<br\s*\/?>/gi, '\n' );
        html = html.replace( /<[^>]+>/g, '' );
        var tmp = document.createElement( 'div' );
        tmp.innerHTML = html;
        return tmp.textContent || '';
    }

    // Parse a GitHub or GitLab /blob/ URL into its parts. Returns null on no match.
    // Mirrors the PHP CSDT_Git_Providers logic. Hash forms recognised:
    // #snippet=name, #L42-L87 (line range, informational only here).
    function snippetFromHash( hash ) {
        var sm = ( hash || '' ).match( /(?:^|[&;])snippet=([A-Za-z0-9_.\-]+)/ );
        return sm ? sm[1] : '';
    }
    function parseGitUrl( url ) {
        if ( ! url ) return null;
        var trimmed = String( url ).trim();

        // GitHub: https://github.com/owner/repo/blob/branch/path
        var m = trimmed.match( /^https?:\/\/github\.com\/([^/\s]+)\/([^/\s]+)\/blob\/([^/\s]+)\/([^#\s]+)(?:#(.*))?$/ );
        if ( m ) {
            return {
                provider: 'github',
                label:    'GitHub',
                host:     'github.com',
                owner:    m[1],
                repo:     m[2],
                branch:   decodeURIComponent( m[3] ),
                path:     decodeURIComponent( m[4] ),
                snippet:  snippetFromHash( m[5] )
            };
        }

        // GitLab (gitlab.com or self-hosted): https://host/owner(/subgroup…)/repo/-/blob/branch/path
        m = trimmed.match( /^https?:\/\/([^/\s]+)\/(.+?)\/-\/blob\/([^/\s]+)\/([^#\s]+)(?:#(.*))?$/ );
        if ( m ) {
            var ns   = m[2].split( '/' );
            var repo = ns.pop();
            var owner = ns.join( '/' );
            if ( owner === '' || ! repo ) return null;
            return {
                provider: 'gitlab',
                label:    'GitLab',
                host:     m[1],
                owner:    owner,
                repo:     repo,
                branch:   decodeURIComponent( m[3] ),
                path:     decodeURIComponent( m[4] ),
                snippet:  snippetFromHash( m[5] )
            };
        }

        return null;
    }
    // Back-compat alias for existing call sites.
    var parseGithubUrl = parseGitUrl;

    // UUID v4 with a getRandomValues fallback for older browsers.
    function newBlockUid() {
        if ( typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function' ) {
            return crypto.randomUUID();
        }
        var bytes = new Uint8Array( 16 );
        if ( typeof crypto !== 'undefined' && crypto.getRandomValues ) {
            crypto.getRandomValues( bytes );
        } else {
            for ( var i = 0; i < 16; i++ ) bytes[i] = Math.floor( Math.random() * 256 );
        }
        bytes[6] = ( bytes[6] & 0x0f ) | 0x40;
        bytes[8] = ( bytes[8] & 0x3f ) | 0x80;
        var hex = [];
        for ( var j = 0; j < 16; j++ ) hex.push( ( bytes[j] + 0x100 ).toString( 16 ).substring( 1 ) );
        return hex.slice(0,4).join('') + '-' + hex.slice(4,6).join('') + '-' +
               hex.slice(6,8).join('') + '-' + hex.slice(8,10).join('') + '-' +
               hex.slice(10,16).join('');
    }

    // Render a relative time like "3 hours ago" from a unix-seconds timestamp.
    function formatRelativeTime( ts ) {
        if ( ! ts ) return '';
        var diff = Math.floor( Date.now() / 1000 ) - ts;
        if ( diff < 60 ) return 'just now';
        if ( diff < 3600 ) return Math.floor( diff / 60 ) + ' min ago';
        if ( diff < 86400 ) return Math.floor( diff / 3600 ) + ' hr ago';
        return Math.floor( diff / 86400 ) + ' days ago';
    }

    function cleanHtml( text ) {
        if ( ! text ) return '';
        text = text.replace( /<br\s*\/?>/gi, '\n' );
        text = text.replace( /<[^>]+>/g, '' );
        var tmp = document.createElement( 'textarea' );
        tmp.innerHTML = text;
        return tmp.value.replace( /\n+$/, '' );
    }

    blocks.registerBlockType( 'cloudscale/code-block', {

        transforms: {
            from: [
                {
                    type: 'block',
                    blocks: [ 'core/code' ],
                    transform: function( attributes ) {
                        return createBlock( 'cloudscale/code-block', {
                            content: attributes.content || '',
                            language: ''
                        } );
                    }
                },
                {
                    type: 'block',
                    blocks: [ 'core/preformatted' ],
                    transform: function( attributes ) {
                        return createBlock( 'cloudscale/code-block', {
                            content: cleanHtml( attributes.content || '' ),
                            language: ''
                        } );
                    }
                },
                {
                    // HTML <pre>/<code> from web-page clipboard (e.g. copying from GitHub, docs sites)
                    type: 'raw',
                    priority: 1,
                    isMatch: function( node ) {
                        if ( ! node || node.nodeType !== 1 ) return false;
                        var tag = node.nodeName.toUpperCase();
                        return tag === 'PRE' || tag === 'CODE';
                    },
                    transform: function( node ) {
                        var code = '';
                        var lang = '';
                        var codeEl = node.querySelector( 'code' );
                        if ( codeEl ) {
                            code = codeEl.textContent || '';
                            var cls = codeEl.getAttribute( 'class' ) || '';
                            var m = cls.match( /language-([a-zA-Z0-9+#._-]+)/ );
                            if ( m ) lang = m[1];
                            if ( ! lang ) lang = codeEl.getAttribute( 'lang' ) || '';
                        } else {
                            code = cleanHtml( node.innerHTML || '' );
                        }
                        return createBlock( 'cloudscale/code-block', {
                            content: code.replace( /\n+$/, '' ),
                            language: lang
                        } );
                    }
                },
                {
                    // Plain-text markdown fence pasted as a <p> with <br> line separators.
                    // Browsers use this format when copying plain text (e.g. from a terminal
                    // or text editor).  Gutenberg never generates PRE for plain-text pastes,
                    // so without this transform the fence markers become stray paragraphs.
                    type: 'raw',
                    priority: 0,
                    isMatch: function( node ) {
                        if ( ! node || node.nodeType !== 1 ) return false;
                        if ( node.nodeName.toUpperCase() !== 'P' ) return false;
                        var text = nodeToText( node ).trim();
                        // Must look like a complete fence: opening ``` line + content
                        return /^[ \t]*(?:`{3,}|~{3,})[^\n]*\n[\s\S]/.test( text );
                    },
                    transform: function( node ) {
                        var text = nodeToText( node ).trim();
                        var fence = detectFence( text );
                        return createBlock( 'cloudscale/code-block', {
                            content: fence ? fence.code : text,
                            language: fence ? ( fence.lang || '' ) : ''
                        } );
                    }
                }
            ]
        },

        edit: function( props ) {
            var attributes = props.attributes;

            var blockProps = useBlockProps( {
                className: 'cs-code-editor-wrapper'
            } );

            // Guard for blocks that mirror a GitHub source: warn the author once per
            // editor session that their manual edit will be overwritten on next sync.
            // Returns true if the edit should proceed, false to cancel.
            var ghConfirmState  = element.useState( false );
            var ghEditOk        = ghConfirmState[0];
            var setGhEditOk     = ghConfirmState[1];
            function guardManagedEdit() {
                if ( ! attributes.githubUrl || ghEditOk ) return true;
                var ok = window.confirm( __( 'This block mirrors a GitHub source. Manual edits will be overwritten on the next sync. Continue?', 'cloudscale-cyber-devtools' ) );
                if ( ok ) setGhEditOk( true );
                return ok;
            }

            function onChangeCode( event ) {
                if ( ! guardManagedEdit() ) {
                    // Snap the textarea value back to the stored attribute.
                    event.target.value = decodeHtmlEntities( attributes.content || '' );
                    return;
                }
                props.setAttributes( { content: event.target.value } );
            }

            // Strip the stored prefix from the start and suffix from the end of the
            // combined content to recover the synced body region. Falls back to the
            // whole content if the prefix/suffix don't actually appear in it (e.g.
            // pre-existing block from before prefix/suffix was set).
            function computeSyncedBody() {
                var c = decodeHtmlEntities( attributes.content || '' );
                var p = attributes.githubPrefix || '';
                var s = attributes.githubSuffix || '';
                var start = ( p !== '' && c.substr( 0, p.length ) === p ) ? p.length : 0;
                var end   = ( s !== '' && c.substr( c.length - s.length ) === s ) ? c.length - s.length : c.length;
                if ( end < start ) end = start;
                return c.substring( start, end );
            }

            // When prefix/suffix change in the split editor, update both the attribute
            // (which persists across syncs) and `content` (so the render reflects it now).
            function onChangePrefix( event ) {
                var newPrefix = event.target.value;
                var body      = computeSyncedBody();
                var suffix    = attributes.githubSuffix || '';
                props.setAttributes( {
                    githubPrefix: newPrefix,
                    content:      newPrefix + body + suffix
                } );
            }
            function onChangeSuffix( event ) {
                var newSuffix = event.target.value;
                var prefix    = attributes.githubPrefix || '';
                var body      = computeSyncedBody();
                props.setAttributes( {
                    githubSuffix: newSuffix,
                    content:      prefix + body + newSuffix
                } );
            }

            function onKeyDown( event ) {
                if ( event.key === 'Tab' ) {
                    event.preventDefault();
                    var ta    = event.target;
                    var start = ta.selectionStart;
                    var end   = ta.selectionEnd;
                    var value = ta.value;
                    ta.value  = value.substring( 0, start ) + '    ' + value.substring( end );
                    ta.selectionStart = ta.selectionEnd = start + 4;
                    props.setAttributes( { content: ta.value } );
                }
            }

            var langLabel = 'Auto Detect';
            if ( attributes.language ) {
                for ( var i = 0; i < languageOptions.length; i++ ) {
                    if ( languageOptions[i].value === attributes.language ) {
                        langLabel = languageOptions[i].label;
                        break;
                    }
                }
            }

            var copyLabelState = element.useState( 'Copy' );
            var getCopyLabel = copyLabelState[0];
            var setCopyLabel = copyLabelState[1];

            function onCopyCode() {
                var code = decodeHtmlEntities( attributes.content || '' );
                if ( ! code ) return;
                if ( navigator.clipboard && navigator.clipboard.writeText ) {
                    navigator.clipboard.writeText( code ).then( function() {
                        setCopyLabel( 'Copied!' );
                        setTimeout( function() { setCopyLabel( 'Copy' ); }, 1500 );
                    } );
                } else {
                    var ta = document.createElement( 'textarea' );
                    ta.value = code;
                    ta.style.position = 'fixed';
                    ta.style.opacity = '0';
                    document.body.appendChild( ta );
                    ta.select();
                    document.execCommand( 'copy' );
                    document.body.removeChild( ta );
                    setCopyLabel( 'Copied!' );
                    setTimeout( function() { setCopyLabel( 'Copy' ); }, 1500 );
                }
            }

            function onClearCode() {
                if ( ! guardManagedEdit() ) return;
                props.setAttributes( { content: '' } );
            }

            function decodeClipboardText( text ) {
                // Gutenberg sometimes JSON-encodes clipboard text internally,
                // producing escape sequences like \n, \u0022, or bare u003c.
                if ( typeof text !== 'string' ) return text;
                var trimmed = text.trim();
                // Case 1: Already a full JSON string value (wrapped in quotes), parse directly.
                if ( trimmed.charAt(0) === '"' && trimmed.charAt( trimmed.length - 1 ) === '"' ) {
                    try { return JSON.parse( trimmed ); } catch(e) {}
                }
                // Case 2: Contains backslash escape sequences (\n, \t, \r, \\, \uXXXX).
                // Decode directly instead of via JSON.parse, which fails when \u0022
                // decodes to " and creates an unterminated string literal.
                if ( /\\[ntr\\]|\\u[0-9a-fA-F]{4}/.test( trimmed ) ) {
                    return trimmed.replace( /\\([ntr\\]|u[0-9a-fA-F]{4})/g, function( _, seq ) {
                        if ( seq === 'n' ) return '\n';
                        if ( seq === 't' ) return '\t';
                        if ( seq === 'r' ) return '\r';
                        if ( seq === '\\' ) return '\\';
                        return String.fromCharCode( parseInt( seq.slice(1), 16 ) );
                    } );
                }
                // Case 3: Bare unicode escapes without backslash (u003c style).
                // Decode directly for the same reason as Case 2.
                if ( /(?:^|[^\\])u[0-9a-fA-F]{4}/.test( trimmed ) ) {
                    return trimmed.replace( /u([0-9a-fA-F]{4})/g, function( _, hex ) {
                        return String.fromCharCode( parseInt( hex, 16 ) );
                    } );
                }
                return text;
            }

            function applyPastedCode( text ) {
                var fence = detectFence( text ); // module-scope
                if ( fence ) {
                    var updates = { content: fence.code };
                    // Auto-set language only when none is explicitly chosen yet
                    if ( fence.lang && ! attributes.language ) {
                        var known = languageOptions.some( function( opt ) {
                            return opt.value === fence.lang;
                        } );
                        if ( known ) updates.language = fence.lang;
                    }
                    props.setAttributes( updates );
                } else {
                    props.setAttributes( { content: text } );
                }
            }

            function onPasteCode( event ) {
                // Called both from the toolbar Paste button (no event) and the
                // textarea's onPaste handler (event.clipboardData available).
                if ( event && event.clipboardData ) {
                    event.preventDefault();
                    if ( ! guardManagedEdit() ) return;
                    var text  = decodeClipboardText( event.clipboardData.getData( 'text' ) );
                    var ta    = event.target;
                    var start = ta.selectionStart;
                    var end   = ta.selectionEnd;
                    var value = ta.value;
                    // When the textarea is empty or fully selected, treat this as a
                    // fresh paste: strip any markdown fence and auto-detect language.
                    if ( value === '' || ( start === 0 && end === value.length ) ) {
                        applyPastedCode( text );
                        return;
                    }
                    // Otherwise insert at the cursor, replacing only the selected
                    // range — the same behaviour as a normal textarea paste.
                    var next  = value.substring( 0, start ) + text + value.substring( end );
                    var caret = start + text.length;
                    props.setAttributes( { content: next } );
                    // Restore the caret after React re-renders the controlled textarea.
                    window.requestAnimationFrame( function () {
                        ta.selectionStart = ta.selectionEnd = caret;
                    } );
                } else if ( navigator.clipboard && navigator.clipboard.readText ) {
                    if ( ! guardManagedEdit() ) return;
                    navigator.clipboard.readText().then( function( text ) {
                        applyPastedCode( decodeClipboardText( text ) );
                    } ).catch( function() {} );
                }
            }

            // When the URL changes, if it carries a #snippet=foo hash and the user
            // hasn't already set a snippet name, auto-populate the snippet field.
            // Also stamp a stable block UID so the cron sweep can locate this exact
            // block inside post_content across edits.
            function onChangeGithubUrl( val ) {
                var updates = { githubUrl: val };
                var parsed  = parseGithubUrl( val );
                if ( parsed && parsed.snippet && ! attributes.githubSnippet ) {
                    updates.githubSnippet = parsed.snippet;
                }
                if ( val && ! attributes.githubBlockId ) {
                    updates.githubBlockId = newBlockUid();
                }
                props.setAttributes( updates );
            }

            var ghBusyState  = element.useState( false );
            var ghBusy       = ghBusyState[0];
            var setGhBusy    = ghBusyState[1];
            var ghErrorState = element.useState( '' );
            var ghError      = ghErrorState[0];
            var setGhError   = ghErrorState[1];
            var ghHelpState  = element.useState( false );
            var ghHelpOpen   = ghHelpState[0];
            var setGhHelpOpen = ghHelpState[1];
            // Toolbar GitHub-icon popover state. When true, render a Popover next
            // to the toolbar's GitHub icon with a URL input + Fetch button so the
            // author can link the block to a GitHub file without opening the
            // right-hand Inspector sidebar.
            var ghPopoverState = element.useState( false );
            var ghPopoverOpen  = ghPopoverState[0];
            var setGhPopoverOpen = ghPopoverState[1];

            // Help modal content. Compact builder so the four sections stay readable
            // in JSX-via-createElement form.
            function helpSection( title, children ) {
                return el( 'section', { style: { marginBottom: '18px' } },
                    el( 'h3', { style: { margin: '0 0 6px', fontSize: '13px', fontWeight: 600 } }, title ),
                    children
                );
            }
            function helpCode( text ) {
                return el( 'pre', {
                    style: {
                        background: '#f6f8fa',
                        border: '1px solid #d0d7de',
                        borderRadius: '4px',
                        padding: '8px 10px',
                        fontSize: '11.5px',
                        lineHeight: '1.5',
                        overflowX: 'auto',
                        margin: '6px 0'
                    }
                }, text );
            }
            function renderGhHelpModal() {
                if ( ! ghHelpOpen ) return null;
                return el( Modal, {
                    title: __( 'Source linking (GitHub / GitLab), how it works', 'cloudscale-cyber-devtools' ),
                    onRequestClose: function () { setGhHelpOpen( false ); },
                    className: 'cs-gh-help-modal',
                    style: { maxWidth: '720px' }
                },
                    el( 'div', { style: { fontSize: '12.5px', lineHeight: '1.55', color: '#1f2328', maxHeight: '70vh', overflowY: 'auto', paddingRight: '6px' } },

                        helpSection( __( '1. Whole-file mirror', 'cloudscale-cyber-devtools' ),
                            el( 'div', {},
                                el( 'p', {}, __( 'Paste a GitHub or GitLab blob URL into the URL field, then click Fetch. The block content becomes a live mirror of that file. Each nightly cron run, and every time you click Refresh, the content is replaced with the latest version on the branch in the URL. Public repos only.', 'cloudscale-cyber-devtools' ) ),
                                helpCode( 'https://github.com/owner/repo/blob/main/path/to/file.py\nhttps://gitlab.com/owner/repo/-/blob/main/path/to/file.py' )
                            )
                        ),

                        helpSection( __( '2. Snippet markers', 'cloudscale-cyber-devtools' ),
                            el( 'div', {},
                                el( 'p', {}, __( 'To mirror only a region of a file, add a pair of markers in the source. The markers work in any language, the parser looks for the marker text on a line, regardless of comment style.', 'cloudscale-cyber-devtools' ) ),
                                helpCode( '// csdt:start name=login-form\n... your code ...\n// csdt:end name=login-form' ),
                                el( 'p', {}, __( 'Reference the snippet from the block in one of two ways:', 'cloudscale-cyber-devtools' ) ),
                                el( 'ul', { style: { margin: '4px 0 6px 18px', padding: 0 } },
                                    el( 'li', {}, __( 'Append #snippet=login-form to the URL', 'cloudscale-cyber-devtools' ) ),
                                    el( 'li', {}, __( 'Or fill in the "Snippet name" field directly', 'cloudscale-cyber-devtools' ) )
                                ),
                                el( 'p', { style: { color: '#57606a' } }, __( 'Marker names accept letters, digits, underscore, dot, and hyphen. If the marker is missing on a later sync, the block keeps its existing content and the failure is logged on the block\'s index row.', 'cloudscale-cyber-devtools' ) )
                            )
                        ),

                        helpSection( __( '3. Prefix and suffix (heredoc wrappers)', 'cloudscale-cyber-devtools' ),
                            el( 'div', {},
                                el( 'p', {}, __( 'Prefix and Suffix wrap text around the synced source content. Useful when the blog block has install-pattern wrappers like cat > FILE << EOF that should stay even after sync.', 'cloudscale-cyber-devtools' ) ),
                                el( 'p', { style: { margin: '6px 0 4px' } }, el( 'strong', {}, __( 'Example, post 9738 (sg-tightener):', 'cloudscale-cyber-devtools' ) ) ),
                                helpCode( "Prefix:\ncat > install.sh << 'EOF'\n\nSuffix:\nEOF" ),
                                el( 'p', {}, __( 'After Refresh, the block content becomes:', 'cloudscale-cyber-devtools' ) ),
                                helpCode( "cat > install.sh << 'EOF'\n<raw install.sh body fetched from the source>\nEOF" ),
                                el( 'p', { style: { color: '#57606a' } }, __( 'The source file itself stays clean and runnable, wrapping happens only inside the block at sync time.', 'cloudscale-cyber-devtools' ) )
                            )
                        ),

                        helpSection( __( '4. Sync model, footer link, and cache', 'cloudscale-cyber-devtools' ),
                            el( 'div', {},
                                el( 'ul', { style: { margin: '0 0 6px 18px', padding: 0 } },
                                    el( 'li', {}, el( 'strong', {}, 'Refresh:' ), ' ', __( 'on-demand sync from inside the editor. Updates content + last-sync time + content SHA.', 'cloudscale-cyber-devtools' ) ),
                                    el( 'li', {}, el( 'strong', {}, 'Nightly cron:' ), ' ', __( 'a daily WP-cron event (csdt_github_sync_daily) walks the linked-blocks index, fetches each file from its provider raw endpoint (GitHub or GitLab), rewrites changed content via wp_update_post (revisions suppressed).', 'cloudscale-cyber-devtools' ) ),
                                    el( 'li', {}, el( 'strong', {}, '"View source" footer:' ), ' ', __( 'a small link rendered below each linked block on the frontend. Toggle visibility with the "Show \'View source\' link" switch above.', 'cloudscale-cyber-devtools' ) ),
                                    el( 'li', {}, el( 'strong', {}, 'Stale flag:' ), ' ', __( 'if the last successful sync is more than 7 days ago, this panel shows a "Stale" badge. Means the cron has been failing or hasn\'t run.', 'cloudscale-cyber-devtools' ) ),
                                    el( 'li', {}, el( 'strong', {}, __( 'When the source is unreachable or the file is missing:', 'cloudscale-cyber-devtools' ) ), ' ', __( 'the existing block content is preserved and the failure is recorded against the block\'s index row. Cron will retry on the next tick.', 'cloudscale-cyber-devtools' ) ),
                                    el( 'li', {}, el( 'strong', {}, __( 'Cloudflare cache:', 'cloudscale-cyber-devtools' ) ), ' ', __( 'updates to public posts may be cached for a few minutes. The deploy and post-update flows purge it automatically; you can also purge manually if needed.', 'cloudscale-cyber-devtools' ) ),
                                    el( 'li', {}, el( 'strong', {}, __( 'Unlinking:', 'cloudscale-cyber-devtools' ) ), ' ', __( 'clear the URL field. On post save, the index row is removed and the block is no longer touched by future syncs.', 'cloudscale-cyber-devtools' ) )
                                )
                            )
                        )
                    )
                );
            }

            function onRefreshFromGithub() {
                if ( ! attributes.githubUrl || ghBusy ) return;
                setGhBusy( true );
                setGhError( '' );
                // Server requires post_id so it can run an edit_post capability
                // check against the post that's actually being modified.
                var postId = ( window.wp && wp.data && wp.data.select( 'core/editor' )
                    ? wp.data.select( 'core/editor' ).getCurrentPostId()
                    : 0 ) || 0;
                apiFetch( {
                    path: '/csdt/v1/github-sync',
                    method: 'POST',
                    data: {
                        url:     attributes.githubUrl,
                        snippet: attributes.githubSnippet || '',
                        post_id: postId
                    }
                } ).then( function( res ) {
                    var rawBody = res.content || '';
                    var prefix  = attributes.githubPrefix || '';
                    var suffix  = attributes.githubSuffix || '';
                    var updates = {
                        content:        prefix + rawBody + suffix,
                        githubLastSync: res.lastSync || Math.floor( Date.now() / 1000 ),
                        githubLastSha:  res.sha || ''
                    };
                    // Auto-set language only when the user hasn't already picked one.
                    if ( res.language && ! attributes.language ) {
                        updates.language = res.language;
                    }
                    if ( ! attributes.githubBlockId ) {
                        updates.githubBlockId = newBlockUid();
                    }
                    props.setAttributes( updates );
                    setGhBusy( false );
                    // Close the toolbar popover (if it triggered the fetch) so the
                    // newly-rendered split view is unobstructed. Safe no-op when
                    // the sidebar Refresh button initiated the call.
                    setGhPopoverOpen( false );
                } ).catch( function( err ) {
                    var msg = ( err && err.message ) ? err.message : __( 'Refresh failed.', 'cloudscale-cyber-devtools' );
                    setGhError( msg );
                    setGhBusy( false );
                } );
            }

            var parsedGh = parseGithubUrl( attributes.githubUrl );
            var ghStatus = null;
            var STALE_THRESHOLD_SEC = 7 * 86400;
            var isStale = !! ( attributes.githubUrl && attributes.githubLastSync &&
                ( Math.floor( Date.now() / 1000 ) - attributes.githubLastSync > STALE_THRESHOLD_SEC ) );
            if ( attributes.githubUrl ) {
                if ( parsedGh ) {
                    var summary = parsedGh.owner + '/' + parsedGh.repo + ' @ ' + parsedGh.branch;
                    var pathLine = parsedGh.path;
                    var lastSync = attributes.githubLastSync
                        ? __( 'Last synced: ', 'cloudscale-cyber-devtools' ) + formatRelativeTime( attributes.githubLastSync )
                        : __( 'Last synced: never', 'cloudscale-cyber-devtools' );
                    ghStatus = el( 'div', { className: 'cs-gh-status cs-gh-status-ok' },
                        el( 'div', { className: 'cs-gh-status-line' }, summary ),
                        el( 'div', { className: 'cs-gh-status-path' }, pathLine ),
                        el( 'div', { className: 'cs-gh-status-meta' },
                            lastSync,
                            isStale
                                ? el( 'span', { className: 'cs-gh-stale-badge', title: __( 'Sync is older than 7 days, the nightly cron may not be running, or the last sync errored.', 'cloudscale-cyber-devtools' ) }, __( 'Stale', 'cloudscale-cyber-devtools' ) )
                                : null
                        )
                    );
                } else {
                    ghStatus = el( 'div', { className: 'cs-gh-status cs-gh-status-error' },
                        __( 'Not a valid GitHub or GitLab blob URL. Expected: https://github.com/OWNER/REPO/blob/BRANCH/path  or  https://gitlab.com/OWNER/REPO/-/blob/BRANCH/path', 'cloudscale-cyber-devtools' )
                    );
                }
            }

            return el( Fragment, {},
                el( InspectorControls, {},
                    el( PanelBody, { title: __( 'Code Settings', 'cloudscale-cyber-devtools' ), initialOpen: true },
                        el( SelectControl, {
                            label: __( 'Language', 'cloudscale-cyber-devtools' ),
                            help: __( 'Leave on Auto Detect to let highlight.js guess the language.', 'cloudscale-cyber-devtools' ),
                            value: attributes.language,
                            options: languageOptions,
                            onChange: function( val ) { props.setAttributes( { language: val } ); }
                        } ),
                        el( TextControl, {
                            label: __( 'Title', 'cloudscale-cyber-devtools' ),
                            help: __( 'Optional label shown above the code (e.g. filename).', 'cloudscale-cyber-devtools' ),
                            value: attributes.title,
                            onChange: function( val ) { props.setAttributes( { title: val } ); }
                        } ),
                        el( SelectControl, {
                            label: __( 'Dark / Light Mode', 'cloudscale-cyber-devtools' ),
                            help: __( 'Override for this block. The color theme is set site wide in Tools > CloudScale Code and SQL.', 'cloudscale-cyber-devtools' ),
                            value: attributes.theme,
                            options: themeOptions,
                            onChange: function( val ) { props.setAttributes( { theme: val } ); }
                        } )
                    ),
                    el( PanelBody, { title: __( 'Source linking (GitHub / GitLab)', 'cloudscale-cyber-devtools' ), initialOpen: false },
                        el( 'div', { style: { marginBottom: '8px' } },
                            el( Button, {
                                variant: 'link',
                                onClick: function () { setGhHelpOpen( true ); },
                                style: { padding: 0, fontSize: '12px' }
                            }, __( '?  How does source linking work?', 'cloudscale-cyber-devtools' ) )
                        ),
                        renderGhHelpModal(),
                        el( TextControl, {
                            label: __( 'Source URL', 'cloudscale-cyber-devtools' ),
                            help: __( 'Paste a GitHub or GitLab /blob/ URL. Add #snippet=NAME to pull only a marked region.', 'cloudscale-cyber-devtools' ),
                            value: attributes.githubUrl,
                            onChange: onChangeGithubUrl,
                            placeholder: 'https://github.com/owner/repo/blob/main/path/to/file.php'
                        } ),
                        el( TextControl, {
                            label: __( 'Snippet name (optional)', 'cloudscale-cyber-devtools' ),
                            help: __( 'Wrap the region in your source with: csdt:start name=NAME / csdt:end name=NAME (in any comment style). Leave blank to mirror the whole file.', 'cloudscale-cyber-devtools' ),
                            value: attributes.githubSnippet,
                            onChange: function( val ) { props.setAttributes( { githubSnippet: val } ); }
                        } ),
                        el( TextareaControl, {
                            label: __( 'Prefix (optional)', 'cloudscale-cyber-devtools' ),
                            help: __( "Text prepended to the synced content. Useful for install patterns like cat > FILE << 'EOF'. Updates apply on next Refresh/sync.", 'cloudscale-cyber-devtools' ),
                            value: attributes.githubPrefix,
                            rows: 2,
                            onChange: function( val ) { props.setAttributes( { githubPrefix: val } ); }
                        } ),
                        el( TextareaControl, {
                            label: __( 'Suffix (optional)', 'cloudscale-cyber-devtools' ),
                            help: __( 'Text appended to the synced content (e.g. EOF). Updates apply on next Refresh/sync.', 'cloudscale-cyber-devtools' ),
                            value: attributes.githubSuffix,
                            rows: 2,
                            onChange: function( val ) { props.setAttributes( { githubSuffix: val } ); }
                        } ),
                        el( ToggleControl, {
                            label: __( "Show 'View source' link", 'cloudscale-cyber-devtools' ),
                            checked: !! attributes.githubShowLink,
                            onChange: function( val ) { props.setAttributes( { githubShowLink: !! val } ); }
                        } ),
                        ghStatus,
                        el( 'div', { className: 'cs-gh-actions' },
                            el( Button, {
                                variant: 'primary',
                                disabled: ! parsedGh || ghBusy,
                                isBusy: ghBusy,
                                onClick: onRefreshFromGithub
                            }, ghBusy
                                ? __( 'Refreshing…', 'cloudscale-cyber-devtools' )
                                : ( attributes.githubLastSync
                                    ? sprintf( __( 'Refresh from %s', 'cloudscale-cyber-devtools' ), parsedGh ? parsedGh.label : 'source' )
                                    : sprintf( __( 'Fetch from %s', 'cloudscale-cyber-devtools' ), parsedGh ? parsedGh.label : 'source' ) )
                            )
                        ),
                        ghError
                            ? el( 'div', { className: 'cs-gh-status cs-gh-status-error' }, ghError )
                            : null,
                        el( 'p', { className: 'cs-gh-phase-note' },
                            el( 'em', {},
                                __( 'Linked blocks auto-refresh nightly. Use Refresh to update right away.', 'cloudscale-cyber-devtools' )
                            )
                        )
                    )
                ),
                el( 'div', blockProps,
                    el( 'div', { className: 'cs-code-editor-toolbar' },
                        el( 'span', { className: 'cs-code-editor-icon' },
                            el( 'svg', { xmlns: 'http://www.w3.org/2000/svg', width: '16', height: '16', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round' },
                                el( 'polyline', { points: '16 18 22 12 16 6' } ),
                                el( 'polyline', { points: '8 6 2 12 8 18' } )
                            )
                        ),
                        el( 'span', { className: 'cs-code-editor-label' }, 'CloudScale Code' ),
                        attributes.language
                            ? el( 'span', { className: 'cs-code-editor-lang' }, langLabel )
                            : el( 'span', { className: 'cs-code-editor-lang cs-code-editor-lang-auto' }, 'Auto Detect' ),
                        attributes.title
                            ? el( 'span', { className: 'cs-code-editor-title' }, attributes.title )
                            : null,
                        el( 'div', { className: 'cs-code-editor-toolbar-actions' },
                            el( 'span', { className: 'cs-code-editor-gh-wrap' },
                                el( 'button', {
                                    className: 'cs-code-editor-btn cs-code-editor-btn-gh'
                                        + ( attributes.githubUrl ? ' is-linked' : '' ),
                                    type: 'button',
                                    title: attributes.githubUrl
                                        ? __( 'GitHub-linked, click to edit URL or refresh', 'cloudscale-cyber-devtools' )
                                        : __( 'Link this block to a file on GitHub', 'cloudscale-cyber-devtools' ),
                                    'aria-expanded': ghPopoverOpen ? 'true' : 'false',
                                    onClick: function () { setGhPopoverOpen( ! ghPopoverOpen ); }
                                },
                                    // GitHub mark (Octocat).
                                    el( 'svg', {
                                        xmlns: 'http://www.w3.org/2000/svg',
                                        width: '14', height: '14',
                                        viewBox: '0 0 24 24',
                                        fill: 'currentColor',
                                        'aria-hidden': 'true'
                                    },
                                        el( 'path', { d: 'M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.1.79-.25.79-.56v-2.2c-3.2.7-3.88-1.36-3.88-1.36-.52-1.32-1.27-1.67-1.27-1.67-1.04-.71.08-.69.08-.69 1.15.08 1.76 1.18 1.76 1.18 1.02 1.75 2.68 1.24 3.34.95.1-.74.4-1.24.73-1.53-2.55-.29-5.24-1.28-5.24-5.7 0-1.26.45-2.29 1.18-3.1-.12-.29-.51-1.46.11-3.04 0 0 .97-.31 3.17 1.18a11 11 0 0 1 2.88-.39c.98 0 1.97.13 2.88.39 2.2-1.49 3.17-1.18 3.17-1.18.62 1.58.23 2.75.11 3.04.74.81 1.18 1.84 1.18 3.1 0 4.43-2.69 5.41-5.25 5.69.41.36.78 1.06.78 2.14v3.17c0 .31.21.67.8.56C20.21 21.39 23.5 17.08 23.5 12 23.5 5.65 18.35.5 12 .5z' } )
                                    ),
                                    attributes.githubUrl
                                        ? el( 'span', { className: 'cs-code-editor-btn-gh-label' }, parsedGh ? parsedGh.label : __( 'Linked', 'cloudscale-cyber-devtools' ) )
                                        : el( 'span', { className: 'cs-code-editor-btn-gh-label' }, __( 'Source', 'cloudscale-cyber-devtools' ) )
                                ),
                                ghPopoverOpen
                                    ? el( Popover, {
                                        className: 'cs-code-editor-gh-popover',
                                        position: 'bottom right',
                                        placement: 'bottom-end',
                                        focusOnMount: 'firstElement',
                                        onClose: function () { setGhPopoverOpen( false ); }
                                    },
                                        el( 'div', { className: 'cs-code-editor-gh-popover-body' },
                                            el( 'div', { className: 'cs-code-editor-gh-popover-title' },
                                                __( 'Link to source (GitHub / GitLab)', 'cloudscale-cyber-devtools' )
                                            ),
                                            el( TextControl, {
                                                label: __( 'Source URL', 'cloudscale-cyber-devtools' ),
                                                help: __( 'Paste a GitHub or GitLab /blob/ URL. Add #snippet=NAME to mirror a marker-bounded region.', 'cloudscale-cyber-devtools' ),
                                                value: attributes.githubUrl,
                                                onChange: onChangeGithubUrl,
                                                placeholder: 'https://github.com/owner/repo/blob/main/path/to/file.php'
                                            } ),
                                            el( TextControl, {
                                                label: __( 'Snippet name (optional)', 'cloudscale-cyber-devtools' ),
                                                value: attributes.githubSnippet,
                                                onChange: function ( val ) { props.setAttributes( { githubSnippet: val } ); }
                                            } ),
                                            ghError
                                                ? el( 'div', { className: 'cs-gh-status cs-gh-status-error' }, ghError )
                                                : null,
                                            el( 'div', { className: 'cs-code-editor-gh-popover-actions' },
                                                el( Button, {
                                                    variant: 'primary',
                                                    disabled: ! parseGithubUrl( attributes.githubUrl ) || ghBusy,
                                                    isBusy: ghBusy,
                                                    onClick: function () {
                                                        onRefreshFromGithub();
                                                    }
                                                }, ghBusy
                                                    ? __( 'Fetching…', 'cloudscale-cyber-devtools' )
                                                    : ( attributes.githubLastSync
                                                        ? __( 'Refresh from GitHub', 'cloudscale-cyber-devtools' )
                                                        : __( 'Fetch from GitHub', 'cloudscale-cyber-devtools' ) )
                                                ),
                                                el( Button, {
                                                    variant: 'tertiary',
                                                    onClick: function () { setGhPopoverOpen( false ); }
                                                }, __( 'Close', 'cloudscale-cyber-devtools' ) )
                                            ),
                                            el( 'p', { className: 'cs-code-editor-gh-popover-hint' },
                                                __( 'More options (prefix, suffix, footer link) in the block sidebar.', 'cloudscale-cyber-devtools' )
                                            )
                                        )
                                    )
                                    : null
                            ),
                            el( 'button', {
                                className: 'cs-code-editor-btn cs-code-editor-btn-copy',
                                type: 'button',
                                title: __( 'Copy code to clipboard', 'cloudscale-cyber-devtools' ),
                                onClick: onCopyCode
                            }, getCopyLabel ),
                            // Hide Paste / Clear when the block is GitHub-linked, they'd
                            // destructively wipe the structured prefix / body / suffix layout.
                            attributes.githubUrl ? null : el( 'button', {
                                className: 'cs-code-editor-btn cs-code-editor-btn-paste',
                                type: 'button',
                                title: __( 'Paste clipboard content', 'cloudscale-cyber-devtools' ),
                                onClick: onPasteCode
                            }, 'Paste' ),
                            attributes.githubUrl ? null : el( 'button', {
                                className: 'cs-code-editor-btn cs-code-editor-btn-clear',
                                type: 'button',
                                title: __( 'Clear all code', 'cloudscale-cyber-devtools' ),
                                onClick: onClearCode
                            }, 'Clear' )
                        )
                    ),
                    // When the block is linked to GitHub, replace the single textarea
                    // with a three-zone split editor: editable Prefix on top, read-only
                    // synced Body in the middle, editable Suffix at the bottom. Plus a
                    // URL banner header with a Refresh button.
                    attributes.githubUrl
                        ? ( function () {
                            var prefix = attributes.githubPrefix || '';
                            var suffix = attributes.githubSuffix || '';
                            var body   = computeSyncedBody();
                            var rowsFor = function ( s, min ) {
                                return Math.max( min, ( ( s || '' ).split( '\n' ).length || 1 ) + 1 );
                            };
                            return el( 'div', { className: 'cs-gh-split' },
                                el( 'div', { className: 'cs-gh-split-header' },
                                    el( 'svg', { xmlns: 'http://www.w3.org/2000/svg', width: '12', height: '12', viewBox: '0 0 24 24', fill: 'currentColor', 'aria-hidden': 'true' },
                                        el( 'path', { d: 'M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.1.79-.25.79-.56v-2.2c-3.2.7-3.88-1.36-3.88-1.36-.52-1.32-1.27-1.67-1.27-1.67-1.04-.71.08-.69.08-.69 1.15.08 1.76 1.18 1.76 1.18 1.02 1.75 2.68 1.24 3.34.95.1-.74.4-1.24.73-1.53-2.55-.29-5.24-1.28-5.24-5.7 0-1.26.45-2.29 1.18-3.1-.12-.29-.51-1.46.11-3.04 0 0 .97-.31 3.17 1.18a11 11 0 0 1 2.88-.39c.98 0 1.97.13 2.88.39 2.2-1.49 3.17-1.18 3.17-1.18.62 1.58.23 2.75.11 3.04.74.81 1.18 1.84 1.18 3.1 0 4.43-2.69 5.41-5.25 5.69.41.36.78 1.06.78 2.14v3.17c0 .31.21.67.8.56C20.21 21.39 23.5 17.08 23.5 12 23.5 5.65 18.35.5 12 .5z' } )
                                    ),
                                    el( 'input', {
                                        type: 'text',
                                        className: 'cs-gh-split-url-input',
                                        value: attributes.githubUrl,
                                        onChange: function ( e ) {
                                            // Reuse the same handler as the sidebar URL field so
                                            // hash-snippet auto-population + UUID stamping behaviour
                                            // stays identical no matter which surface is edited.
                                            onChangeGithubUrl( e.target.value );
                                        },
                                        spellCheck: false,
                                        autoComplete: 'off',
                                        autoCorrect: 'off',
                                        autoCapitalize: 'off',
                                        title: __( 'Source blob URL (GitHub or GitLab), edit to change the source', 'cloudscale-cyber-devtools' )
                                    } ),
                                    // Only render the external-link anchor when the URL passes
                                    // parseGithubUrl. Without this guard, a crafted githubUrl
                                    // like "javascript:..." would render as a clickable link
                                    // and run in a higher-privileged editor's session.
                                    parsedGh ? el( 'a', {
                                        className: 'cs-gh-split-url-open',
                                        href: attributes.githubUrl,
                                        target: '_blank',
                                        rel: 'noopener noreferrer',
                                        title: sprintf( __( 'Open this file on %s', 'cloudscale-cyber-devtools' ), parsedGh.host )
                                    },
                                        el( 'svg', { xmlns: 'http://www.w3.org/2000/svg', width: '12', height: '12', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': 'true' },
                                            el( 'path', { d: 'M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6' } ),
                                            el( 'polyline', { points: '15 3 21 3 21 9' } ),
                                            el( 'line', { x1: '10', y1: '14', x2: '21', y2: '3' } )
                                        )
                                    ) : null,
                                    el( Button, {
                                        variant: 'secondary',
                                        isSmall: true,
                                        disabled: ghBusy,
                                        isBusy: ghBusy,
                                        onClick: onRefreshFromGithub
                                    }, ghBusy ? __( 'Refreshing…', 'cloudscale-cyber-devtools' ) : __( 'Refresh', 'cloudscale-cyber-devtools' ) )
                                ),

                                el( 'div', { className: 'cs-gh-split-zone' },
                                    el( 'div', { className: 'cs-gh-split-label' }, __( 'Prefix, your text above the synced code:', 'cloudscale-cyber-devtools' ) ),
                                    el( 'textarea', {
                                        className: 'cs-code-editor-textarea cs-gh-split-prefix',
                                        value: prefix,
                                        onChange: onChangePrefix,
                                        placeholder: __( "Optional. e.g. cat > FILE << 'EOF'", 'cloudscale-cyber-devtools' ),
                                        rows: rowsFor( prefix, 1 ),
                                        spellCheck: false,
                                        autoComplete: 'off',
                                        autoCorrect: 'off',
                                        autoCapitalize: 'off'
                                    } )
                                ),

                                el( 'div', { className: 'cs-gh-split-zone cs-gh-split-zone-body' },
                                    el( 'div', { className: 'cs-gh-split-label' }, __( 'Synced from GitHub (read-only, refreshes automatically):', 'cloudscale-cyber-devtools' ) ),
                                    el( 'textarea', {
                                        className: 'cs-code-editor-textarea cs-gh-split-body',
                                        value: body,
                                        readOnly: true,
                                        rows: rowsFor( body, 4 ),
                                        spellCheck: false
                                    } )
                                ),

                                el( 'div', { className: 'cs-gh-split-zone' },
                                    el( 'div', { className: 'cs-gh-split-label' }, __( 'Suffix, your text below the synced code:', 'cloudscale-cyber-devtools' ) ),
                                    el( 'textarea', {
                                        className: 'cs-code-editor-textarea cs-gh-split-suffix',
                                        value: suffix,
                                        onChange: onChangeSuffix,
                                        placeholder: __( 'Optional. e.g. EOF', 'cloudscale-cyber-devtools' ),
                                        rows: rowsFor( suffix, 1 ),
                                        spellCheck: false,
                                        autoComplete: 'off',
                                        autoCorrect: 'off',
                                        autoCapitalize: 'off'
                                    } )
                                )
                            );
                        } )()
                        : el( 'textarea', {
                            className: 'cs-code-editor-textarea',
                            value: decodeHtmlEntities( attributes.content ),
                            onChange: onChangeCode,
                            onKeyDown: onKeyDown,
                            onPaste: onPasteCode,
                            placeholder: __( 'Paste or type your code here...', 'cloudscale-cyber-devtools' ),
                            rows: Math.max( 4, ( decodeHtmlEntities( attributes.content || '' ).split( '\n' ).length || 1 ) + 1 ),
                            spellCheck: false,
                            autoComplete: 'off',
                            autoCorrect: 'off',
                            autoCapitalize: 'off'
                        } )
                )
            );
        },
        save: function() {
            return null;
        }
    } );

} )(
    window.wp.blocks,
    window.wp.element,
    window.wp.blockEditor,
    window.wp.components,
    window.wp.i18n,
    window.wp.apiFetch
);
