<?php

namespace GriffinForms\Admin\App\Html\Modals\ConditionalLogic;

trait Scripts
{
    // Emits all required JavaScript snippets for the conditional logic modal UI.
    public function getModalJs() {
        $this->modalPreludeJs();
        $this->openSubeditorJs();
        $this->modalEventBindingsJs();
        $this->subeditorToggleJs();
        $this->getMessageDisplayJs();
        $this->itemsDropdownChangeJs();
        $this->itemSelectDropdownChangeJs();
        $this->attributeSelectDropdownChangeJs();
        $this->attributeConditionDropdownChangeJs();
        $this->addRuleJs();
        $this->showRulesListJs();
        $this->backToListJs();
        $this->modalShowEventJs();
        $this->modalHideEventJs();
        $this->subeditorBlockToggleJs();
        $this->optionGroupInputJs();
        $this->likertRowInputJs();
        $this->richInputJs();
        $this->getRequestsJs();
    }
    
    // Outputs shared helpers and state setup used across modal scripts.
    protected function modalPreludeJs()
    {
        echo '
        const listWrapper = $(".griffinforms-logic-list-wrapper");
        const editorWrapper = $(".griffinforms-logic-editor-wrapper");
        const subeditorWrapper = $(".griffinforms-logic-subeditor-wrapper");
        const subeditorOverlay = $("#griffinforms-logic-subeditor-overlay");
        const subeditorOverlayTrigger = $("#griffinforms-logic-subeditor-overlay-trigger");
        const ruleTitleEl = $(".griffinforms-active-rule-title");
        if (typeof window.griffinformsActiveRuleIndex === "undefined") {
            window.griffinformsActiveRuleIndex = null;
        }

        function griffinformsSetCurrentEntryPayload(payload) {
            const normalized = (payload && typeof payload === "object") ? payload : {};
            subeditorWrapper.data("gf-entry-payload", normalized);
        }

        function griffinformsGetCurrentEntryPayload() {
            const stored = subeditorWrapper.data("gf-entry-payload");
            if (stored && typeof stored === "object") {
                return stored;
            }
            return {};
        }

        window.griffinformsSetCurrentEntryPayload = griffinformsSetCurrentEntryPayload;
        window.griffinformsGetCurrentEntryPayload = griffinformsGetCurrentEntryPayload;

        function griffinformsParseEntryPayload(rawPayload) {
            if (rawPayload && typeof rawPayload === "object") {
                return jQuery.extend(true, {}, rawPayload);
            }

            if (typeof rawPayload === "string" && rawPayload.length) {
                try {
                    const parsed = JSON.parse(rawPayload);
                    if (parsed && typeof parsed === "object") {
                        return parsed;
                    }
                } catch (err) {
                    return null;
                }
            }

            return null;
        }

        function griffinformsExtractEntryPayload(element) {
            const el = element && element.length ? element : null;
            if (!el) {
                return null;
            }

            const explicitPayload = el.data("gfEntryPayload");
            if (explicitPayload && typeof explicitPayload === "object") {
                return jQuery.extend(true, {}, explicitPayload);
            }

            const rawPayload = el.attr("data-gf-entry");
            if (typeof rawPayload === "string" && rawPayload.length) {
                return griffinformsParseEntryPayload(rawPayload);
            }

            return null;
        }

        function griffinformsSetRuleTitle(title) {
            if (!ruleTitleEl.length) {
                return;
            }
            const safeTitle = typeof title === "string" ? title.trim() : "";
            if (safeTitle.length) {
                ruleTitleEl.html(safeTitle).removeClass("d-none");
            } else {
                ruleTitleEl.html("").addClass("d-none");
            }
        }

        const HIGHLIGHT_CLASS = "griffinforms-logic-edit-highlight";

        function griffinformsClearHighlights() {
            editorWrapper.find("." + HIGHLIGHT_CLASS).removeClass(HIGHLIGHT_CLASS + " border-dark");
            listWrapper.find("." + HIGHLIGHT_CLASS).removeClass(HIGHLIGHT_CLASS + " border-dark");
        }

        function griffinformsHighlightItem(type, ruleIndex, entryIndex, groupKey) {
            griffinformsClearHighlights();

            const parsedRule = parseInt(ruleIndex, 10);
            const parsedEntry = parseInt(entryIndex, 10);
            const normalizedGroup = typeof groupKey === "string" ? groupKey.toLowerCase() : "";

            if (!Number.isFinite(parsedRule) || !Number.isFinite(parsedEntry)) {
                return;
            }

            let selector;
            if (type === "action") {
                selector = `.griffinforms-logic-action-item[data-gf-rule-index="${parsedRule}"][data-gf-entry-index="${parsedEntry}"]`;
            } else {
                const groupSegment = normalizedGroup === "any"
                    ? `[data-gf-condition-group="any"]`
                    : `[data-gf-condition-group="all"]`;
                selector = `.griffinforms-logic-condition-item${groupSegment}[data-gf-rule-index="${parsedRule}"][data-gf-entry-index="${parsedEntry}"]`;
            }
            const target = editorWrapper.find(selector);

            if (target.length) {
                target.addClass(HIGHLIGHT_CLASS + " border-dark");
            }
        }

        window.griffinformsHighlightItem = griffinformsHighlightItem;
        window.griffinformsClearHighlights = griffinformsClearHighlights;

        function griffinformsShowSubeditorOverlay(forceRefresh) {
            if (!subeditorOverlay.length || !subeditorWrapper.length || subeditorWrapper.hasClass("d-none")) {
                return;
            }

            const wasHidden = subeditorOverlay.hasClass("d-none") || subeditorOverlay.is(":hidden");

            subeditorOverlay
                .stop(true, true)
                .removeClass("d-none")
                .attr("aria-hidden", "false");

            if (wasHidden) {
                subeditorOverlay
                    .css("opacity", 0)
                    .animate({ opacity: 1 }, 180, function () {
                        $(this).css("opacity", "");
                    });
            }

            griffinformsOverlayPrepare(!!forceRefresh);
        }

        function griffinformsHideSubeditorOverlay() {
            if (!subeditorOverlay.length) {
                return;
            }

            const isAlreadyHidden = subeditorOverlay.hasClass("d-none") || !subeditorOverlay.is(":visible");

            if (isAlreadyHidden) {
                subeditorOverlay
                    .addClass("d-none")
                    .attr("aria-hidden", "true")
                    .css("opacity", "");
            } else {
                subeditorOverlay
                    .stop(true, true)
                    .animate({ opacity: 0 }, 160, function () {
                        subeditorOverlay
                            .addClass("d-none")
                            .attr("aria-hidden", "true")
                            .css("opacity", "");
                    });
            }

            griffinformsOverlaySetState({ targetInput: null });
        }

        window.griffinformsShowSubeditorOverlay = griffinformsShowSubeditorOverlay;
        window.griffinformsHideSubeditorOverlay = griffinformsHideSubeditorOverlay;

        const subeditorSaveButton = $("#griffinforms-logic-subeditor-save");

        function griffinformsSetSaveButtonDisabled(disabled) {
            if (!subeditorSaveButton.length) {
                return;
            }
            const shouldDisable = !!disabled;
            subeditorSaveButton.prop("disabled", shouldDisable);
            subeditorSaveButton.toggleClass("is-disabled", shouldDisable);
        }

        window.griffinformsSetSaveButtonDisabled = griffinformsSetSaveButtonDisabled;

        function griffinformsApplyConnectorDelays() {
            const tips = $(".griffinforms-logic-subeditor-connector-tip");
            if (!tips.length) {
                return;
            }

            let fallbackDuration = 2.4;
            tips.each(function(index) {
                const tip = $(this);
                const durationCss = tip.css("animation-duration") || "";
                const parsedDuration = parseFloat(durationCss.replace("s", ""));
                const duration = Number.isFinite(parsedDuration) && parsedDuration > 0
                    ? parsedDuration
                    : fallbackDuration;

                fallbackDuration = duration;

                const delay = index * duration;
                tip.css("--connector-delay", delay + "s");
                tip.css("animation-delay", delay + "s");
            });
        }

        function griffinformsPauseConnectorAnimation() {
            const tips = $(".griffinforms-logic-subeditor-connector-tip");
            if (!tips.length) {
                return;
            }
            tips.removeClass("is-active");
            tips.css("animation-play-state", "paused");
        }

        function griffinformsResumeConnectorAnimation() {
            const tips = $(".griffinforms-logic-subeditor-connector-tip");
            if (!tips.length) {
                return;
            }

            tips.removeClass("is-active");

            tips.each(function() {
                const tip = $(this);
                tip.css("animation", "none");
                void tip[0].offsetHeight;
                tip.css("animation", "");
            });

            griffinformsApplyConnectorDelays();

            window.requestAnimationFrame(function() {
                tips.addClass("is-active");
                tips.css("animation-play-state", "running");
            });
        }

        window.griffinformsApplyConnectorDelays = griffinformsApplyConnectorDelays;
        window.griffinformsPauseConnectorAnimation = griffinformsPauseConnectorAnimation;
        window.griffinformsResumeConnectorAnimation = griffinformsResumeConnectorAnimation;
        griffinformsPauseConnectorAnimation();

        const overlayStrings = {
            selectFieldPlaceholder: "' . esc_js(__('Select a field…', 'griffinforms-form-builder')) . '",
            selectAttributePlaceholder: "' . esc_js(__('Select an attribute…', 'griffinforms-form-builder')) . '",
            loadingFields: "' . esc_js(__('Loading fields…', 'griffinforms-form-builder')) . '",
            previewPlaceholder: "' . esc_js(__('Select a field to generate a variable.', 'griffinforms-form-builder')) . '",
            valueLabel: "' . esc_js(__('Value', 'griffinforms-form-builder')) . '",
            iterationCountLabel: "' . esc_js(__('Iteration count', 'griffinforms-form-builder')) . '",
            valueCountLabel: "' . esc_js(__('Value count', 'griffinforms-form-builder')) . '",
            productNameLabel: "' . esc_js(__('Product name', 'griffinforms-form-builder')) . '",
            productQuantityLabel: "' . esc_js(__('Product quantity', 'griffinforms-form-builder')) . '",
            allIterations: "' . esc_js(__('All iterations', 'griffinforms-form-builder')) . '",
            iterationOption: "' . esc_js(__('Iteration %d', 'griffinforms-form-builder')) . '",
            insertErrorNoInput: "' . esc_js(__('No active input is available for insertion.', 'griffinforms-form-builder')) . '",
            insertErrorIncomplete: "' . esc_js(__('Select a field and attribute before inserting.', 'griffinforms-form-builder')) . '",
            insertErrorNumber: "' . esc_js(__('Variables cannot be inserted into number inputs.', 'griffinforms-form-builder')) . '",
            insertSuccess: "' . esc_js(__('Variable inserted successfully.', 'griffinforms-form-builder')) . '"
        };

        function griffinformsOverlayGetElements() {
            return {
                overlay: subeditorOverlay,
                itemType: $("#griffinforms-logic-subeditor-overlay-item-type"),
                field: $("#griffinforms-logic-subeditor-overlay-field"),
                attribute: $("#griffinforms-logic-subeditor-overlay-attribute"),
                iterationGroup: $("#griffinforms-logic-subeditor-overlay-iteration-group"),
                iteration: $("#griffinforms-logic-subeditor-overlay-iteration"),
                insert: $("#griffinforms-logic-subeditor-overlay-insert"),
                preview: $("#griffinforms-logic-subeditor-overlay-preview"),
                spinner: $("#griffinforms-logic-subeditor-overlay-spinner")
            };
        }

        function griffinformsOverlayGetState() {
            if (!subeditorOverlay.length) {
                return {};
            }
            let state = subeditorOverlay.data("gfOverlayState");
            if (!state || typeof state !== "object") {
                state = {
                    itemType: "field",
                    fieldId: "",
                    fieldType: "",
                    attribute: "",
                    iteration: "",
                    fields: [],
                    targetInput: null
                };
                subeditorOverlay.data("gfOverlayState", state);
            }
            return state;
        }

        function griffinformsOverlaySetState(update) {
            if (!subeditorOverlay.length) {
                return {};
            }
            const state = griffinformsOverlayGetState();
            if (update && typeof update === "object") {
                Object.keys(update).forEach(function(key) {
                    state[key] = update[key];
                });
            }
            subeditorOverlay.data("gfOverlayState", state);
            return state;
        }

        function griffinformsOverlaySetLoading(isLoading) {
            const elements = griffinformsOverlayGetElements();
            if (!elements.overlay.length) {
                return;
            }
            if (isLoading) {
                elements.spinner.removeClass("d-none");
                elements.field.prop("disabled", true);
                elements.attribute.prop("disabled", true);
                elements.iteration.prop("disabled", true);
                elements.insert.prop("disabled", true);
            } else {
                elements.spinner.addClass("d-none");
            }
        }

        function griffinformsOverlayResetControls() {
            const elements = griffinformsOverlayGetElements();
            elements.field.prop("disabled", true).empty().append(
                $("<option>", { value: "", text: overlayStrings.selectFieldPlaceholder })
            );
            elements.attribute.prop("disabled", true).empty().append(
                $("<option>", { value: "", text: overlayStrings.selectAttributePlaceholder })
            );
            elements.iteration.prop("disabled", true).empty().append(
                $("<option>", { value: "", text: overlayStrings.allIterations })
            );
            elements.iterationGroup.addClass("d-none");
            elements.insert.prop("disabled", true);
            elements.preview.text(overlayStrings.previewPlaceholder);
            griffinformsOverlaySetState({
                fieldId: "",
                fieldType: "",
                attribute: "",
                iteration: ""
            });
        }

        function griffinformsOverlayFindFieldMeta(fieldId) {
            const state = griffinformsOverlayGetState();
            const fields = Array.isArray(state.fields) ? state.fields : [];
            if (!fieldId || !fields.length) {
                return null;
            }
            const lookup = fieldId.toString();
            for (let index = 0; index < fields.length; index += 1) {
                const candidate = fields[index];
                if (candidate && String(candidate.id) === lookup) {
                    return candidate;
                }
            }
            return null;
        }

        function griffinformsOverlayPopulateFieldOptions(fields) {
            const elements = griffinformsOverlayGetElements();
            const select = elements.field;
            const previousValue = (select.val() || "").toString();

            select.empty().append($("<option>", { value: "", text: overlayStrings.selectFieldPlaceholder }));

            if (Array.isArray(fields) && fields.length) {
                fields.forEach(function(field) {
                    if (!field) {
                        return;
                    }
                    const id = String(field.id || "");
                    if (!id.length) {
                        return;
                    }
                    const label = field.label && field.label.length ? field.label : `#${id}`;
                    const fieldType = field.type || "";
                    const iterations = field.iterations || {};
                    const parsedMin = parseInt(iterations.min, 10);
                    const parsedMax = parseInt(iterations.max, 10);
                    const option = $("<option>", {
                        value: id,
                        text: label
                    });
                    option.attr("data-gf-field-type", fieldType);
                    option.attr("data-gf-iterations-allow", iterations.allow ? "1" : "0");
                    option.attr("data-gf-iterations-min", Number.isFinite(parsedMin) ? parsedMin : "");
                    option.attr("data-gf-iterations-max", Number.isFinite(parsedMax) ? parsedMax : "");
                    select.append(option);
                });
                select.prop("disabled", false);
            } else {
                select.prop("disabled", true);
            }

            if (previousValue && select.find(`option[value="${previousValue}"]`).length) {
                select.val(previousValue);
            } else {
                select.val("");
            }
        }

        function griffinformsOverlayPopulateAttributeOptions(fieldMeta) {
            const elements = griffinformsOverlayGetElements();
            const select = elements.attribute;
            const previousValue = (select.val() || "").toString();

            select.empty().append($("<option>", { value: "", text: overlayStrings.selectAttributePlaceholder }));

            if (fieldMeta) {
                const fieldType = (fieldMeta.type || "").toLowerCase();
                let attrs;
                if ("payment" === fieldType) {
                    attrs = [
                        { value: "value", label: overlayStrings.valueLabel },
                        { value: "product_name", label: overlayStrings.productNameLabel },
                        { value: "product_quantity", label: overlayStrings.productQuantityLabel }
                    ];
                } else {
                    attrs = [
                        { value: "value", label: overlayStrings.valueLabel },
                        { value: "iteration_count", label: overlayStrings.iterationCountLabel }
                    ];
                    if ("inputcheckbox" === fieldType) {
                        attrs.push({ value: "value_count", label: overlayStrings.valueCountLabel });
                    }
                }
                attrs.forEach(function(option) {
                    select.append($("<option>", { value: option.value, text: option.label }));
                });
                select.prop("disabled", false);
            } else {
                select.prop("disabled", true);
                griffinformsOverlaySetState({ attribute: "", iteration: "" });
            }

            if (previousValue && select.find(`option[value="${previousValue}"]`).length) {
                select.val(previousValue);
            } else {
                select.val("");
                griffinformsOverlaySetState({ attribute: "", iteration: "" });
            }
        }

        function griffinformsOverlayPopulateIterationOptions(fieldMeta) {
            const elements = griffinformsOverlayGetElements();
            const select = elements.iteration;
            const state = griffinformsOverlayGetState();
            const attribute = (state.attribute || "").toLowerCase();
            const previousValue = (select.val() || "").toString();

            select.empty().append($("<option>", { value: "", text: overlayStrings.allIterations }));
            select.prop("disabled", true);
            elements.iterationGroup.addClass("d-none");

            if (!fieldMeta || attribute !== "value") {
                griffinformsOverlaySetState({ iteration: "" });
                return;
            }

            const iterations = fieldMeta.iterations || {};
            if (!iterations.allow) {
                griffinformsOverlaySetState({ iteration: "" });
                return;
            }

            let maxIterations = parseInt(iterations.max, 10);
            const minIterations = parseInt(iterations.min, 10);
            if (!Number.isFinite(maxIterations) || maxIterations <= 0) {
                if (Number.isFinite(minIterations) && minIterations > 0) {
                    maxIterations = minIterations;
                } else {
                    maxIterations = 5;
                }
            }

            if (previousValue && Number(previousValue) > maxIterations) {
                maxIterations = Number(previousValue);
            }

            for (let index = 1; index <= maxIterations; index += 1) {
                const label = overlayStrings.iterationOption.replace("%d", index);
                select.append($("<option>", { value: String(index), text: label }));
            }

            select.prop("disabled", false);
            elements.iterationGroup.removeClass("d-none");

            if (previousValue && select.find(`option[value="${previousValue}"]`).length) {
                select.val(previousValue);
            } else {
                select.val("");
                griffinformsOverlaySetState({ iteration: "" });
            }
        }

        function griffinformsOverlayHandleFieldChange() {
            const elements = griffinformsOverlayGetElements();
            const select = elements.field;
            const selectedOption = select.find("option:selected");
            const fieldId = (selectedOption.val() || "").toString();
            const fieldType = selectedOption.length ? (selectedOption.data("gf-field-type") || "").toString() : "";
            griffinformsOverlaySetState({
                fieldId: fieldId,
                fieldType: fieldType
            });
            const fieldMeta = griffinformsOverlayFindFieldMeta(fieldId);
            griffinformsOverlayPopulateAttributeOptions(fieldMeta);
            griffinformsOverlayPopulateIterationOptions(fieldMeta);
            griffinformsOverlayUpdatePreview();
        }

        function griffinformsOverlayHandleAttributeChange() {
            const elements = griffinformsOverlayGetElements();
            const attrValue = (elements.attribute.val() || "").toString();
            griffinformsOverlaySetState({ attribute: attrValue });
            const state = griffinformsOverlayGetState();
            const fieldMeta = griffinformsOverlayFindFieldMeta(state.fieldId);
            griffinformsOverlayPopulateIterationOptions(fieldMeta);
            griffinformsOverlayUpdatePreview();
        }

        function griffinformsOverlayHandleIterationChange() {
            const elements = griffinformsOverlayGetElements();
            const iterationValue = (elements.iteration.val() || "").toString();
            griffinformsOverlaySetState({ iteration: iterationValue });
            griffinformsOverlayUpdatePreview();
        }

        function griffinformsOverlayBuildPayload() {
            const state = griffinformsOverlayGetState();
            const itemType = (state.itemType || "field").toLowerCase();
            if (itemType !== "field") {
                return null;
            }

            const fieldId = (state.fieldId || "").toString();
            const attribute = (state.attribute || "").toLowerCase();

            if (!fieldId.length || !attribute.length) {
                return null;
            }

            const payload = {
                type: "field",
                id: fieldId
            };

            payload.attr = attribute.length ? attribute : "value";

            if ("value" === payload.attr) {
                const iterationValue = (state.iteration || "").toString();
                const parsedIteration = parseInt(iterationValue, 10);
                if (Number.isFinite(parsedIteration) && parsedIteration > 0) {
                    payload.iteration = parsedIteration;
                }
            }

            return payload;
        }

        function griffinformsOverlayUpdateInsertButton() {
            const elements = griffinformsOverlayGetElements();
            const payload = griffinformsOverlayBuildPayload();
            elements.insert.prop("disabled", !payload);
        }

        function griffinformsOverlayUpdatePreview() {
            const elements = griffinformsOverlayGetElements();
            const payload = griffinformsOverlayBuildPayload();
            if (payload) {
                const jsonPreview = JSON.stringify(payload, null, 2);
                elements.preview.text(jsonPreview);
            } else {
                elements.preview.text(overlayStrings.previewPlaceholder);
            }
            griffinformsOverlayUpdateInsertButton();
        }

        function griffinformsOverlayFindTargetInput() {
            if (!subeditorWrapper.length) {
                return null;
            }
            const activeBlock = subeditorWrapper.find(".griffinforms-logic-subeditor-block").not(".d-none").first();
            if (!activeBlock.length) {
                return null;
            }

            const input = activeBlock.find(".griffinforms-condition-input, .griffinforms-action-input").first();
            if (!input.length) {
                return null;
            }
            // If this is a rich div, return the paired hidden input instead
            if (input.attr("data-gf-rich-input") === "1") {
                const $hidden = input.siblings(".gf-logic-rich-input-hidden");
                return $hidden.length ? $hidden : input;
            }
            return input;
        }

        function griffinformsOverlayPrefillSelections() {
            const elements = griffinformsOverlayGetElements();
            const state = griffinformsOverlayGetState();
            let desiredFieldId = (state.fieldId || "").toString();

            if (!desiredFieldId.length) {
                const activeFieldOption = $("#griffinforms-logic-subeditor-item-selection-wrapper").find("select option:selected");
                if (activeFieldOption.length) {
                    desiredFieldId = (activeFieldOption.val() || "").toString();
                }
            }

            if (desiredFieldId.length && elements.field.find(`option[value="${desiredFieldId}"]`).length) {
                elements.field.val(desiredFieldId);
                griffinformsOverlaySetState({ fieldId: desiredFieldId });
            } else {
                elements.field.val("");
                griffinformsOverlaySetState({ fieldId: "" });
            }

            griffinformsOverlayHandleFieldChange();
        }

        function griffinformsOverlayPrepare(forceRefresh) {
            const state = griffinformsOverlaySetState({
                targetInput: griffinformsOverlayFindTargetInput()
            });

            const elements = griffinformsOverlayGetElements();
            if (elements.itemType.length) {
                elements.itemType.val(state.itemType || "field");
            }

            griffinformsOverlaySetLoading(true);
            let fetchHadError = false;
            let fetchErrorMessage = overlayStrings.previewPlaceholder;

            griffinformsOverlayFetchFields({
                forceRefresh: !!forceRefresh,
                beforeSend: function() {
                    griffinformsOverlaySetLoading(true);
                    griffinformsOverlayResetControls();
                    const elements = griffinformsOverlayGetElements();
                    elements.preview.text(overlayStrings.loadingFields);
                },
                onSuccess: function(fields) {
                    const cloned = Array.isArray(fields) ? fields.slice() : [];
                    griffinformsOverlaySetState({ fields: cloned });
                    griffinformsOverlayPopulateFieldOptions(cloned);
                    griffinformsOverlayPrefillSelections();
                },
                onError: function(message) {
                    fetchHadError = true;
                    fetchErrorMessage = message || overlayStrings.previewPlaceholder;
                    const elements = griffinformsOverlayGetElements();
                    elements.preview.text(fetchErrorMessage);
                },
                onComplete: function() {
                    griffinformsOverlaySetLoading(false);
                    if (fetchHadError) {
                        const elements = griffinformsOverlayGetElements();
                        elements.insert.prop("disabled", true);
                        elements.preview.text(fetchErrorMessage);
                    } else {
                        griffinformsOverlayUpdatePreview();
                    }
                }
            });
        }

        function griffinformsOverlayInsertPayload() {
            const payload = griffinformsOverlayBuildPayload();
            if (!payload) {
                griffinformsShowLogicMessage("error", overlayStrings.insertErrorIncomplete);
                return;
            }

            const state = griffinformsOverlayGetState();
            let targetInput = state.targetInput;
            if (!targetInput || !targetInput.length) {
                targetInput = griffinformsOverlayFindTargetInput();
                griffinformsOverlaySetState({ targetInput: targetInput });
            }

            if (!targetInput || !targetInput.length) {
                griffinformsShowLogicMessage("error", overlayStrings.insertErrorNoInput);
                return;
            }

            const inputDataType = (targetInput.attr("data-gf-input-type") || targetInput.data("gfInputType") || "").toString().toLowerCase();
            const inputTypeAttr = (targetInput.attr("type") || "").toString().toLowerCase();
            const effectiveInputType = inputDataType.length ? inputDataType : inputTypeAttr;
            if (effectiveInputType === "number") {
                griffinformsShowLogicMessage("error", overlayStrings.insertErrorNumber);
                return;
            }

            const jsonString = JSON.stringify(payload);

            // Resolve rich container: targetInput may be the hidden input or the rich div itself
            let $richContainer = null;
            let $hiddenInput = null;

            console.log("[gfLogic] targetInput tag:", targetInput.prop("tagName"), "rich:", targetInput.attr("data-gf-rich-input"), "hidden:", targetInput.attr("data-gf-rich-input-hidden"), "el:", targetInput[0]);

            if (targetInput.attr("data-gf-rich-input") === "1") {
                $richContainer = targetInput;
                $hiddenInput = targetInput.siblings(".gf-logic-rich-input-hidden");
            } else if (targetInput.attr("data-gf-rich-input-hidden") === "1") {
                $hiddenInput = targetInput;
                $richContainer = targetInput.siblings(".gf-logic-rich-input[data-gf-rich-input=\"1\"]");
            }

            console.log("[gfLogic] richContainer:", $richContainer && $richContainer.length ? $richContainer[0] : null, "hiddenInput:", $hiddenInput && $hiddenInput.length ? $hiddenInput[0] : null);

            if ($richContainer && $richContainer.length && typeof gfLogicBuildTag === "function") {
                // Build label from overlay state
                const overlayElements = griffinformsOverlayGetElements();
                const fieldLabel = overlayElements.field.find("option:selected").text().trim();
                const attrLabel = overlayElements.attribute.find("option:selected").text().trim();
                var labelText = fieldLabel;
                if (attrLabel && attrLabel.length) {
                    labelText = fieldLabel + " • " + attrLabel;
                }

                const tag = gfLogicBuildTag(jsonString, labelText.trim() || jsonString)[0];
                const sel = window.getSelection();
                if (sel && sel.rangeCount && $.contains($richContainer[0], sel.anchorNode)) {
                    const range = sel.getRangeAt(0);
                    range.deleteContents();
                    range.insertNode(tag);
                    range.setStartAfter(tag);
                    range.collapse(true);
                    sel.removeAllRanges();
                    sel.addRange(range);
                } else {
                    $richContainer.append(tag);
                }

                if ($hiddenInput && $hiddenInput.length) {
                    var serialized = gfLogicSerialize($richContainer);
                    console.log("[gfLogic] serialized:", serialized);
                    $richContainer.contents().each(function() {
                        console.log("[gfLogic] node type:", this.nodeType, "name:", this.nodeName, "token attr:", this.nodeType === 1 ? this.getAttribute("data-gf-token") : this.nodeValue);
                    });
                    $hiddenInput.val(serialized);
                }
            } else {
                // Legacy path for non-rich inputs
                const inputEl = targetInput.get(0);
                const originalValue = (targetInput.val() || "").toString();

                let selectionStart = originalValue.length;
                let selectionEnd = originalValue.length;
                let selectionSupported = false;

                try {
                    if (typeof inputEl.selectionStart === "number" && typeof inputEl.selectionEnd === "number") {
                        selectionStart = inputEl.selectionStart;
                        selectionEnd = inputEl.selectionEnd;
                        selectionSupported = true;
                    }
                } catch (err) {
                    selectionSupported = false;
                }

                const before = originalValue.slice(0, selectionStart);
                const after = originalValue.slice(selectionEnd);
                const needsSpaceBefore = before.length && !/\s$/.test(before);
                const needsSpaceAfter = after.length && !/^\s/.test(after);

                let insertion = jsonString;
                if (needsSpaceBefore) { insertion = " " + insertion; }
                if (needsSpaceAfter) { insertion = insertion + " "; }

                const updatedValue = before + insertion + after;
                targetInput.val(updatedValue);

                const inputType = (inputEl.type || "").toLowerCase();
                const canSetSelection = selectionSupported
                    && typeof inputEl.setSelectionRange === "function"
                    && !["email", "number", "date", "color", "range"].includes(inputType);

                if (canSetSelection) {
                    const newCursor = before.length + insertion.length;
                    try {
                        inputEl.setSelectionRange(newCursor, newCursor);
                    } catch (err) {
                        // Some input types may still reject selection changes; ignore silently.
                    }
                }

                targetInput.trigger("input").trigger("change");
            }

            griffinformsShowLogicMessage("success", overlayStrings.insertSuccess);
            griffinformsOverlayUpdatePreview();
            if (typeof griffinformsHideSubeditorOverlay === "function") {
                griffinformsHideSubeditorOverlay();
            }
        }

        window.griffinformsOverlayPrepare = griffinformsOverlayPrepare;
        window.griffinformsOverlayHandleFieldChange = griffinformsOverlayHandleFieldChange;
        window.griffinformsOverlayHandleAttributeChange = griffinformsOverlayHandleAttributeChange;
        window.griffinformsOverlayHandleIterationChange = griffinformsOverlayHandleIterationChange;
        window.griffinformsOverlayInsertPayload = griffinformsOverlayInsertPayload;

        function griffinformsFadeSwap(hideEl, showEl) {
            const duration = 180;

            const reveal = function (el) {
                if (!el || !el.length) {
                    return;
                }
                if (!el.hasClass("d-none") && el.is(":visible")) {
                    return;
                }
                el.removeClass("d-none")
                    .stop(true, true)
                    .css("display", "none")
                    .fadeIn(duration, function () {
                        $(this).css("display", "");
                    });
            };

            if (hideEl && hideEl.length && !hideEl.hasClass("d-none")) {
                hideEl.stop(true, true).fadeOut(duration, function () {
                    hideEl.addClass("d-none").css("display", "");
                    reveal(showEl);
                });
                return;
            }

            reveal(showEl);
        }

        function griffinformsInitConditionInputs(scope) {
            const context = scope && scope.length ? scope : subeditorWrapper;
            if (!context || !context.length) {
                return;
            }

            context.find(".griffinforms-condition-input[data-gf-input-type=\\"date\\"]").each(function () {
                const field = $(this);
                const format = (field.data("gfDateFormat") || "yy-mm-dd").toString();

                if (typeof field.datepicker === "function") {
                    if (field.hasClass("hasDatepicker")) {
                        field.datepicker("destroy");
                    }

                    field.datepicker({
                        dateFormat: format,
                        changeMonth: true,
                        changeYear: true,
                        showButtonPanel: true
                    });

                    const currentValue = (field.val() || "").toString().trim();
                    if (currentValue.length && $.datepicker && typeof $.datepicker.parseDate === "function") {
                        try {
                            const parsedDate = $.datepicker.parseDate(format, currentValue);
                            field.datepicker("setDate", parsedDate);
                        } catch (err) {
                            // Ignore parse errors so the user can correct the value.
                        }
                    }
                }
            });
        }

        window.griffinformsInitConditionInputs = function(scope) {
            griffinformsInitConditionInputs(scope);
            if (typeof gfLogicInitRichInputs === "function") {
                gfLogicInitRichInputs(scope);
            }
        };

        function griffinformsDateValueToTimestamp(value, format) {
            const rawValue = (value || "").toString().trim();
            if (!rawValue.length) {
                return null;
            }

            if (/^-?\\d+$/.test(rawValue)) {
                const numericValue = parseInt(rawValue, 10);
                if (Number.isFinite(numericValue)) {
                    return numericValue;
                }
            }

            let parsedDate = null;

            if ($.datepicker && typeof $.datepicker.parseDate === "function") {
                try {
                    parsedDate = $.datepicker.parseDate(format || "yy-mm-dd", rawValue);
                } catch (err) {
                    parsedDate = null;
                }
            }

            if (parsedDate instanceof Date && !Number.isNaN(parsedDate.getTime())) {
                return Math.floor(Date.UTC(
                    parsedDate.getFullYear(),
                    parsedDate.getMonth(),
                    parsedDate.getDate()
                ) / 1000);
            }

            const isoPattern = /^\\s*(\\d{4})-(\\d{1,2})-(\\d{1,2})\\s*$/;
            const isoMatch = rawValue.match(isoPattern);
            if (isoMatch) {
                const year = parseInt(isoMatch[1], 10);
                const month = parseInt(isoMatch[2], 10) - 1;
                const day = parseInt(isoMatch[3], 10);

                if (
                    Number.isFinite(year) && year >= 0 &&
                    Number.isFinite(month) && month >= 0 && month <= 11 &&
                    Number.isFinite(day) && day >= 1 && day <= 31
                ) {
                    return Math.floor(Date.UTC(year, month, day) / 1000);
                }
            }

            const fallback = Date.parse(rawValue);
            if (Number.isFinite(fallback)) {
                const fallbackDate = new Date(fallback);
                if (!Number.isNaN(fallbackDate.getTime())) {
                    return Math.floor(Date.UTC(
                        fallbackDate.getFullYear(),
                        fallbackDate.getMonth(),
                        fallbackDate.getDate()
                    ) / 1000);
                }
            }

            return null;
        }

        window.griffinformsDateValueToTimestamp = griffinformsDateValueToTimestamp;

        function openEditorForRule(ruleId = null) {
            const parsedRuleId = parseInt(ruleId, 10);
            let targetRuleIndex = Number.isFinite(parsedRuleId) ? parsedRuleId : null;

            if (!Number.isFinite(targetRuleIndex)) {
                const activeAttr = parseInt(editorWrapper.attr("data-gf-active-rule"), 10);
                if (Number.isFinite(activeAttr)) {
                    targetRuleIndex = activeAttr;
                }
            }

            if (!Number.isFinite(targetRuleIndex) && Number.isFinite(window.griffinformsActiveRuleIndex)) {
                targetRuleIndex = window.griffinformsActiveRuleIndex;
            }

            window.griffinformsActiveRuleIndex = Number.isFinite(targetRuleIndex) ? targetRuleIndex : null;

            griffinformsFadeSwap(listWrapper, editorWrapper);
            $(".griffinforms-logic-editor-header").removeClass("d-none");
            editorWrapper.find(".griffinforms-add-condition-slab, .griffinforms-add-action-slab").show();
            editorWrapper.attr("data-gf-active-rule", Number.isFinite(targetRuleIndex) ? targetRuleIndex : "");

            if (typeof griffinformsResetRuleEditor === "function") {
                griffinformsResetRuleEditor();
            }

            if (Number.isFinite(targetRuleIndex) && typeof griffinformsGetRule === "function") {
                griffinformsGetRule(targetRuleIndex);
            }
        }
        ';
    }

