(function($) {
    'use strict';

    const ButlerChat = {
        init: function() {
            this.selectors = {
                container: '.gf-butler-chat-container',
                log: '#gf-butler-chat-log',
                input: '#gf-butler-chat-input',
                sendBtn: '#gf-butler-send-btn',
                undoBtn: '#gf-butler-undo-btn',
                resetBtn: '#gf-butler-reset-btn',
                bottom: '#butler-chat-bottom',
                pill: '#gf-butler-new-message-pill',
                chips: '.gf-butler-prompt-chip',
                advisoryAction: '.gf-butler-advisory-action',
                advisoryChoice: '.gf-butler-advisory-choice',
                advisoryPrompt: '.gf-butler-advisory-prompt',
                chatModeToggle: '#gf-butler-chatmode-toggle',
                translateBtn: '#gf-butler-translate-btn'
            };

            this.$container = $(this.selectors.container);
            if (!this.$container.length) return;

            this.$log = $(this.selectors.log);
            this.$input = $(this.selectors.input);
            this.$sendBtn = $(this.selectors.sendBtn);
            this.$undoBtn = $(this.selectors.undoBtn);
            this.$resetBtn = $(this.selectors.resetBtn);
            this.$pill = $(this.selectors.pill);
            this.$suggestions = $('.gf-butler-suggestions');
            this.$translateBtn = $(this.selectors.translateBtn);

            this.providerIcon = (this.$container.data('gf-provider-icon') || '').toString();
            this.providerName = (this.$container.data('gf-provider-name') || '').toString();
            this.assistIcon = (this.$container.data('gf-assist-icon') || '').toString();
            this.userAvatar = (this.$container.data('gf-user-avatar') || '').toString();
            this.userName = (this.$container.data('gf-user-name') || '').toString();
            this.siteLanguage = (this.$container.data('gf-site-language') || '').toString();
            this.surface = (this.$container.data('gf-surface') || 'form_builder').toString();
            this.presentationMode = (this.$container.data('gf-presentation-mode') || 'docked_sidebar').toString();
            this.chatModeToggleEnabled = this.$container.data('gf-chat-mode-toggle') === 1 || this.$container.data('gf-chat-mode-toggle') === '1';
            this.defaultChatMode = this.$container.data('gf-default-chat-mode') === 1 || this.$container.data('gf-default-chat-mode') === '1';
            this.nonce = this.$container.data('gf-nonce');
            this.undoNonce = this.$container.data('gf-undo-nonce');
            this.resetNonce = this.$container.data('gf-reset-nonce');
            this.formId = this.$container.data('gf-form-id');
            this.initialPrompt = (this.$container.data('gf-initial-prompt') || '').toString();
            this.autoStart = this.$container.data('gf-auto-start') === 1 || this.$container.data('gf-auto-start') === '1';
            this.assistKeyConfigured = this.$container.data('gf-assist-key-configured') === 1 || this.$container.data('gf-assist-key-configured') === '1';
            this.settingsUrl = (this.$container.data('gf-settings-url') || 'admin.php?page=griffinforms-settings-ai').toString();
            this.packageContext = this.parsePackageContext(this.$container.data('gf-package-context') || {});
            this.chatModeStorageKey = `gfButlerChatMode:${this.surface}`;
            this.$chatModeToggle = $(this.selectors.chatModeToggle);

            this.autoScroll = true;
            this.statusIntervals = [];
            this.pendingMutation = null;
            this.isProcessing = false;
            this._hasMutations = false;
            this.chatMode = this.readChatMode();
            this.selectionObserver = null;

            this.bindEvents();
            this.observeSelectionChanges();
            this.updateChatModeUI();
            this.renderContextualPromptChips();
            this.syncUndoResetState();
            this.scrollToBottom();
            this.syncInputHeight();

            if (!this.assistKeyConfigured) {
                this.$sendBtn.prop('disabled', true);
                this.$undoBtn.prop('disabled', true);
                this.$resetBtn.prop('disabled', true);
                this.$input.prop('disabled', true);
                this.$chatModeToggle.prop('disabled', true);
                return;
            }

            if (this.autoStart && this.initialPrompt) {
                if (this.chatMode) {
                    this.chatMode = false;
                    try { window.localStorage.setItem(this.chatModeStorageKey, '0'); } catch (e) {}
                    this.updateChatModeUI();
                }
                this.submitPrompt(this.initialPrompt, { renderUser: false });
            }
        },

        bindEvents: function() {
            this.$sendBtn.on('click', () => this.sendMessage());
            this.$undoBtn.on('click', () => this.runDraftAction('undo_ai_draft', this.undoNonce));
            this.$resetBtn.on('click', () => this.runDraftAction('reset_ai_draft', this.resetNonce));
            this.$translateBtn.on('click', () => this.runTranslateAction());
            this.$chatModeToggle.on('click', () => this.toggleChatMode());
            this.$input.on('keypress', (e) => {
                if (e.which === 13 && !e.shiftKey) {
                    e.preventDefault();
                    this.sendMessage();
                }
            });
            this.$input.on('input', () => this.syncInputHeight());

            this.$log.on('scroll', () => this.handleScroll());
            this.$pill.on('click', () => {
                this.autoScroll = true;
                this.scrollToBottom();
                this.$pill.fadeOut();
            });

            document.addEventListener('form-layout', () => this.renderContextualPromptChips());

            $(document).on('click', '.gf-butler-activate-connector-btn', (e) => {
                const $btn = $(e.currentTarget);
                this.activateConnectorProvider($btn.data('provider'), $btn.data('nonce'), $btn);
            });

            this.$log.on('click', '.gf-potential-path-btn', (e) => {
                if (this.isProcessing) {
                    return;
                }
                const $btn = $(e.currentTarget);
                $btn.closest('.gf-potential-paths').find('.gf-potential-path-btn').prop('disabled', true);
                const token = ($btn.attr('data-gf-token') || '').toString().trim();
                if (!token) {
                    return;
                }
                const userLabel = $btn.text().trim();
                this.resolvePathToken(token, userLabel);
            });

            $(document).on('click', this.selectors.chips, (e) => {
                if (this.isProcessing) {
                    return;
                }
                const $button = $(e.currentTarget);
                const suggestedPrompt = ($button.attr('data-gf-suggested-prompt') || '').toString().trim();
                const renderLabel = ($button.attr('data-gf-render-label') || $button.text() || '').toString().trim();
                const semanticIntent = ($button.attr('data-gf-semantic-intent') || 'builder_mutation').toString();
                const suggestionId = ($button.attr('data-gf-suggestion-id') || '').toString().trim();
                const suggestionMeta = ($button.attr('data-gf-suggestion-meta') || '').toString().trim();
                const selectedContext = this.getSelectedContext() || this.extractSelectedContextFromButton($button);
                const localMutation = this.extractLocalMutationFromButton($button);

                if (!suggestedPrompt) {
                    const text = $button.text();
                    this.$input.val(text);
                    this.sendMessage();
                    return;
                }

                if (localMutation) {
                    if (localMutation.mutation_type && localMutation.mutation_type.indexOf('local_column_') === 0) {
                        this.runLocalColumnMutation(localMutation, renderLabel);
                    } else {
                        this.runLocalTextMutation(localMutation, renderLabel);
                    }
                    return;
                }

                if (this.isLocalColumnAction(renderLabel, selectedContext)) {
                    this.runLocalColumnMutation(this.buildLocalColumnMutationFromLabel(renderLabel), renderLabel);
                    return;
                }

                if (this.chatMode) {
                    this.runChatModeSelection(suggestedPrompt, renderLabel, semanticIntent, selectedContext, suggestionId, suggestionMeta);
                    return;
                }

                this.runSuggestedAction(suggestedPrompt, renderLabel, semanticIntent, selectedContext, suggestionId, suggestionMeta);
            });

            this.$log.on('click', `${this.selectors.advisoryAction}, ${this.selectors.advisoryChoice}, ${this.selectors.advisoryPrompt}`, (e) => {
                const $button = $(e.currentTarget);
                if ($button.prop('disabled')) {
                    return;
                }

                const suggestedPrompt = ($button.attr('data-gf-suggested-prompt') || '').toString().trim();
                const renderLabel = ($button.attr('data-gf-render-label') || $button.text() || '').toString().trim();
                const semanticIntent = ($button.attr('data-gf-semantic-intent') || 'builder_mutation').toString();
                const suggestionId = ($button.attr('data-gf-suggestion-id') || '').toString().trim();
                const suggestionMeta = ($button.attr('data-gf-suggestion-meta') || '').toString().trim();
                const selectedContext = this.extractSelectedContextFromButton($button);
                const localMutation = this.extractLocalMutationFromButton($button);
                const isAdvisoryChoice = $button.hasClass('gf-butler-advisory-choice');
                this.disableAdvisoryButtons($button.closest('.gf-butler-advisory-group'));

                if (localMutation) {
                    if (localMutation.mutation_type && localMutation.mutation_type.indexOf('local_column_') === 0) {
                        this.runLocalColumnMutation(localMutation, renderLabel);
                    } else {
                        this.runLocalTextMutation(localMutation, renderLabel);
                    }
                    return;
                }

                if (!suggestedPrompt) {
                    return;
                }

                // Advisory buttons are already-resolved choices — always commit directly.
                // Chat mode only applies to free-text input and suggestion chips, not to
                // clicking a rendered advisory action/choice (which would loop back into advisory).
                // Only guard advisory-prompt buttons (free-text follow-up suggestions) — not
                // advisory-action or advisory-choice buttons, which are server-resolved and specific.
                if (
                    $button.hasClass('gf-butler-advisory-prompt') &&
                    (this.isGenericAdvisoryPrompt(suggestedPrompt) || this.isGenericAdvisoryPrompt(renderLabel))
                ) {
                    this.renderMessage('assistant', 'That suggested action was too vague to run safely. Ask Griffin Assist for a more specific change.');
                    return;
                }

                this.runSuggestedAction(suggestedPrompt, renderLabel, semanticIntent, selectedContext, suggestionId, suggestionMeta);
            });

            document.addEventListener('form-html-received', () => {
                this.renderContextualPromptChips();
                if (!this.pendingMutation) {
                    return;
                }

                document.dispatchEvent(new CustomEvent('gf-ai-mutation-applied', {
                    detail: this.pendingMutation
                }));

                this.pendingMutation = null;
            });
        },

        sendMessage: function() {
            const message = this.$input.val().trim();
            if (!message) return;

            this.$input.val('');
            this.syncInputHeight();
            this.submitPrompt(message, { renderUser: true });
        },

        submitPrompt: function(message, options = {}) {
            if (this.isProcessing) {
                return;
            }

            if (!this.assistKeyConfigured) {
                return;
            }

            const renderUser = options.renderUser !== false;
            const requestContext = this.normalizeRequestContext(options.requestContext || {});
            const suggestionContext = options.suggestionContext || null;
            const userText = typeof options.userText === 'string' && options.userText.trim() !== '' ? options.userText.trim() : message;

            if (renderUser) {
                this.renderMessage('user', userText);
                this.scrollToBottom();
            }

            this.isProcessing = true;
            this.$sendBtn.prop('disabled', true);
            this.$undoBtn.prop('disabled', true);
            this.$resetBtn.prop('disabled', true);
            this.$input.prop('disabled', true);
            this.setChipsLocked(true);
            this.startProgressiveDisclosure(requestContext);

            const liveContext = this.getSelectedContext();
            const selectedContext = options.selectedContextOverride
                || ((liveContext && (liveContext.item_type || '') !== 'form') ? liveContext : null)
                || this._lastFieldContext
                || liveContext;

            const data = {
                action: 'gf_ai_butler_broker',
                prompt: message,
                form_id: this.formId,
                nonce: this.nonce,
                selected_context: selectedContext ? JSON.stringify(selectedContext) : '',
                request_context: JSON.stringify(Object.assign({}, requestContext, suggestionContext ? { suggestion_context: suggestionContext } : {}))
            };

            $.post(ajaxurl, data, (response) => {
                const thinkingLog = response && response.data && Array.isArray(response.data.thinking_log) ? response.data.thinking_log : [];
                if (thinkingLog.length > 0) {
                    var thinkingHtml = thinkingLog.map((entry) => {
                        return '<p class="gf-thinking-message gf-thinking-phase-' + this.escapeHtml(entry.phase) + '">' + this.escapeHtml(entry.message) + '</p>';
                    }).join('');
                    if (!this.$log.find('#gf-thinking-stream').length) {
                        this.$log.append('<div id="gf-thinking-stream" class="gf-thinking-stream"></div>');
                    }
                    this.$log.find('#gf-thinking-stream').html(thinkingHtml);
                    this.scrollToBottom();
                }
                const batchStatusItems = response && response.data && Array.isArray(response.data.batch_status) ? response.data.batch_status : [];
                if (batchStatusItems.length >= 2) {
                    var batchHtml = '<div class="gf-batch-status-list">';
                    batchStatusItems.forEach(function(item) {
                        var statusClass = 'gf-batch-status-' + (item.status || 'unknown');
                        var icon = item.status === 'done'    ? '✓'
                                 : item.status === 'failed'  ? '✗'
                                 : item.status === 'working' ? '…'
                                 : '·';
                        batchHtml += '<div class="gf-batch-status-row ' + statusClass + '">'
                            + '<span class="gf-batch-status-icon" aria-hidden="true">' + icon + '</span>'
                            + '<span class="gf-batch-status-label">' + this.escapeHtml(item.label || item.handler_key || '') + '</span>'
                            + '</div>';
                    }, this);
                    batchHtml += '</div>';
                    this.$log.append(batchHtml);
                    this.scrollToBottom();
                }
                this.clearStatusMessages();
                this.unlockBuilderCanvas();
                if (response.success) {
                    const responseData = response && response.data ? response.data : {};

                    if (Array.isArray(responseData.potential_paths) && responseData.potential_paths.length > 0) {
                        var pathMessage = responseData.message || "I wasn't sure what you meant. Did you want to:";
                        this.renderMessage('assistant', pathMessage);
                        var pathHtml = '<div class="gf-potential-paths">';
                        responseData.potential_paths.forEach((path) => {
                            pathHtml += '<button type="button" class="btn btn-sm gf-potential-path-btn" data-gf-token="' + this.gfEscAttr(path.token) + '">'
                                + this.escapeHtml(path.user_label) + '</button>';
                        });
                        pathHtml += '</div>';
                        var $lastMsg = this.$log.find('.gf-butler-msg-assistant').last();
                        $lastMsg.find('.gf-butler-msg-advisory').html(pathHtml);
                        if (this.autoScroll) this.scrollToBottom();
                        return;
                    }

                    const patchPlan = responseData.patch_plan || {};
                    const derivedMessage = responseData.message
                        || patchPlan.success_message
                        || (responseData.status === 'applied' ? 'Draft updated successfully.' : 'Here is a suggestion.');
                    if (response.data.advisory) {
                        // Attach the original selected context and user prompt so advisory
                        // action buttons can carry them back when the user clicks an action.
                        const advisoryPayload = Object.assign({}, response.data.advisory);
                        if (!advisoryPayload.selected_context && selectedContext) {
                            advisoryPayload.selected_context = selectedContext;
                        }
                        advisoryPayload.original_prompt = message;
                        this.renderMessage('assistant', derivedMessage, advisoryPayload);
                    } else {
                        this.renderMessage('assistant', derivedMessage);
                    }

                    if (responseData.mutated_ids && responseData.mutated_ids.length > 0) {
                        this.pendingMutation = {
                            mutated_ids: responseData.mutated_ids,
                            lineage: responseData.lineage || null,
                            is_new: responseData.is_new || false
                        };
                        this._hasMutations = true;
                    }

                    if (responseData.mutated_ids && responseData.mutated_ids.length > 0) {
                        document.dispatchEvent(new Event('form-layout'));
                    }

                    const batchStatus = responseData.batch_status || [];
                    const handlerKey = batchStatus.length > 0 ? (batchStatus[0].handler_key || '') : '';
                    if (handlerKey === 'generate_notification_action') {
                        window.gfNotifNeedsRefresh = true;
                    }

                    if (!responseData.mutated_ids || responseData.mutated_ids.length === 0) {
                        this.pendingMutation = null;
                    }
                    this.renderContextualPromptChips();
                } else {
                    const errorMessage = response && response.data && response.data.message
                        ? response.data.message
                        : 'Something went wrong. Please try again.';
                    this.renderMessage('assistant', this.formatFailureMessage(errorMessage, response && response.data ? response.data.suggestions : []));
                }
            }).fail(() => {
                this.clearStatusMessages();
                this.unlockBuilderCanvas();
                this.renderMessage('assistant', 'Connection error. Please check your settings.');
            }).always(() => {
            this.isProcessing = false;
            this.$sendBtn.prop('disabled', false);
            this.$input.prop('disabled', false).focus();
            this.setChipsLocked(false);
            this.syncUndoResetState();
            this.syncInputHeight();
        });
    },

    resolvePathToken: function(token, userLabel) {
            this.renderMessage('user', userLabel);
            this.scrollToBottom();

            this.isProcessing = true;
            this.$sendBtn.prop('disabled', true);
            this.$undoBtn.prop('disabled', true);
            this.$resetBtn.prop('disabled', true);
            this.$input.prop('disabled', true);
            this.setChipsLocked(true);
            this.startProgressiveDisclosure({ response_mode: 'patch_required', semantic_intent: 'builder_mutation' });

            const selectedContext = this.getSelectedContext();

            $.post(ajaxurl, {
                action: 'gf_ai_resolve_path_token',
                nonce: this.nonce,
                form_id: this.formId,
                path_token: token,
                selected_context: selectedContext ? JSON.stringify(selectedContext) : ''
            }, (response) => {
                this.clearStatusMessages();
                this.unlockBuilderCanvas();
                if (response && response.success) {
                    const responseData = response.data || {};
                    const derivedMessage = responseData.message
                        || (responseData.status === 'applied' ? 'Draft updated successfully.' : 'Done.');
                    this.renderMessage('assistant', derivedMessage);
                    if (responseData.mutated_ids && responseData.mutated_ids.length > 0) {
                        this.pendingMutation = {
                            mutated_ids: responseData.mutated_ids,
                            lineage: responseData.lineage || null,
                            is_new: responseData.is_new || false
                        };
                        this._hasMutations = true;
                        document.dispatchEvent(new Event('form-layout'));
                    }
                    this.renderContextualPromptChips();
                } else {
                    const errorMessage = response && response.data && response.data.message
                        ? response.data.message
                        : 'Something went wrong. Please try again.';

                    if (response && response.data && response.data.status === 'token_expired') {
                        this.renderMessage('assistant', errorMessage);
                    } else {
                        this.renderMessage('assistant', this.formatFailureMessage(errorMessage, response && response.data ? response.data.suggestions : []));
                    }
                }
            }).fail(() => {
                this.clearStatusMessages();
                this.unlockBuilderCanvas();
                this.renderMessage('assistant', 'Connection error. Please check your settings.');
            }).always(() => {
                this.isProcessing = false;
                this.$sendBtn.prop('disabled', false);
                this.$input.prop('disabled', false).focus();
                this.setChipsLocked(false);
                this.syncUndoResetState();
                this.syncInputHeight();
            });
        },

    runTranslateAction: function() {
        if (this.isProcessing) {
            return;
        }

        const targetLanguage = this.$translateBtn.attr('data-gf-translate-language') || this.siteLanguage;
        const promptText = `Translate the form to ${targetLanguage}`;

        this.isProcessing = true;
        this.$sendBtn.prop('disabled', true);
        this.$undoBtn.prop('disabled', true);
        this.$resetBtn.prop('disabled', true);
        this.$translateBtn.prop('disabled', true);
        this.$input.prop('disabled', true);

        this.renderMessage('user', promptText);
        this.scrollToBottom();
        this.renderStatus('Translating your form…');

        $.post(ajaxurl, {
            action: 'translate_form',
            nonce: this.nonce,
            form_id: this.formId,
            target_language: targetLanguage
        }, (response) => {
            this.clearStatusMessages();
            this.unlockBuilderCanvas();
            if (response && response.success) {
                const responseData = response.data || {};
                const patchPlan = responseData.patch_plan || {};
                const derivedMessage = responseData.message
                    || patchPlan.success_message
                    || 'Form translated successfully.';

                this.renderMessage('assistant', derivedMessage);

                if (responseData.mutated_ids && responseData.mutated_ids.length > 0) {
                    this.pendingMutation = {
                        mutated_ids: responseData.mutated_ids,
                        lineage: responseData.lineage || null,
                        is_new: responseData.is_new || false
                    };
                    document.dispatchEvent(new Event('form-layout'));
                }
            } else {
                const errorMessage = response && response.data && response.data.message
                    ? response.data.message
                    : 'Translation failed. Please try again.';
                this.renderMessage('assistant', errorMessage);
            }
        }).fail(() => {
            this.clearStatusMessages();
            this.unlockBuilderCanvas();
            this.renderMessage('assistant', 'Connection error during translation.');
        }).always(() => {
            this.isProcessing = false;
            this.$sendBtn.prop('disabled', false);
            this.$translateBtn.prop('disabled', false);
            this.$input.prop('disabled', false).focus();
            this.setChipsLocked(false);
            this.syncUndoResetState();
            this.syncInputHeight();
        });
    },

        runDraftAction: function(action, nonce) {
            if (this.isProcessing) {
                return;
            }

            this.isProcessing = true;
            this.$sendBtn.prop('disabled', true);
            this.$undoBtn.prop('disabled', true);
            this.$resetBtn.prop('disabled', true);
            this.$input.prop('disabled', true);
            this.renderStatus(action === 'reset_ai_draft' ? 'Resetting draft…' : 'Undoing last AI action…');

            $.post(ajaxurl, {
                action: action,
                nonce: nonce,
                form_id: this.formId
            }, (response) => {
                this.clearStatusMessages();
                this.unlockBuilderCanvas();
                if (response && response.success) {
                    const message = response.data && response.data.message ? response.data.message : 'Draft updated.';
                    this.renderMessage('assistant', message);
                    this.pendingMutation = null;
                    if (action === 'reset_ai_draft') {
                        this._hasMutations = false;
                    }
                    document.dispatchEvent(new Event('form-layout'));
                    this.renderContextualPromptChips();
                    return;
                }

                const errorMessage = response && response.data && response.data.message
                    ? response.data.message
                    : 'Something went wrong. Please try again.';
                this.renderMessage('assistant', this.formatFailureMessage(errorMessage, response && response.data ? response.data.suggestions : []));
            }).fail(() => {
                this.clearStatusMessages();
                this.unlockBuilderCanvas();
                this.renderMessage('assistant', 'Connection error. Please try again.');
            }).always(() => {
                this.isProcessing = false;
                this.$sendBtn.prop('disabled', false);
                this.$input.prop('disabled', false);
                this.setChipsLocked(false);
                this.syncUndoResetState();
                this.syncInputHeight();
            });
        },

        startProgressiveDisclosure: function(requestContext = {}) {
            this.lockBuilderCanvas();
            this.clearStatusMessages();

            const isAdvisory = (requestContext.response_mode || '') === 'text_only'
                || (requestContext.semantic_intent || '') === 'builder_explanation';

            const schedule = isAdvisory
                ? [
                    { time: 0, text: 'Reviewing your question...' },
                    { time: 1500, text: 'Preparing a helpful response...' },
                    { time: 7000, text: 'Still working — this may take a moment...' },
                    { time: 15000, text: 'This is taking longer than expected. Hang tight...' }
                ]
                : [
                    { time: 0, text: 'Validating request...' },
                    { time: 1500, text: 'Generating structure...' },
                    { time: 4500, text: 'Applying draft changes...' },
                    { time: 8000, text: 'Still working — complex requests can take a moment...' },
                    { time: 15000, text: 'This is taking longer than expected. Hang tight...' }
                ];

            schedule.forEach(item => {
                const timeout = setTimeout(() => {
                    this.renderStatus(item.text);
                }, item.time);
                this.statusIntervals.push(timeout);
            });
        },

        clearStatusMessages: function() {
            this.statusIntervals.forEach(clearTimeout);
            this.statusIntervals = [];
            this.$log.find('.gf-butler-msg-system').remove();
            this.$log.find('.gf-batch-status-list').remove();
            this.$log.find('#gf-thinking-stream').fadeOut(300, function() { $(this).remove(); });
        },

        lockBuilderCanvas: function() {
            var $canvas = $('#griffinforms-app-wrapper, #griffinforms-form-area, #griffinforms-app-presentationarea').first();
            if (!$canvas.length) { return; }
            if ($canvas.css('position') === 'static') {
                $canvas.css('position', 'relative');
            }
            if (!$canvas.find('#gf-builder-lock-overlay').length) {
                $canvas.append('<div id="gf-builder-lock-overlay"></div>');
            }
            $('#gf-builder-lock-overlay').addClass('gf-builder-lock--active');
        },

        unlockBuilderCanvas: function() {
            $('#gf-builder-lock-overlay').removeClass('gf-builder-lock--active');
        },

        renderMessage: function(role, message, advisory = null) {
            const isUser = role === 'user';
            const className = isUser ? 'gf-butler-msg-user align-self-end' : 'gf-butler-msg-assistant align-self-start';
            const avatar = isUser
                ? `<div class="gf-butler-msg-avatar">${this.renderUserAvatar()}</div>`
                : `<div class="gf-butler-msg-avatar me-2">${this.renderAssistMark('xs')}</div>`;
            const visibleMessage = isUser ? message : this.getVisibleAssistantMessage(message);
            const escaped = this.escapeHtml(visibleMessage);

            const html = `
                <div class="gf-butler-msg d-flex mb-3 ${className}">
                    ${avatar}
                    <div class="gf-butler-msg-bubble p-2 rounded">
                        <span class="gf-butler-msg-text">${isUser ? escaped : ''}</span>
                        ${!isUser ? '<div class="gf-butler-msg-advisory"></div>' : ''}
                    </div>
                </div>
            `;

            const $message = $(html);
            this.$log.append($message);
            if (!isUser) {
                this.typeAssistantMessage($message.find('.gf-butler-msg-text'), visibleMessage);
                if (advisory && typeof advisory === 'object') {
                    this.renderAdvisoryPayload($message.find('.gf-butler-msg-advisory'), advisory);
                }
            }
            if (this.autoScroll) this.scrollToBottom();
        },

        getVisibleAssistantMessage: function(message) {
            const text = (message || '').toString().replace(/\s+/g, ' ').trim();
            if (!text) {
                return '';
            }

            const maxLength = 260;
            if (text.length <= maxLength) {
                return text;
            }

            const clipped = text.slice(0, maxLength);
            const sentenceBoundary = Math.max(
                clipped.lastIndexOf('.'),
                clipped.lastIndexOf('!'),
                clipped.lastIndexOf('?')
            );

            if (sentenceBoundary > 120) {
                return `${text.slice(0, sentenceBoundary + 1).trim()}…`;
            }

            const wordBoundary = clipped.lastIndexOf(' ');
            if (wordBoundary > 120) {
                return `${text.slice(0, wordBoundary).trim()}…`;
            }

            return `${clipped.trim()}…`;
        },

        renderAdvisoryPayload: function($target, advisory) {
            if (!$target || !$target.length || !advisory) {
                return;
            }

            const parts = [];
            const actions = Array.isArray(advisory.actions) ? advisory.actions : [];
            const choices = Array.isArray(advisory.choices) ? advisory.choices : [];
            const requiredInputs = Array.isArray(advisory.required_inputs) ? advisory.required_inputs : [];
            const suggestedPrompts = Array.isArray(advisory.suggested_prompts) ? advisory.suggested_prompts : [];
            const selectedContext = advisory.selected_context || {};

            const advisoryMessage = (typeof advisory.message === 'string' ? advisory.message : '').trim();
            const originalPrompt = (typeof advisory.original_prompt === 'string' ? advisory.original_prompt : '').trim();

            if (actions.length) {
                parts.push(`<div class="gf-butler-advisory-group"><div class="gf-butler-advisory-label">Suggested actions</div><div class="gf-butler-advisory-list">${actions.map((item) => this.renderSuggestionButton(item, 'gf-butler-advisory-action', selectedContext, advisoryMessage, originalPrompt)).join('')}</div></div>`);
            }

            if (choices.length) {
                parts.push(`<div class="gf-butler-advisory-group"><div class="gf-butler-advisory-label">Choose one</div><div class="gf-butler-advisory-list">${choices.map((item) => this.renderSuggestionButton(item, 'gf-butler-advisory-choice', selectedContext, advisoryMessage, originalPrompt)).join('')}</div></div>`);
            }

            if (requiredInputs.length) {
                parts.push(`<div class="gf-butler-advisory-group"><div class="gf-butler-advisory-label">Need your input</div><div class="gf-butler-advisory-list">${requiredInputs.map((item) => this.renderSuggestionButton(item, 'gf-butler-advisory-action', selectedContext, advisoryMessage, originalPrompt)).join('')}</div></div>`);
            }

            if (suggestedPrompts.length) {
                parts.push(`<div class="gf-butler-advisory-group"><div class="gf-butler-advisory-label">Suggested prompts</div><div class="gf-butler-advisory-list">${suggestedPrompts.map((prompt) => this.renderPromptButton(prompt, selectedContext)).join('')}</div></div>`);
            }

            $target.html(parts.join(''));
        },

        disableAdvisoryButtons: function($group) {
            if (!$group || !$group.length) {
                return;
            }

            $group.find('button').prop('disabled', true).attr('aria-disabled', 'true');
            $group.addClass('gf-butler-advisory-group--resolved');
        },

        renderSuggestionButton: function(item, className, selectedContext = {}, advisoryMessage = '', originalPrompt = '') {
            const definition = this.normalizeSuggestionDefinition(item);
            const label = this.escapeHtml(definition.label || '');
            const suggestedPrompt = this.escapeHtml(definition.suggested_prompt || '');
            const semanticIntent = this.escapeHtml(definition.semantic_intent || 'builder_mutation');
            const suggestionId = this.escapeHtml(definition.suggestion_id || '');
            const contextAttrs = this.renderSelectedContextAttributes(selectedContext);
            const localMutationAttrs = this.renderLocalMutationAttributes(definition.local_mutation || null);

            // Merge advisory context and original user prompt into suggestion_meta
            // so they travel with the action when the button is clicked.
            let metaObj = {};
            try { metaObj = JSON.parse(definition.suggestion_meta || '{}'); } catch(e) {}
            if (advisoryMessage) { metaObj.advisory_context = advisoryMessage; }
            if (originalPrompt) { metaObj.original_prompt = originalPrompt; }
            const suggestionMeta = this.escapeHtml(JSON.stringify(metaObj));

            return `<button type="button" class="btn btn-sm ${className}" data-gf-suggestion-id="${suggestionId}" data-gf-suggestion-meta="${suggestionMeta}" data-gf-render-label="${label}" data-gf-suggested-prompt="${suggestedPrompt}" data-gf-semantic-intent="${semanticIntent}" ${contextAttrs} ${localMutationAttrs}>${label}</button>`;
        },

        renderPromptButton: function(prompt, selectedContext = {}) {
            const escaped = this.escapeHtml(prompt || '');
            const contextAttrs = this.renderSelectedContextAttributes(selectedContext);
            return `<button type="button" class="btn btn-sm gf-butler-advisory-prompt" data-gf-suggestion-id="advisory.prompt.${this.slugifySuggestionLabel(prompt || '')}" data-gf-render-label="${escaped}" data-gf-suggested-prompt="${escaped}" data-gf-semantic-intent="builder_mutation" ${contextAttrs}>${escaped}</button>`;
        },

        observeSelectionChanges: function() {
            if (this.selectionObserver) {
                return;
            }

            const target = document.body;
            if (!target) {
                return;
            }

            let scheduled = null;
            const scheduleRefresh = () => {
                if (scheduled) {
                    window.clearTimeout(scheduled);
                }

                scheduled = window.setTimeout(() => {
                    this.renderContextualPromptChips();
                    scheduled = null;
                }, 30);
            };

            this.selectionObserver = new MutationObserver((mutations) => {
                const relevant = mutations.some((mutation) => {
                    if (mutation.type !== 'attributes') {
                        return false;
                    }

                    return ['data-gf-selected', 'data-gf-selected-child', 'data-gf-itemtype', 'data-gf-itemid', 'data-gf-fieldtype']
                        .includes(mutation.attributeName);
                });

                if (relevant) {
                    scheduleRefresh();
                }
            });

            this.selectionObserver.observe(target, {
                attributes: true,
                subtree: true,
                attributeFilter: ['data-gf-selected', 'data-gf-selected-child', 'data-gf-itemtype', 'data-gf-itemid', 'data-gf-fieldtype']
            });
        },

        renderContextualPromptChips: function() {
            if (!this.$suggestions || !this.$suggestions.length) {
                return;
            }

            const context = this.getSelectedContext() || {};
            if ((context.item_type || '') === 'field' && context.item_id) {
                this._lastFieldContext = context;
            } else if ((context.item_type || '') !== 'field') {
                this._lastFieldContext = null;
            }
            const chips = this.getSuggestionChipsForContext(context);
            const html = this.renderSuggestionChipHtml(chips, context);
            this.$suggestions.html(`<div class="d-flex flex-wrap gap-1">${html}</div>`);
            if (this.isProcessing) {
                this.$suggestions.find(this.selectors.chips).prop('disabled', true);
            }
        },

        getSuggestionChipsForContext: function(context = {}) {
            const itemType = (context.item_type || '').toString();
            const parentType = (context.parent_type || '').toString();
            const fieldType = (context.field_type || '').toString();

            const catalog = this.getSuggestionCatalog();

            if (itemType === 'field' && catalog.choice_fields[fieldType]) {
                return catalog.choice_fields[fieldType];
            }

            if (itemType === 'field' && catalog.special_fields[fieldType]) {
                return catalog.special_fields[fieldType];
            }

            if (itemType === 'field') {
                return catalog.field;
            }

            if (itemType === 'formpage') {
                return catalog.formpage.filter((definition) => {
                    const normalized = this.normalizeSuggestionDefinition(definition);
                    if (normalized.suggestion_id !== 'page.move_down') {
                        return true;
                    }

                    return !this.isSelectedPageLast(context);
                });
            }

            if (itemType === 'rowcolumn' || parentType === 'rowcolumn') {
                return this.getColumnSuggestionsForContext(context, catalog);
            }

            if (itemType === 'pagerow' || parentType === 'pagerow') {
                return catalog.pagerow;
            }

            if (itemType === 'form') {
                return catalog.form;
            }

            return catalog.generic;
        },

        renderSuggestionChipHtml: function(chips, context = {}) {
            return (Array.isArray(chips) ? chips : []).map((definition) => {
                const normalized = this.normalizeSuggestionDefinition(definition);
                const escapedLabel = this.escapeHtml(normalized.label || '');
                const attrs = this.renderSelectedContextAttributes(context);
                const meta = this.escapeHtml(normalized.suggestion_meta || '');
                const localMutationAttrs = this.renderLocalMutationAttributes(normalized.local_mutation || null);
                return `<button type="button" class="btn btn-outline-secondary btn-sm gf-butler-prompt-chip" data-gf-suggestion-id="${this.escapeHtml(normalized.suggestion_id || '')}" data-gf-suggestion-meta="${meta}" data-gf-render-label="${escapedLabel}" data-gf-suggested-prompt="${this.escapeHtml(normalized.suggested_prompt || normalized.label || '')}" data-gf-semantic-intent="${this.escapeHtml(normalized.semantic_intent || 'builder_mutation')}" ${attrs} ${localMutationAttrs}>${escapedLabel}</button>`;
            }).join('');
        },

        getColumnSuggestionsForContext: function(context = {}, catalog = null) {
            const sourceCatalog = catalog || this.getSuggestionCatalog();
            const base = Array.isArray(sourceCatalog.rowcolumn) ? sourceCatalog.rowcolumn.slice() : [];
            const columnContext = this.getSelectedColumnContext(context);

            if (!columnContext) {
                return base;
            }

            if (columnContext.total_columns <= 1) {
                return base;
            }

            return base.filter((definition) => {
                const normalized = this.normalizeSuggestionDefinition(definition);
                if (normalized.suggestion_id === 'column.make_full_width' && !columnContext.is_single) {
                    return false;
                }

                return true;
            });
        },

        getSelectedColumnContext: function(context = {}) {
            const columnId = parseInt(context.item_id || 0, 10) || 0;
            if (!columnId) {
                return null;
            }

            const $column = $(`.griffinforms-app-formlayout-rowcolumn[data-gf-itemid="${columnId}"]`).first();
            if (!$column.length) {
                return null;
            }

            const $row = $column.closest('.griffinforms-app-formlayout-pagerow');
            const $siblings = $row.find('.griffinforms-app-formlayout-rowcolumn');

            return {
                column_id: columnId,
                width: parseInt($column.attr('data-gf-width') || $column.data('gf-width') || $column.attr('class').match(/col-md-(\d+)/)?.[1] || '0', 10) || 0,
                index: $siblings.index($column),
                total_columns: $siblings.length,
                is_first: $siblings.index($column) === 0,
                is_last: $siblings.index($column) === ($siblings.length - 1),
                is_single: $siblings.length <= 1
            };
        },

        parsePackageContext: function(rawContext = {}) {
            if (typeof rawContext === 'string' && rawContext.trim() !== '') {
                try {
                    const decoded = JSON.parse(rawContext);
                    if (decoded && typeof decoded === 'object') {
                        return decoded;
                    }
                } catch (error) {}
            }

            if (rawContext && typeof rawContext === 'object') {
                return rawContext;
            }

            return {};
        },

        getPackageState: function(packageId = '') {
            const packages = this.packageContext && typeof this.packageContext.packages === 'object' ? this.packageContext.packages : {};
            return packages[packageId] || null;
        },

        isPackageAvailable: function(packageId = '') {
            const state = this.getPackageState(packageId);
            if (!state) {
                return false;
            }

            return !!state.is_active && (state.entitlement_state === 'verified_active' || state.entitlement_state === 'grace_period' || state.entitlement_state === '');
        },

        getProGenericSuggestions: function() {
            return [
                this.buildSuggestion('field.add_signature', 'Add signature field', 'Add signature field'),
                this.buildSuggestion('field.add_rating', 'Add rating field', 'Add rating field'),
                this.buildSuggestion('field.add_rich_text', 'Add rich text field', 'Add rich text field'),
                this.buildSuggestion('field.add_likert', 'Add Likert scale field', 'Add Likert scale field')
            ];
        },

        getProSpecialFieldSuggestions: function() {
            return {
                signature: [
                    this.buildSuggestion('signature.improve_label', 'Improve field label', 'Improve field label'),
                    this.buildSuggestion(
                        'signature.polish_description',
                        'Polish field description',
                        'Polish field description',
                        'builder_mutation',
                        JSON.stringify({
                            prompt_rules: [
                                'Write one short, user-facing signature description.',
                                'Tell the user to sign clearly in the box.',
                                'Keep the wording clear and professional.'
                            ],
                            ideal_field_description_examples: [
                                'Sign your name in the box below.',
                                'Use your full legal signature.',
                                'Add a clear signature before submitting.',
                                'Sign here to confirm your submission.',
                                'Provide your signature to continue.'
                            ]
                        })
                    )
                ],
                inputrating: [
                    this.buildSuggestion('inputrating.improve_label', 'Improve field label', 'Improve field label'),
                    this.buildSuggestion(
                        'inputrating.polish_description',
                        'Polish field description',
                        'Polish field description',
                        'builder_mutation',
                        JSON.stringify({
                            prompt_rules: [
                                'Write one short, user-facing rating description.',
                                'Explain what the score or stars represent.',
                                'Keep the wording clear and concise.'
                            ],
                            ideal_field_description_examples: [
                                'Choose the rating that best reflects your experience.',
                                'Select the number of stars that fits your answer.',
                                'Rate your experience using the scale below.',
                                'Pick the score that best matches your feedback.',
                                'Tell us how you would rate this item.'
                            ]
                        })
                    )
                ],
                inputrichtext: [
                    this.buildSuggestion('inputrichtext.improve_label', 'Improve field label', 'Improve field label'),
                    this.buildSuggestion(
                        'inputrichtext.polish_description',
                        'Polish field description',
                        'Polish field description',
                        'builder_mutation',
                        JSON.stringify({
                            prompt_rules: [
                                'Write one short, user-facing rich text description.',
                                'Tell the user they can format their response if needed.',
                                'Keep the wording clear and practical.'
                            ],
                            ideal_field_description_examples: [
                                'Use this field for formatted text or longer notes.',
                                'Add a detailed response with headings or bold text if needed.',
                                'Enter formatted content for this section.',
                                'Provide a longer response and use formatting as needed.',
                                'Share additional details with rich text formatting if helpful.'
                            ]
                        })
                    )
                ],
                inputlikert: [
                    this.buildSuggestion('inputlikert.improve_label', 'Improve field label', 'Improve field label'),
                    this.buildSuggestion(
                        'inputlikert.polish_row_labels',
                        'Polish row labels',
                        'Polish row labels',
                        'builder_mutation',
                        JSON.stringify({
                            prompt_rules: [
                                'Improve the existing Likert row labels — do not add or remove rows.',
                                'Preserve the number of rows and their existing ids.',
                                'Make each label clearer, more professional, and user-facing.',
                                'Use sentence case and natural language.',
                                'Do not use generic placeholders like Row 1 or Row 2.'
                            ]
                        })
                    ),
                    this.buildSuggestion(
                        'inputlikert.polish_column_labels',
                        'Polish column labels',
                        'Polish column labels',
                        'builder_mutation',
                        JSON.stringify({
                            prompt_rules: [
                                'Improve the existing Likert column labels — do not add or remove columns.',
                                'Preserve the number of columns and their existing ids.',
                                'Make each label clearer and more user-facing.',
                                'Keep the scale consistent and natural to read.',
                                'Do not use generic placeholders like Column 1 or Col 2.'
                            ]
                        })
                    )
                ]
            };
        },

        getSuggestionCatalog: function() {
            const proAvailable = this.isPackageAvailable('pro');
            const generic = [
                this.buildSuggestion('field.add_email', 'Add email field', 'Add email field'),
                this.buildSuggestion('field.add_phone', 'Add phone field', 'Add phone field'),
                this.buildSuggestion('field.add_message', 'Add message field', 'Add message field'),
                this.buildSuggestion('field.add_file_upload', 'Add file upload field', 'Add file upload field')
            ];

            if (proAvailable) {
                generic.push(...this.getProGenericSuggestions());
            }

            return {
                generic: generic,
                form: [
                    this.buildSuggestion('form.rewrite_name', 'Rewrite form name', 'Rewrite form name'),
                    this.buildSuggestion(
                        'form.improve_description',
                        'Improve form description',
                        'Improve form description',
                        'builder_mutation',
                        JSON.stringify({
                            prompt_rules: [
                                'Write 3-4 sentences.',
                                'Keep it public-facing, warm, and professional.',
                                'Align the wording with the form name, form heading, page headings, page descriptions, row headings, row descriptions, field labels, field options, and field descriptions.'
                            ]
                        })
                    ),
                    this.buildSuggestion('form.update_submit_label', 'Update submit button label', 'Update submit button label'),
                    this.buildSuggestion(
                        'form.translate_to_site_language',
                        this.buildTranslateToSiteLanguageLabel(),
                        this.buildTranslateToSiteLanguageLabel(),
                        'builder_mutation',
                        JSON.stringify({
                            target_language: this.siteLanguage || '',
                            prompt_rules: [
                                'Translate the form copy into the site language.',
                                'Keep the result natural and user-facing.',
                                'Preserve the meaning of labels, descriptions, and button text.'
                            ]
                        })
                    ),
                    this.buildSuggestion('form.polish_copy', 'Polish form copy', 'Polish form copy'),
                    this.buildSuggestion('form.apply_random_theme', 'Apply random theme', 'Apply random theme', 'builder_mutation', 'theme_selection')
                ],
                formpage: [
                    this.buildSuggestion('page.improve_heading', 'Improve page heading', 'Improve page heading'),
                    this.buildSuggestion('page.improve_description', 'Improve page description', 'Improve page description'),
                    this.buildSuggestion('page.move_up', 'Move page up', 'Move page up'),
                    this.buildSuggestion('page.move_down', 'Move page down', 'Move page down'),
                    this.buildSuggestion('page.add_section_2col', 'Add section with 2 columns', 'Add section with 2 columns'),
                    this.buildSuggestion('page.add_section_3col', 'Add section with 3 columns', 'Add section with 3 columns'),
                    this.buildSuggestion(
                        'page.polish_copy',
                        'Polish Page Copy',
                        'Polish Page Copy',
                        'builder_mutation',
                        JSON.stringify({
                            prompt_rules: [
                                'Rewrite the page heading, page description, included row headings, included row descriptions, included field labels, and included field descriptions.',
                                'Keep the wording user-facing and aligned with the page purpose.',
                                'Preserve meaning while improving clarity, tone, and consistency.'
                            ],
                            ideal_page_heading_examples: [
                                'Personal Information',
                                'Contact Details',
                                'Delivery Preferences'
                            ],
                            ideal_page_description_examples: [
                                'Tell us a little more so we can complete your request.',
                                'Use this section to share the details requested below.',
                                'Provide the information that best matches this page.'
                            ],
                            ideal_row_heading_examples: [
                                'Contact Information',
                                'Reservation Details',
                                'Billing Information'
                            ],
                            ideal_row_description_examples: [
                                'Share the details requested in this section.',
                                'Provide the information that helps us continue.',
                                'Use this section to add any relevant context.'
                            ],
                            ideal_field_label_examples: [
                                'Full Name',
                                'Email Address',
                                'Phone Number'
                            ],
                            ideal_field_description_examples: [
                                'Enter your full name as it should appear on the form.',
                                'Use a valid email address where we can contact you.',
                                'Add a phone number we can use if we need more information.'
                            ]
                        })
                    )
                ],
                pagerow: [
                    this.buildSuggestion(
                        'row.improve_heading',
                        'Improve row heading',
                        'Improve row heading',
                        'builder_mutation',
                        JSON.stringify({
                            prompt_rules: [
                                'Write one concise public-facing row heading.',
                                'Use the fields in the row and the surrounding form structure as context.',
                                'Keep it professional and aligned with the form tone.'
                            ],
                            ideal_row_heading_examples: [
                                'Contact Information',
                                'Reservation Details',
                                'Event Preferences',
                                'Billing Information',
                                'Additional Comments'
                            ]
                        })
                    ),
                    this.buildSuggestion(
                        'row.polish_description',
                        'Polish row description',
                        'Polish row description',
                        'builder_mutation',
                        JSON.stringify({
                            prompt_rules: [
                                'Write one public-facing row description.',
                                'Use the fields in the row and the surrounding form structure as context.',
                                'Keep it warm, professional, and aligned with the form tone.'
                            ]
                        })
                    ),
                    this.buildSuggestion('row.move_up', 'Move row up', 'Move row up'),
                    this.buildSuggestion('row.move_down', 'Move row down', 'Move row down')
                ],
                rowcolumn: [
                    this.buildSuggestion(
                        'column.increase_width',
                        'Increase width',
                        'Increase width',
                        'builder_mutation',
                        '',
                        {
                            mutation_type: 'local_column_width',
                            action: 'increase_width',
                            delta: 2
                        }
                    ),
                    this.buildSuggestion(
                        'column.shrink_width',
                        'Shrink width',
                        'Shrink width',
                        'builder_mutation',
                        '',
                        {
                            mutation_type: 'local_column_width',
                            action: 'shrink_width',
                            delta: -2
                        }
                    ),
                    this.buildSuggestion(
                        'column.move_left',
                        'Move left',
                        'Move left',
                        'builder_mutation',
                        '',
                        {
                            mutation_type: 'local_column_move',
                            action: 'move_left'
                        }
                    ),
                    this.buildSuggestion(
                        'column.move_right',
                        'Move right',
                        'Move right',
                        'builder_mutation',
                        '',
                        {
                            mutation_type: 'local_column_move',
                            action: 'move_right'
                        }
                    ),
                    this.buildSuggestion(
                        'column.make_full_width',
                        'Make full width',
                        'Make full width',
                        'builder_mutation',
                        '',
                        {
                            mutation_type: 'local_column_width',
                            action: 'make_full_width',
                            width: 12
                        }
                    )
                ],
                field: [
                    this.buildSuggestion('field.improve_label', 'Improve field label', 'Improve field label'),
                    this.buildSuggestion(
                        'field.polish_description',
                        'Polish field description',
                        'Polish field description',
                        'builder_mutation',
                        JSON.stringify({
                            prompt_rules: [
                                'Write one short, public-facing help sentence for the field.',
                                'Tell the user what to enter or why the field is needed — do not describe the field type.',
                                'Keep it under 20 words.',
                                'Do not start with "This field", "Use this field", or "This is a".',
                                'Use plain, warm, conversational language.'
                            ],
                            ideal_field_description_examples: [
                                'Enter your full name as it appears on official documents.',
                                'We will use this to send you a confirmation.',
                                'Let us know the best way to reach you.',
                                'Include any details that will help us process your request.',
                                'Enter the address where you would like us to send correspondence.'
                            ]
                        })
                    ),
                    this.buildSuggestion('field.update_placeholder', 'Update placeholder', 'Update placeholder'),
                    this.buildSuggestion('field.turn_label_into_question', 'Turn label into a question', 'Turn label into a question')
                ],
                choice_fields: {
                    selectfield: [
                    this.buildSuggestion('selectfield.improve_label', 'Improve label', 'Improve label'),
                        this.buildSuggestion(
                            'selectfield.polish_description',
                            'Polish description',
                            'Polish description',
                            'builder_mutation',
                            JSON.stringify({
                                prompt_rules: [
                                    'Write one short, user-facing dropdown description.',
                                    'Make the purpose of the choices clear.',
                                    'Keep the wording natural and easy to scan.'
                                ],
                                ideal_field_description_examples: [
                                    'Choose the option that best matches your request.',
                                    'Select one answer from the list below.',
                                    'Pick the category that applies to you.',
                                    'Choose the service or topic that fits best.',
                                    'Select the option that best describes your preference.'
                                ]
                            })
                        ),
                        this.buildSuggestion(
                            'selectfield.refine_options',
                            'Refine options',
                            'Refine options',
                            'builder_mutation',
                            JSON.stringify({
                                prompt_rules: [
                                    'Improve the existing option labels — do not add or remove options.',
                                    'Preserve the number of options and their underlying values.',
                                    'Make labels clearer, more consistent, and easier to scan.',
                                    'Use sentence case and natural language.',
                                    'Do not use generic placeholders like Option 1 or Choice A.'
                                ],
                                ideal_option_label_examples: [
                                    'General Inquiry',
                                    'Product Support',
                                    'Billing Question',
                                    'Partnership Opportunity',
                                    'Press or Media',
                                    'Other'
                                ]
                            })
                        ),
                        this.buildSuggestion(
                            'selectfield.add_option_groups',
                            'Add option groups',
                            'Add option groups',
                            'builder_mutation',
                            JSON.stringify({
                                prompt_rules: [
                                    'Reorganize the existing options into semantic optgroup groups.',
                                    'Group options by their logical category or theme.',
                                    'Each group must have a short, descriptive group name.',
                                    'Preserve all existing options — do not add or remove any.',
                                    'Keep option values unchanged.',
                                    'If all options belong to one natural group, use a single group with a clear name.'
                                ],
                                ideal_group_name_examples: [
                                    'Contact Topics',
                                    'Product Categories',
                                    'Support Type',
                                    'Account Options',
                                    'Service Areas'
                                ]
                            })
                        )
                    ],
                    inputcheckbox: [
                        this.buildSuggestion('inputcheckbox.improve_label', 'Improve field label', 'Improve field label'),
                        this.buildSuggestion(
                            'inputcheckbox.polish_description',
                            'Polish field description',
                            'Polish field description',
                            'builder_mutation',
                            JSON.stringify({
                                prompt_rules: [
                                    'Write one short, user-facing checkbox description.',
                                    'Explain what the user is confirming or selecting.',
                                    'Keep the wording clear and concise.'
                                ],
                                ideal_field_description_examples: [
                                    'Select all options that apply to your request.',
                                    'Tick this box if you would like to receive updates.',
                                    'Choose the items you want included in your submission.',
                                    'Select any preferences that match your needs.',
                                    'Use this section to confirm the choices below.'
                                ]
                            })
                        ),
                        this.buildSuggestion('inputcheckbox.divide_2_columns', 'Divide options into 2 columns', 'Divide options into 2 columns'),
                        this.buildSuggestion('inputcheckbox.divide_3_columns', 'Divide options into 3 columns', 'Divide options into 3 columns')
                    ],
                    inputradio: [
                        this.buildSuggestion('inputradio.improve_label', 'Improve field label', 'Improve field label'),
                        this.buildSuggestion(
                            'inputradio.polish_description',
                            'Polish field description',
                            'Polish field description',
                            'builder_mutation',
                            JSON.stringify({
                                prompt_rules: [
                                    'Write one short, user-facing radio description.',
                                    'Explain that the user should pick a single option.',
                                    'Keep the wording natural and direct.'
                                ],
                                ideal_field_description_examples: [
                                    'Select one option that best matches your request.',
                                    'Choose the answer that applies to you.',
                                    'Pick the single option that fits best.',
                                    'Select one preference from the list below.',
                                    'Choose the response that best describes your situation.'
                                ]
                            })
                        ),
                        this.buildSuggestion('inputradio.divide_2_columns', 'Divide options into 2 columns', 'Divide options into 2 columns'),
                        this.buildSuggestion('inputradio.divide_3_columns', 'Divide options into 3 columns', 'Divide options into 3 columns')
                    ]
                },
                special_fields: {
                    textarea: [
                        this.buildSuggestion('textarea.improve_label', 'Improve field label', 'Improve field label'),
                        this.buildSuggestion(
                            'textarea.polish_description',
                            'Polish field description',
                            'Polish field description',
                            'builder_mutation',
                            JSON.stringify({
                                prompt_rules: [
                                    'Write one short, user-facing textarea description.',
                                    'Give the user a clear sense of what to enter.',
                                    'Keep the wording helpful and not overly formal.'
                                ],
                                ideal_field_description_examples: [
                                    'Tell us anything else we should know about your request.',
                                    'Use this space to add any helpful details.',
                                    'Share any notes that will help us respond.',
                                    'Enter additional information if needed.',
                                    'Provide any context that supports your answer.'
                                ]
                            })
                        ),
                        this.buildSuggestion('textarea.update_placeholder', 'Update placeholder', 'Update placeholder'),
                        this.buildSuggestion(
                            'textarea.add_char_limit_hint',
                            'Add character limit hint',
                            'Add character limit hint',
                            'builder_mutation',
                            JSON.stringify({
                                prompt_rules: [
                                    'Write one short field description that tells the user how much to write.',
                                    'Use the field constraints provided below — do not guess or invent values.',
                                    'If both min_chars and max_chars are set, state the range (e.g. "Please write between 50 and 500 characters.").',
                                    'If only max_chars is set, say the response must be under that limit (e.g. "Keep your response under 300 characters.").',
                                    'If only min_chars is set, say the response must be at least that length (e.g. "Please write at least 100 characters.").',
                                    'If no constraints are set, write a short contextual hint from the field label and form heading that guides the expected length or depth of response.',
                                    'Keep the result under 20 words. Do NOT return a generic sentence like "Enter your message here."'
                                ],
                                ideal_char_limit_hint_examples: [
                                    'Please write between 50 and 500 characters.',
                                    'Keep your response under 300 characters.',
                                    'Write at least 100 characters describing your experience.',
                                    'Share a brief note — no more than 200 characters.',
                                    'Describe your request in a few sentences, up to 1000 characters.'
                                ]
                            })
                        )
                    ],
                    inputemail: [
                        this.buildSuggestion('inputemail.improve_label', 'Improve field label', 'Improve field label'),
                        this.buildSuggestion(
                            'inputemail.polish_description',
                            'Polish field description',
                            'Polish field description',
                            'builder_mutation',
                            JSON.stringify({
                                prompt_rules: [
                                    'Write one short, user-facing email description.',
                                    'Explain that a valid email address is needed.',
                                    'Keep the wording clear and reassuring.'
                                ],
                                ideal_field_description_examples: [
                                    'Use a valid email address where we can contact you.',
                                    'Enter the email address you use most often.',
                                    'Provide an email address we can send updates to.',
                                    'Use your preferred email for this form.',
                                    'Add an email address we can use if we need more information.'
                                ]
                            })
                        ),
                        this.buildSuggestion('inputemail.update_placeholder', 'Update placeholder', 'Update placeholder'),
                        this.buildSuggestion('inputemail.add_email_format_hint', 'Add email format hint', 'Add a short description that tells the user to enter a valid email address in the format name@example.com')
                    ],
                    inputphone: [
                        this.buildSuggestion('inputphone.improve_label', 'Improve field label', 'Improve field label'),
                        this.buildSuggestion(
                            'inputphone.polish_description',
                            'Polish field description',
                            'Polish field description',
                            'builder_mutation',
                            JSON.stringify({
                                prompt_rules: [
                                    'Write one short, user-facing phone description.',
                                    'Explain what kind of number should be entered.',
                                    'Keep the wording clear and easy to follow.'
                                ],
                                ideal_field_description_examples: [
                                    'Add a phone number we can use if we need more information.',
                                    'Enter the best phone number to reach you.',
                                    'Use a number we can call during business hours.',
                                    'Provide your preferred contact number.',
                                    'Include a phone number that is currently in use.'
                                ]
                            })
                        ),
                        this.buildSuggestion('inputphone.update_placeholder', 'Update placeholder', 'Update placeholder'),
                        this.buildSuggestion('inputphone.turn_label_into_question', 'Turn label into a question', 'Turn label into a question')
                    ],
                    inputaddress: [
                        this.buildSuggestion('inputaddress.improve_label', 'Improve field label', 'Improve field label'),
                        this.buildSuggestion(
                            'inputaddress.polish_description',
                            'Polish field description',
                            'Polish field description',
                            'builder_mutation',
                            JSON.stringify({
                                prompt_rules: [
                                    'Write one short, user-facing address description.',
                                    'Clarify that a complete address is needed when relevant.',
                                    'Keep the wording practical and concise.'
                                ],
                                ideal_field_description_examples: [
                                    'Enter your full mailing address.',
                                    'Use the address where we should send your delivery.',
                                    'Provide the address you want us to use for contact.',
                                    'Add your street address, city, and postcode.',
                                    'Enter the location details that best apply here.'
                                ]
                            })
                        ),
                        this.buildSuggestion('inputaddress.update_placeholder', 'Update placeholder', 'Update placeholder'),
                        this.buildSuggestion(
                            'inputaddress.improve_sublabels',
                            'Improve sub-field labels',
                            'Improve sub-field labels',
                            'builder_mutation',
                            JSON.stringify({
                                prompt_rules: [
                                    'Update the field_settings.labels object with improved sub-field labels for line1, line2, city, state, postal, and country.',
                                    'Use the form heading, parent page heading, and field label to infer the address context (e.g. shipping, billing, mailing, residential).',
                                    'Each sub-label must be short, public-facing, and specific to the inferred context.',
                                    'Do not change any other field properties — only field_settings.labels.',
                                    'Always include all six keys: line1, line2, city, state, postal, country.'
                                ],
                                ideal_sublabel_examples: [
                                    { context: 'shipping', line1: 'Street Address', line2: 'Apt, Suite, Unit', city: 'City', state: 'State / Province', postal: 'ZIP / Postal Code', country: 'Country' },
                                    { context: 'billing', line1: 'Billing Street', line2: 'Suite or Floor', city: 'City', state: 'State', postal: 'Postal Code', country: 'Country' },
                                    { context: 'residential', line1: 'Home Address', line2: 'Apartment or Unit', city: 'City', state: 'State', postal: 'Postcode', country: 'Country' }
                                ]
                            })
                        )
                    ],
                    inputnumber: [
                        this.buildSuggestion('inputnumber.improve_label', 'Improve field label', 'Improve field label'),
                        this.buildSuggestion(
                            'inputnumber.polish_description',
                            'Polish field description',
                            'Polish field description',
                            'builder_mutation',
                            JSON.stringify({
                                prompt_rules: [
                                    'Write one short, user-facing number description.',
                                    'Clarify what the number represents.',
                                    'Keep the wording simple and direct.'
                                ],
                                ideal_field_description_examples: [
                                    'Enter the quantity that applies to your request.',
                                    'Use a number that matches your answer.',
                                    'Provide the amount you want to submit.',
                                    'Add a numeric value where required.',
                                    'Enter the total number requested here.'
                                ]
                            })
                        ),
                        this.buildSuggestion('inputnumber.update_placeholder', 'Update placeholder', 'Update placeholder'),
                        this.buildSuggestion(
                            'inputnumber.add_range_hint',
                            'Add range hint',
                            'Add range hint',
                            'builder_mutation',
                            JSON.stringify({
                                prompt_rules: [
                                    'Write one short field description that tells the user what number to enter.',
                                    'Check the field validations for min, max, number_type, allow_decimal, and multiples.',
                                    'If min and max are set, state the allowed range (e.g. "Enter a number between 1 and 100.").',
                                    'If only min is set, say the number must be at least that value.',
                                    'If only max is set, say the number must be no more than that value.',
                                    'If number_type is positive, say the number must be positive.',
                                    'If allow_decimal is false, say whole numbers only.',
                                    'If multiples is set, say the number must be a multiple of that value.',
                                    'If no constraints are set, write a short contextual hint from the field label and form heading.',
                                    'Keep the result under 20 words. Do NOT return a generic sentence like "Enter a number."'
                                ],
                                ideal_range_hint_examples: [
                                    'Enter a number between 1 and 10.',
                                    'Must be a whole number of 5 or more.',
                                    'Enter a positive number up to 1000.',
                                    'Use a multiple of 5, for example 5, 10, or 15.',
                                    'Enter the number of guests, between 1 and 20.'
                                ]
                            })
                        )
                    ],
                    inputdate: [
                        this.buildSuggestion('inputdate.improve_label', 'Improve field label', 'Improve field label'),
                        this.buildSuggestion(
                            'inputdate.polish_description',
                            'Polish field description',
                            'Polish field description',
                            'builder_mutation',
                            JSON.stringify({
                                prompt_rules: [
                                    'Write one short, user-facing date description.',
                                    'Clarify what date should be entered.',
                                    'Keep the wording clear and calendar-friendly.'
                                ],
                                ideal_field_description_examples: [
                                    'Choose the date that best applies to your request.',
                                    'Enter your preferred appointment date.',
                                    'Provide the date you want us to use.',
                                    'Select a date in the format shown below.',
                                    'Add the date the event or request will occur.'
                                ]
                            })
                        ),
                        this.buildSuggestion('inputdate.update_placeholder', 'Update placeholder', 'Update placeholder'),
                        this.buildSuggestion(
                            'inputdate.add_date_window_hint',
                            'Add date window hint',
                            'Add date window hint',
                            'builder_mutation',
                            JSON.stringify({
                                prompt_rules: [
                                    'Write one short field description that tells the user what date to select.',
                                    'Use the field constraints provided below — do not guess or invent values.',
                                    'If date_type is fixed_date and both min_date and max_date are set, state the allowed date range.',
                                    'If date_type is fixed_date and only min_date is set, say the date must be on or after that date.',
                                    'If date_type is fixed_date and only max_date is set, say the date must be on or before that date.',
                                    'If date_type is relative_date, use the relative_min and relative_max values to describe the window in plain language (e.g. "Select a date within the next 30 days.").',
                                    'If day_type is "weekdays", say weekdays only.',
                                    'If day_type is "weekends", say weekends only.',
                                    'If day_type is a specific day (e.g. "sunday", "monday"), say that specific day only (e.g. "Sundays only").',
                                    'If no constraints are set, write a short contextual hint from the field label and form heading.',
                                    'Keep the result under 20 words. Do NOT return a generic sentence like "Select a date."'
                                ],
                                ideal_date_window_hint_examples: [
                                    'Choose a date between 1 January and 31 March 2025.',
                                    'Select a weekday within the next 30 days.',
                                    'Pick a date on or after today.',
                                    'Choose your preferred appointment date before 31 December 2025.',
                                    'Select a date up to 3 months from today, weekdays only.'
                                ]
                            })
                        )
                    ],
                    inputtime: [
                        this.buildSuggestion('inputtime.improve_label', 'Improve field label', 'Improve field label'),
                        this.buildSuggestion(
                            'inputtime.polish_description',
                            'Polish field description',
                            'Polish field description',
                            'builder_mutation',
                            JSON.stringify({
                                prompt_rules: [
                                    'Write one short, user-facing time field description.',
                                    'Clarify what time the user should enter and in what context.',
                                    'Keep the wording clear and helpful.'
                                ],
                                ideal_field_description_examples: [
                                    'Select the time that works best for your appointment.',
                                    'Enter the time you would like us to contact you.',
                                    'Choose the time you prefer for this booking.',
                                    'Provide the start time for your event.',
                                    'Select your preferred time using the picker above.'
                                ]
                            })
                        ),
                        this.buildSuggestion('inputtime.update_placeholder', 'Update placeholder', 'Update placeholder'),
                        this.buildSuggestion('inputtime.turn_label_into_question', 'Turn label into a question', 'Turn label into a question')
                    ],
                    inputpassword: [
                        this.buildSuggestion('inputpassword.improve_label', 'Improve field label', 'Improve field label'),
                        this.buildSuggestion(
                            'inputpassword.polish_description',
                            'Polish field description',
                            'Polish field description',
                            'builder_mutation',
                            JSON.stringify({
                                prompt_rules: [
                                    'Write one short, user-facing password field description.',
                                    'Give the user a clear sense of what to enter and any requirements.',
                                    'Keep the wording reassuring and concise.'
                                ],
                                ideal_field_description_examples: [
                                    'Choose a strong password you will remember.',
                                    'Enter a password to protect your account.',
                                    'Use at least 8 characters for your password.',
                                    'Create a secure password for this submission.',
                                    'Enter your chosen password to continue.'
                                ]
                            })
                        ),
                        this.buildSuggestion('inputpassword.update_placeholder', 'Update placeholder', 'Update placeholder'),
                        this.buildSuggestion('inputpassword.turn_label_into_question', 'Turn label into a question', 'Turn label into a question')
                    ],
                    termsconditions: [
                        this.buildSuggestion('termsconditions.improve_label', 'Improve field label', 'Improve field label'),
                        this.buildSuggestion(
                            'termsconditions.polish_description',
                            'Polish field description',
                            'Polish field description',
                            'builder_mutation',
                            JSON.stringify({
                                prompt_rules: [
                                    'Write one short, user-facing terms field description.',
                                    'Clarify what the user is agreeing to.',
                                    'Keep the wording clear and legally neutral.'
                                ],
                                ideal_field_description_examples: [
                                    'Please read and accept our terms before submitting.',
                                    'Review the terms and conditions linked below before proceeding.',
                                    'Tick the box to confirm you have read and agree to our terms.',
                                    'By submitting this form you agree to the terms linked below.',
                                    'Read our terms of service and check the box to continue.'
                                ]
                            })
                        ),
                        this.buildSuggestion(
                            'termsconditions.improve_checkbox_label',
                            'Improve checkbox label',
                            'Improve checkbox label',
                            'builder_mutation',
                            JSON.stringify({
                                prompt_rules: [
                                    'Rewrite the field_settings.checkbox_label with a clear, concise confirmation phrase.',
                                    'The label must read as something the user actively agrees to.',
                                    'Keep it under 12 words.',
                                    'Do not change any other field properties — only field_settings.checkbox_label.'
                                ],
                                ideal_checkbox_label_examples: [
                                    'I agree to the terms and conditions.',
                                    'I have read and accept the terms.',
                                    'I accept the terms of service.',
                                    'I confirm I have read the terms.',
                                    'I agree to the privacy policy and terms.'
                                ]
                            })
                        ),
                        this.buildSuggestion(
                            'termsconditions.improve_anchor_text',
                            'Improve anchor text',
                            'Improve anchor text',
                            'builder_mutation',
                            JSON.stringify({
                                prompt_rules: [
                                    'Rewrite the field_settings.anchor_text with a clear, inviting link label.',
                                    'The text should encourage the user to click and read the terms.',
                                    'Keep it under 8 words.',
                                    'Do not change any other field properties — only field_settings.anchor_text.'
                                ],
                                ideal_anchor_text_examples: [
                                    'Read our terms and conditions',
                                    'View the full terms of service',
                                    'Read the privacy policy and terms',
                                    'Review our terms before agreeing',
                                    'Click here to read the terms'
                                ]
                            })
                        )
                    ],
                    payment: [
                        this.buildSuggestion('payment.improve_label', 'Improve field label', 'Improve field label'),
                        this.buildSuggestion(
                            'payment.polish_description',
                            'Polish field description',
                            'Polish field description',
                            'builder_mutation',
                            JSON.stringify({
                                prompt_rules: [
                                    'Write one short, user-facing payment field description.',
                                    'Explain what the user is selecting or purchasing.',
                                    'Keep the wording clear and professional.'
                                ],
                                ideal_field_description_examples: [
                                    'Choose the items you would like to include in your order.',
                                    'Select the products or services that apply to your request.',
                                    'Pick the options you want to add to your submission.',
                                    'Review the available items and select what you need.',
                                    'Choose from the options below to complete your order.'
                                ]
                            })
                        ),
                        this.buildSuggestion(
                            'payment.improve_product_descriptions',
                            'Improve product descriptions',
                            'Improve product descriptions',
                            'builder_mutation',
                            JSON.stringify({
                                prompt_rules: [
                                    'Rewrite the description of each product in field_options to be more engaging and clear.',
                                    'Keep each product description under 20 words.',
                                    'Do not change product names or prices — only the description field of each product.',
                                    'Use plain, benefit-focused language that helps the user understand what they are getting.',
                                    'Preserve all existing products — do not add or remove any.'
                                ],
                                ideal_product_description_examples: [
                                    'Everything you need to get started quickly.',
                                    'Includes priority support and full feature access.',
                                    'A complete package with no hidden extras.',
                                    'Best value for ongoing monthly use.',
                                    'Ideal for teams who need flexible access.'
                                ]
                            })
                        )
                    ],
                    fileupload: [
                        this.buildSuggestion('fileupload.improve_label', 'Improve field label', 'Improve field label'),
                        this.buildSuggestion(
                            'fileupload.polish_description',
                            'Polish field description',
                            'Polish field description',
                            'builder_mutation',
                            JSON.stringify({
                                prompt_rules: [
                                    'Write one short, user-facing upload description.',
                                    'Explain what kinds of files should be attached.',
                                    'Keep the wording clear and helpful.'
                                ],
                                ideal_field_description_examples: [
                                    'Upload a file that helps us review your submission.',
                                    'Attach the document or image we requested above.',
                                    'Add a file that supports your response.',
                                    'Upload any supporting files we should review.',
                                    'Attach the file that best matches your request.'
                                ]
                            })
                        ),
                        this.buildSuggestion(
                            'fileupload.describe_upload_requirements',
                            'Describe upload requirements',
                            'Describe upload requirements',
                            'builder_mutation',
                            JSON.stringify({
                                prompt_rules: [
                                    'Write one short field description that tells the user exactly what to upload.',
                                    'Use the field constraints provided below — do not guess or invent values.',
                                    'If custom_mimes are set, name the accepted file types in plain language (e.g. "PDF, JPG, or PNG").',
                                    'If max_size_mb is set, state the maximum file size.',
                                    'If max_files is set and greater than 1, say how many files can be uploaded.',
                                    'Combine all constraints naturally into one sentence.',
                                    'If no constraints are set, write a short contextual hint from the field label and form heading.',
                                    'Keep the result under 25 words. Do NOT return a generic sentence like "Upload a file."'
                                ],
                                ideal_upload_requirement_examples: [
                                    'Upload a PDF or JPG, up to 5 MB.',
                                    'Attach up to 3 images in JPG or PNG format, each under 2 MB.',
                                    'Upload your CV as a PDF, maximum 10 MB.',
                                    'Attach a scanned copy of your ID in PDF or JPG format.',
                                    'Upload up to 5 files — PDFs and images accepted, each under 8 MB.'
                                ]
                            })
                        )
                    ],
                    ...(proAvailable ? this.getProSpecialFieldSuggestions() : {})
                }
            };
        },

        buildSuggestion: function(suggestionId, label, suggestedPrompt, semanticIntent = 'builder_mutation', suggestionMeta = '', localMutation = null) {
            return {
                suggestion_id: suggestionId,
                label: label,
                suggested_prompt: suggestedPrompt,
                semantic_intent: semanticIntent,
                suggestion_meta: suggestionMeta || '',
                local_mutation: localMutation || null
            };
        },

        buildTranslateToSiteLanguageLabel: function() {
            const language = this.normalizeSiteLanguage(this.siteLanguage);
            return language ? `Translate to ${language}` : 'Translate to site language';
        },

        isSelectedPageLast: function(context = {}) {
            if (!context || (context.item_type || '').toString() !== 'formpage') {
                return false;
            }

            const $selectedPage = this.getSelectedPageElement(context.item_id);
            if (!$selectedPage.length) {
                return false;
            }

            const pageIndex = parseInt($selectedPage.attr('data-gf-index') || '0', 10);
            const totalPages = $('.griffinforms-app-formlayout-formpage').length;
            if (!Number.isFinite(pageIndex) || !Number.isFinite(totalPages) || totalPages <= 0) {
                return false;
            }

            return pageIndex >= (totalPages - 1);
        },

        getSelectedPageElement: function(pageId) {
            const normalizedId = parseInt(pageId || 0, 10) || 0;
            if (!normalizedId) {
                return $();
            }

            return $(`.griffinforms-app-formlayout-formpage[data-gf-itemid="${normalizedId}"]`).first();
        },

        normalizeSiteLanguage: function(language) {
            const value = (language || '').toString().trim();
            if (!value) {
                return '';
            }

            const normalized = value.replace('_', '-');
            const parts = normalized.split('-');
            if (!parts.length) {
                return value;
            }

            const lang = parts[0].toLowerCase();
            const region = parts[1] ? parts[1].toUpperCase() : '';
            const languageNames = {
                en: 'English',
                es: 'Spanish',
                fr: 'French',
                de: 'German',
                it: 'Italian',
                pt: 'Portuguese',
                nl: 'Dutch',
                pl: 'Polish',
                ja: 'Japanese',
                zh: 'Chinese',
                hi: 'Hindi',
                ar: 'Arabic'
            };

            const name = languageNames[lang] || lang;
            return region ? `${name} (${region})` : name;
        },

        normalizeSuggestionDefinition: function(item) {
            if (typeof item === 'string') {
                const label = item.toString();
                return {
                    suggestion_id: `legacy.${this.slugifySuggestionLabel(label)}`,
                    label: label,
                    suggested_prompt: label,
                    semantic_intent: 'builder_mutation',
                    suggestion_meta: '',
                    local_mutation: null
                };
            }

            const definition = item && typeof item === 'object' ? item : {};
            const label = (definition.label || '').toString();
            return {
                suggestion_id: (definition.suggestion_id || `legacy.${this.slugifySuggestionLabel(label)}`).toString(),
                label: label,
                suggested_prompt: (definition.suggested_prompt || label).toString(),
                semantic_intent: (definition.semantic_intent || 'builder_mutation').toString(),
                suggestion_meta: (definition.suggestion_meta || '').toString(),
                local_mutation: definition.local_mutation || null
            };
        },

        slugifySuggestionLabel: function(value) {
            return (value || '')
                .toString()
                .toLowerCase()
                .replace(/['"]/g, '')
                .replace(/[^a-z0-9]+/g, '.')
                .replace(/^\.+|\.+$/g, '');
        },

        runSuggestedAction: function(prompt, renderLabel, semanticIntent, selectedContext, suggestionId = '', suggestionMeta = '') {
            let advisoryContext = '';
            let originalPrompt = '';
            try {
                const meta = JSON.parse(suggestionMeta || '{}');
                advisoryContext = (meta.advisory_context || '').toString().trim();
                originalPrompt = (meta.original_prompt || '').toString().trim();
            } catch(e) {}

            this.submitPrompt(prompt, {
                renderUser: true,
                userText: renderLabel,
                selectedContextOverride: selectedContext,
                suggestionContext: this.buildSuggestionContext(suggestionId, suggestionMeta, renderLabel, prompt),
                requestContext: {
                    surface: 'form_builder',
                    origin: 'ui_button',
                    response_mode: 'patch_required',
                    semantic_intent: semanticIntent || 'builder_mutation',
                    suggestion_source: 'butler_suggestion_pill',
                    advisory_context: advisoryContext,
                    original_prompt: originalPrompt
                }
            });
        },

        runChatModeSelection: function(prompt, renderLabel, semanticIntent, selectedContext, suggestionId = '', suggestionMeta = '') {
            this.submitPrompt(renderLabel || prompt, {
                renderUser: true,
                userText: renderLabel || prompt,
                selectedContextOverride: selectedContext,
                suggestionContext: this.buildSuggestionContext(suggestionId, suggestionMeta, renderLabel, prompt),
                requestContext: {
                    surface: 'form_builder',
                    origin: 'ui_button',
                    response_mode: 'text_only',
                    semantic_intent: semanticIntent || 'builder_explanation',
                    suggestion_source: 'butler_suggestion_pill'
                }
            });
        },

        buildSuggestionContext: function(suggestionId, suggestionMeta, renderLabel, prompt) {
            return {
                source: 'butler_suggestion_pill',
                suggestion_id: (suggestionId || '').toString(),
                suggestion_meta: (suggestionMeta || '').toString(),
                render_label: (renderLabel || '').toString(),
                suggested_prompt: (prompt || '').toString()
            };
        },

        renderSelectedContextAttributes: function(selectedContext = {}) {
            const context = selectedContext && typeof selectedContext === 'object' ? selectedContext : {};
            const attrs = {
                'data-gf-selected-item-type': (context.item_type || '').toString(),
                'data-gf-selected-item-id': String(parseInt(context.item_id || 0, 10) || 0),
                'data-gf-selected-parent-type': (context.parent_type || '').toString(),
                'data-gf-selected-parent-id': String(parseInt(context.parent_id || 0, 10) || 0),
                'data-gf-selected-form-id': String(parseInt(context.form_id || 0, 10) || 0),
                'data-gf-selected-item-label': (context.item_label || '').toString(),
                'data-gf-selected-field-type': (context.field_type || '').toString()
            };

            return Object.keys(attrs)
                .map((key) => `${key}="${this.escapeHtml(attrs[key])}"`)
                .join(' ');
        },

        extractSelectedContextFromButton: function($button) {
            if (!$button || !$button.length) {
                return null;
            }

            const context = {
                item_type: ($button.attr('data-gf-selected-item-type') || '').toString(),
                item_id: parseInt($button.attr('data-gf-selected-item-id') || '0', 10) || 0,
                parent_type: ($button.attr('data-gf-selected-parent-type') || '').toString(),
                parent_id: parseInt($button.attr('data-gf-selected-parent-id') || '0', 10) || 0,
                form_id: parseInt($button.attr('data-gf-selected-form-id') || '0', 10) || 0,
                item_label: ($button.attr('data-gf-selected-item-label') || '').toString(),
                field_type: ($button.attr('data-gf-selected-field-type') || '').toString()
            };

            return context.item_type ? context : null;
        },

        renderLocalMutationAttributes: function(localMutation) {
            if (!localMutation || typeof localMutation !== 'object') {
                return '';
            }

            const attrs = {
                'data-gf-local-mutation-type': (localMutation.mutation_type || '').toString(),
                'data-gf-local-action': (localMutation.action || '').toString(),
                'data-gf-local-delta': String(parseInt(localMutation.delta || 0, 10) || 0),
                'data-gf-local-value': (localMutation.value || '').toString(),
                'data-gf-local-width': String(parseInt(localMutation.width || 0, 10) || 0)
            };

            if (localMutation.selected_context && typeof localMutation.selected_context === 'object') {
                attrs['data-gf-local-selected-context'] = JSON.stringify(localMutation.selected_context);
            }

            return Object.keys(attrs)
                .filter((key) => attrs[key] !== '')
                .map((key) => `${key}="${this.escapeHtml(attrs[key])}"`)
                .join(' ');
        },

        isLocalColumnAction: function(renderLabel, selectedContext = {}) {
            if ((selectedContext.item_type || '').toString() !== 'rowcolumn') {
                return false;
            }

            return ['increase width', 'shrink width', 'move left', 'move right', 'make full width']
                .includes((renderLabel || '').toString().trim().toLowerCase());
        },

        buildLocalColumnMutationFromLabel: function(renderLabel) {
            const label = (renderLabel || '').toString().trim().toLowerCase();
            const map = {
                'increase width': { mutation_type: 'local_column_width', action: 'increase_width', delta: 2 },
                'shrink width': { mutation_type: 'local_column_width', action: 'shrink_width', delta: -2 },
                'move left': { mutation_type: 'local_column_move', action: 'move_left' },
                'move right': { mutation_type: 'local_column_move', action: 'move_right' },
                'make full width': { mutation_type: 'local_column_width', action: 'make_full_width', width: 12 }
            };

            return map[label] || null;
        },

        extractLocalMutationFromButton: function($button) {
            if (!$button || !$button.length) {
                return null;
            }

            const mutationType = ($button.attr('data-gf-local-mutation-type') || '').toString();
            const action = ($button.attr('data-gf-local-action') || '').toString();
            const delta = parseInt($button.attr('data-gf-local-delta') || '0', 10) || 0;
            const width = parseInt($button.attr('data-gf-local-width') || '0', 10) || 0;
            const value = ($button.attr('data-gf-local-value') || '').toString();
            const selectedContextRaw = ($button.attr('data-gf-local-selected-context') || '').toString();

            if (!mutationType) {
                return null;
            }

            let selectedContext = null;
            if (selectedContextRaw) {
                try {
                    selectedContext = JSON.parse(selectedContextRaw);
                } catch (error) {
                    selectedContext = null;
                }
            }

            return {
                mutation_type: mutationType,
                action: action,
                delta: delta,
                width: width,
                value: value,
                selected_context: selectedContext
            };
        },

        runLocalTextMutation: function(localMutation, renderLabel) {
            if (this.isProcessing || !localMutation || localMutation.mutation_type !== 'local_text') {
                return;
            }

            this.renderMessage('user', renderLabel);
            this.scrollToBottom();

            this.isProcessing = true;
            this.$sendBtn.prop('disabled', true);
            this.$undoBtn.prop('disabled', true);
            this.$resetBtn.prop('disabled', true);
            this.$input.prop('disabled', true);
            this.startProgressiveDisclosure({
                response_mode: 'patch_required',
                semantic_intent: 'builder_mutation'
            });

            $.post(ajaxurl, {
                action: 'gf_ai_apply_local_text_mutation',
                nonce: this.nonce,
                form_id: this.formId,
                target_property: localMutation.target_property,
                value: localMutation.value,
                source_prompt: `Local text mutation: ${localMutation.target_property}`,
                selected_context: localMutation.selected_context ? JSON.stringify(localMutation.selected_context) : ''
            }, (response) => {
                this.clearStatusMessages();
                this.unlockBuilderCanvas();
                if (response && response.success) {
                    const message = response.data && response.data.message ? response.data.message : 'Text updated successfully.';
                    this.renderMessage('assistant', message);
                    if (response.data && response.data.mutated_ids && response.data.mutated_ids.length > 0) {
                        this.pendingMutation = {
                            mutated_ids: response.data.mutated_ids,
                            lineage: response.data.lineage || null,
                            is_new: response.data.is_new || false
                        };
                        this._hasMutations = true;
                        document.dispatchEvent(new Event('form-layout'));
                    }
                    return;
                }

                const errorMessage = response && response.data && response.data.message
                    ? response.data.message
                    : 'Something went wrong. Please try again.';
                this.renderMessage('assistant', this.formatFailureMessage(errorMessage, response && response.data ? response.data.suggestions : []));
            }).fail(() => {
                this.clearStatusMessages();
                this.unlockBuilderCanvas();
                this.renderMessage('assistant', 'Connection error. Please try again.');
            }).always(() => {
                this.isProcessing = false;
                this.$sendBtn.prop('disabled', false);
                this.$input.prop('disabled', false);
                this.setChipsLocked(false);
                this.syncUndoResetState();
                this.syncInputHeight();
            });
        },

        runLocalColumnMutation: function(localMutation, renderLabel) {
            if (this.isProcessing || !localMutation) {
                return;
            }

            const $selected = $('.griffinforms-app-formlayout-rowcolumn[data-gf-selected=true]').first();
            if (!$selected.length) {
                this.renderMessage('assistant', 'Please select a column first.');
                return;
            }

            const columnContext = this.getSelectedColumnContext({
                item_type: 'rowcolumn',
                item_id: parseInt($selected.attr('data-gf-itemid') || '0', 10) || 0
            });
            if (!columnContext) {
                this.renderMessage('assistant', 'Unable to locate the selected column.');
                return;
            }

            this.renderMessage('user', renderLabel);
            this.scrollToBottom();

            const container = $selected.closest('.griffinforms-app-formlayout-pagerow-columns-container');
            const siblings = container.children('.griffinforms-app-formlayout-rowcolumn');
            const index = siblings.index($selected);

                if (localMutation.mutation_type === 'local_column_move') {
                    if (columnContext.is_single) {
                        this.renderMessage('assistant', 'This row has only one column, so it cannot be moved.');
                        this.finishLocalMutation();
                        return;
                    }

                    if (localMutation.action === 'move_left') {
                        if (columnContext.is_first) {
                            this.renderMessage('assistant', 'This column is already the left-most column.');
                            this.finishLocalMutation();
                            return;
                        }
                        $selected.insertBefore(siblings.eq(index - 1));
                        this.renderMessage('assistant', 'Column moved left.');
                    } else if (localMutation.action === 'move_right') {
                        if (columnContext.is_last) {
                            this.renderMessage('assistant', 'This column is already the right-most column.');
                            this.finishLocalMutation();
                            return;
                        }
                        $selected.insertAfter(siblings.eq(index + 1));
                        this.renderMessage('assistant', 'Column moved right.');
                    }
            }

            if (localMutation.mutation_type === 'local_column_width') {
                const currentWidth = Math.max(2, columnContext.width || parseInt($selected.attr('class').match(/col-md-(\d+)/)?.[1] || '0', 10) || 0);
                if (localMutation.action === 'make_full_width') {
                    if (!columnContext.is_single) {
                        this.renderMessage('assistant', 'This column can only become full width when it is the only column in the row.');
                        this.finishLocalMutation();
                        return;
                    }
                    this.applyColumnWidth($selected, 12);
                    this.renderMessage('assistant', 'Column expanded to full width.');
                } else if (localMutation.action === 'increase_width') {
                    const targetWidth = currentWidth + 2;
                    const remaining = 12 - targetWidth;
                    const otherColumns = Math.max(0, columnContext.total_columns - 1);
                    if (remaining < otherColumns * 2) {
                        const message = columnContext.is_single
                            ? 'This column is already at full width.'
                            : 'This column cannot be made any wider without making another column too narrow.';
                        this.renderMessage('assistant', message);
                        this.finishLocalMutation();
                        return;
                    }
                    this.redistributeColumnWidths(container, $selected, targetWidth);
                    this.renderMessage('assistant', 'Column width increased.');
                } else if (localMutation.action === 'shrink_width') {
                    const targetWidth = currentWidth - 2;
                    if (targetWidth < 2) {
                        this.renderMessage('assistant', 'This column is already at its minimum width.');
                        this.finishLocalMutation();
                        return;
                    }
                    this.redistributeColumnWidths(container, $selected, targetWidth);
                    this.renderMessage('assistant', 'Column width reduced.');
                }
            }

            if (localMutation.mutation_type === 'local_column_move' || localMutation.mutation_type === 'local_column_width') {
                this.persistColumnOrderAndWidths(container);
                this.finishLocalMutation();
            }
        },

        finishLocalMutation: function() {
            this.pendingMutation = null;
            this._hasMutations = true;
            this.isProcessing = false;
            document.dispatchEvent(new Event('form-layout'));
            this.renderContextualPromptChips();
            this.$sendBtn.prop('disabled', false);
            this.$input.prop('disabled', false);
            this.setChipsLocked(false);
            this.syncUndoResetState();
            this.syncInputHeight();
        },

        setChipsLocked: function(locked) {
            if (this.$suggestions && this.$suggestions.length) {
                this.$suggestions.find(this.selectors.chips).prop('disabled', !!locked);
            }
        },

        syncUndoResetState: function() {
            const enabled = this._hasMutations && !this.isProcessing;
            this.$undoBtn.prop('disabled', !enabled);
            this.$resetBtn.prop('disabled', !enabled);
        },

        applyColumnWidth: function($column, width) {
            if (!$column || !$column.length) {
                return;
            }
            const normalizedWidth = Math.max(2, Math.min(12, parseInt(width || 0, 10) || 0));
            $column.removeClass((_, className) => (className.match(/(^|\s)col-\S+/g) || []).join(' '));
            $column.addClass(`col-md-${normalizedWidth}`);
        },

        redistributeColumnWidths: function($container, $selected, targetWidth) {
            if (!$container || !$container.length || !$selected || !$selected.length) {
                return;
            }
            const columns = $container.children('.griffinforms-app-formlayout-rowcolumn').toArray().map((el) => $(el));
            const totalOther = columns.reduce((sum, $col) => {
                if ($col[0] === $selected[0]) {
                    return sum;
                }
                return sum + (parseInt($col.attr('class').match(/col-md-(\d+)/)?.[1] || '0', 10) || 0);
            }, 0);
            const remaining = 12 - targetWidth;
            const others = columns.filter(($col) => $col[0] !== $selected[0]);
            const scale = totalOther > 0 ? remaining / totalOther : 0;
            let assigned = 0;
            others.forEach(($col, idx) => {
                let width = Math.max(2, Math.floor(((parseInt($col.attr('class').match(/col-md-(\d+)/)?.[1] || '0', 10) || 0) * scale)));
                if (idx === others.length - 1) {
                    width = Math.max(2, remaining - assigned);
                }
                assigned += width;
                this.applyColumnWidth($col, width);
            });
            this.applyColumnWidth($selected, targetWidth);
        },

        persistColumnOrderAndWidths: function($container) {
            if (!$container || !$container.length) {
                return;
            }
            const rowId = $container.attr('data-gf-itemid');
            const columnIds = $container.children('.griffinforms-app-formlayout-rowcolumn').map(function() {
                return $(this).attr('data-gf-itemid');
            }).get();
            updatePageRowColumns(rowId, columnIds);
            updateRowColumnWidths($container.children('.griffinforms-app-formlayout-rowcolumn').first());
            updateColumnMoveBtnState();
        },

        normalizeRequestContext: function(requestContext) {
            const hasExplicitResponseMode = !!requestContext.response_mode;
            const hasExplicitSemanticIntent = !!requestContext.semantic_intent;
            const normalized = {
                surface: (requestContext.surface || this.surface || 'form_builder').toString(),
                origin: (requestContext.origin || 'chat_prompt').toString(),
                response_mode: (hasExplicitResponseMode ? requestContext.response_mode : (this.chatMode ? 'text_only' : 'classify')).toString(),
                semantic_intent: (hasExplicitSemanticIntent ? requestContext.semantic_intent : '').toString()
            };
            // Pass through advisory continuation keys so they reach the PHP handler.
            if (requestContext.advisory_context) { normalized.advisory_context = requestContext.advisory_context.toString(); }
            if (requestContext.original_prompt)  { normalized.original_prompt  = requestContext.original_prompt.toString(); }
            if (requestContext.suggestion_source) { normalized.suggestion_source = requestContext.suggestion_source.toString(); }
            return normalized;
        },

        readChatMode: function() {
            if (!this.chatModeToggleEnabled) {
                return false;
            }

            try {
                const stored = window.localStorage.getItem(this.chatModeStorageKey);
                if (stored === '1') {
                    return true;
                }
                if (stored === '0') {
                    return false;
                }
            } catch (error) {
                // Ignore storage failures and use default.
            }

            return this.defaultChatMode;
        },

        toggleChatMode: function() {
            if (!this.chatModeToggleEnabled) {
                return;
            }

            this.chatMode = !this.chatMode;

            try {
                window.localStorage.setItem(this.chatModeStorageKey, this.chatMode ? '1' : '0');
            } catch (error) {
                // Ignore storage failures.
            }

            this.updateChatModeUI();
        },

        updateChatModeUI: function() {
            if (!this.$chatModeToggle.length) {
                return;
            }

            this.$chatModeToggle
                .toggleClass('is-active', !!this.chatMode)
                .attr('aria-pressed', this.chatMode ? 'true' : 'false');

            this.$container.toggleClass('gf-butler-chat-mode-active', !!this.chatMode);

            if (this.$suggestions && this.$suggestions.length) {
                this.$suggestions.toggleClass('d-none', !!this.chatMode);
            }

            const $status = this.$container.find('.gf-butler-status');
            if ($status.length) {
                $status.text(this.chatMode
                    ? 'Chat mode — ask questions, get ideas'
                    : 'Ready to assist with your form'
                );
            }

            if (this.$input && this.$input.length) {
                this.$input.attr(
                    'placeholder',
                    this.chatMode
                        ? 'Ask a question, brainstorm ideas, or request suggestions...'
                        : 'Ask a question or describe a change...'
                );
            }
        },

        isGenericAdvisoryPrompt: function(text) {
            const normalized = String(text || '').trim().toLowerCase();
            if (!normalized) {
                return false;
            }

            return /^(implement( it)?|do it|use this|go ahead|proceed|apply|apply it|select this|try this|yes|no|cancel|continue|set new label)$/.test(normalized);
        },

        formatFailureMessage: function(message, suggestions) {
            const items = Array.isArray(suggestions) ? suggestions.filter(Boolean) : [];
            if (!items.length) {
                return message;
            }

            const lines = items.slice(0, 3).map((item) => `- ${item}`);
            return `${message}\n\n${lines.join('\n')}`;
        },

        renderStatus: function(text) {
            this.$log.find('.gf-butler-msg-system').remove();
            const html = `
                <div class="gf-butler-msg gf-butler-msg-system d-flex mb-3 align-self-start">
                    <div class="gf-butler-msg-bubble p-2 rounded bg-white border small text-muted d-flex">
                        <span class="gf-butler-loader me-2" aria-hidden="true"></span>
                        ${text}
                    </div>
                </div>
            `;
            this.$log.append(html);
            if (this.autoScroll) this.scrollToBottom();
        },

        scrollToBottom: function() {
            this.$log.scrollTop(this.$log[0].scrollHeight);
        },

        syncInputHeight: function() {
            if (!this.$input || !this.$input.length) {
                return;
            }

            const element = this.$input.get(0);
            element.style.height = 'auto';
            const computed = window.getComputedStyle(element);
            const lineHeight = parseFloat(computed.lineHeight || '20') || 20;
            const maxHeight = Math.round(lineHeight * 2 + 18);
            element.style.height = Math.min(element.scrollHeight, maxHeight) + 'px';
            element.style.overflowY = element.scrollHeight > maxHeight ? 'auto' : 'hidden';
        },

        handleScroll: function() {
            const threshold = 40;
            const position = this.$log.scrollTop() + this.$log.innerHeight();
            const height = this.$log[0].scrollHeight;

            if (height - position > threshold) {
                this.autoScroll = false;
                this.$pill.fadeIn();
            } else {
                this.autoScroll = true;
                this.$pill.fadeOut();
            }
        },

        getSelectedContext: function() {
            let $selected = $('[data-gf-selected=true][data-gf-itemtype][data-gf-itemid]').first();
            if (!$selected.length) {
                const $wrapper = $('[data-gf-selected=true]').first();
                if ($wrapper.length) {
                    $selected = $wrapper.find('[data-gf-itemtype][data-gf-itemid]').first();
                }
            }

            if (!$selected.length) {
                const $selectedPageContainer = $('.griffinforms-app-formlayout-formpage-container[data-gf-selected=true]').first();
                if ($selectedPageContainer.length) {
                    return {
                        item_type: 'formpage',
                        item_id: parseInt($selectedPageContainer.attr('data-gf-itemid') || '0', 10) || 0,
                        parent_type: 'form',
                        parent_id: parseInt($('.griffinforms-app-formlayout-form').attr('data-gf-itemid') || '0', 10) || 0,
                        form_id: parseInt($('.griffinforms-app-formlayout-form').attr('data-gf-itemid') || '0', 10) || 0,
                        item_label: $selectedPageContainer.find('.griffinforms-app-formlayout-formpage').first().find('textarea, input').first().val() || '',
                        field_type: ''
                    };
                }
            }

            if (!$selected.length) {
                const $form = $('.griffinforms-app-formlayout-form').first();
                if (!$form.length) {
                    return null;
                }

                return {
                    item_type: 'form',
                    item_id: parseInt($form.attr('data-gf-itemid') || '0', 10) || 0,
                    parent_type: '',
                    parent_id: 0,
                    form_id: parseInt($form.attr('data-gf-itemid') || '0', 10) || 0,
                    item_label: this.getFormLabel(),
                    field_type: ''
                };
            }

            const context = {
                item_type: ($selected.attr('data-gf-itemtype') || '').toString(),
                item_id: parseInt($selected.attr('data-gf-itemid') || '0', 10) || 0,
                parent_type: ($selected.attr('data-gf-parenttype') || '').toString(),
                parent_id: parseInt($selected.attr('data-gf-parentid') || '0', 10) || 0,
                form_id: parseInt($('.griffinforms-app-formlayout-form').attr('data-gf-itemid') || '0', 10) || 0,
                item_label: this.getSelectedItemLabel($selected),
                field_type: ($selected.attr('data-gf-fieldtype') || '').toString()
            };

            return context;
        },

        getSelectedItemLabel: function($selected) {
            if (!$selected || !$selected.length) {
                return '';
            }

            const itemType = ($selected.attr('data-gf-itemtype') || '').toString();
            if (itemType === 'field') {
                const text = $selected.find('.griffinforms-app-formlayout-field-heading, .griffinforms-field-label, [data-gf-prop="heading"]').first().text().trim();
                return text || '';
            }

            if (itemType === 'formpage') {
                const text = $selected.find('textarea, input').first().val();
                return (text || '').toString().trim();
            }

            return '';
        },

        getFormLabel: function() {
            const $form = $('.griffinforms-app-formlayout-form').first();
            if (!$form.length) {
                return '';
            }

            const text = $form.find('.griffinforms-app-text_update_field, [data-gf-prop="name"]').first().val();
            return (text || '').toString().trim();
        },

        typeAssistantMessage: function($target, message) {
            if (!$target || !$target.length) {
                return;
            }

            const text = String(message || '');
            if (!text) {
                return;
            }

            let index = 0;
            const total = text.length;
            const step = total > 160 ? 4 : total > 90 ? 3 : 2;
            const delay = total > 160 ? 10 : 14;

            const tick = () => {
                index = Math.min(total, index + step);
                const current = text.slice(0, index);
                const cursor = index < total ? '<span class="gf-butler-typed-cursor">|</span>' : '';
                $target.html(this.escapeHtml(current) + cursor);
                if (this.autoScroll) {
                    this.scrollToBottom();
                }

                if (index < total) {
                    window.setTimeout(tick, delay);
                }
            };

            tick();
        },

        renderAssistMark: function(size = 'sm') {
            const badge = this.providerIcon
                ? `<span class="gf-butler-mark__provider-badge"><img src="${this.providerIcon}" alt="${this.escapeHtml(this.providerName || 'AI')}" width="10" height="10"></span>`
                : '';
            const icon = this.assistIcon
                ? `<img class="gf-butler-mark__icon" src="${this.escapeHtml(this.assistIcon)}" alt="" width="16" height="16">`
                : `<span class="gf-butler-mark__glyph">GA</span>`;

            return `<span class="gf-butler-mark gf-butler-mark--${size}" aria-hidden="true">${icon}${badge}</span>`;
        },

        renderUserAvatar: function() {
            if (this.userAvatar) {
                return `<img class="gf-butler-user-avatar" src="${this.escapeHtml(this.userAvatar)}" alt="${this.escapeHtml(this.userName || 'User')}" width="24" height="24">`;
            }

            const initial = (this.userName || 'U').trim().charAt(0).toUpperCase() || 'U';
            return `<span class="gf-butler-user-avatar gf-butler-user-avatar--fallback" aria-hidden="true">${this.escapeHtml(initial)}</span>`;
        },

        activateConnectorProvider: function(providerId, nonce, $btn) {
            if (!providerId || !nonce) return;

            $btn.prop('disabled', true).text($btn.text() + '…');

            $.post(ajaxurl, {
                action:      'griffinforms_activate_provider_from_connector',
                provider_id: providerId,
                nonce:       nonce,
            }, (response) => {
                if (response && response.success) {
                    // Reload the page so the chat initialises with the newly active provider.
                    window.location.reload();
                } else {
                    const msg = (response && response.data && response.data.msg)
                        ? response.data.msg
                        : 'Failed to activate provider.';
                    $btn.prop('disabled', false).text($btn.text().replace('…', ''));
                    alert(msg);
                }
            }).fail(() => {
                $btn.prop('disabled', false).text($btn.text().replace('…', ''));
                alert('Failed to activate provider.');
            });
        },

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

        gfEscAttr: function(s) {
            return $('<div>').text(String(s)).html().replace(/"/g, '&quot;');
        }
    };

    $(document).ready(() => ButlerChat.init());

})(jQuery);
