/* FormForge Admin — Drag & Drop */
(function($) {
    'use strict';

    var fields = [];
    try { fields = JSON.parse($('#formforge-fields-json').val() || '[]'); } catch(e) {}
    var nextId = fields.length + 1;
    var selectedStyleTarget = null;
    var ffI18n = (typeof formforgeAdmin !== 'undefined' && formforgeAdmin.i18n) ? formforgeAdmin.i18n : {};

    function t(key, fallback) {
        return ffI18n[key] || fallback;
    }

    var builderDirty = false;
    var suppressLeaveWarning = false;

    function markBuilderDirty() {
        builderDirty = true;
        $('.formforge-save-bar').addClass('formforge-save-bar--dirty');
    }

    function markBuilderClean() {
        builderDirty = false;
        $('.formforge-save-bar').removeClass('formforge-save-bar--dirty');
    }

    window.addEventListener('beforeunload', function(e) {
        if (!builderDirty || suppressLeaveWarning) return;
        e.preventDefault();
        e.returnValue = '';
    });

    $(document).on('click', 'a[href]', function(e) {
        if (!builderDirty || suppressLeaveWarning) return;
        if (this.target === '_blank' || this.href.indexOf('#') === this.href.length - 1) return;
        if (!window.confirm(t('leaveUnsaved', 'You have unsaved form changes. Leave this page?'))) {
            e.preventDefault();
        }
    });

    function ffEsc(v) {
        return $('<div>').text(v == null ? '' : String(v)).html();
    }

    function showBuilderToast(message, type) {
        var safeType = type === 'error' ? 'error' : 'success';
        var $toast = $('<div class="formforge-toast"></div>');
        $toast
            .addClass('formforge-toast--' + safeType)
            .text(message || (safeType === 'error' ? t('saveFailed', 'Save failed.') : t('formSaved', 'Form saved!')));
        $('body').append($toast);
        setTimeout(function() { $toast.fadeOut(300, function() { $toast.remove(); }); }, safeType === 'error' ? 4500 : 2000);
    }

    function ajaxErrorMessage(response, fallback) {
        if (response && response.data) {
            if (typeof response.data === 'string') return response.data;
            if (response.data.message) return response.data.message;
            if (response.data.error) return response.data.error;
        }
        return fallback || t('saveFailed', 'Save failed.');
    }

    $(document).on('click', '.ff-inspector-collapse', function(e) {
        e.preventDefault();
        e.stopPropagation();
        var $button = $(this);
        var $panel = $button.closest('.ff-inspector-panel, .ff-preview-card');
        var bodyId = $button.attr('aria-controls');
        var $body = bodyId ? $('#' + bodyId) : $panel.find('.ff-inspector-panel-body, .ff-preview-frame').first();
        var collapsed = !$panel.hasClass('is-collapsed');
        $panel.toggleClass('is-collapsed', collapsed);
        $button.attr('aria-expanded', collapsed ? 'false' : 'true');
        $body.attr('aria-hidden', collapsed ? 'true' : 'false');
    });

    // ── Sortable: reorder existing fields + receive from sidebar ──
    $('#formforge-fields-list').sortable({
        handle: '.formforge-field-drag-handle',
        placeholder: 'formforge-drop-indicator',
        connectWith: false,
        tolerance: 'pointer',
        receive: function(event, ui) {
            // This fires when a draggable is dropped into sortable
        },
        update: function() { syncFieldsFromDOM(); notifyFieldsChanged(); schedulePreviewRefresh(); }
    });

    // ── Sidebar items: draggable into the form area (clone, don't move) ──
    $('.formforge-sidebar-draggable').draggable({
        helper: 'clone',
        connectToSortable: '#formforge-fields-list',
        revert: 'invalid',
        revertDuration: 150,
        cursor: 'grabbing',
        appendTo: 'body',
        zIndex: 10000,
        start: function(e, ui) {
            ui.helper.addClass('formforge-drag-ghost');
            $('#formforge-fields-list').addClass('formforge-drop-active');
            $('#formforge-empty').hide();
        },
        stop: function(e, ui) {
            $('#formforge-fields-list').removeClass('formforge-drop-active');
        }
    });

    // When a cloned sidebar item lands in sortable, replace it with a real field card
    $('#formforge-fields-list').on('sortstop', function(event, ui) {
        var $item = ui.item;
        // Check if it's a sidebar clone (has the sidebar class, not a field-card)
        if ($item.hasClass('formforge-sidebar-draggable')) {
            var type = $item.data('type');
            var label = $item.data('label');
            var field = { id: 'f' + nextId++, type: type, label: label, required: false, placeholder: '', options: [] };

            var pos = $item.index();
            fields.splice(pos, 0, field);

            var $card = buildFieldCard(field, pos);
            $item.replaceWith($card);
            // Same hygiene as the click-to-add path — drop the empty-state
            // placeholder once a real field arrives.
            $('#formforge-fields-list .formforge-empty-state, #formforge-empty').remove();
            $card.addClass('formforge-field-card--just-added');
            setTimeout(function() { $card.removeClass('formforge-field-card--just-added'); }, 600);

            syncFieldsFromDOM();
            notifyFieldsChanged();
            schedulePreviewRefresh();
        }
    });

    // ── Click to add (fallback) ──
    $(document).on('click', '.formforge-sidebar-draggable', function() {
        var type = $(this).data('type');
        var label = $(this).data('label');
        var field = { id: 'f' + nextId++, type: type, label: label, required: false, placeholder: '', options: [] };
        fields.push(field);
        var $card = buildFieldCard(field, fields.length - 1);
        // Remove the empty-state placeholder so it doesn't pollute
        // the cards' .index() computation used by Edit/Apply/Delete.
        // (Hiding alone leaves the element as a sibling and the card's
        // index becomes 1 instead of 0 — Apply's `.eq(idx)` then returns
        // empty, silently dropping all field-setting changes.)
        $('#formforge-fields-list .formforge-empty-state, #formforge-empty').remove();
        $('#formforge-fields-list').append($card);
        $('#formforge-empty').hide();

        // Scroll to new field
        $card[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
        $card.addClass('formforge-field-card--just-added');
        setTimeout(function() { $card.removeClass('formforge-field-card--just-added'); }, 600);
        notifyFieldsChanged();
        schedulePreviewRefresh();
    });

    function buildFieldCard(field, index) {
        var req = field.required ? '<span style="color:#DC2626;">*</span>' : '';
        var typeLabel = fieldTypeLabel(field.type);
        var html = '<div class="formforge-field-card" data-index="' + index + '" data-type="' + field.type + '" data-field-id="' + ffEsc(field.id || '') + '">' +
            '<div class="formforge-field-card-header">' +
            '<span class="formforge-field-drag-handle">&#9776;</span>' +
            '<span class="formforge-field-type-badge">' + typeLabel + '</span>' +
            '<strong>' + field.label + ' ' + req + '</strong>' +
            '<span class="formforge-field-actions">' +
            '<button type="button" class="formforge-edit-field" title="Edit">&#9998;</button>' +
            '<button type="button" class="formforge-delete-field" title="Delete">&times;</button>' +
            '</span></div></div>';
        var $card = $(html);
        $card.data('fieldData', field);
        return $card;
    }

    function fieldTypeLabel(type) {
        var labels = (window.formforgeAdmin && window.formforgeAdmin.fieldTypeLabels) || {
            text: 'Text',
            email: 'Email',
            phone: 'Phone',
            number: 'Number',
            textarea: 'Textarea',
            select: 'Dropdown',
            radio: 'Radio',
            checkbox: 'Checkbox',
            date: 'Date',
            time: 'Time',
            file_upload: 'File Upload',
            hidden: 'Hidden',
            url: 'URL',
            password: 'Password',
            name: 'Name (First + Last)',
            address: 'Address',
            rating: 'Rating (Stars)',
            range: 'Range (Slider)',
            color: 'Color Picker',
            heading: 'Heading',
            html_block: 'HTML Block',
            divider: 'Divider',
            page_break: 'Page Break',
            repeater: 'Repeater',
            calculation: 'Calculation',
            map_address: 'Address (Map)',
            payment: 'Payment (Stripe)',
            calendar_picker: 'Date (Calendar)'
        };
        if (labels[type]) return labels[type];
        return String(type || 'Field').replace(/_/g, ' ').replace(/\b\w/g, function(ch) { return ch.toUpperCase(); });
    }

    function syncFieldsFromDOM() {
        // Rebuild fields array from DOM order
        // Each card stores its field data in jQuery .data('fieldData')
        var newOrder = [];
        $('#formforge-fields-list .formforge-field-card').each(function(i) {
            var fd = $(this).data('fieldData');
            if (fd) {
                newOrder.push(fd);
            }
            $(this)
                .attr('data-index', i)
                .attr('data-field-id', fd && fd.id ? fd.id : '');
        });
        if (newOrder.length > 0) {
            fields = newOrder;
        }
    }

    // Store field data on each existing card from PHP
    function initFieldDataOnCards() {
        $('#formforge-fields-list .formforge-field-card').each(function(i) {
            if (!$(this).data('fieldData') && fields[i]) {
                $(this).data('fieldData', fields[i]);
            }
            var fd = $(this).data('fieldData') || fields[i];
            if (fd && fd.id) {
                $(this).attr('data-field-id', fd.id);
            }
        });
    }
    initFieldDataOnCards();

    function fieldsForPost() {
        syncFieldsFromDOM();
        return fields.map(function(f) {
            if (!f) return f;
            var clone = $.extend(true, {}, f);
            delete clone._cl_pending;
            return clone;
        });
    }

    function notifyFieldsChanged() {
        markBuilderDirty();
        var payload = fieldsForPost();
        $('#formforge-fields-json').val(JSON.stringify(payload));
        $(document).trigger('formforge:fields-changed', [payload]);
    }
    window.formforgeGetFields = fieldsForPost;

    function readFormSettings() {
        var settings = {};
        try { settings = JSON.parse($('#formforge-settings-json').val() || '{}'); } catch(e) {}
        if (!settings || Array.isArray(settings) || typeof settings !== 'object') settings = {};
        return settings;
    }

    function writeFormSettings(settings) {
        $('#formforge-settings-json').val(JSON.stringify(settings || {}));
    }

    function findFieldById(fieldId) {
        syncFieldsFromDOM();
        var match = null;
        fields.some(function(field) {
            if (field && String(field.id || '') === String(fieldId || '')) {
                match = field;
                return true;
            }
            return false;
        });
        return match;
    }

    function findFieldCardById(fieldId) {
        var wanted = String(fieldId || '');
        if (!wanted) return $();
        syncFieldsFromDOM();
        return $('#formforge-fields-list .formforge-field-card').filter(function(i) {
            var fd = $(this).data('fieldData') || fields[i];
            return fd && String(fd.id || '') === wanted;
        }).first();
    }

    function readAiEditorRequest() {
        if (!$('#formforge-fields-list').length) return null;
        var params;
        try {
            params = new URLSearchParams(window.location.search || '');
        } catch (e) {
            return null;
        }
        var fieldId = params.get('ff_ai_field') || '';
        if (!fieldId) return null;
        return {
            fieldId: fieldId,
            action: params.get('ff_ai_action') || '',
            title: params.get('ff_ai_title') || '',
            note: params.get('ff_ai_note') || ''
        };
    }

    function renderAiEditorNote(request, field) {
        var title = request.title || t('aiRecommendation', 'AI recommendation');
        var note = request.note || t('aiOpenedField', 'Review this field based on the selected AI recommendation.');
        var status = request.status || '';
        var fieldLabel = field && field.label ? field.label : request.fieldId;
        var html = '<div class="ff-ai-editor-note" role="note">' +
            '<div class="ff-ai-editor-note__eyebrow">' + ffEsc(t('fromAiAnalysis', 'From AI Analysis')) + '</div>' +
            '<strong>' + ffEsc(title) + '</strong>' +
            '<p>' + ffEsc(note) + '</p>' +
            (status ? '<div class="ff-ai-editor-note__status">' + ffEsc(status) + '</div>' : '') +
            '<span>' + ffEsc(t('targetField', 'Target field')) + ': ' + ffEsc(fieldLabel) + '</span>' +
            '</div>';
        $('#formforge-settings-content .ff-ai-editor-note').remove();
        $('#formforge-settings-content').prepend(html);
    }

	    function markAiTargetCard($card) {
        $('.formforge-field-card').removeClass('formforge-field-card--ai-target').removeAttr('data-ai-target');
        $('.ff-ai-target-badge').remove();
        if (!$card || !$card.length) return;

	        $card.addClass('formforge-field-card--ai-target').attr('data-ai-target', '1');
	        var $header = $card.find('.formforge-field-card-header').first();
	        if ($header.length) {
	            $header.append('<span class="ff-ai-target-badge">' + ffEsc(t('aiTargetField', 'AI suggestion')) + '</span>');
	        }
	    }

    function openAiTargetField(request) {
        if (!request || !request.fieldId) return false;
        var $card = findFieldCardById(request.fieldId);
        if (!$card.length) return false;
        var field = $card.data('fieldData') || findFieldById(request.fieldId);
        markAiTargetCard($card);
        $card[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
        $card.find('.formforge-edit-field').trigger('click');
        renderAiEditorNote(request, field);
        return true;
    }

    window.formforgeOpenAiTargetField = openAiTargetField;

    function openAiEditorRequest() {
        var request = readAiEditorRequest();
        if (!request) return;
        openAiTargetField(request);
    }

    function styleStoreForTarget(target) {
        if (!target) return null;
        if (target.scope === 'field') {
            var field = findFieldById(target.fieldId);
            if (!field) return null;
            if (!field.styles || Array.isArray(field.styles) || typeof field.styles !== 'object') {
                field.styles = {};
            }
            field.styles[target.target] = field.styles[target.target] || {};
            return field.styles[target.target];
        }

        var settings = readFormSettings();
        if (!settings.styles || Array.isArray(settings.styles) || typeof settings.styles !== 'object') {
            settings.styles = {};
        }
        settings.styles[target.target] = settings.styles[target.target] || {};
        writeFormSettings(settings);
        return settings.styles[target.target];
    }

    function saveStyleStoreForTarget(target, store) {
        if (!target) return;
        if (target.scope === 'field') {
            var field = findFieldById(target.fieldId);
            if (!field) return;
            field.styles = field.styles || {};
            field.styles[target.target] = store;
            $('#formforge-fields-list .formforge-field-card').each(function() {
                var fd = $(this).data('fieldData');
                if (fd && String(fd.id || '') === String(target.fieldId || '')) {
                    $(this).data('fieldData', field);
                }
            });
            markBuilderDirty();
            return;
        }

        var settings = readFormSettings();
        settings.styles = settings.styles || {};
        settings.styles[target.target] = store;
        writeFormSettings(settings);
        markBuilderDirty();
    }

    function styleControlDefs(target) {
        var commonBorder = [
            { key: 'border_color', id: 'border-color', label: t('borderColor', 'Border color'), type: 'color', fallback: '#e2e8f0' },
            { key: 'border_width', id: 'border-width', label: t('borderWidth', 'Border width'), type: 'number', min: 0, max: 20 },
            { key: 'border_radius', id: 'border-radius', label: t('borderRadius', 'Border radius'), type: 'number', min: 0, max: 80 }
        ];
        var custom = { key: 'custom_css', id: 'custom-css', label: t('customCssDeclarations', 'Custom CSS declarations'), type: 'textarea', placeholder: 'text-align: center;' };

        if (!target) return [];
        if (target.target === 'label') {
            return [
                { key: 'font_size', id: 'font-size', label: t('fontSize', 'Font size'), type: 'number', min: 8, max: 72 },
                { key: 'text_color', id: 'text-color', label: t('textColor', 'Text color'), type: 'color', fallback: '#1e293b' },
                { key: 'font_weight', id: 'font-weight', label: t('fontWeight', 'Font weight'), type: 'select', options: ['400', '500', '600', '700'] },
                custom
            ];
        }
        if (target.target === 'control') {
            return [
                { key: 'font_size', id: 'font-size', label: t('fontSize', 'Font size'), type: 'number', min: 8, max: 72 },
                { key: 'text_color', id: 'text-color', label: t('textColor', 'Text color'), type: 'color', fallback: '#0f172a' },
                { key: 'background_color', id: 'background-color', label: t('background', 'Background'), type: 'color', fallback: '#ffffff' }
            ].concat(commonBorder).concat([
                { key: 'padding', id: 'padding', label: t('padding', 'Padding'), type: 'number', min: 0, max: 80 },
                custom
            ]);
        }
        if (target.target === 'description') {
            return [
                { key: 'font_size', id: 'font-size', label: t('fontSize', 'Font size'), type: 'number', min: 8, max: 72 },
                { key: 'text_color', id: 'text-color', label: t('textColor', 'Text color'), type: 'color', fallback: '#64748b' },
                custom
            ];
        }
        if (target.target === 'submit' || target.target === 'message') {
            return [
                { key: 'background_color', id: 'background-color', label: t('background', 'Background'), type: 'color', fallback: target.target === 'submit' ? '#3b82f6' : '#ecfdf5' },
                { key: 'text_color', id: 'text-color', label: t('textColor', 'Text color'), type: 'color', fallback: target.target === 'submit' ? '#ffffff' : '#065f46' },
                { key: 'font_size', id: 'font-size', label: t('fontSize', 'Font size'), type: 'number', min: 8, max: 72 }
            ].concat(commonBorder).concat([
                { key: 'padding', id: 'padding', label: t('padding', 'Padding'), type: 'number', min: 0, max: 80 },
                custom
            ]);
        }

        return [
            { key: 'background_color', id: 'background-color', label: t('background', 'Background'), type: 'color', fallback: '#ffffff' }
        ].concat(commonBorder).concat([
            { key: 'padding', id: 'padding', label: t('padding', 'Padding'), type: 'number', min: 0, max: 80 },
            target.target === 'wrapper' ? { key: 'margin_bottom', id: 'margin-bottom', label: t('marginBottom', 'Margin bottom'), type: 'number', min: 0, max: 120 } : null,
            custom
        ]).filter(Boolean);
    }

    function fieldStyleTargetLabel(targetName) {
        if (targetName === 'wrapper') return t('fieldWrapper', 'Field wrapper');
        if (targetName === 'label') return t('label', 'Label');
        if (targetName === 'control') return t('input', 'Input');
        if (targetName === 'description') return t('description', 'Description');
        return targetName;
    }

    function selectedStyleTitle(target) {
        if (!target) return '';
        if (target.scope === 'field') {
            var field = findFieldById(target.fieldId);
            var label = (field && field.label) ? field.label : target.fieldId;
            return label + ' / ' + fieldStyleTargetLabel(target.target);
        }
        if (target.target === 'submit') return t('submitButton', 'Submit button');
        if (target.target === 'message') return t('message', 'Success message');
        return t('form', 'Form');
    }

    function fieldTargetSwitcherHtml(target) {
        if (!target || target.scope !== 'field') return '';
        return '<div class="ff-style-target-switcher">' +
            ['wrapper', 'label', 'control', 'description'].map(function(part) {
                var active = target.target === part ? ' is-active' : '';
                return '<button type="button" class="ff-style-target-switch' + active + '" data-style-target-switch="' + part + '">' + ffEsc(fieldStyleTargetLabel(part)) + '</button>';
            }).join('') +
        '</div>';
    }

    function globalTargetSwitcherHtml(target) {
        var activeTarget = target && target.scope === 'settings' ? target.target : '';
        var targets = [
            { key: 'form', label: t('form', 'Form') },
            { key: 'submit', label: t('submitButton', 'Submit button') },
            { key: 'message', label: t('message', 'Success message') }
        ];
        return '<div class="ff-style-global-targets" aria-label="' + ffEsc(t('globalStyleTargets', 'Global style targets')) + '">' +
            targets.map(function(item) {
                var active = activeTarget === item.key ? ' is-active' : '';
                return '<button type="button" class="ff-style-global-target' + active + '" data-style-global-target="' + item.key + '">' + ffEsc(item.label) + '</button>';
            }).join('') +
        '</div>';
    }

    function styleInputHtml(def, store) {
        var value = store && store[def.key] !== undefined ? store[def.key] : '';
        var id = 'ff-selected-style-' + def.id;
        var controlClass = 'ff-style-control ff-style-control--' + ffEsc(def.type);
        var labelText = '<span class="ff-style-control-label">' + ffEsc(def.label) + '</span>';
        if (def.type === 'textarea') {
            return '<label class="' + controlClass + '" for="' + id + '">' + labelText +
                '<textarea id="' + id + '" data-style-key="' + ffEsc(def.key) + '" rows="3" placeholder="' + ffEsc(def.placeholder || '') + '">' + ffEsc(value) + '</textarea></label>';
        }
        if (def.type === 'select') {
            var options = (def.options || []).map(function(opt) {
                return '<option value="' + ffEsc(opt) + '"' + (String(value || '600') === String(opt) ? ' selected' : '') + '>' + ffEsc(opt) + '</option>';
            }).join('');
            return '<label class="' + controlClass + '" for="' + id + '">' + labelText +
                '<select id="' + id + '" data-style-key="' + ffEsc(def.key) + '">' + options + '</select></label>';
        }

        var attrs = '';
        if (def.type === 'number') {
            attrs += def.min !== undefined ? ' min="' + def.min + '"' : '';
            attrs += def.max !== undefined ? ' max="' + def.max + '"' : '';
        }
        if (def.type === 'color') {
            value = value || def.fallback || '#ffffff';
        }
        return '<label class="' + controlClass + '" for="' + id + '">' + labelText +
            '<input id="' + id + '" data-style-key="' + ffEsc(def.key) + '" type="' + ffEsc(def.type) + '"' + attrs + ' value="' + ffEsc(value) + '"></label>';
    }

    function styleControlsHtml(defs, store) {
        var html = '';
        var colorBuffer = [];
        function flushColors() {
            if (!colorBuffer.length) return;
            html += '<div class="ff-style-color-row">' + colorBuffer.map(function(def) {
                return styleInputHtml(def, store);
            }).join('') + '</div>';
            colorBuffer = [];
        }
        defs.forEach(function(def) {
            if (def.type === 'color') {
                colorBuffer.push(def);
                return;
            }
            flushColors();
            html += styleInputHtml(def, store);
        });
        flushColors();
        return html;
    }

    function renderSelectedStylePanel() {
        var $empty = $('#ff-selected-style-empty');
        var $content = $('#ff-selected-style-content');
        var $title = $('#ff-selected-style-target');
        var $fields = $('#ff-selected-style-fields');
        $('.ff-style-global-targets').remove();
        if (!$content.length || !selectedStyleTarget) {
            $empty.show();
            if ($empty.length) $empty.after(globalTargetSwitcherHtml(null));
            $content.hide();
            return;
        }

        var store = styleStoreForTarget(selectedStyleTarget) || {};
        $empty.hide();
        $content.show();
        $title.text(selectedStyleTitle(selectedStyleTarget));
        $content.find('.ff-style-target-switcher').remove();
        if (selectedStyleTarget.scope === 'field') {
            $title.after(fieldTargetSwitcherHtml(selectedStyleTarget));
        } else {
            $title.after(globalTargetSwitcherHtml(selectedStyleTarget));
        }
        $fields.html(styleControlsHtml(styleControlDefs(selectedStyleTarget), store));
    }

    function styleTargetFromClick(clicked) {
        var $clicked = $(clicked);
        var $styleNode = $clicked.closest('[data-ff-style-target]');
        if ($styleNode.length) {
            var target = String($styleNode.data('ff-style-target') || '');
            if (target === 'label' || target === 'control' || target === 'description') {
                var $field = $styleNode.closest('.formforge-field[data-field-id]');
                var fieldId = $field.data('field-id');
                var field = findFieldById(fieldId);
                var typeName = target === 'control' ? t('input', 'Input') : (target === 'label' ? t('label', 'Label') : t('description', 'Description'));
                return {
                    scope: 'field',
                    target: target,
                    fieldId: fieldId,
                    title: ((field && field.label) ? field.label : fieldId) + ' / ' + typeName
                };
            }
            if (target === 'submit') {
                return { scope: 'settings', target: 'submit', title: t('submitButton', 'Submit button') };
            }
            if (target === 'message') {
                return { scope: 'settings', target: 'message', title: t('message', 'Success message') };
            }
        }

        var $wrapper = $clicked.closest('.formforge-field[data-field-id]');
        if ($wrapper.length) {
            var wrapperFieldId = $wrapper.data('field-id');
            var wrapperField = findFieldById(wrapperFieldId);
            return {
                scope: 'field',
                target: 'wrapper',
                fieldId: wrapperFieldId,
                title: ((wrapperField && wrapperField.label) ? wrapperField.label : wrapperFieldId) + ' / ' + t('fieldWrapper', 'Field wrapper')
            };
        }

        if ($clicked.closest('.formforge-wrap').length) {
            return { scope: 'settings', target: 'form', title: t('form', 'Form') };
        }

        return null;
    }

    function selectorForStyleTarget(target) {
        if (!target) return '';
        if (target.scope === 'field') {
            var base = '.formforge-field[data-field-id="' + String(target.fieldId || '').replace(/"/g, '\\"') + '"]';
            if (target.target === 'label') return base + ' [data-ff-style-target="label"]';
            if (target.target === 'control') return base + ' [data-ff-style-target="control"]';
            if (target.target === 'description') return base + ' [data-ff-style-target="description"]';
            return base;
        }
        if (target.target === 'form') return '.formforge-wrap';
        return '[data-ff-style-target="' + target.target + '"]';
    }

    function previewIframe() {
        return document.getElementById('formforge-preview-iframe');
    }

    function previewDocument() {
        var iframe = previewIframe();
        return iframe && iframe.contentDocument && iframe.contentDocument.body ? iframe.contentDocument : null;
    }

    function previewRoot() {
        var doc = previewDocument();
        return doc ? $(doc.body) : $('#formforge-preview-frame');
    }

    function previewFrameStyle() {
        return [
            'html{box-sizing:border-box;background:#f8fafc;}',
            '*,*::before,*::after{box-sizing:inherit;}',
            'body.formforge-preview-body{margin:0;padding:18px;background:#f8fafc;color:#1e293b;}',
            '.ff-preview-selectable{cursor:pointer;}',
            '.ff-preview-selectable:hover{outline:2px dashed rgba(59,130,246,.55);outline-offset:2px;}',
            '.ff-preview-selected{outline:2px solid #3B82F6!important;outline-offset:2px;}'
        ].join('');
    }

    function previewDocumentHtml(html) {
        var cssUrl = (typeof formforgeAdmin !== 'undefined' && formforgeAdmin.frontendCssUrl) ? formforgeAdmin.frontendCssUrl : '';
        var convCssUrl = (typeof formforgeAdmin !== 'undefined' && formforgeAdmin.conversationalCssUrl) ? formforgeAdmin.conversationalCssUrl : '';
        var baseUrl = (typeof formforgeAdmin !== 'undefined' && formforgeAdmin.previewBaseUrl) ? formforgeAdmin.previewBaseUrl : window.location.origin + '/';
        return '<!doctype html><html><head><meta charset="utf-8">'
            + '<base href="' + ffEsc(baseUrl) + '">'
            + (cssUrl ? '<link rel="stylesheet" href="' + ffEsc(cssUrl) + '">' : '')
            + (convCssUrl ? '<link rel="stylesheet" href="' + ffEsc(convCssUrl) + '">' : '')
            + '<style>' + previewFrameStyle() + '</style>'
            + '</head><body class="formforge-preview-body">' + html + '</body></html>';
    }

    function previewParseCalcMap($el) {
        var raw = $el.attr('data-field-map') || '{}';
        try {
            var parsed = JSON.parse(raw);
            return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
        } catch(e) {
            return {};
        }
    }

    function previewCalcSourceValue($form, fieldId) {
        var baseName = 'formforge_field_' + fieldId;
        var $els = $form.find('[name="' + baseName + '"], [name="' + baseName + '[]"]');
        if (!$els.length) return 0;
        var first = $els.get(0);
        var raw;
        if (first && first.type === 'radio') {
            raw = $form.find('[name="' + baseName + '"]:checked').val();
        } else if (first && first.type === 'checkbox') {
            var total = 0;
            $form.find('[name="' + baseName + '"], [name="' + baseName + '[]"]').filter(':checked').each(function() {
                total += parseFloat($(this).val()) || 0;
            });
            return total;
        } else {
            raw = $els.first().val();
            if (Array.isArray(raw)) raw = raw[0];
        }
        return parseFloat(raw) || 0;
    }

    function updatePreviewCalculations(doc) {
        $(doc.body).find('.formforge-calculation').each(function() {
            var $calc = $(this);
            var formula = $calc.attr('data-formula') || $calc.data('formula');
            if (!formula) return;
            var $form = $calc.closest('.formforge-form');
            var fieldMap = previewParseCalcMap($calc);
            var decimalsRaw = $calc.attr('data-decimals');
            var decimals = decimalsRaw === '' || decimalsRaw == null ? null : parseInt(decimalsRaw, 10);
            var expression = formula.replace(/\{([^}]+)\}/g, function(match, token) {
                token = String(token || '').trim();
                var fieldId = fieldMap[token] || token;
                return String(previewCalcSourceValue($form, fieldId));
            });

            try {
                expression = expression.replace(/[^0-9+\-*/().\s]/g, '');
                if (!expression.trim()) return;
                var val = new Function('"use strict"; return (' + expression + ');')();
                if (!isFinite(val)) return;
                if (decimals !== null && !isNaN(decimals)) {
                    decimals = Math.max(0, Math.min(6, decimals));
                    val = Number(val).toFixed(decimals);
                } else {
                    val = Math.round(val * 100) / 100;
                }
                $calc.find('.formforge-calc-result').text(val);
                $calc.find('.formforge-calc-input').val(val);
            } catch(e) {}
        });
    }

    function installPreviewCalculations(doc) {
        $(doc).off('.formforgePreviewCalc')
            .on('input.formforgePreviewCalc change.formforgePreviewCalc', '.formforge-form input, .formforge-form select, .formforge-form textarea', function() {
                updatePreviewCalculations(doc);
            });
        updatePreviewCalculations(doc);
    }

    function installPreviewConditionalLogic(doc) {
        $(doc.body).find('.formforge-logic-data').each(function() {
            var rules;
            try {
                rules = JSON.parse($(this).text());
            } catch(e) {
                rules = [];
            }
            if (!rules || !rules.length) return;
            var $wrap = $(this).closest('.formforge-wrap');

            function sourceValue(sourceId) {
                var sel = '[name="formforge_field_' + sourceId + '"], [name="formforge_field_' + sourceId + '[]"]';
                var $els = $wrap.find(sel);
                if (!$els.length) return '';
                var first = $els.get(0);
                if (first && first.type === 'radio') {
                    var checked = $wrap.find('[name="formforge_field_' + sourceId + '"]:checked');
                    return checked.length ? String(checked.val() || '') : '';
                }
                if (first && first.type === 'checkbox') {
                    var vals = [];
                    $wrap.find(sel + ':checked').each(function() { vals.push(String($(this).val() || '')); });
                    return vals.join('\n');
                }
                var v = $els.first().val();
                if (Array.isArray(v)) return v.map(String).join('\n');
                return v == null ? '' : String(v);
            }

            function evalRule(rule) {
                var cur = sourceValue(rule.source);
                var val = rule.value == null ? '' : String(rule.value);
                switch (rule.operator) {
                    case 'equals': return cur === val;
                    case 'not_equals': return cur !== val;
                    case 'contains': return val !== '' && cur.indexOf(val) >= 0;
                    case 'not_contains': return val === '' || cur.indexOf(val) < 0;
                    case 'greater_than': return !isNaN(parseFloat(cur)) && !isNaN(parseFloat(val)) && parseFloat(cur) > parseFloat(val);
                    case 'less_than': return !isNaN(parseFloat(cur)) && !isNaN(parseFloat(val)) && parseFloat(cur) < parseFloat(val);
                    case 'is_empty': return cur === '';
                    case 'is_not_empty':
                    case 'not_empty': return cur !== '';
                }
                return false;
            }

            function applyLogic() {
                rules.forEach(function(entry) {
                    if (!entry || !entry.target) return;
                    var passed;
                    if (Array.isArray(entry.rules) && entry.rules.length) {
                        var match = entry.match === 'any' ? 'any' : 'all';
                        passed = match === 'all' ? entry.rules.every(evalRule) : entry.rules.some(evalRule);
                    } else {
                        passed = evalRule(entry);
                    }
                    var show = entry.action === 'hide' ? !passed : passed;
                    var $target = $wrap.find('[data-field-id="' + entry.target + '"]').filter('.formforge-field');
                    if (!$target.length) {
                        $target = $wrap.find('[data-field-id="' + entry.target + '"]').closest('.formforge-field');
                    }
                    $target.attr('data-hidden', show ? 'false' : 'true');
                });
                $wrap.trigger('formforge:conditional-updated');
            }

            $wrap.off('.formforgePreviewLogic')
                .on('input.formforgePreviewLogic change.formforgePreviewLogic', 'input, select, textarea', applyLogic);
            applyLogic();
        });
    }

    function installPreviewConversational(doc) {
        $(doc.body).find('.ff-conversational').each(function() {
            var $wrap = $(this);
            var $allSteps = $wrap.find('.ff-conv-step');
            var current = 0;
            if (!$allSteps.length) return;

            var labels = (typeof formforgeAdmin !== 'undefined' && formforgeAdmin.i18n) ? formforgeAdmin.i18n : {};
            var nextLabel = labels.convNext || 'Next →';
            var backLabel = labels.convBack || '← Back';
            var submitLabel = labels.convSubmit || 'Submit';

            if (!$wrap.children('.ff-conv-progress-bar').length) {
                $wrap.prepend('<div class="ff-conv-progress-bar"><div class="ff-conv-progress-fill"></div></div>');
            }
            // Inject counter inside each step-inner (per-step), matching
            // the public frontend layout. See conversational.js for the
            // rationale — counter should sit flush above the question,
            // not float at the corner.
            $allSteps.each(function() {
                var $inner = $(this).find('.ff-conv-step-inner').first();
                if ($inner.length && !$inner.find('> .ff-conv-counter').length) {
                    $inner.prepend('<div class="ff-conv-counter"></div>');
                }
            });
            var $progress = $wrap.children('.ff-conv-progress-bar').first();
            var $counter = $wrap.find('.ff-conv-counter');

            // Inject Next/Back nav into each non-submit step so users can
            // walk through the conversational flow inside the builder
            // preview. Mirrors the live frontend runtime in
            // assets/js/conversational.js — but without validation, since
            // the preview is meant for layout review, not data entry.
            $allSteps.each(function(i) {
                var $step = $(this);
                if ($step.hasClass('ff-conv-step--submit')) return;
                var $inner = $step.find('.ff-conv-step-inner').first();
                if (!$inner.length || $inner.find('.ff-conv-nav').length) return;
                var navHtml = '<div class="ff-conv-nav">'
                    + '<button type="button" class="ff-conv-btn-next" data-action="next">' + nextLabel + '</button>';
                if (i > 0) {
                    navHtml += '<button type="button" class="ff-conv-btn-back" data-action="back">' + backLabel + '</button>';
                }
                navHtml += '</div>';
                $inner.append(navHtml);
            });

            function isStepVisible($step) {
                if ($step.hasClass('ff-conv-step--submit')) return true;
                var $fields = $step.find('.formforge-field');
                if (!$fields.length) return true;
                return $fields.filter(function() {
                    var $field = $(this);
                    return $field.attr('data-hidden') !== 'true' && $field.css('display') !== 'none';
                }).length > 0;
            }

            function visibleSteps() {
                return $allSteps.filter(function() { return isStepVisible($(this)); });
            }

            function showStep(idx) {
                var $visible = visibleSteps();
                var total = $visible.length;
                if (!total) return;
                if (idx < 0) idx = 0;
                if (idx >= total) idx = total - 1;
                current = idx;
                $visible.each(function(visibleIdx) {
                    var $step = $(this);
                    $step.find('.ff-conv-step-number span').first().text(visibleIdx + 1);
                    if ($step.hasClass('ff-conv-step--submit')) return;
                    var $nextVisible = $visible.eq(visibleIdx + 1);
                    var nextIsSubmit = $nextVisible.length && $nextVisible.hasClass('ff-conv-step--submit');
                    $step.find('.ff-conv-btn-next').text(nextIsSubmit ? submitLabel : nextLabel);
                    $step.find('.ff-conv-btn-back').toggle(visibleIdx > 0);
                });
                var $target = $visible.eq(current);
                var targetDomIdx = $allSteps.index($target);
                $allSteps.each(function(i) {
                    var $step = $(this);
                    $step.removeClass('ff-conv-step--active ff-conv-step--prev');
                    if ($step.is($target)) {
                        $step.addClass('ff-conv-step--active');
                    } else if (i < targetDomIdx) {
                        $step.addClass('ff-conv-step--prev');
                    }
                });
                $progress.find('.ff-conv-progress-fill').css('width', (((current + 1) / total) * 100) + '%');
                $counter.text((current + 1) + ' / ' + total);
            }

            function goNext() {
                var $visible = visibleSteps();
                if (current < $visible.length - 1) {
                    showStep(current + 1);
                    resizePreviewIframe();
                }
            }

            function goBack() {
                if (current > 0) {
                    showStep(current - 1);
                    resizePreviewIframe();
                }
            }

            $wrap.off('.formforgePreviewConv')
                .on('formforge:conditional-updated.formforgePreviewConv', function() {
                    showStep(current);
                    resizePreviewIframe();
                })
                .on('click.formforgePreviewConv', '[data-action="next"], .ff-conv-submit-btn', function(e) {
                    e.preventDefault();
                    e.stopPropagation();
                    goNext();
                })
                .on('click.formforgePreviewConv', '[data-action="back"]', function(e) {
                    e.preventDefault();
                    e.stopPropagation();
                    goBack();
                });
            showStep(0);
        });
    }

    function installPreviewRuntime() {
        var doc = previewDocument();
        if (!doc) return;
        installPreviewCalculations(doc);
        installPreviewConversational(doc);
        installPreviewConditionalLogic(doc);
    }

    function resizePreviewIframe() {
        var iframe = previewIframe();
        var doc = previewDocument();
        if (!iframe || !doc) return;
        var height = Math.max(
            doc.body.scrollHeight,
            doc.documentElement ? doc.documentElement.scrollHeight : 0,
            160
        );
        iframe.style.height = height + 'px';
    }

    function previewSuccessMessageText() {
        var settings = readFormSettings();
        var message = settings.success_message || $('#ff-success-message').val() || $('#ff-success-message').attr('placeholder') || '';
        message = $.trim(String(message || ''));
        return message || t('message', 'Success message');
    }

    function applyPreviewSuccessDemo() {
        var $root = previewRoot();
        if (!$root.length) return;
        var $message = $root.find('[data-ff-style-target="message"]').first();
        if (!$message.length) return;

        var isMessageTarget = selectedStyleTarget &&
            selectedStyleTarget.scope === 'settings' &&
            selectedStyleTarget.target === 'message';

        if (isMessageTarget) {
            $message
                .attr('data-ff-preview-demo', 'success')
                .addClass('formforge-message--success formforge-success-state')
                .text(previewSuccessMessageText())
                .show()
                .css('display', 'block');
            return;
        }

        if ($message.attr('data-ff-preview-demo') === 'success') {
            $message
                .removeAttr('data-ff-preview-demo')
                .removeClass('formforge-message--success formforge-success-state')
                .empty()
                .hide();
        }
    }

    function highlightSelectedStyleTarget() {
        var $root = previewRoot();
        applyPreviewSuccessDemo();
        $root.find('.ff-preview-selected').removeClass('ff-preview-selected');
        var selector = selectorForStyleTarget(selectedStyleTarget);
        if (selector) $root.find(selector).first().addClass('ff-preview-selected');
        resizePreviewIframe();
    }

    function decoratePreviewTargets() {
        var $root = previewRoot();
        $root.find('.formforge-wrap').attr('data-ff-style-target', 'form').addClass('ff-preview-selectable');
        $root.find('.formforge-field[data-field-id], [data-ff-style-target]').addClass('ff-preview-selectable');
        highlightSelectedStyleTarget();
    }

    function handlePreviewTargetClick(e) {
        // Interactive controls that are NOT themselves a style-pick target
        // (checkbox/radio inputs nested inside a `.formforge-*-label`, plus
        // <option> elements inside an open <select>) keep their native
        // behavior — toggling them is how the calculation and
        // conditional-logic preview is tested. Text inputs / selects /
        // textareas that carry `data-ff-style-target="control"` directly
        // ARE style-pick targets, so we fall through and let the handler
 // record them. previously the guard returned for ALL
        // input/select/textarea clicks, so clicking the actual input
        // silently kept the previously-selected wrapper (e.g. the label)
        // as the active style target.
        var $clicked = $(e.target);
        if ($clicked.is('input, textarea, select, option') && !$clicked.is('[data-ff-style-target]')) {
            return;
        }
        e.preventDefault();
        e.stopImmediatePropagation();
        e.stopPropagation();
        var target = styleTargetFromClick(e.target);
        if (!target) return;
        selectedStyleTarget = target;
        renderSelectedStylePanel();
        highlightSelectedStyleTarget();
    }

    function handlePreviewCanvasClick(e) {
        if ($(e.target).closest('.formforge-wrap, .formforge-field, [data-ff-style-target]').length) return;
        selectedStyleTarget = { scope: 'settings', target: 'form', title: t('form', 'Form') };
        renderSelectedStylePanel();
        highlightSelectedStyleTarget();
    }

    function bindPreviewDocumentEvents() {
        var doc = previewDocument();
        if (!doc) return;
        $(doc).off('.formforgePreview')
            .on('click.formforgePreview', '.formforge-wrap, .formforge-field, [data-ff-style-target]', handlePreviewTargetClick)
            .on('click.formforgePreview', handlePreviewCanvasClick)
            .on('submit.formforgePreview', 'form', function(e) { e.preventDefault(); });
    }

    function renderPreviewHtml(html) {
        var iframe = previewIframe();
        var $frame = $('#formforge-preview-frame');
        if (!iframe) {
            $frame.html(html);
            $frame.find('form').on('submit', function(e) { e.preventDefault(); });
            decoratePreviewTargets();
            return;
        }
        iframe.onload = function() {
            bindPreviewDocumentEvents();
            installPreviewRuntime();
            decoratePreviewTargets();
            resizePreviewIframe();
            setTimeout(resizePreviewIframe, 80);
            setTimeout(resizePreviewIframe, 300);
        };
        iframe.srcdoc = previewDocumentHtml(html);
    }

    function shortcodeHelpMarkup() {
        var instruction = t('shortcodeInstruction', 'Copy this shortcode and paste it into a page, post, widget, or page builder module.');
        var docsUrl = (typeof formforgeAdmin !== 'undefined' && formforgeAdmin.shortcodeDocsUrl) ? formforgeAdmin.shortcodeDocsUrl : '';
        var title = t('embeddingForms', 'Embedding forms');
        var summary = t('shortcodeDocsSummary', 'Learn where to paste the shortcode and how to test the published form.');
        var html = '<span class="ff-shortcode-help"><span class="ff-shortcode-help-text">' + ffEsc(instruction) + '</span>';
        if (docsUrl) {
            html += '<a href="' + ffEsc(docsUrl) + '" target="_blank" rel="noopener" class="forge-tip"'
                + ' aria-label="' + ffEsc(title + ' - ' + summary) + '"'
                + ' data-tip-title="' + ffEsc(title) + '"'
                + ' data-tip-summary="' + ffEsc(summary) + '">'
                + '<span class="forge-tip__icon" aria-hidden="true">?</span>'
                + '<span class="forge-tip__panel" role="tooltip">'
                    + '<strong class="forge-tip__title">' + ffEsc(title) + '</strong>'
                    + '<span class="forge-tip__summary">' + ffEsc(summary) + '</span>'
                    + '<span class="forge-tip__cta">' + ffEsc(t('readDocs', 'Read the docs')) + ' &rarr;</span>'
                + '</span>'
            + '</a>';
        }
        return html + '</span>';
    }

    var previewTimer = null;
    var previewRequestId = 0;
    function schedulePreviewRefresh() {
        if (!$('#formforge-preview-frame').length) return;
        clearTimeout(previewTimer);
        previewTimer = setTimeout(refreshPreview, 180);
    }

    function refreshPreview() {
        var $frame = $('#formforge-preview-frame');
        if (!$frame.length) return;
        if (typeof window.formforgeSyncFormSettings === 'function') {
            window.formforgeSyncFormSettings();
        }
        var requestId = ++previewRequestId;
        $.post(formforgeAdmin.ajaxUrl, {
            action: 'formforge_preview_form',
            nonce: formforgeAdmin.nonce,
            title: $('#formforge-title').val(),
            fields: JSON.stringify(fieldsForPost()),
            settings: $('#formforge-settings-json').val() || '{}',
            conditional_logic: $('#formforge-conditional-json').val() || '[]'
        }, function(r) {
            if (requestId !== previewRequestId) return;
            if (r && r.success && r.data && typeof r.data.html === 'string') {
                renderPreviewHtml(r.data.html);
            } else {
                $frame.html('<p class="ff-preview-error">Preview unavailable.</p>');
            }
        });
    }
    window.formforgeRefreshPreview = refreshPreview;
    $(document).on('click', '#ff-refresh-preview', refreshPreview);
    $(document).on('input change', '#ff-formsettings-body input, #ff-formsettings-body textarea, #ff-formsettings-body select, #formforge-title', function() {
        markBuilderDirty();
        schedulePreviewRefresh();
    });
    $(document).on('click', '#formforge-preview-frame .formforge-wrap, #formforge-preview-frame .formforge-field, #formforge-preview-frame [data-ff-style-target]', handlePreviewTargetClick);
    $(document).on('click', '#formforge-preview-frame', function(e) {
        if (e.target !== this) return;
        handlePreviewCanvasClick(e);
    });
    $(document).on('input change', '#ff-selected-style-content [data-style-key]', function() {
        if (!selectedStyleTarget) return;
        var store = styleStoreForTarget(selectedStyleTarget) || {};
        var key = $(this).data('style-key');
        var value = $(this).val();
        if (value === '' || value === null) {
            delete store[key];
        } else if ($(this).attr('type') === 'number') {
            value = parseInt(value, 10);
            if (isNaN(value)) {
                delete store[key];
            } else {
                store[key] = value;
            }
        } else {
            store[key] = value;
        }
        saveStyleStoreForTarget(selectedStyleTarget, store);
        schedulePreviewRefresh();
    });
    $(document).on('click', '.ff-style-target-switch', function() {
        if (!selectedStyleTarget || selectedStyleTarget.scope !== 'field') return;
        selectedStyleTarget.target = $(this).data('style-target-switch');
        renderSelectedStylePanel();
        highlightSelectedStyleTarget();
    });

    $(document).on('click', '.ff-style-global-target', function() {
        var target = String($(this).data('style-global-target') || 'form');
        if (target !== 'form' && target !== 'submit' && target !== 'message') target = 'form';
        selectedStyleTarget = { scope: 'settings', target: target };
        renderSelectedStylePanel();
        highlightSelectedStyleTarget();
    });
    $(function() { schedulePreviewRefresh(); });

    // ── Delete field ──
    $(document).on('click', '.formforge-delete-field', function(e) {
        e.preventDefault();
        e.stopPropagation();
        var $card = $(this).closest('.formforge-field-card');
        var idx = $card.index();
        fields.splice(idx, 1);
        $card.slideUp(150, function() { $(this).remove(); syncFieldsFromDOM(); notifyFieldsChanged(); schedulePreviewRefresh(); });
    });

    // ── Edit field ──
    $(document).on('click', '.formforge-field-card', function(e) {
        if ($(e.target).closest('.formforge-field-actions button, .formforge-field-drag-handle, input, textarea, select, a').length) {
            return;
        }
        $(this).find('.formforge-edit-field').trigger('click');
    });

    $(document).on('click', '.formforge-edit-field', function(e) {
        e.preventDefault();
        e.stopPropagation();

        if ($('#formforge-settings-panel').is(':visible')) {
            applyFieldSettings({ closePanel: false, refresh: false });
        }

        var $card = $(this).closest('.formforge-field-card');
        var idx = $card.index();
        var field = $card.data('fieldData') || fields[idx];

        // Fallback: reconstruct field from DOM if data is missing
        if (!field) {
            field = {
                id: 'f' + (idx + 1),
                type: $card.data('type') || 'text',
                label: $card.find('strong').text().replace('*', '').trim(),
                required: $card.find('strong').html().indexOf('DC2626') >= 0,
                placeholder: '',
                description: '',
                options: []
            };
            $card.data('fieldData', field);
            if (!fields[idx]) { fields[idx] = field; }
        }

        // Highlight active card
        $('.formforge-field-card').removeClass('formforge-field-card--editing');
        $card.addClass('formforge-field-card--editing');

        // Helper: HTML-escape values written into attribute / textContent
        // contexts (the panel uses string concatenation, so a raw quote in
        // a previously-saved value would break out of the attribute).
        function aifsEsc(v) {
            if (v === null || v === undefined) return '';
            return String(v)
                .replace(/&/g, '&amp;')
                .replace(/"/g, '&quot;')
                .replace(/'/g, '&#39;')
                .replace(/</g, '&lt;')
                .replace(/>/g, '&gt;');
        }

        var layoutTypes = ['heading', 'html_block', 'divider', 'page_break'];
        var isLayoutField = layoutTypes.indexOf(field.type) >= 0;
        var html = '';

        if (field.type === 'heading') {
            var headingLevel = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].indexOf(field.heading_level) >= 0 ? field.heading_level : 'h3';
            html += '<label>Heading text<input type="text" id="aifs-label" value="' + aifsEsc(field.label) + '"></label>';
            html += '<label>Heading level<select id="aifs-heading-level">';
            ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].forEach(function(level) {
                html += '<option value="' + level + '"' + (headingLevel === level ? ' selected' : '') + '>' + level.toUpperCase() + '</option>';
            });
            html += '</select></label>';
            html += '<div class="aifs-layout-note">Heading renders display text only. It is not submitted and does not use placeholder, required, or validation settings.</div>';
        } else if (field.type === 'html_block') {
            html += '<label>HTML content<textarea id="aifs-html-content" rows="5" placeholder="<p>Intro copy</p>">' + aifsEsc(field.content || '') + '</textarea></label>';
            html += '<div class="aifs-layout-note">HTML Block renders safe formatted content only. It is not submitted and does not use placeholder, required, or validation settings.</div>';
        } else if (field.type === 'divider') {
            html += '<div class="aifs-layout-note">Divider renders a visual separator only. It has no submitted value or input validation settings.</div>';
        } else if (field.type === 'page_break') {
            html += '<div class="aifs-layout-note">Page Break starts the next form step. It is handled by the multi-step renderer and has no submitted value.</div>';
        } else {
            html += '<label>Label<input type="text" id="aifs-label" value="' + aifsEsc(field.label) + '"></label>';
            if (field.type !== 'payment') {
                html += '<label>Placeholder<input type="text" id="aifs-placeholder" value="' + aifsEsc(field.placeholder) + '"></label>';
            }
            html += '<label><input type="checkbox" id="aifs-required" ' + (field.required ? 'checked' : '') + '> Required</label>';
            html += '<label>Description<input type="text" id="aifs-description" value="' + aifsEsc(field.description) + '"></label>';
        }

        if (['select', 'radio', 'checkbox'].indexOf(field.type) >= 0) {
            var opts = (field.options || []).map(function(o) { return typeof o === 'string' ? o : o.value || ''; }).join('\n');
            html += '<label>Options (one per line)<textarea id="aifs-options" rows="4">' + aifsEsc(opts) + '</textarea></label>';
        }

        // ── Payment (Stripe) settings ──────────────────────────────────────
        // audit-r3: surface Amount / Currency / Mode / Description for the
        // payment field. Previously these lived only in raw fields JSON; now
        // an editor can set them from the Field Settings panel.
        if (field.type === 'payment') {
            // Use closure-level `fields` (line 5) rather than re-reading,
            // so calculation fields the user just added are visible too.
            var fieldsForPay = (typeof fields !== 'undefined' ? fields : []);
            // Resolve currently-stored amount: new shape (amount_type+amount_value)
            // wins; legacy `amount` (cents) decays into the fixed editor as dollars.
            var amountType = field.amount_type || (field.amount !== undefined && field.amount !== '' ? 'fixed' : 'fixed');
            var amountValue = '';
            if (field.amount_value !== undefined && field.amount_value !== '') {
                amountValue = field.amount_value;
            } else if (field.amount !== undefined && field.amount !== '') {
                // Legacy cents → dollars for the input (49.99 not 4999).
                var cents = parseInt(field.amount, 10);
                if (!isNaN(cents) && cents > 0) amountValue = (cents / 100).toFixed(2);
            }
            var amountFieldId = field.amount_field_id || '';
            var fcurrency = (field.currency || 'usd').toLowerCase();
            var pmode = field.payment_mode || 'global';
            var pdesc = field.payment_description || '';

            var pSection = '<fieldset class="aifs-section" data-section="payment">';
            pSection += '<legend>Payment (Stripe)</legend>';

            // Amount type radio
            pSection += '<div class="aifs-grid">';
            pSection += '<label class="aifs-checkbox-inline"><input type="radio" name="aifs-amount-type" value="fixed" ' + (amountType === 'fixed' ? 'checked' : '') + '> Fixed amount</label>';
            pSection += '<label class="aifs-checkbox-inline"><input type="radio" name="aifs-amount-type" value="dynamic" ' + (amountType === 'dynamic' ? 'checked' : '') + '> Dynamic (linked to Calculation)</label>';
            pSection += '</div>';

            // Fixed input
            pSection += '<div id="aifs-pay-fixed-wrap" style="' + (amountType === 'dynamic' ? 'display:none;' : '') + '">';
            pSection += '<label>Amount <input type="number" id="aifs-amount-value" min="0.50" step="0.01" value="' + aifsEsc(amountValue) + '" placeholder="9.99"></label>';
            pSection += '<p style="margin:4px 0 0;font-size:12px;color:#94a3b8;">Stored in major currency units (e.g. 49.99 = USD $49.99). Stripe minimum is 0.50.</p>';
            pSection += '</div>';

            // Dynamic dropdown
            pSection += '<div id="aifs-pay-dynamic-wrap" style="' + (amountType === 'dynamic' ? '' : 'display:none;') + '">';
            pSection += '<label>Linked Calculation field <select id="aifs-amount-field-id">';
            pSection += '<option value="">Select a Calculation field...</option>';
            fieldsForPay.forEach(function (f) {
                if (!f || !f.id) return;
                if (f.type !== 'calculation') return;
                pSection += '<option value="' + aifsEsc(f.id) + '"' + (f.id === amountFieldId ? ' selected' : '') + '>' + aifsEsc(f.label || f.id) + '</option>';
            });
            pSection += '</select></label>';
            pSection += '<p style="margin:4px 0 0;font-size:12px;color:#94a3b8;">The visitor is charged whatever the Calculation field evaluates to at submit time.</p>';
            pSection += '</div>';

            // Currency dropdown — Stripe-supported list, common subset.
            var currencies = [
                ['usd','USD — US Dollar'], ['eur','EUR — Euro'], ['gbp','GBP — Pound Sterling'],
                ['cad','CAD — Canadian Dollar'], ['aud','AUD — Australian Dollar'],
                ['jpy','JPY — Japanese Yen'], ['rub','RUB — Russian Ruble'],
                ['cny','CNY — Chinese Yuan'], ['inr','INR — Indian Rupee'],
                ['brl','BRL — Brazilian Real'], ['mxn','MXN — Mexican Peso']
            ];
            pSection += '<label>Currency<select id="aifs-payment-currency">';
            currencies.forEach(function (c) {
                pSection += '<option value="' + c[0] + '"' + (c[0] === fcurrency ? ' selected' : '') + '>' + aifsEsc(c[1]) + '</option>';
            });
            pSection += '</select></label>';

            // Mode (per-field override of global Stripe mode)
            pSection += '<label>Mode<select id="aifs-payment-mode">';
            pSection += '<option value="global"' + (pmode === 'global' ? ' selected' : '') + '>Inherit from global Stripe settings</option>';
            pSection += '<option value="test"' + (pmode === 'test' ? ' selected' : '') + '>Test (force test keys)</option>';
            pSection += '<option value="live"' + (pmode === 'live' ? ' selected' : '') + '>Live (force live keys)</option>';
            pSection += '</select></label>';

            // Description sent to Stripe only. Visitor-facing copy uses the
            // standard field Description setting above.
            pSection += '<label>Stripe payment description<input type="text" id="aifs-payment-description" value="' + aifsEsc(pdesc) + '" placeholder="Premium subscription monthly"></label>';
            pSection += '<p style="margin:4px 0 0;font-size:12px;color:#94a3b8;">Shown in Stripe Dashboard; use the regular Description field for visitor-facing copy.</p>';

            pSection += '</fieldset>';
            html += pSection;
        }

        // Calculation settings: formula + display formatting. Formulas use
        // field tokens such as {price} or {quantity}; the frontend resolves
        // both field IDs and sanitized labels so tester/user examples work.
        if (field.type === 'calculation') {
            var calcSection = '<fieldset class="aifs-section ff-calc-section" data-section="calculation">';
            calcSection += '<legend>Calculation</legend>';
            calcSection += '<label>Formula<textarea id="aifs-calc-formula" rows="3" placeholder="{price} * {quantity}">' + aifsEsc(field.formula || '') + '</textarea></label>';

            var calcSources = (typeof fields !== 'undefined' ? fields : []);
            var nonCalcSources = ['heading', 'html_block', 'divider', 'page_break', 'hidden', 'file_upload', 'repeater', 'map_address', 'payment', 'calculation'];
            var tokenHtml = '';
            function calcTokenSlug(v) {
                v = String(v || '').toLowerCase();
                v = v.replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '');
                return v;
            }
            function addCalcToken(tokens, token) {
                token = calcTokenSlug(token);
                if (token && tokens.indexOf(token) < 0) tokens.push(token);
            }
            calcSources.forEach(function(f) {
                if (!f || !f.id || String(f.id) === String(field.id || '')) return;
                if (nonCalcSources.indexOf(f.type) >= 0) return;
                var tokens = [];
                addCalcToken(tokens, f.id);
                addCalcToken(tokens, f.label || '');
                tokens.forEach(function(token) {
                    tokenHtml += '<button type="button" class="button ff-calc-token" data-token="{' + aifsEsc(token) + '}">{' + aifsEsc(token) + '}</button>';
                });
            });
            if (tokenHtml) {
                calcSection += '<div class="ff-calc-token-label">Available tokens</div>';
                calcSection += '<div class="ff-calc-token-list">' + tokenHtml + '</div>';
            } else {
                calcSection += '<p class="ff-calc-help">Add number fields before this calculation to use them as formula tokens.</p>';
            }

            calcSection += '<div class="aifs-grid">';
            calcSection += '<label>Prefix<input type="text" id="aifs-calc-prefix" value="' + aifsEsc(field.prefix || '') + '" placeholder="$"></label>';
            calcSection += '<label>Suffix<input type="text" id="aifs-calc-suffix" value="' + aifsEsc(field.suffix || '') + '" placeholder="total"></label>';
            calcSection += '<label>Decimal places<input type="number" id="aifs-calc-decimals" min="0" max="6" value="' + aifsEsc(field.decimals !== undefined && field.decimals !== null ? field.decimals : '') + '" placeholder="2"></label>';
            calcSection += '</div>';
            calcSection += '<p class="ff-calc-help">Use standard math operators: +, -, *, / and parentheses. Example: {price} * {quantity}.</p>';
            calcSection += '</fieldset>';
            html += calcSection;
        }

        // ── Validation section ─────────────────────────────────────────────
        // Renders type-conditional validation controls. The data model
        // already supports min_length/max_length/regex/min/max/accept/max_size
        // (see class-field-types.php::validate and class-form-handler.php),
        // we just expose them here. HTML5 native validation (minlength,
        // maxlength, pattern, min, max) takes care of frontend UX before
        // submit; PHP re-validates on submit as defense-in-depth.
        var hasValidation = false;
        var validationHtml = '<fieldset class="aifs-section" data-section="validation">';
        validationHtml += '<legend>Validation</legend>';

        // text / email / url / tel / textarea / password — character length + pattern (text only for pattern)
        if (['text','email','url','phone','tel','textarea','password'].indexOf(field.type) >= 0) {
            hasValidation = true;
            validationHtml += '<div class="aifs-grid">';
            validationHtml += '<label>Min length<input type="number" id="aifs-min-length" min="0" value="' + aifsEsc(field.min_length || '') + '"></label>';
            validationHtml += '<label>Max length<input type="number" id="aifs-max-length" min="0" value="' + aifsEsc(field.max_length || '') + '"></label>';
            validationHtml += '</div>';
            // Pattern only for text-class single-line fields (textarea has no pattern attr in HTML5)
            if (['text','password'].indexOf(field.type) >= 0) {
                validationHtml += '<label>Pattern (regex)<input type="text" id="aifs-pattern" value="' + aifsEsc(field.regex || '') + '" placeholder="^[a-zA-Z0-9]+$"></label>';
                validationHtml += '<label>Pattern error message<input type="text" id="aifs-regex-message" value="' + aifsEsc(field.regex_message || '') + '" placeholder="Format is invalid"></label>';
            }
        }

        // number / range — min/max/step
        if (field.type === 'number' || field.type === 'range') {
            hasValidation = true;
            validationHtml += '<div class="aifs-grid">';
            validationHtml += '<label>Min value<input type="number" id="aifs-min" value="' + aifsEsc(field.min !== undefined && field.min !== '' ? field.min : '') + '"></label>';
            validationHtml += '<label>Max value<input type="number" id="aifs-max" value="' + aifsEsc(field.max !== undefined && field.max !== '' ? field.max : '') + '"></label>';
            validationHtml += '</div>';
            validationHtml += '<label>Step<input type="number" id="aifs-step" min="0" step="any" value="' + aifsEsc(field.step !== undefined && field.step !== '' ? field.step : '') + '" placeholder="1"></label>';
        }

        // file_upload — accepted types + max size
        if (field.type === 'file_upload') {
            hasValidation = true;
            var acceptCsv = field.accept || '';
            // The renderer puts this string straight into the HTML accept="" attribute.
            // We give the user checkboxes for common types AND a free-text override field.
            var acceptPresets = [
                { v: 'image/jpeg,image/png,image/gif,image/webp', l: 'Images (JPG/PNG/GIF/WebP)' },
                { v: 'application/pdf', l: 'PDF' },
                { v: 'application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document', l: 'Word (DOC/DOCX)' },
                { v: 'text/plain,text/csv', l: 'Text/CSV' }
            ];
            validationHtml += '<div style="font-size:13px;font-weight:600;color:#64748b;margin-bottom:4px;">Allowed file types</div>';
            validationHtml += '<div class="aifs-file-presets">';
            acceptPresets.forEach(function(p, pi) {
                var allMatch = p.v.split(',').every(function(t){ return acceptCsv.indexOf(t) >= 0; });
                validationHtml += '<label class="aifs-checkbox-inline"><input type="checkbox" class="aifs-accept-preset" data-types="' + aifsEsc(p.v) + '" ' + (allMatch ? 'checked' : '') + '> ' + aifsEsc(p.l) + '</label>';
            });
            validationHtml += '</div>';
            validationHtml += '<label>Custom accept (overrides checkboxes)<input type="text" id="aifs-accept" value="' + aifsEsc(acceptCsv) + '" placeholder=".jpg,.pdf or image/*"></label>';
 validationHtml += '<label>Max file size (MB)<input type="number" id="aifs-max-size" min="1" max="100" value="' + aifsEsc(field.max_size_mb || '') + '" placeholder="10"></label>';
 validationHtml += '<p style="margin:4px 0 0;font-size:12px;color:#94a3b8;">Server-side cap is also enforced. Free plan: 5 MB; PRO: up to 100 MB.</p>';
 }

 validationHtml += '</fieldset>';
 if (hasValidation) html += validationHtml;

 // ── Advanced section ───────────────────────────────────────────────
 // Default value (for input-class fields), CSS class (universal).
 var hasAdvanced = false;
 var advancedHtml = '<fieldset class="aifs-section" data-section="advanced">';
 advancedHtml += '<legend>Advanced</legend>';

 // Default value — supported by hidden + most input-style fields.
 if (!isLayoutField && ['text','email','url','phone','tel','number','range','textarea','password','hidden','color','date','time'].indexOf(field.type) >= 0) {
 hasAdvanced = true;
 advancedHtml += '<label>Default value<input type="text" id="aifs-default-value" value="' + aifsEsc(field.default_value || '') + '"></label>';
 }

 // CSS classes — universal, attaches to wrapper div.
 if (!isLayoutField) {
 hasAdvanced = true;
 advancedHtml += '<label>CSS classes<input type="text" id="aifs-css-class" value="' + aifsEsc(field.css_class || '') + '" placeholder="my-field highlighted"></label>';
 advancedHtml += '<p style="margin:4px 0 0;font-size:12px;color:#94a3b8;">Space-separated. Added to the wrapper div on the rendered form.</p>';
 }

 advancedHtml += '</fieldset>';
 if (hasAdvanced) html += advancedHtml;

 // ── Conditional Logic section (PRO) ──────────────────────────────
 // Lets the editor declare "show/hide THIS field based on values of
 // OTHER fields", with multi-rule + AND/OR matching. The rule data
 // is stored on `field._cl_pending` until Apply, then gathered into
 // the top-level `conditional_logic` array on Save Form.
 //
 // Skipped entirely for layout-only fields and for fields without an
 // id (newly-added pre-save fields with no stable id yet would be
 // useless as a target).
 var clTargetable = field.id && ['heading', 'html_block', 'divider', 'page_break'].indexOf(field.type) < 0;
 if (clTargetable) {
 var existingCL = window.__ffGetRuleForField ? window.__ffGetRuleForField(field.id) : null;
 // Use field._cl_pending (in-memory edit cache) if the user has
 // edited this field already in this session without saving yet.
 if (field._cl_pending !== undefined) existingCL = field._cl_pending;

 var clEnabled = !!(existingCL && Array.isArray(existingCL.rules) && existingCL.rules.length > 0);
 var clAction = (existingCL && existingCL.action) || 'show';
 var clMatch = (existingCL && existingCL.match) || 'all';
 var clRules = (existingCL && Array.isArray(existingCL.rules) && existingCL.rules.length)
 ? existingCL.rules.slice
 : [{ source: '', operator: 'equals', value: '' }];

 html += '<fieldset class="aifs-section ff-cl-section" data-section="conditional-logic">';
 html += '<legend>Conditional Logic <span class="ff-cl-badge">PRO</span></legend>';

 var isPro = (typeof formforgeAdmin !== 'undefined' && formforgeAdmin.isPro);
 if (!isPro) {
 var upgradeHref = (typeof formforgeAdmin !== 'undefined' && formforgeAdmin.upgradeUrl) ? formforgeAdmin.upgradeUrl : '#';
 html += '<p class="ff-cl-locked">Show or hide this field based on what visitors entered in earlier fields. ';
 html += '<a href="' + aifsEsc(upgradeHref) + '" target="_blank" rel="noopener">Upgrade to Pro</a> to use Conditional Logic.</p>';
 html += '</fieldset>';
 } else {
 html += '<label class="ff-cl-toggle"><input type="checkbox" id="aifs-cl-enabled" ' + (clEnabled ? 'checked' : '') + '> Enable conditional logic for this field</label>';

 html += '<div id="aifs-cl-builder" style="' + (clEnabled ? '' : 'display:none;') + '">';

 html += '<div class="aifs-grid">';
 html += '<label>Action<select id="aifs-cl-action">';
 html += '<option value="show"' + (clAction === 'show' ? ' selected' : '') + '>Show this field</option>';
 html += '<option value="hide"' + (clAction === 'hide' ? ' selected' : '') + '>Hide this field</option>';
 html += '</select></label>';

 html += '<label>Match<select id="aifs-cl-match">';
 html += '<option value="all"' + (clMatch === 'all' ? ' selected' : '') + '>All conditions (AND)</option>';
 html += '<option value="any"' + (clMatch === 'any' ? ' selected' : '') + '>Any condition (OR)</option>';
 html += '</select></label>';
 html += '</div>';

 // Build the source-field options once. We exclude the field
 // being edited (a field can't condition on itself) and the
 // layout-only types that don't capture user input.
 var sourceOptions = '';
 var allFields = (typeof fields !== 'undefined' ? fields : );
 var nonInputTypes = ['heading', 'html_block', 'divider', 'page_break'];
 allFields.forEach(function(f) {
 if (!f || !f.id) return;
 if (f.id === field.id) return;
 if (nonInputTypes.indexOf(f.type) >= 0) return;
 var lbl = (f.label || f.id) + ' (' + (f.type || 'text') + ')';
 sourceOptions += '<option value="' + aifsEsc(f.id) + '">' + aifsEsc(lbl) + '</option>';
 });

 html += '<div id="aifs-cl-rules" data-source-options="' + aifsEsc(sourceOptions) + '">';
 html += '</div>';

	 html += '<button type="button" class="button ff-cl-add-rule">+ Add rule</button>';
	 html += '<div class="ff-cl-summary" id="aifs-cl-summary"></div>';
	 html += '<p class="ff-cl-help">When the rule below ' + (clAction === 'show' ? 'matches, this field appears' : 'matches, this field is hidden') + ' on the published form. Hidden fields are skipped from validation and not saved.</p>';

 html += '</div>'; // /aifs-cl-builder

 html += '</fieldset>';

 // Stash rule list + the per-field map onto the panel so we
 // can rehydrate on rule-render below.
 window.__ffPendingClRules = clRules;
 window.__ffPendingClField = field;
 }
 }

 // availability settings live on the calendar_picker FIELD,
 // not on the Google Calendar form-level card. The field can work
 // standalone (no Google) — it just renders all configured slots.
 // When Google IS connected, the form-level card adds busy filtering
 // and event creation on top.
 if (field.type === 'calendar_picker') {
 var ws = field.work_start !== undefined ? field.work_start : 9;
 var we = field.work_end !== undefined ? field.work_end : 17;
 var sd = field.slot_duration !== undefined ? field.slot_duration : 30;
 var adv = field.advance_days !== undefined ? field.advance_days : 30;
 var days = Array.isArray(field.available_days) && field.available_days.length
 ? field.available_days
 : [1,2,3,4,5]; // Mon–Fri default
 html += '<fieldset style="border:1px solid #e2e8f0;padding:10px;border-radius:6px;margin-top:10px;">';
 html += '<legend style="font-size:12px;font-weight:600;color:#64748b;text-transform:uppercase;letter-spacing:.4px;padding:0 6px;">Availability</legend>';
 html += '<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;">';
 html += '<label>Work start hour (0–23)<input type="number" id="aifs-work-start" min="0" max="23" value="' + ws + '"></label>';
 html += '<label>Work end hour (1–24)<input type="number" id="aifs-work-end" min="1" max="24" value="' + we + '"></label>';
 html += '<label>Slot duration (min)<input type="number" id="aifs-slot-duration" min="5" max="240" step="5" value="' + sd + '"></label>';
 html += '<label>Advance days<input type="number" id="aifs-advance-days" min="1" max="365" value="' + adv + '"></label>';
 html += '</div>';
 html += '<div style="margin-top:8px;font-size:13px;font-weight:600;color:#64748b;">Available days</div>';
 var labels = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];
 html += '<div style="display:flex;gap:6px;margin-top:4px;flex-wrap:wrap;">';
 for (var di = 0; di < 7; di++) {
 var d = di + 1; // 1=Mon..7=Sun (ISO)
 var checked = days.indexOf(d) >= 0 ? 'checked' : '';
 html += '<label style="display:inline-flex;align-items:center;gap:4px;font-weight:normal;background:#f8fafc;padding:4px 8px;border-radius:4px;border:1px solid #e2e8f0;">'
 + '<input type="checkbox" class="aifs-avail-day" data-day="' + d + '" ' + checked + '>'
 + labels[di] + '</label>';
 }
 html += '</div>';
 html += '<p style="margin:8px 0 0;font-size:12px;color:#94a3b8;">These rules render slots even when Google Calendar isn\'t connected. With Google connected, busy slots are additionally filtered.</p>';
 html += '</fieldset>';
 }

 html += '<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:12px;">';
 html += '<span style="font-size:12px;color:#64748b;">' + ffEsc(t('autoDraft', 'Changes update the draft automatically.')) + '</span>';
 html += '<button type="button" class="button" id="aifs-cancel">' + ffEsc(t('close', 'Close')) + '</button>';
 html += '</div>';

	 $('#formforge-settings-content').html(html).attr('data-index', idx).attr('data-field-id', field.id || '');
 $('#formforge-settings-panel').show;
 if (field && field.id) {
 selectedStyleTarget = {
 scope: 'field',
 target: 'wrapper',
 fieldId: field.id,
 title: (field.label || field.id) + ' / ' + t('fieldWrapper', 'Field wrapper')
 };
 renderSelectedStylePanel;
 highlightSelectedStyleTarget;
 }

 // Hydrate the conditional-logic rule rows now that the container is in
 // the DOM. We render rules as HTML rather than re-using a templated
 // string in `html += ...` because we want each row to have a stable
 // index for the remove button to reference.
 if (window.__ffPendingClRules && $('#aifs-cl-rules').length) {
 var clRulesArr = window.__ffPendingClRules;
 var $clRules = $('#aifs-cl-rules');
 $clRules.empty;
	 clRulesArr.forEach(function(rule, ri) {
	 $clRules.append(buildClRuleRow(rule, ri));
	 });
	 updateClSummary;
	 }

 if (window.matchMedia && window.matchMedia('(max-width: 1200px)').matches) {
 var panel = document.getElementById('formforge-settings-panel');
 if (panel) {
 setTimeout(function {
 panel.scrollIntoView({ behavior: 'smooth', block: 'start' });
 }, 40);
 }
 }
 });

 // Build a single conditional-logic rule row. Operators kept in sync with
 // class-form-handler.php::compute_hidden_fields and frontend.js evalRule.
	 function buildClRuleRow(rule, index) {
 var ops = [
 { v: 'equals', l: 'equals' },
 { v: 'not_equals', l: 'does not equal' },
 { v: 'contains', l: 'contains' },
 { v: 'not_contains', l: 'does not contain' },
 { v: 'greater_than', l: 'greater than' },
 { v: 'less_than', l: 'less than' },
 { v: 'is_empty', l: 'is empty' },
 { v: 'is_not_empty', l: 'is not empty' }
 ];
 var sourceOptions = $('#aifs-cl-rules').attr('data-source-options') || '';
 var sel = (rule && rule.source) || '';
 var op = (rule && rule.operator) || 'equals';
 var val = (rule && rule.value) || '';

 var noValueOp = (op === 'is_empty' || op === 'is_not_empty');

 var $row = $('<div class="ff-cl-rule" data-rule-index="' + index + '"></div>');

 // Source field selector
 var $srcLabel = $('<label class="ff-cl-rule-source">If field</label>');
 var $src = $('<select class="aifs-cl-rule-source">' + sourceOptions + '</select>');
 $src.find('option[value="' + sel.replace(/"/g, '\\"') + '"]').prop('selected', true);
 $srcLabel.append($src);

 // Operator selector
 var $opLabel = $('<label class="ff-cl-rule-op">Condition</label>');
 var $op = $('<select class="aifs-cl-rule-op"></select>');
 ops.forEach(function(o) {
 $op.append('<option value="' + o.v + '"' + (o.v === op ? ' selected' : '') + '>' + o.l + '</option>');
 });
 $opLabel.append($op);

 // Value input
 var $valLabel = $('<label class="ff-cl-rule-val">Value</label>');
 var $val = $('<input type="text" class="aifs-cl-rule-val">');
 $val.val(val);
 if (noValueOp) {
 $val.prop('disabled', true).attr('placeholder', 'n/a');
 }
 $valLabel.append($val);

 // Remove button
 var $rm = $('<button type="button" class="ff-cl-remove-rule" title="Remove rule">×</button>');

 $row.append($srcLabel).append($opLabel).append($valLabel).append($rm);
	 return $row;
	 }

	 function updateClSummary {
	 var $summary = $('#aifs-cl-summary');
	 if (!$summary.length) return;
	 if (!$('#aifs-cl-enabled').is(':checked')) {
	 $summary.text('Conditional logic is off for this field.');
	 return;
	 }
	 var rules = ;
	 $('#aifs-cl-rules .ff-cl-rule').each(function {
	 var $row = $(this);
	 var sourceText = $row.find('.aifs-cl-rule-source option:selected').text || '';
	 var opText = $row.find('.aifs-cl-rule-op option:selected').text || '';
	 var value = $row.find('.aifs-cl-rule-val').val || '';
	 if (!sourceText) return;
	 rules.push(sourceText + ' ' + opText + (value ? ' "' + value + '"' : ''));
	 });
	 var match = $('#aifs-cl-match option:selected').text || 'All conditions';
	 $summary.text(rules.length ? (rules.length + ' rule' + (rules.length === 1 ? '' : 's') + ': ' + match + ' — ' + rules.join('; ')) : 'No active rules yet.');
	 }

 // Toggle the rule-builder visibility when the user flips the enable checkbox.
	 $(document).on('change', '#aifs-cl-enabled', function {
 var on = $(this).is(':checked');
 $('#aifs-cl-builder').toggle(on);
 // Auto-add a first empty row if turning ON and no rules exist yet.
	 if (on && $('#aifs-cl-rules').children.length === 0) {
	 $('#aifs-cl-rules').append(buildClRuleRow({ source: '', operator: 'equals', value: '' }, 0));
	 }
	 updateClSummary;
	 });

 // Add a new empty rule row.
 $(document).on('click', '.ff-cl-add-rule', function {
	 var $rules = $('#aifs-cl-rules');
	 var idx = $rules.children.length;
	 $rules.append(buildClRuleRow({ source: '', operator: 'equals', value: '' }, idx));
	 updateClSummary;
	 });

 // Remove a rule row. If it's the only row, leave one empty row so the
 // user always has somewhere to type — refusing to delete would be
 // surprising. We just blank it instead.
 $(document).on('click', '.ff-cl-remove-rule', function {
 var $row = $(this).closest('.ff-cl-rule');
 var $rules = $row.parent;
 if ($rules.children.length <= 1) {
	 $row.find('select.aifs-cl-rule-source').val('');
	 $row.find('select.aifs-cl-rule-op').val('equals').trigger('change');
	 $row.find('input.aifs-cl-rule-val').val('').prop('disabled', false);
	 updateClSummary;
	 return;
	 }
	 $row.remove;
	 updateClSummary;
	 });

 // Disable the value input for is_empty / is_not_empty (no value needed).
 $(document).on('change', '.aifs-cl-rule-op', function {
 var $row = $(this).closest('.ff-cl-rule');
 var op = $(this).val;
	 var noVal = (op === 'is_empty' || op === 'is_not_empty');
	 $row.find('input.aifs-cl-rule-val').prop('disabled', noVal).attr('placeholder', noVal ? 'n/a' : '');
	 updateClSummary;
	 });

	 $(document).on('input change', '.aifs-cl-rule-source, .aifs-cl-rule-val, #aifs-cl-action, #aifs-cl-match', updateClSummary);

 $(document).on('click', '.ff-calc-token', function(e) {
 e.preventDefault;
 var token = $(this).data('token') || $(this).text;
 var $formula = $('#aifs-calc-formula');
 if (!$formula.length || !token) return;
 var el = $formula.get(0);
 var current = $formula.val || '';
 if (typeof el.selectionStart === 'number' && typeof el.selectionEnd === 'number') {
 var start = el.selectionStart;
 var end = el.selectionEnd;
 $formula.val(current.slice(0, start) + token + current.slice(end));
 el.selectionStart = el.selectionEnd = start + String(token).length;
 } else {
 $formula.val(current + token);
 }
 $formula.trigger('input').focus;
 });

 // Helper used during edit-panel render to find an existing rule for this
 // field across ALL forms of the conditional_logic JSON (legacy flat or
 // grouped). Returns a normalized grouped object {target, action, match,
 // rules:[{source,operator,value}]}.
 window.__ffGetRuleForField = function(fieldId) {
 try {
 var raw = $('#formforge-conditional-json').val || '';
 var arr = JSON.parse(raw);
 if (!Array.isArray(arr)) return null;
 // Look for a grouped entry first.
 for (var i = 0; i < arr.length; i++) {
 var e = arr[i];
 if (e && e.target === fieldId && Array.isArray(e.rules)) {
 return {
 target: fieldId,
 action: e.action || 'show',
 match: e.match || 'all',
 rules: e.rules.map(function(r) {
 return {
 source: r.source || '',
 operator: r.operator || 'equals',
 value: r.value == null ? '' : String(r.value)
 };
 })
 };
 }
 }
 // Then collect any flat-shape rules targeting this field and
 // normalize to grouped — preserving the user's existing AI/REST
 // rules in the new editor.
 var flatRules = ;
 var seenAction = 'show';
 for (var j = 0; j < arr.length; j++) {
 var f = arr[j];
 if (f && f.target === fieldId && !Array.isArray(f.rules)) {
 flatRules.push({
 source: f.source || '',
 operator: f.operator || 'equals',
 value: f.value == null ? '' : String(f.value)
 });
 if (f.action) seenAction = f.action;
 }
 }
 if (flatRules.length) {
 return { target: fieldId, action: seenAction, match: 'all', rules: flatRules };
 }
 return null;
 } catch(e) { return null; }
 };

 // ── Field settings update the draft immediately ──
 function applyFieldSettings(options) {
 options = options || {};
 var closePanel = options.closePanel !== false;
	 var idx = parseInt($('#formforge-settings-content').attr('data-index') || $('#aifs-apply').data('index'), 10);
	 var fieldId = $('#formforge-settings-content').attr('data-field-id') || '';
	 if (isNaN(idx) && !fieldId) return false;
	 var $card = fieldId ? findFieldCardById(fieldId) : $('#formforge-fields-list .formforge-field-card').eq(idx);
	 if (!$card.length && !isNaN(idx)) {
	 $card = $('#formforge-fields-list .formforge-field-card').eq(idx);
	 }
	 var field = $card.data('fieldData') || (fieldId ? findFieldById(fieldId) : null) || fields[idx];
	 if (!field) return false;
	 if ($('#aifs-label').length) {
	 field.label = $('#aifs-label').val;
	 }
	 if ($('#aifs-placeholder').length) {
	 field.placeholder = $('#aifs-placeholder').val;
	 }
 if ($('#aifs-required').length) {
 field.required = $('#aifs-required').is(':checked');
 }
 if ($('#aifs-description').length) {
 field.description = $('#aifs-description').val;
 }
 if ($('#aifs-heading-level').length) {
 var headingLevel = $('#aifs-heading-level').val || 'h3';
 field.heading_level = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].indexOf(headingLevel) >= 0 ? headingLevel : 'h3';
 }
 if ($('#aifs-html-content').length) {
 field.content = $('#aifs-html-content').val || '';
 }

 if ($('#aifs-options').length) {
 field.options = $('#aifs-options').val.split('\n').filter(Boolean).map(function(v) { return { value: v.trim, label: v.trim }; });
 }

 // ── Persist Validation settings ────────────────────────────────────
 // Convert to int where applicable; an empty input becomes undefined
 // so we don't write `min_length: 0` (which is a falsy validation
 // result anyway, but keeps the JSON clean).
 function aifsIntOrEmpty(id) {
 var $el = $('#' + id);
 if (!$el.length) return undefined;
 var v = $el.val;
 if (v === '' || v === null) return '';
 var n = parseInt(v, 10);
 return isNaN(n) ? '' : n;
 }
 function aifsStrOrEmpty(id) {
 var $el = $('#' + id);
 if (!$el.length) return undefined;
 return $el.val;
 }

 var ml = aifsIntOrEmpty('aifs-min-length'); if (ml !== undefined) field.min_length = ml;
 var Ml = aifsIntOrEmpty('aifs-max-length'); if (Ml !== undefined) field.max_length = Ml;
 var pat = aifsStrOrEmpty('aifs-pattern'); if (pat !== undefined) field.regex = pat;
 var patMsg = aifsStrOrEmpty('aifs-regex-message'); if (patMsg !== undefined) field.regex_message = patMsg;

 if (field.type === 'number' || field.type === 'range') {
 var mn = aifsIntOrEmpty('aifs-min'); if (mn !== undefined) field.min = mn;
 var mx = aifsIntOrEmpty('aifs-max'); if (mx !== undefined) field.max = mx;
 var $stepEl = $('#aifs-step');
 if ($stepEl.length) {
 var stepVal = $stepEl.val;
 if (stepVal === '' || stepVal === null) {
 field.step = '';
 } else {
 var stepN = parseFloat(stepVal);
 field.step = isNaN(stepN) ? '' : stepN;
 }
 }
 }

 // audit-r3: persist Payment (Stripe) settings.
 if (field.type === 'payment') {
 var amtType = $('input[name="aifs-amount-type"]:checked').val || 'fixed';
 field.amount_type = amtType;
 if (amtType === 'fixed') {
 var amtVal = $('#aifs-amount-value').val;
 if (amtVal === '' || amtVal === null) {
 field.amount_value = '';
 } else {
 var amtNum = parseFloat(amtVal);
 field.amount_value = isNaN(amtNum) ? '' : amtNum;
 }
 // Drop the legacy `amount` (cents) so re-saves don't carry
 // a stale value alongside the new amount_value (dollars).
 delete field.amount;
 field.amount_field_id = '';
 } else if (amtType === 'dynamic') {
 field.amount_field_id = $('#aifs-amount-field-id').val || '';
 field.amount_value = '';
 delete field.amount;
 }
 field.currency = ($('#aifs-payment-currency').val || 'usd').toLowerCase;
 field.payment_mode = $('#aifs-payment-mode').val || 'global';
 field.payment_description = $('#aifs-payment-description').val || '';
 }

 if (field.type === 'calculation') {
 field.formula = $('#aifs-calc-formula').val || '';
 field.prefix = $('#aifs-calc-prefix').val || '';
 field.suffix = $('#aifs-calc-suffix').val || '';
 var calcDecimals = aifsIntOrEmpty('aifs-calc-decimals');
 if (calcDecimals !== undefined) {
 if (calcDecimals === '') {
 field.decimals = '';
 } else {
 if (calcDecimals < 0) calcDecimals = 0;
 if (calcDecimals > 6) calcDecimals = 6;
 field.decimals = calcDecimals;
 }
 }
 }

 if (field.type === 'file_upload') {
 // If user typed a custom accept string, that wins; otherwise
 // collect the checked presets.
 var customAccept = $('#aifs-accept').val || '';
 var presetTokens = ;
 $('.aifs-accept-preset:checked').each(function{
 var t = $(this).data('types') || '';
 if (typeof t === 'string') {
 t.split(',').forEach(function(token){
 token = token.trim;
 if (token && presetTokens.indexOf(token) < 0) presetTokens.push(token);
 });
 }
 });
 // If the customAccept is the same set as the presets (i.e. user
 // didn't manually edit), recompute from checkboxes; else trust
 // user's custom string.
 var presetCsv = presetTokens.join(',');
 var presetSet = presetTokens.slice.sort.join(',');
 var customSet = customAccept.split(',').map(function(s){return s.trim;}).filter(Boolean).sort.join(',');
 if (customAccept === '' || customSet === presetSet) {
 field.accept = presetCsv;
 } else {
 field.accept = customAccept;
 }
 var maxSize = aifsIntOrEmpty('aifs-max-size');
 if (maxSize !== undefined) field.max_size_mb = maxSize;
 }

 // ── Persist Advanced settings ─────────────────────────────────────
 var dv = aifsStrOrEmpty('aifs-default-value'); if (dv !== undefined) field.default_value = dv;
 var cls = aifsStrOrEmpty('aifs-css-class'); if (cls !== undefined) field.css_class = cls;

 // Field styles are now edited from the Live preview inspector. Preserve
 // existing style data when applying non-style field settings.

 // persist availability settings into the field itself.
 if (field.type === 'calendar_picker' && $('#aifs-work-start').length) {
 field.work_start = parseInt($('#aifs-work-start').val || 9, 10);
 field.work_end = parseInt($('#aifs-work-end').val || 17, 10);
 field.slot_duration = parseInt($('#aifs-slot-duration').val || 30, 10);
 field.advance_days = parseInt($('#aifs-advance-days').val || 30, 10);
 field.available_days = $('.aifs-avail-day:checked').map(function{
 return parseInt($(this).data('day'), 10);
 }).get;
 }

 // ── Persist Conditional Logic settings ─────────────────────────────
 // Collect rules from the DOM into `field._cl_pending`. The Save Form
 // handler reads this from every field and assembles the top-level
 // `conditional_logic` array to POST to the server.
 if ($('#aifs-cl-enabled').length) {
 if ($('#aifs-cl-enabled').is(':checked')) {
 var ruleRows = ;
 $('#aifs-cl-rules .ff-cl-rule').each(function {
 var $row = $(this);
 var src = $row.find('.aifs-cl-rule-source').val || '';
 var op = $row.find('.aifs-cl-rule-op').val || 'equals';
 var val = $row.find('.aifs-cl-rule-val').val || '';
 // Skip empty source — half-filled rule, ignore so we don't
 // emit `{source:'', operator:'equals', value:''}` which
 // would make the field permanently hidden (every value
 // equals empty trivially false).
 if (!src && op !== 'is_empty' && op !== 'is_not_empty') return;
 ruleRows.push({ source: src, operator: op, value: val });
 });
 if (ruleRows.length) {
 field._cl_pending = {
 target: field.id,
 action: $('#aifs-cl-action').val || 'show',
 match: $('#aifs-cl-match').val || 'all',
 rules: ruleRows
 };
 } else {
 // Toggle ON but no usable rule — treat as disabled.
 field._cl_pending = null;
 }
 } else {
 // Toggle OFF — explicitly clear so Save removes any existing
 // rule for this field.
 field._cl_pending = null;
 }
 }

 // Update card display and store updated field data
 var req = field.required ? '<span style="color:#DC2626;">*</span>' : '';
 $card.find('strong').html(field.label + ' ' + req);
 $card.find('.formforge-field-type-badge').text(fieldTypeLabel(field.type));
 $card.data('fieldData', field);
 if (closePanel) {
 $card.removeClass('formforge-field-card--editing');
 $('#formforge-settings-panel').hide;
 }
 notifyFieldsChanged;
 if (options.refresh === false) {
 schedulePreviewRefresh;
 } else {
 refreshPreview;
 }
 return true;
 }

 $(document).on('click', '#aifs-apply', function {
 applyFieldSettings({ closePanel: true });
 });

 var aifsAutoTimer = null;
 $(document).on('input change', '#formforge-settings-content input, #formforge-settings-content textarea, #formforge-settings-content select', function(e) {
 if ($(e.target).is('#aifs-cancel')) return;
 clearTimeout(aifsAutoTimer);
 aifsAutoTimer = setTimeout(function {
 applyFieldSettings({ closePanel: false, refresh: false });
 }, 120);
 });

 $(document).on('click', '#aifs-cancel', function {
 applyFieldSettings({ closePanel: true, refresh: false });
 $('.formforge-field-card').removeClass('formforge-field-card--editing');
 $('#formforge-settings-panel').hide;
 });

 // audit-r3: Payment field — swap fixed-amount input vs dynamic-field
 // dropdown when the radio changes.
 $(document).on('change', 'input[name="aifs-amount-type"]', function{
 var v = this.value;
 if (v === 'dynamic') {
 $('#aifs-pay-fixed-wrap').hide;
 $('#aifs-pay-dynamic-wrap').show;
 } else {
 $('#aifs-pay-fixed-wrap').show;
 $('#aifs-pay-dynamic-wrap').hide;
 }
 });

 // When the user ticks/unticks a file-type preset, mirror the combined
 // tokens into the visible "Custom accept" input so they see what will
 // be saved. Manual edits to that input always win.
 $(document).on('change', '.aifs-accept-preset', function{
 var tokens = ;
 $('.aifs-accept-preset:checked').each(function{
 var t = $(this).data('types') || '';
 if (typeof t === 'string') {
 t.split(',').forEach(function(token){
 token = token.trim;
 if (token && tokens.indexOf(token) < 0) tokens.push(token);
 });
 }
 });
 $('#aifs-accept').val(tokens.join(','));
 });

 // ── Save form ──
 $('#formforge-save').on('click', function {
 var $btn = $(this);
 $btn.prop('disabled', true).text('Saving...');

 // Collect fields from DOM data
 syncFieldsFromDOM;

 // ── Build the conditional_logic payload ────────────────────────────
 // Strategy: start from whatever the hidden `formforge-conditional-json`
 // currently holds (legacy rules from AI insights / REST imports we
 // haven't touched), then for each field that the user edited in this
 // session (has `_cl_pending` defined), REPLACE that field's entries.
 //
 // `_cl_pending = null` means the user explicitly disabled CL on the
 // field — strip every rule targeting it.
 // `_cl_pending = {target, action, match, rules}` means the user set
 // up rules — strip any old entry for the target, then push the new one.
 // `_cl_pending = undefined` means the user never opened the editor for
 // this field — leave existing rules untouched.
 var existingCl = ;
 try {
 var raw = $('#formforge-conditional-json').val || '';
 existingCl = JSON.parse(raw);
 if (!Array.isArray(existingCl)) existingCl = ;
 } catch (e) { existingCl = ; }

 var stripTargets = {};
 var addEntries = ;
 fields.forEach(function(f) {
 if (!f || !f.id) return;
 if (typeof f._cl_pending === 'undefined') return;
 stripTargets[f.id] = true;
 if (f._cl_pending && f._cl_pending.rules && f._cl_pending.rules.length) {
 addEntries.push({
 target: f.id,
 action: f._cl_pending.action || 'show',
 match: f._cl_pending.match || 'all',
 rules: f._cl_pending.rules
 });
 }
 });
 var newCl = existingCl.filter(function(entry) {
 return !(entry && entry.target && stripTargets[entry.target]);
 }).concat(addEntries);

 // Clean up the in-memory pending markers so they don't bloat the
 // fields JSON we POST.
 var fieldsForPost = fields.map(function(f) {
 if (!f) return f;
 var clone = $.extend({}, f);
 delete clone._cl_pending;
 return clone;
 });

 $.post(formforgeAdmin.ajaxUrl, {
 action: 'formforge_save_form',
 nonce: formforgeAdmin.nonce,
 form_id: $('#formforge-form-id').val,
 title: $('#formforge-title').val,
 fields: JSON.stringify(fieldsForPost),
 settings: $('#formforge-settings-json').val || '{}',
 conditional_logic: JSON.stringify(newCl)
 }, function(r) {
 $btn.prop('disabled', false).text('Save Form');
 if (r.success) {
 var isNew = !$('#formforge-form-id').val || $('#formforge-form-id').val === '0';
 markBuilderClean;

 // Update form ID for subsequent saves
 if (r.data.id) {
 $('#formforge-form-id').val(r.data.id);
 // Update URL without reload so browser back works
 if (isNew && window.history.replaceState) {
 window.history.replaceState(null, '', formforgeAdmin.ajaxUrl.replace('admin-ajax.php', 'admin.php?page=formforge-new&form_id=' + r.data.id));
 }
 }

 // Refresh the hidden conditional-json input so subsequent
 // re-opens of the field-settings panel see the just-saved
 // rules (not the pre-save state).
 $('#formforge-conditional-json').val(JSON.stringify(newCl));
 // Clear in-memory pending markers — they're now persisted.
 fields.forEach(function(f) { if (f) delete f._cl_pending; });

 // Show/update shortcode
 if (r.data.id && !$('.ff-shortcode-wrap').length) {
 var sc = '[formforge id="' + r.data.id + '"]';
 var $wrap = $('<span class="ff-shortcode-cluster"><span class="ff-shortcode-wrap">' +
 '<code class="ff-shortcode-code" id="ff-shortcode-text">' + sc + '</code>' +
 '<button type="button" class="ff-shortcode-copy" id="ff-copy-shortcode" title="' + ffEsc(t('copyShortcode', 'Copy shortcode')) + '" aria-label="' + ffEsc(t('copyShortcode', 'Copy shortcode')) + '">' +
 '<span class="dashicons dashicons-clipboard"></span></button></span>' + shortcodeHelpMarkup + '</span>');
 $('.formforge-save-bar').append($wrap);
 // Bind copy
 $wrap.find('.ff-shortcode-copy').on('click', function{
 navigator.clipboard.writeText(sc);
 var $b = $(this);
 $b.html('<span class="dashicons dashicons-yes-alt"></span>').addClass('copied');
 setTimeout(function{ $b.html('<span class="dashicons dashicons-clipboard"></span>').removeClass('copied'); }, 1500);
 });
 }

 showBuilderToast((r.data && r.data.message) || 'Form saved!', 'success');
 } else {
 showBuilderToast(ajaxErrorMessage(r, 'Save failed.'), 'error');
 }
 }).fail(function(xhr) {
 $btn.prop('disabled', false).text('Save Form');
 var response = null;
 try { response = xhr.responseJSON || JSON.parse(xhr.responseText || '{}'); } catch (e) {}
 showBuilderToast(ajaxErrorMessage(response, 'Save failed.'), 'error');
 });
 });

 // ── Global: load template fields into builder without saving ──
 window.formforgeLoadTemplate = function(title, tplFields) {
 
 // Set title
 var $title = $('#formforge-title');
 if ($title.length) $title.val(title);

 // Clear existing fields
 var $list = $('#formforge-fields-list');
 $list.find('.formforge-field-card, .formforge-empty-state').remove;
 fields = ;
 nextId = 1;

 // Add each field from template
 if (!tplFields || !tplFields.length) { console.warn('[FormForge] No fields in template'); return; }
 tplFields.forEach(function(f) {
 var field = $.extend({}, { id: 'f' + nextId++, type: 'text', label: 'Field', required: false, placeholder: '', options: }, f);
 fields.push(field);
 var $card = buildFieldCard(field, fields.length - 1);
 $list.append($card);
 });
 
 notifyFieldsChanged;
 };

 // ── Delete form from list ──
 $(document).on('click', '.formforge-delete-form', function(e) {
 e.preventDefault;
 if (!confirm('Delete this form and all submissions?')) return;
 $.post(formforgeAdmin.ajaxUrl, { action: 'formforge_delete_form', nonce: formforgeAdmin.nonce, form_id: $(this).data('id') }, function { location.reload; });
 });

 // ── AI Generate stub ──
 $('#formforge-ai-generate').on('click', function {
 var prompt = $('#formforge-ai-prompt').val;
 if (!prompt) { $('#formforge-ai-prompt').focus; return; }
 var $btn = $(this);
 $btn.prop('disabled', true).html('<span class="dashicons dashicons-update spin"></span> Generating...');
 $.ajax({
 url: formforgeAdmin.restUrl + '/generate',
 method: 'POST',
 contentType: 'application/json',
 data: JSON.stringify({ prompt: prompt }),
 headers: { 'X-WP-Nonce': formforgeAdmin.restNonce },
 success: function(r) {
 $btn.prop('disabled', false).html('<span class="dashicons dashicons-controls-play" style="margin-top:3px;"></span> Generate with AI');
 if (r.error === 'credits_exhausted') {
 var resetDate = r.reset_date ? new Date(r.reset_date).toLocaleDateString : '';
 var msg = 'Monthly AI credits exhausted.';
 if (resetDate) msg += ' Resets on ' + resetDate + '.';
 alert(msg);
 return;
 }
 if (r.fields && r.fields.length) {
 // If AI returns fields, add them
 r.fields.forEach(function(f) {
 f.id = 'f' + nextId++;
 fields.push(f);
 var $card = buildFieldCard(f, fields.length - 1);
 $('#formforge-fields-list').append($card);
 });
 $('#formforge-empty').hide;
 notifyFieldsChanged;
 } else {
 alert(r.message || 'AI generation coming soon. Configure API key in Settings.');
 }
 }
 });
 });

 $(function {
 renderSelectedStylePanel;
 openAiEditorRequest;
 });

})(jQuery);

/* ============================================================
 bug 3: Free-plan UI lock for Pro integration cards
 ============================================================
 Backend (class-form-builder.php save handler) strips Pro
 features on save when license=free — but the UI lets users
 fill in API keys / toggles first, which is confusing UX.
 This block detects Pro-marked .ff-settings-card containers
 on the form editor page and applies a CSS class that the
 admin.css "ff-settings-card--pro-locked" rule then renders
 as a greyed-out, non-interactive card with a small "Install
 Pro to enable" overlay.
 Runs only when body has .ff-admin--free (added by Free
 FORMFORGE_License via class-core.php add_body_class).
*/
(function () {
    function markProCards() {
        if (!document.body || !document.body.classList.contains('ff-admin--free')) return;
        var cards = document.querySelectorAll('.ff-settings-card');
        cards.forEach(function (card) {
            if (card.classList.contains('ff-settings-card--pro-locked')) return;
            var head = card.children[0];
            if (!head) return;
            var hasChip = !!card.querySelector('.formforge-pro-chip');
            var hasInlinePro = false;
            var spans = head.querySelectorAll('span');
            for (var i = 0; i < spans.length; i++) {
                if (/^PRO$/i.test((spans[i].textContent || '').trim())) {
                    hasInlinePro = true;
                    break;
                }
            }
            if (hasChip || hasInlinePro) {
                card.classList.add('ff-settings-card--pro-locked');
            }
        });
    }
    // Run on initial DOM ready and again on AJAX-driven re-renders.
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', markProCards);
    } else {
        markProCards();
    }
    // Re-scan after a short delay in case the form editor builds
    // integration cards asynchronously.
    setTimeout(markProCards, 500);
    setTimeout(markProCards, 1500);
})();