    // Registers modal-level event listeners for rule editing interactions.
    protected function modalEventBindingsJs()
    {
        echo '

        function closeSubeditor() {
            editorWrapper.removeClass("griffinforms-editor-shrink");
            subeditorWrapper.addClass("d-none");
            griffinformsClearHighlights();
            if (typeof griffinformsHideSubeditorOverlay === "function") {
                griffinformsHideSubeditorOverlay();
            }

            subeditorWrapper.attr("data-gf-context-type", "");
            subeditorWrapper.attr("data-gf-rule-index", "");
            subeditorWrapper.attr("data-gf-entry-index", "");
            subeditorWrapper.attr("data-gf-condition-group", "");
            subeditorWrapper.data("gf-context-type", "");
            subeditorWrapper.data("gf-rule-index", null);
            subeditorWrapper.data("gf-entry-index", null);
            subeditorWrapper.data("gf-condition-group", "");

            $("#griffinforms-logic-context-type").val("");
            $("#griffinforms-logic-context-rule").val("");
            $("#griffinforms-logic-context-entry").val("");
            $("#griffinforms-logic-context-group").val("");

            if (typeof griffinformsSetCurrentEntryPayload === "function") {
                griffinformsSetCurrentEntryPayload({});
            }
        }

        const conditionDeleteConfirm = "' . esc_js(__('Deleting this condition will remove it from the rule. Do you want to continue?', 'griffinforms-form-builder')) . '";
        const actionDeleteConfirm = "' . esc_js(__('Deleting this action will remove it from the rule. Do you want to continue?', 'griffinforms-form-builder')) . '";

        if (subeditorOverlayTrigger.length) {
            subeditorOverlayTrigger
                .off("click.griffinformsOverlay")
                .on("click.griffinformsOverlay", function (event) {
                    event.preventDefault();
                    event.stopPropagation();
                    if (typeof griffinformsShowSubeditorOverlay === "function") {
                        griffinformsShowSubeditorOverlay();
                    }
                });
        }

        if (subeditorOverlay.length) {
            subeditorOverlay
                .off("click.griffinformsOverlayStop")
                .on("click.griffinformsOverlayStop", function (event) {
                    event.stopPropagation();
                });
        }

        $(document)
            .off("click.griffinformsOverlayDismiss")
            .on("click.griffinformsOverlayDismiss", function (event) {
                if (!subeditorOverlay.length || subeditorOverlay.hasClass("d-none")) {
                    return;
                }
                const target = $(event.target);
                if (target.closest("#griffinforms-logic-subeditor-overlay").length || target.closest("#griffinforms-logic-subeditor-overlay-trigger").length) {
                    return;
                }
                if (typeof griffinformsHideSubeditorOverlay === "function") {
                    griffinformsHideSubeditorOverlay();
                }
            });

        if (typeof griffinformsOverlayGetElements === "function") {
            const overlayElements = griffinformsOverlayGetElements();
            if (overlayElements.itemType && overlayElements.itemType.length) {
                overlayElements.itemType
                    .off("change.griffinformsOverlay")
                    .on("change.griffinformsOverlay", function () {
                        const value = ($(this).val() || "").toString().toLowerCase();
                        griffinformsOverlaySetState({ itemType: value.length ? value : "field" });
                        griffinformsOverlayUpdatePreview();
                    });
            }
            if (overlayElements.field && overlayElements.field.length) {
                overlayElements.field
                    .off("change.griffinformsOverlay")
                    .on("change.griffinformsOverlay", function () {
                        griffinformsOverlayHandleFieldChange();
                    });
            }
            if (overlayElements.attribute && overlayElements.attribute.length) {
                overlayElements.attribute
                    .off("change.griffinformsOverlay")
                    .on("change.griffinformsOverlay", function () {
                        griffinformsOverlayHandleAttributeChange();
                    });
            }
            if (overlayElements.iteration && overlayElements.iteration.length) {
                overlayElements.iteration
                    .off("change.griffinformsOverlay")
                    .on("change.griffinformsOverlay", function () {
                        griffinformsOverlayHandleIterationChange();
                    });
            }
            if (overlayElements.insert && overlayElements.insert.length) {
                overlayElements.insert
                    .off("click.griffinformsOverlay")
                    .on("click.griffinformsOverlay", function (event) {
                        event.preventDefault();
                        console.log("[gfLogic] insert clicked, disabled:", $(this).prop("disabled"), "payload:", griffinformsOverlayBuildPayload());
                        griffinformsOverlayInsertPayload();
                    });
            }
        }

        subeditorWrapper
            .off("focusin.griffinformsOverlayTarget")
            .on("focusin.griffinformsOverlayTarget", ".griffinforms-condition-input, .griffinforms-action-input", function () {
                griffinformsOverlaySetState({ targetInput: $(this) });
            });

        // Bind events to simulate transitions
        listWrapper.on("click", ".griffinforms-logic-rule-edit", function (event) {
            event.preventDefault();
            const parentRule = $(this).closest(".griffinforms-logic-rule-item");
            const ruleIndex = parentRule.data("gf-rule-index");
            openEditorForRule(ruleIndex);
        });

        listWrapper.on("click", ".griffinforms-logic-rule-delete", function (event) {
            event.preventDefault();
            const ruleItem = $(this).closest(".griffinforms-logic-rule-item");
            if (!ruleItem.length) {
                return;
            }

            const ruleIndexRaw = ruleItem.data("gf-rule-index");
            const ruleIndex = parseInt(ruleIndexRaw, 10);
            if (!Number.isFinite(ruleIndex)) {
                return;
            }

            const confirmMessage = "' . esc_js(__('Deleting this rule is irreversible. Do you want to continue?', 'griffinforms-form-builder')) . '";
            if (!window.confirm(confirmMessage)) {
                return;
            }

            if (typeof griffinformsDeleteRule === "function") {
                griffinformsDeleteRule(ruleIndex);
            }
        });

        listWrapper.on("click", ".griffinforms-condition-item", function() {
            openEditorForRule();
        });

        editorWrapper.on("click", ".griffinforms-logic-condition-edit", function (event) {
            event.preventDefault();
            const conditionItem = $(this).closest(".griffinforms-logic-condition-item");
            const ruleIndex = parseInt(conditionItem.data("gf-rule-index"), 10);
            const entryIndex = parseInt(conditionItem.data("gf-entry-index"), 10);
            const groupKey = (conditionItem.data("gf-condition-group") || "").toString();
            const entryData = griffinformsExtractEntryPayload(conditionItem);
            openSubeditor("condition", ruleIndex, entryIndex, groupKey, entryData);
        });

        editorWrapper.on("click", ".griffinforms-logic-action-edit", function (event) {
            event.preventDefault();
            const actionItem = $(this).closest(".griffinforms-logic-action-item");
            const ruleIndex = parseInt(actionItem.data("gf-rule-index"), 10);
            const entryIndex = parseInt(actionItem.data("gf-entry-index"), 10);
            const entryData = griffinformsExtractEntryPayload(actionItem);
            openSubeditor("action", ruleIndex, entryIndex, null, entryData);
        });

        editorWrapper.on("click", ".griffinforms-logic-condition-delete", function (event) {
            event.preventDefault();
            const conditionItem = $(this).closest(".griffinforms-logic-condition-item");
            if (!conditionItem.length) {
                return;
            }
            if (!window.confirm(conditionDeleteConfirm)) {
                return;
            }
            const ruleIndex = parseInt(conditionItem.data("gf-rule-index"), 10);
            const entryIndex = parseInt(conditionItem.data("gf-entry-index"), 10);
            const groupKey = (conditionItem.data("gf-condition-group") || "").toString();
            if (typeof griffinformsClearHighlights === "function") {
                griffinformsClearHighlights();
            }
            if (typeof griffinformsDeleteRuleEntry === "function") {
                const currentContext = (subeditorWrapper.data("gf-context-type") || "").toString().toLowerCase();
                const currentRule = parseInt(subeditorWrapper.data("gf-rule-index"), 10);
                const currentEntry = parseInt(subeditorWrapper.data("gf-entry-index"), 10);
                const currentGroup = (subeditorWrapper.data("gf-condition-group") || "").toString().toLowerCase();
                const normalizedGroup = (groupKey || "").toString().toLowerCase();
                if (currentContext === "condition" && Number.isFinite(currentRule) && currentRule === ruleIndex && Number.isFinite(currentEntry) && currentEntry === entryIndex && currentGroup === normalizedGroup) {
                    closeSubeditor();
                }
                griffinformsDeleteRuleEntry("condition", ruleIndex, entryIndex, groupKey);
            }
        });

        editorWrapper.on("click", ".griffinforms-logic-action-delete", function (event) {
            event.preventDefault();
            const actionItem = $(this).closest(".griffinforms-logic-action-item");
            if (!actionItem.length) {
                return;
            }
            if (!window.confirm(actionDeleteConfirm)) {
                return;
            }
            const ruleIndex = parseInt(actionItem.data("gf-rule-index"), 10);
            const entryIndex = parseInt(actionItem.data("gf-entry-index"), 10);
            if (typeof griffinformsClearHighlights === "function") {
                griffinformsClearHighlights();
            }
            if (typeof griffinformsDeleteRuleEntry === "function") {
                const currentContext = (subeditorWrapper.data("gf-context-type") || "").toString().toLowerCase();
                const currentRule = parseInt(subeditorWrapper.data("gf-rule-index"), 10);
                const currentEntry = parseInt(subeditorWrapper.data("gf-entry-index"), 10);
                if (currentContext === "action" && Number.isFinite(currentRule) && currentRule === ruleIndex && Number.isFinite(currentEntry) && currentEntry === entryIndex) {
                    closeSubeditor();
                }
                griffinformsDeleteRuleEntry("action", ruleIndex, entryIndex);
            }
        });

        subeditorWrapper.on("click", ".griffinforms-close-subeditor", function () {
            closeSubeditor();
        });

        $("[data-gf-context].griffinforms-add-condition-slab, [data-gf-context].griffinforms-add-action-slab").on("click", function () {
            const contextType = ($(this).data("gf-context") || "condition").toString();
            const activeRuleAttr = parseInt(editorWrapper.attr("data-gf-active-rule"), 10);
            const ruleIndex = Number.isFinite(activeRuleAttr) ? activeRuleAttr : null;
            const groupKey = ($(this).data("gf-group") || "").toString();
            openSubeditor(contextType, ruleIndex, null, groupKey);
        });

        $(".griffinforms-condition-item").on("click", function () {
            const activeRuleAttr = parseInt(editorWrapper.attr("data-gf-active-rule"), 10);
            const ruleIndex = Number.isFinite(activeRuleAttr) ? activeRuleAttr : null;
            const groupKey = ($(this).data("gf-condition-group") || "").toString();
            openSubeditor("condition", ruleIndex, null, groupKey);
        });

        $(document).on("click", "#griffinforms-logic-subeditor-cancel", function () {
            closeSubeditor();
        });
    ';
    }
    // Outputs JavaScript that opens and populates the subeditor pane.
    protected function openSubeditorJs()
    {
        echo '
        function openSubeditor(type, ruleIndex, entryIndex = null, groupKey = null, entryData = null) {
            const normalizedType = (typeof type === "string" && type.length ? type : "condition").toLowerCase();
            const contextType = normalizedType === "action" ? "action" : "condition";

            const parsedRuleIndex = parseInt(ruleIndex, 10);
            let ruleValue = "";
            if (Number.isFinite(parsedRuleIndex)) {
                ruleValue = parsedRuleIndex;
            } else {
                const activeRuleAttr = parseInt(editorWrapper.attr("data-gf-active-rule"), 10);
                if (Number.isFinite(activeRuleAttr)) {
                    ruleValue = activeRuleAttr;
                }
            }
            const parsedEntryIndex = parseInt(entryIndex, 10);
            const entryValue = Number.isFinite(parsedEntryIndex) ? parsedEntryIndex : "";
            let groupValue = "";
            if (typeof groupKey === "string" && groupKey.length) {
                groupValue = groupKey.toLowerCase();
            } else {
                const storedGroup = subeditorWrapper.data("gf-condition-group");
                if (typeof storedGroup === "string" && storedGroup.length) {
                    groupValue = storedGroup.toLowerCase();
                }
            }
            if (contextType === "condition") {
                groupValue = groupValue === "any" ? "any" : "all";
            } else {
                groupValue = "";
            }

            const contextTypeField = $("#griffinforms-logic-context-type");
            const ruleField = $("#griffinforms-logic-context-rule");
            const entryField = $("#griffinforms-logic-context-entry");
            const groupField = $("#griffinforms-logic-context-group");

            if (contextTypeField.length) {
                contextTypeField.val(contextType);
            }
            if (ruleField.length) {
                ruleField.val(ruleValue);
            }
            if (entryField.length) {
                entryField.val(entryValue);
            }
            if (groupField.length) {
                groupField.val(groupValue);
            }

            const parsedPayload = griffinformsParseEntryPayload(entryData);
            const entryPayload = parsedPayload && typeof parsedPayload === "object"
                ? parsedPayload
                : {};

            griffinformsSetCurrentEntryPayload(entryPayload);

            subeditorWrapper.attr("data-gf-context-type", contextType);
            subeditorWrapper.attr("data-gf-rule-index", ruleValue);
            subeditorWrapper.attr("data-gf-entry-index", entryValue);
            subeditorWrapper.attr("data-gf-condition-group", groupValue);
            subeditorWrapper.data("gf-context-type", contextType);
            subeditorWrapper.data("gf-rule-index", ruleValue === "" ? null : ruleValue);
            subeditorWrapper.data("gf-entry-index", entryValue === "" ? null : entryValue);
            subeditorWrapper.data("gf-condition-group", groupValue);

            const highlightRuleIndex = Number.isFinite(parsedRuleIndex) ? parsedRuleIndex : parseInt(ruleValue, 10);
            const highlightEntryIndex = Number.isFinite(parsedEntryIndex) ? parsedEntryIndex : parseInt(entryValue, 10);
            const highlightGroupKey = contextType === "condition" ? groupValue : "";
            if (contextType === "condition" || contextType === "action") {
                griffinformsHighlightItem(contextType, highlightRuleIndex, highlightEntryIndex, highlightGroupKey);
            } else {
                griffinformsClearHighlights();
            }

            editorWrapper.addClass("griffinforms-editor-shrink");
            subeditorWrapper.removeClass("d-none");
            if (contextType === "condition") {
                if (typeof showConditionSubeditorBlock === "function") {
                    showConditionSubeditorBlock();
                }
            } else if (typeof showActionSubeditorBlock === "function") {
                showActionSubeditorBlock();
            }
            // TODO: Load fields based on `type`, `ruleIndex`, and `entryIndex` if given, else show blank form
        }
        ';
    }
    // Provides functions to toggle between condition and action subeditor blocks.
    protected function subeditorBlockToggleJs()
    {
        echo '
        function showConditionSubeditorBlock() {
            const wrapper = $(".griffinforms-logic-subeditor-wrapper");
            const block = wrapper.find("[data-gf-context=\"condition\"]");
            const actionBlock = wrapper.find("[data-gf-context=\"action\"]");

            if (!block.length) {
                return;
            }

            block.removeClass("d-none").css("display", "");

            if (actionBlock.length) {
                actionBlock.addClass("d-none").css("display", "none");
                actionBlock.find("#griffinforms-logic-subeditor-action-items-container").empty();
                actionBlock.find("#griffinforms-logic-subeditor-action-field-wrapper").empty();
                actionBlock.find("#griffinforms-logic-subeditor-action-attribute-wrapper").empty();
                actionBlock.find("#griffinforms-logic-subeditor-action-operation-wrapper").empty();
                actionBlock.find("#griffinforms-logic-subeditor-action-input-wrapper").empty();
            }

        if (typeof griffinformsGetConditionItemTypes === "function") {
            griffinformsGetConditionItemTypes();
        }
        }

        function showActionSubeditorBlock() {
            const wrapper = $(".griffinforms-logic-subeditor-wrapper");
            const conditionBlock = wrapper.find("[data-gf-context=\"condition\"]");
            const actionBlock = wrapper.find("[data-gf-context=\"action\"]");

            if (conditionBlock.length) {
                conditionBlock.addClass("d-none").css("display", "none");
                conditionBlock.find("#griffinforms-logic-subeditor-items-container").empty();
                conditionBlock.find("#griffinforms-logic-subeditor-item-selection-wrapper").empty();
                conditionBlock.find("#griffinforms-logic-subeditor-item-attributes-wrapper").empty();
                conditionBlock.find("#griffinforms-logic-subeditor-attribute-evaluators-wrapper").empty();
                conditionBlock.find("#griffinforms-logic-subeditor-condition-input-wrapper").empty();
            }

            if (!actionBlock.length) {
                return;
            }

            actionBlock.removeClass("d-none").css("display", "");
            actionBlock.find("#griffinforms-logic-subeditor-action-items-container").empty();
            actionBlock.find("#griffinforms-logic-subeditor-action-field-wrapper").empty();
            actionBlock.find("#griffinforms-logic-subeditor-action-attribute-wrapper").empty();
            actionBlock.find("#griffinforms-logic-subeditor-action-operation-wrapper").empty();
            actionBlock.find("#griffinforms-logic-subeditor-action-input-wrapper").empty();

            if (typeof griffinformsGetActionItemTypes === "function") {
                griffinformsGetActionItemTypes();
            }
        }
        ';
    }
    // Handles enabling and disabling the subeditor controls while AJAX runs.
    protected function subeditorToggleJs()
    {
        echo '
        let griffinformsSubeditorLockCounter = 0;

        function griffinformsToggleSubeditor(disable = false) {
            const subeditorWrapper = $(".griffinforms-logic-subeditor-wrapper");
            if (!subeditorWrapper.length) {
                return;
            }

            if (disable) {
                griffinformsSubeditorLockCounter++;
            } else if (griffinformsSubeditorLockCounter > 0) {
                griffinformsSubeditorLockCounter--;
            }

            const shouldDisable = griffinformsSubeditorLockCounter > 0;

            const formElements = subeditorWrapper.find("input, select, textarea, button, .gf-btn, .btn");
            formElements.prop("disabled", shouldDisable);

            const pseudoButtons = subeditorWrapper.find("[role=\"button\"]");
            pseudoButtons.each(function () {
                const el = $(this);

                if (shouldDisable) {
                    if (typeof el.data("griffinformsOriginalTabIndex") === "undefined") {
                        el.data("griffinformsOriginalTabIndex", el.attr("tabindex") || "");
                    }
                    el.attr("aria-disabled", "true");
                    el.addClass("is-disabled");
                    el.attr("tabindex", "-1");
                } else {
                    el.attr("aria-disabled", "false");
                    el.removeClass("is-disabled");
                    const originalTabIndex = el.data("griffinformsOriginalTabIndex");
                    if (typeof originalTabIndex !== "undefined") {
                        if (originalTabIndex === "") {
                            el.removeAttr("tabindex");
                        } else {
                            el.attr("tabindex", originalTabIndex);
                        }
                    } else {
                        el.attr("tabindex", "0");
                    }
                }
            });

            subeditorWrapper.toggleClass("is-disabled", shouldDisable);
            subeditorWrapper.attr("aria-busy", shouldDisable ? "true" : "false");
        }';
    }
    // Outputs helper for showing feedback messages within the modal.
    protected function getMessageDisplayJs() {
        echo '
        function griffinformsShowLogicMessage(type, text, replace = true) {
            const container = $(".griffinforms-logic-message-container.gf-notice-container");
            if (!container.length) {
                return;
            }

            if (type !== "error") {
                if (replace) {
                    container.empty();
                }
                return;
            }

            const message = typeof text === "string" ? text : "";
            const msgHtml = `<div class="gf-notice gf-notice-danger">${message}</div>`;
            if (replace) {
                container.html(msgHtml);
            } else {
                container.append(msgHtml);
            }
        }
        ';
    }
    // Binds dropdown change handlers for condition item selections.
    protected function itemsDropdownChangeJs()
    {
        echo '
        $(document).on("change", "#griffinforms-logic-subeditor-items", function () {
            if (typeof griffinformsGetConditionFieldTargets === "function") {
                griffinformsGetConditionFieldTargets();
            }
        });

        $(document).on("change", "#griffinforms-logic-subeditor-action-items", function () {
            if (typeof griffinformsGetActionFieldTargets === "function") {
                griffinformsGetActionFieldTargets();
            }
        });
        ';
    }
    // Binds secondary item selection change handlers for conditions and actions.
    protected function itemSelectDropdownChangeJs()
    {
        echo '
        $(document).on("change", "#griffinforms-logic-subeditor-item-selection-wrapper select", function () {
            if (typeof griffinformsGetConditionAttributes === "function") {
                griffinformsGetConditionAttributes();
            }
        });

        $(document).on("change", "#griffinforms-logic-subeditor-action-field", function () {
            if (typeof griffinformsGetActionAttributes === "function") {
                griffinformsGetActionAttributes();
            }
        });
        ';
    }
    // Binds attribute selection change handlers to refresh dependent inputs.
    protected function attributeSelectDropdownChangeJs()
    {
        echo '
        $(document).on("change", "#griffinforms-logic-subeditor-attributes-wrapper select", function () {
            if (typeof griffinformsGetConditionEvaluators === "function") {
                griffinformsGetConditionEvaluators();
            }
        });

        $(document).on("change", "#griffinforms-logic-subeditor-action-attributes", function () {
            if (typeof griffinformsGetActionOperations === "function") {
                griffinformsGetActionOperations();
            }
        });
        ';
    }
    // Binds evaluator and operation dropdown changes to refresh forms.
    protected function attributeConditionDropdownChangeJs()
    {
        echo '
        $(document).on("change", "#griffinforms-logic-subeditor-attribute-evaluators-wrapper select", function () {
            if (typeof griffinformsGetConditionInput === "function") {
                griffinformsGetConditionInput();
            }
        });

        $(document).on("change", "#griffinforms-logic-subeditor-action-operation", function () {
            if (typeof griffinformsGetActionInput === "function") {
                griffinformsGetActionInput();
            }
        });
        ';
    }
    // Keeps option group checkboxes synced with their hidden JSON inputs.
    protected function optionGroupInputJs()
    {
        echo '
        function griffinformsSyncOptionGroupInput(container) {
            const scope = container && container.length ? container : $("#griffinforms-logic-subeditor-action-input-wrapper");
            if (!scope.length) {
                return;
            }
            scope.find(".griffinforms-option-groups-control").each(function () {
                const hiddenInput = $(this).find("input[type=\"hidden\"][data-gf-input-type=\"option_groups\"]");
                if (!hiddenInput.length) {
                    return;
                }
                const selectedGroups = [];
                $(this).find(".griffinforms-option-group-toggle:checked").each(function () {
                    const groupName = ($(this).attr("data-gf-group") || "").toString();
                    if (groupName.length) {
                        selectedGroups.push(groupName);
                    }
                });
                hiddenInput.val(JSON.stringify(selectedGroups));
            });
        }

        $(document).on("change", ".griffinforms-option-group-toggle", function () {
            const container = $(this).closest(".griffinforms-option-groups-control");
            griffinformsSyncOptionGroupInput(container);
        });

        const overlayTriggerHiddenInputTypes = ["likert_rows", "option_groups"];

        function griffinformsUpdateOverlayTrigger(inputType) {
            if (!subeditorOverlayTrigger.length) {
                return;
            }
            const triggerWrap = subeditorOverlayTrigger.closest(".griffinforms-logic-subeditor-overlay-trigger");
            const shouldHide = !!inputType && overlayTriggerHiddenInputTypes.indexOf(inputType) !== -1;
            triggerWrap.toggleClass("d-none", shouldHide);
        }

        $(document).on("griffinforms:logic-action-input-rendered", function (event, context) {
            const scope = context && context.wrapper ? context.wrapper : $("#griffinforms-logic-subeditor-action-input-wrapper");
            griffinformsSyncOptionGroupInput(scope);
            griffinformsSyncLikertRowInput(scope);
            griffinformsUpdateOverlayTrigger(context && context.input_type ? context.input_type : null);
        });
        ';
    }

    // Keeps Likert row checkboxes synced with their hidden JSON inputs.
    protected function likertRowInputJs()
    {
        echo '
        function griffinformsSyncLikertRowInput(container) {
            const scope = container && container.length ? container : $("#griffinforms-logic-subeditor-action-input-wrapper");
            if (!scope.length) {
                return;
            }
            scope.find(".griffinforms-likert-rows-control").each(function () {
                const hiddenInput = $(this).find("input[type=\"hidden\"][data-gf-input-type=\"likert_rows\"]");
                if (!hiddenInput.length) {
                    return;
                }
                const selectedIds = [];
                $(this).find(".griffinforms-likert-row-toggle:checked").each(function () {
                    const rowId = ($(this).attr("data-gf-row-id") || "").toString();
                    if (rowId.length) {
                        selectedIds.push(rowId);
                    }
                });
                hiddenInput.val(JSON.stringify(selectedIds));
            });
        }

        $(document).on("change", ".griffinforms-likert-row-toggle", function () {
            const container = $(this).closest(".griffinforms-likert-rows-control");
            griffinformsSyncLikertRowInput(container);
        });
        ';
    }

    // Emits helper functions and event bindings for the contenteditable variable tag input.
    protected function richInputJs()
    {
        echo '
        function gfLogicEscHtml(s) { return $("<div>").text(String(s)).html(); }

        function gfLogicBuildTag(tokenJson, label) {
            var $span = $("<span>").addClass("gf-logic-var-tag");
            $span[0].setAttribute("contenteditable", "false");
            $span[0].setAttribute("data-gf-token", tokenJson);
            $span.append($("<span>").addClass("gf-logic-var-tag-text").text(label || tokenJson));
            $span.append(
                $("<button>").attr({
                    type: "button",
                    tabindex: "-1",
                    "aria-label": "' . esc_js(__('Remove variable', 'griffinforms-form-builder')) . '"
                }).addClass("gf-logic-var-tag-remove").text("×")
            );
            return $span;
        }

        function gfLogicSerialize($container) {
            var parts = [];
            function walk(node) {
                if (!node) { return; }
                if (node.nodeType === 3) {
                    parts.push(node.nodeValue);
                    return;
                }
                if (node.nodeType !== 1) { return; }
                var $node = $(node);
                if ($node.hasClass("gf-logic-var-tag")) {
                    var token = node.getAttribute("data-gf-token");
                    if (typeof token === "string") {
                        parts.push(token);
                    }
                    return;
                }
                // browser-injected wrapper (div, span, br, etc.) — recurse into children
                if (node.nodeName === "BR") {
                    parts.push("\n");
                    return;
                }
                var child = node.firstChild;
                while (child) {
                    walk(child);
                    child = child.nextSibling;
                }
            }
            var child = $container[0].firstChild;
            while (child) {
                walk(child);
                child = child.nextSibling;
            }
            return parts.join("");
        }

        function gfLogicDeserialize(value, $container) {
            $container.empty();
            if (!value) { return; }
            var parts = value.split(/(\{[^{}]*\})/);
            parts.forEach(function(part) {
                if (/^\{[^{}]*\}$/.test(part)) {
                    var label = part;
                    try {
                        var obj = JSON.parse(part);
                        label = (obj.label || obj.type || "") + (obj.attr ? " • " + obj.attr : "");
                    } catch(e) {}
                    $container.append(gfLogicBuildTag(part, label.trim() || part));
                } else if (part) {
                    $container.append(document.createTextNode(part));
                }
            });
        }

        function gfLogicInitRichInputs(scope) {
            var context = scope && scope.length ? scope : subeditorWrapper;
            if (!context || !context.length) { return; }
            context.find(".gf-logic-rich-input[data-gf-rich-input=\"1\"]").each(function() {
                var $container = $(this);
                var $hidden = $container.siblings(".gf-logic-rich-input-hidden");
                if ($hidden.length) {
                    gfLogicDeserialize($hidden.val(), $container);
                }
            });
        }

        window.gfLogicInitRichInputs = gfLogicInitRichInputs;

        $(document).on("input", ".gf-logic-rich-input[data-gf-rich-input=\"1\"]", function() {
            var $container = $(this);
            var $hidden = $container.siblings(".gf-logic-rich-input-hidden");
            if ($hidden.length) {
                $hidden.val(gfLogicSerialize($container));
            }
        });

        $(document).on("click", ".gf-logic-var-tag-remove", function(e) {
            e.preventDefault();
            var $tag = $(this).closest(".gf-logic-var-tag");
            var $container = $tag.closest(".gf-logic-rich-input");
            var $hidden = $container.siblings(".gf-logic-rich-input-hidden");
            $tag.remove();
            if ($hidden.length) {
                $hidden.val(gfLogicSerialize($container));
            }
        });

        $(document).on("focusin.griffinformsOverlayTarget", ".gf-logic-rich-input[data-gf-rich-input=\"1\"]", function() {
            var $hidden = $(this).siblings(".gf-logic-rich-input-hidden");
            if ($hidden.length) {
                griffinformsOverlaySetState({ targetInput: $hidden });
            }
        });

        $(document).on("griffinforms:logic-action-input-rendered", function(event, context) {
            var scope = context && context.wrapper ? context.wrapper : $("#griffinforms-logic-subeditor-action-input-wrapper");
            gfLogicInitRichInputs(scope);
        });
        ';
    }

    // Wires up the add-rule button to launch a fresh editor instance.
    protected function addRuleJs() {
        echo '
        $(".griffinforms-add-rule-slab").on("click", function () {
            window.griffinformsActiveRuleIndex = null;
            editorWrapper.attr("data-gf-active-rule", "");
            openEditorForRule();
        });
        ';
    }
    // Defines helper to swap back to the rule list and optionally refetch.
    protected function showRulesListJs() {
        echo '
        function showRulesList(shouldFetch) {
            const fetchList = !!shouldFetch;
            editorWrapper.removeClass("griffinforms-editor-shrink");
            editorWrapper.attr("data-gf-active-rule", "");
            $(".griffinforms-logic-editor-header").addClass("d-none");
            subeditorWrapper.addClass("d-none");
            subeditorWrapper.attr("data-gf-condition-group", "");
            subeditorWrapper.data("gf-condition-group", "");
            $("#griffinforms-logic-context-group").val("");
            griffinformsClearHighlights();
            griffinformsFadeSwap(editorWrapper, listWrapper);
            griffinformsSetRuleTitle("");
            window.griffinformsActiveRuleIndex = null;
            listWrapper.find(".griffinforms-add-rule-slab, .griffinforms-add-condition-slab, .griffinforms-add-action-slab").show();
            if (fetchList && typeof griffinformsFetchRules === "function") {
                griffinformsFetchRules();
            }
        }
        ';
    }
    // Connects the back button to display the rule list view.
    protected function backToListJs() {
        echo '
        $(".griffinforms-back-to-list").on("click", function () {
            showRulesList(true);
        });
        ';
    }
    // Initializes modal show handler to reset the interface when opened.
    protected function modalShowEventJs() {
        $modal_id = $this->getId('modal');
        if (empty($modal_id)) {
            return;
        }
        echo '
        const conditionalLogicModalEl = $("#' . esc_js($modal_id) . '");
        function griffinformsConditionalLogicModalOnShow() {
            // Reset/prepare the editor UI when modal opens
            showRulesList(true);
        }
        conditionalLogicModalEl.on("show.bs.modal", griffinformsConditionalLogicModalOnShow);
        ';
    }
    // Sets up modal hide handler to clean up the interface when closed.
    protected function modalHideEventJs() {
        $modal_id = $this->getId('modal');
        if (empty($modal_id)) {
            return;
        }
        echo '
        function griffinformsConditionalLogicModalOnHidden() {
            // Cleanup/reset UI when modal closes
            showRulesList(true);
        }
        conditionalLogicModalEl.on("hidden.bs.modal", griffinformsConditionalLogicModalOnHidden);
        ';
    }
}
