<?php

namespace GriffinForms\Frontend\Html\Forms;

class FormPage extends \GriffinForms\Frontend\Html\Forms\Format
{
    protected $bs_formpage_container_classes = 'pt-2 mb-2';
    protected $bs_formpage_header_classes = '';
    protected $bs_formpage_rows_container_classes = '';
    protected $bs_formpage_footer_classes = '';
    protected $bs_formpage_error_alert_classes = 'text-danger small';
    protected $bs_formpage_prev_btn_classes = 'button button-secondary wp-element-button wp-block-button__link';
    protected $bs_formpage_next_btn_classes = 'button button-primary wp-element-button wp-block-button__link';
    protected $bs_formpage_submit_btn_classes = 'button button-primary wp-element-button wp-block-button__link';

    public function html()
    {
        $is_theme_set = $this->data['is_theme_set'] ?? false;
        $attr = '';
        if ($this->data['position'] === 'middle' or $this->data['position'] === 'last') {
            $attr = ' style="display:none;"';
        }

        $captcha_context = $this->antispam->getCaptchaClientContext($this->data['position'] ?? '');
        $captcha_context_attr = esc_attr(wp_json_encode($captcha_context));
        $html = '<div id="' . esc_attr($this->html_ids['container']) . '" class="griffinforms-formpage-container row" data-gf-page-type="page" data-gf-form-id="' . absint($this->data['form']) . '" data-gf-captcha-context="' . $captcha_context_attr . '"' . $attr . '>';
        $html .= $this->formPageHeader();
        $html .= $this->pageRows();
        $html .= $this->externalFields();
        $html .= $this->FormPageFooter();
        $html .= $this->errorAlert($is_theme_set);
        $html .= '</div>';
        return $html;
    }
    
    /**
     * Renders the form page header if a heading or description is present.
     *
     * @return string HTML markup for the header section or an empty string if both values are empty.
     */
    private function formPageHeader()
    {
        $heading = $this->item->getProp('heading');
        $description = $this->item->getProp('description');

        if (empty($heading) && empty($description)) {
            return '';
        }
        // Determine the margin class based on the presence of a description
        $margin_class = empty($description) ? 'mb-2' : 'mb-4 gf-formpage-has-description';
        $html = '<div class="griffinforms-formpage-header col-md-12 ' . esc_attr($margin_class) . '">';
        if (!empty($heading)) {
            $html .= '<div class="h4 mb-1" data-gf-element="page-heading">' . esc_html($heading) . '</div>';
        }

        if (!empty($description)) {
            $html .= '<div data-gf-element="page-description">' . esc_html($description) . '</div>';
        }

        $html .= '</div>';
        return $html;
    }
    
    private function pageRows()
    {
        $pagerow_ids = $this->item->getProp('pagerows');
        
        if (empty($pagerow_ids) or !is_array($pagerow_ids)) {
            return;
        }
        
        $html = '<div class="griffinforms-formpage-children-container col-md-12">';
        
        foreach ($pagerow_ids as $pagerow_id) {
            
            if (!$this->sql->itemExists('pagerow', $pagerow_id)) {
                continue;
            }
            
            $pagerow = new PageRow($pagerow_id, $this->data);
            $html .= $pagerow->html();
        }
        
        $html .= '</div>';
        return $html;
    }

    private function externalFields()
    {
        $html = $this->antispam->getCaptchaField($this->data['position']);
        $html .= $this->antispam->getHoneypotField();
        return $html;
    }
    
    private function formPageFooter()
    {
        $is_theme_set = $this->data['is_theme_set'] ?? false;
        $footer_html = $this->data['position'] . 'PageFooter';
        $justify_class = in_array($this->data['position'], ['first', 'single'], true) ? 'justify-content-center' : 'justify-content-between';
        $html = '<div class="col-md-12 py-3 d-flex ' . esc_attr($justify_class) . ' griffinforms-button-row">';
        $html .= $this->$footer_html($is_theme_set);
        $html .= '</div>';
        return $html;
    }

    private function firstPageFooter($is_theme_set = false)
    {
        return $this->nextBtn($this->lang->getText('next_btn_label'), $is_theme_set);
    }

    private function middlePageFooter($is_theme_set = false)
    {
        $html = $this->backBtn($this->lang->getText('prev_btn_label'), $is_theme_set);
        $html .= $this->nextBtn($this->lang->getText('next_btn_label'), $is_theme_set);
        return $html;
    }

    private function lastPageFooter($is_theme_set = false)
    {
        $html = $this->backBtn($this->lang->getText('prev_btn_label'), $is_theme_set);
        $html .= $this->submitBtn($this->lang->getText('submit_btn_label'), $is_theme_set);
        return $html;
    }

    private function singlePageFooter($is_theme_set = false)
    {
        return $this->submitBtn($this->lang->getText('submit_btn_label'), $is_theme_set);
    }
    
    private function backBtn($label, $is_theme_set = false)
    {
        $btn_label = !empty($this->item->getProp('previous_label')) ? $this->item->getProp('previous_label') : $label;
        $btn_classes = '';
        if (!$is_theme_set && !empty($this->bs_formpage_prev_btn_classes)) {
            $btn_classes = esc_attr($this->bs_formpage_prev_btn_classes);
        }
        $html = '<button id="' . esc_attr($this->html_ids['back_btn']) . '" ';
        $html .= 'type="button" class="' . $btn_classes . '" data-gf-button-type="previous">';
        $html .= esc_html($btn_label);
        $html .= '</button>';
        return $html;
    }

    private function nextBtn($label, $is_theme_set = false)
    {
        $btn_label = !empty($this->item->getProp('next_label')) ? $this->item->getProp('next_label') : $label;
        $btn_classes = '';
        if (!$is_theme_set && !empty($this->bs_formpage_next_btn_classes)) {
            $btn_classes = esc_attr($this->bs_formpage_next_btn_classes);
        }
        $html = '<button id="' . esc_attr($this->html_ids['action_btn']) . '" ';
        $html .= 'type="button" class="' . $btn_classes . '" aria-label="' . esc_attr($label) . '" data-gf-button-type="next">';
        $html .= '<span class="spinner-border spinner-border-sm visually-hidden"></span>';
        $html .= '<span class="action-btn-label" role="status">';
        $html .= esc_html($btn_label);
        $html .= '</span></button>';
        return $html;
    }

    private function submitBtn($label, $is_theme_set = false)
    {
        $btn_label = !empty($this->data['submit_label']) ? $this->data['submit_label'] : $label;
        $btn_classes = '';
        if (!$is_theme_set && !empty($this->bs_formpage_submit_btn_classes)) {
            $btn_classes = esc_attr($this->bs_formpage_submit_btn_classes);
        }
        $html = '<button id="' . esc_attr($this->html_ids['action_btn']) . '" ';
        $html .= 'type="button" class="' . $btn_classes . '" aria-label="' . esc_attr($label) . '" data-gf-button-type="submit">';
        $html .= '<span class="spinner-border spinner-border-sm visually-hidden"></span>';
        $html .= '<span class="action-btn-label" role="status">';
        $html .= esc_html($btn_label);
        $html .= '</span></button>';
        return $html;
    }
    
    private function errorAlert($is_theme_set = false)
    {
        $alert_classes = 'griffinforms-formpage-alert';
        if (!$is_theme_set && !empty($this->bs_formpage_error_alert_classes)) {
            $alert_classes .= ' ' . esc_attr($this->bs_formpage_error_alert_classes);
        }
        $html = '<div id="griffinforms-formpage' . $this->id . '-alert" class="' . esc_attr($alert_classes) . '" role="alert" aria-live="polite" data-gf-element="error" data-gf-form-id="' . absint($this->data['form']) . '" style="display:none;">';
        $html .= $this->lang->getText('errors_on_page');
        $html .= '</div>';
        return $html;
    }

    private function getAccountRegistrationValidationConfig(): array
    {
        $form_id = isset($this->data['form']) ? absint($this->data['form']) : 0;
        if ($form_id <= 0) {
            return [];
        }

        $validator = new \GriffinForms\Frontend\Security\FormValidations\AccountRegistration();
        $field_ids = $validator->getProtectedEmailFieldIds($form_id);
        if (empty($field_ids)) {
            return [];
        }

        $config = [];
        foreach ($field_ids as $field_id) {
            $config[(string) $field_id] = [
                'field_id' => (int) $field_id,
                'selector' => '.griffinforms-formcontrol-' . (int) $field_id,
            ];
        }

        return $config;
    }
    
    protected function backBtnJs()
    {
        echo '$(backBtn).click(function(){
                    flipPage(prevPage);
                });';
    }
    
    protected function actionBtnJs()
    {
        echo '$(actionBtn).click(function(){
                    let errorCount = checkFieldErrors();
                    let pageAlert = "#griffinforms-formpage' . absint($this->id) . '-alert";
                    if (errorCount > 0) {
                        $(pageAlert).show();
                    } else {
                        $(pageAlert).hide();
                        saveFormpage();
                    }
                });';
    }
    
    public function itemJs()
    {
        // Start output buffering to capture the JS content
        ob_start();

        $pageId = absint($this->id);
        $formId = absint($this->data['form']);
        $page = $this->html_ids['container'];
        $actionBtn = $this->html_ids['action_btn'];
        $backBtn = $this->html_ids['back_btn'];
        $position = $this->data['position'];
        $userActionType = isset($this->data['user_action']['type']) ? $this->data['user_action']['type'] : '';
        $userActionUrl = isset($this->data['user_action']['url']) ? $this->data['user_action']['url'] : '';
        $nonce = wp_create_nonce('save_formpage');
        $account_validation_nonce = wp_create_nonce('validate_account_registration_email');
        $ajaxUrl = admin_url('admin-ajax.php');
        $account_validation_config = $this->getAccountRegistrationValidationConfig();

        ?>

        /**
         * Displays a hardcoded success message on page reload after form submission
         * if the user selected "do nothing" as the post-submission action.
         *
         * @param {number} formId - The form ID used to identify the relevant DOM elements and session key.
         */
        function handleReloadSuccessMessage(formId) {
            const successFlag = 'griffinformsFormSubmitted_' + formId;
            if (sessionStorage.getItem(successFlag) === '1') {
                jQuery(".griffinforms-form" + formId + "-success_message").fadeIn();
                sessionStorage.removeItem(successFlag);
            }
        }

        jQuery(document).ready(function($) {
            const pageId = <?php echo esc_js($pageId); ?>;
            const formId = <?php echo esc_js($formId); ?>;
            const page = $("#<?php echo esc_js($page); ?>");
            const nextPage = page.next(".griffinforms-formpage-container");
            const prevPage = page.prev(".griffinforms-formpage-container");
            const firstPage = $(".griffinforms-formpage-container").first();
            const pageFields = "page" + pageId + "Fields";
            const ajaxurl = "<?php echo esc_url($ajaxUrl); ?>";
            const actionBtn = "#<?php echo esc_js($actionBtn); ?>";
            const backBtn = "#<?php echo esc_attr($backBtn); ?>";
            const requiresPayment = <?php echo $this->data['requires_payment'] ? 'true' : 'false'; ?>;
            const themeIsSet = <?php echo $this->data['is_theme_set'] ? 'true' : 'false'; ?>;
            const accountValidationConfig = <?php echo wp_json_encode($account_validation_config); ?>;
            const accountValidationNonce = <?php echo wp_json_encode($account_validation_nonce); ?>;
            const reviewContainer = $(".griffinforms-payment-review-container[data-gf-form-id='" + formId + "']");
            const reviewSummaryTarget = reviewContainer.find("[data-gf-review-summary]");
            const reviewBackBtn = reviewContainer.find("[data-gf-review-action='back']");
            const reviewContinueBtn = reviewContainer.find("[data-gf-review-action='continue']");
            if (reviewContinueBtn.length && !reviewContinueBtn.data("defaultLabel")) {
                reviewContinueBtn.data("defaultLabel", reviewContinueBtn.text());
            }
            const formMetaKey = "gfFormMeta" + formId;
            let accountValidationSubmitting = false;

            function readFormMeta() {
                const raw = sessionStorage.getItem(formMetaKey);
                if (!raw) {
                    return {};
                }
                try {
                    const parsed = JSON.parse(raw);
                    return typeof parsed === "object" && parsed !== null ? parsed : {};
                } catch (e) {
                    return {};
                }
            }

            function writeFormMeta(meta) {
                sessionStorage.setItem(formMetaKey, JSON.stringify(meta || {}));
            }

            function resetFormMeta() {
                sessionStorage.removeItem(formMetaKey);
            }

            function getAccountValidationAlert(control) {
                const container = control.closest(".griffinforms-formcontrol-container");
                if (!container.length) {
                    return $();
                }

                let alert = container.find(".griffinforms-alert-account-registration").first();
                if (!alert.length) {
                    alert = $('<div class="griffinforms-alert-account-registration text-danger small" style="display:none;"></div>');
                    container.append(alert);
                }

                return alert;
            }

            function showAccountValidationAlert(control, message) {
                const alert = getAccountValidationAlert(control);
                if (!alert.length) {
                    return;
                }

                alert.text(String(message || "").trim()).show();
                control.addClass("border-danger bg-danger bg-opacity-10");
            }

            function hideAccountValidationAlert(control) {
                const alert = getAccountValidationAlert(control);
                if (alert.length) {
                    alert.hide().text("");
                }
                control.removeClass("border-danger bg-danger bg-opacity-10");
            }

            function clearAllAccountValidationAlerts() {
                $(".griffinforms-alert-account-registration").hide().text("");
                $.each(accountValidationConfig || {}, function(fieldId, fieldConfig) {
                    const selector = fieldConfig && fieldConfig.selector ? fieldConfig.selector : "";
                    if (!selector) {
                        return;
                    }
                    $(selector).removeClass("border-danger bg-danger bg-opacity-10");
                });
            }

            function validateAccountRegistrationControl(element, fieldConfig) {
                const control = $(element);
                const value = $.trim(control.val());
                const emailPattern = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\\.,;:\s@\"]+\.)+[^<>()[\]\\.,;:\s@\"]{2,})$/i;

                if (accountValidationSubmitting) {
                    return;
                }

                if (value === "" || !emailPattern.test(value)) {
                    hideAccountValidationAlert(control);
                    return;
                }

                const fieldSelector = fieldConfig && fieldConfig.selector ? fieldConfig.selector : "";
                const iterationIndex = fieldSelector ? $(fieldSelector).index(control) : 0;

                const data = {
                    action: "validateAccountRegistrationEmail",
                    nonce: accountValidationNonce,
                    form_id: formId,
                    field_id: fieldConfig.field_id,
                    value: value,
                    iteration_index: iterationIndex
                };

                $.post(ajaxurl, data, function(response) {
                    let parsed = response;
                    if (typeof parsed !== "object") {
                        try {
                            parsed = JSON.parse(response);
                        } catch (e) {
                            parsed = null;
                        }
                    }

                    if (!parsed || typeof parsed.valid === "undefined") {
                        return;
                    }

                    if (accountValidationSubmitting) {
                        return;
                    }

                    if (parsed.valid) {
                        hideAccountValidationAlert(control);
                    } else {
                        showAccountValidationAlert(control, parsed.message || "<?php echo esc_js(__('This email already belongs to an existing account. Use a different email address to continue.', 'griffinforms-form-builder')); ?>");
                    }
                }).fail(function() {
                    hideAccountValidationAlert(control);
                });
            }

            function initAccountRegistrationValidation() {
                if (!accountValidationConfig || typeof accountValidationConfig !== "object") {
                    return;
                }

                $.each(accountValidationConfig, function(fieldId, fieldConfig) {
                    const selector = fieldConfig && fieldConfig.selector ? fieldConfig.selector : "";
                    if (!selector) {
                        return;
                    }

                    let timer = null;

                    $(document).on("input change", selector, function() {
                        const element = this;
                        clearTimeout(timer);
                        timer = setTimeout(function() {
                            validateAccountRegistrationControl(element, fieldConfig);
                        }, 400);
                    });

                    $(document).on("blur", selector, function() {
                        clearTimeout(timer);
                        validateAccountRegistrationControl(this, fieldConfig);
                    });
                });
            }

            function ensureFormOpenedAt(meta) {
                const nowIso = new Date().toISOString();
                if (!meta.form_opened_at) {
                    meta.form_opened_at = nowIso;
                }
                if (!meta.page_opened_at || typeof meta.page_opened_at !== "object") {
                    meta.page_opened_at = {};
                }
                return meta;
            }

            function markPageOpenedAt() {
                const meta = ensureFormOpenedAt(readFormMeta());
                const nowIso = new Date().toISOString();
                meta.page_opened_at[String(pageId)] = nowIso;
                writeFormMeta(meta);
            }

            function markReviewOpenedAt() {
                const meta = ensureFormOpenedAt(readFormMeta());
                const nowIso = new Date().toISOString();
                if (!meta.review_opened_at) {
                    meta.review_opened_at = nowIso;
                }
                writeFormMeta(meta);
                return meta.review_opened_at;
            }

            function buildSubmissionEventMeta() {
                const meta = ensureFormOpenedAt(readFormMeta());
                const nowIso = new Date().toISOString();
                const pageOpenedAt = meta.page_opened_at && meta.page_opened_at[String(pageId)]
                    ? meta.page_opened_at[String(pageId)]
                    : (meta.form_opened_at || nowIso);
                if (!meta.submitted_pages || typeof meta.submitted_pages !== "object") {
                    meta.submitted_pages = {};
                }
                if (meta.submitted_pages[String(pageId)]) {
                    writeFormMeta(meta);
                    return null;
                }
                meta.submitted_pages[String(pageId)] = true;
                writeFormMeta(meta);
                return {
                    type: "page_submit",
                    page_id: pageId,
                    timestamp: nowIso,
                    form_opened_at: meta.form_opened_at || nowIso,
                    page_opened_at: pageOpenedAt
                };
            }

            function buildPaymentEventMeta(eventType) {
                const meta = ensureFormOpenedAt(readFormMeta());
                const nowIso = new Date().toISOString();
                const reviewOpenedAt = meta.review_opened_at || nowIso;
                if (eventType === "payment_review_open") {
                    if (meta.review_event_sent) {
                        return null;
                    }
                    meta.review_event_sent = true;
                }
                if (eventType === "payment_submit") {
                    if (meta.payment_submit_sent) {
                        return null;
                    }
                    meta.payment_submit_sent = true;
                }
                if (eventType === "payment_confirmed") {
                    if (meta.payment_confirmed_sent) {
                        return null;
                    }
                    meta.payment_confirmed_sent = true;
                }
                writeFormMeta(meta);
                return {
                    type: eventType,
                    page_id: 0,
                    timestamp: nowIso,
                    form_opened_at: meta.form_opened_at || nowIso,
                    page_opened_at: reviewOpenedAt
                };
            }

            function sendSubmissionMetaEvent(eventPayload) {
                if (!eventPayload) {
                    return;
                }
                const session = getSubmissionSession();
                if (!session || !session.subId || !session.subToken) {
                    return;
                }
                $.ajax({
                    url: ajaxurl,
                    method: "POST",
                    data: {
                        action: "recordSubmissionEvent",
                        nonce: "<?php echo esc_js($nonce); ?>",
                        form: formId,
                        sub_id: session.subId,
                        sub_token: session.subToken,
                        submission_meta_event: JSON.stringify(eventPayload)
                    }
                });
            }

            if (!window.gfRequestLocks) {
                window.gfRequestLocks = {
                    _states: {},
                    getState: function(key) {
                        if (!this._states[key]) {
                            this._states[key] = { channels: {}, counts: 0 };
                        }
                        return this._states[key];
                    },
                    hasChannel: function(key, channel) {
                        const state = this.getState(key);
                        return !!state.channels[channel];
                    },
                    lock: function(key, channel, options) {
                        const state = this.getState(key);
                        state.channels[channel] = (state.channels[channel] || 0) + 1;
                        state.counts += 1;
                        this.updateUi(key, options);
                    },
                    unlock: function(key, channel, options) {
                        const state = this.getState(key);
                        if (state.channels[channel]) {
                            state.channels[channel] -= 1;
                            state.counts = Math.max(0, state.counts - 1);
                            if (state.channels[channel] <= 0) {
                                delete state.channels[channel];
                            }
                        }
                        this.updateUi(key, options);
                    },
                    updateUi: function(key, options) {
                        const state = this.getState(key);
                        const showOverlay = options && options.overlay === true;
                        const $formContainer = $(".griffinforms-form" + key + "-container");
                        const $overlay = $formContainer.find("[data-gf-form-lock]");
                        const $actionBtn = $(".griffinforms-form" + key + "-container").find("[data-gf-button-type=\"submit\"],[data-gf-button-type=\"next\"]").first();
                        const submissionActive = this.hasChannel(key, "submission");

                        if (state.counts > 0) {
                            if ($formContainer.length) {
                                $formContainer.attr("aria-busy", "true");
                            }
                            if (submissionActive && $overlay.length) {
                                $overlay.attr("aria-hidden", "false").css("display", "flex");
                            }
                        } else {
                            if ($formContainer.length) {
                                $formContainer.attr("aria-busy", "false");
                            }
                            if ($overlay.length) {
                                $overlay.attr("aria-hidden", "true").css("display", "none");
                            }
                        }

                        if (!submissionActive && $overlay.length) {
                            $overlay.attr("aria-hidden", "true").css("display", "none");
                        }

                        if (this.hasChannel(key, "validation")) {
                            $actionBtn.prop("disabled", true);
                        } else {
                            $actionBtn.prop("disabled", false);
                        }
                    }
                };
            }

            function lockForm(channel, options) {
                if (window.gfRequestLocks && typeof window.gfRequestLocks.lock === "function") {
                    window.gfRequestLocks.lock(String(formId), channel, options);
                }
            }

            function unlockForm(channel, options) {
                if (window.gfRequestLocks && typeof window.gfRequestLocks.unlock === "function") {
                    window.gfRequestLocks.unlock(String(formId), channel, options);
                }
            }
            const reviewEmptyText = "<?php echo esc_js(__('Your cart is empty. Please choose at least one product.', 'griffinforms-form-builder')); ?>";
            const reviewFieldClass = reviewContainer.attr("data-gf-review-field-class") || "griffinforms-payment-review-field";
            const reviewFieldFallback = reviewContainer.attr("data-gf-review-field-fallback") || "";
            const gatewayHeadingText = "<?php echo esc_js(__('Select a payment method', 'griffinforms-form-builder')); ?>";
            const gatewayErrorText = "<?php echo esc_js(__('Please choose a payment method to continue.', 'griffinforms-form-builder')); ?>";
            const gatewayUnavailableText = "<?php echo esc_js(__('No payment gateways are currently available. Please contact the site administrator.', 'griffinforms-form-builder')); ?>";
            const paypalPopupHintText = "<?php echo esc_js(__('PayPal will open a secure popup window for buyer approval.', 'griffinforms-form-builder')); ?>";
            const paypalButtonPromptText = "<?php echo esc_js(__('Use the PayPal options above to continue with checkout.', 'griffinforms-form-builder')); ?>";
            const paypalContinueLabelText = "<?php echo esc_js(__('Use PayPal options above', 'griffinforms-form-builder')); ?>";
            const paypalCancelText = "<?php echo esc_js(__('PayPal checkout was cancelled. Your submission is still awaiting payment.', 'griffinforms-form-builder')); ?>";
            const paypalInitText = "<?php echo esc_js(__('Unable to initialize PayPal checkout right now. Please try again.', 'griffinforms-form-builder')); ?>";
            const paypalPopupBlockedText = "<?php echo esc_js(__('If the PayPal popup did not appear, please allow popups for this site and try again.', 'griffinforms-form-builder')); ?>";
            const paymentUiContainer = reviewContainer.find("[data-gf-gateway-ui]");
            const paymentFlowNonce = reviewContainer.attr("data-gf-payment-nonce") || "";
            const resumeMode = reviewContainer.attr("data-gf-resume-mode") === "1";
            const resumeCart = reviewContainer.attr("data-gf-resume-cart");
            const resumeGateways = reviewContainer.attr("data-gf-resume-gateways");
            const resumeSubmissionId = reviewContainer.attr("data-gf-resume-submission");
            const resumeToken = reviewContainer.attr("data-gf-resume-token");
            const resumeSubToken = reviewContainer.attr("data-gf-resume-sub-token") || "";
            const stripeMountIdBase = "griffinforms-stripe-card-" + formId;
            const resumeDefaultGateway = reviewContainer.attr("data-gf-resume-default-gateway") || "";
            const paymentStyleTokens = getPaymentStyleTokens();
            window.gfCaptchaHandlers = window.gfCaptchaHandlers || {};
            window.gfCaptchaInitFns = window.gfCaptchaInitFns || {};
            const paymentFlowState = {
                gateway: null,
                ready: false,
                busy: false,
                stripe: {
                    instance: null,
                    elements: null,
                    card: null,
                    clientSecret: "",
                    intentId: "",
                    publishableKey: "",
                    billingDetails: {}
                },
                paypal: {
                    orderData: null,
                    orderId: "",
                    rendered: false
                }
            };
            const paymentGatewayHandlers = {};
            window.griffinformsGatewayHandlers = window.griffinformsGatewayHandlers || {};

            handleReloadSuccessMessage(formId);
            if (page.is(":visible")) {
                markPageOpenedAt();
            }
            $(document).on("pageFlipped", function(event, payload) {
                if (payload && payload.thePage && payload.thePage.attr("id") === page.attr("id")) {
                    markPageOpenedAt();
                }
            });

            if (resumeMode) {
                console.log("GF Resume Mode ON", {
                    formId: formId,
                    submissionId: resumeSubmissionId,
                    token: resumeToken,
                    subToken: resumeSubToken,
                    defaultGateway: resumeDefaultGateway,
                    gatewaysAttr: resumeGateways
                });
                if (resumeSubmissionId && resumeSubToken) {
                    const existingSession = JSON.parse(sessionStorage.getItem("gfForm" + formId) || "{}");
                    sessionStorage.setItem("gfForm" + formId, JSON.stringify({
                        subId: parseInt(resumeSubmissionId, 10),
                        subToken: resumeSubToken,
                        editSession: !!existingSession.editSession
                    }));
                }
                if (reviewBackBtn.length) {
                    reviewBackBtn.hide();
                }
                $(".griffinforms-formpage-container").hide().attr("data-griffinforms-state", "inactive");
                renderResumeSummary();
                if (reviewBackBtn.length) {
                    reviewBackBtn.hide();
                }
                const actionsBar = reviewContainer.find(".griffinforms-payment-review-actions");
                if (actionsBar.length) {
                    actionsBar.removeClass("justify-content-between").addClass("justify-content-end");
                }
                reviewContainer.show().attr("data-griffinforms-state", "review");
                markReviewOpenedAt();
                sendSubmissionMetaEvent(buildPaymentEventMeta("payment_review_open"));
            } else {
                flipPage(firstPage);
            }

            sessionStorage.setItem(pageFields, JSON.stringify([]));

            function checkFieldErrors() {
                let errorCount = 0;
                let fields = JSON.parse(sessionStorage.getItem(pageFields));

                if (fields.length === 0) {
                    return errorCount;
                }

                $.each(fields, function(index, id) {
                    const fieldErrors = JSON.parse(sessionStorage.getItem("mfFieldAlerts" + id));
                    const fieldClass = ".griffinforms-formcontrol-" + id;

                    if (typeof fieldErrors === "object" && $.isEmptyObject(fieldErrors) === false) {
                        $.each(fieldErrors, function(iteration, errors) {
                            if (typeof errors === "object") {
                                $.each(errors, function(errorType, errorStatus) {
                                    if(errorStatus) {
                                        $(fieldClass).eq(iteration).siblings(".griffinforms-alert-" + errorType).fadeIn();
                                        errorCount++;
                                    }
                                });
                            }
                        });
                    }
                });

                return errorCount;
            }

            function saveFormpage() {
                $("#griffinforms-form-alert-danger").hide();
                accountValidationSubmitting = true;
                clearAllAccountValidationAlerts();
                btnSpinner(true);
                lockForm("submission", { overlay: true });

                let formData = JSON.parse(sessionStorage.getItem("gfForm" + formId));
                if (!formData || !formData["subId"] || !formData["subToken"]) {
                    resetFormMeta();
                }

                let data = {
                    "action": "saveFormpage",
                    "form": formId,
                    "page": pageId,
                    "sub_id": formData["subId"],
                    "sub_token": formData["subToken"],
                    "edit_session": formData["editSession"] ? 1 : 0,
                    "page_data": JSON.stringify(formpageData()),
                    "antispam_data": "",
                    "position": "<?php echo esc_js($position); ?>",
                    "nonce": "<?php echo esc_js($nonce); ?>",
                    "form_url": window.location.href
                };
                const submissionEventMeta = buildSubmissionEventMeta();
                if (submissionEventMeta) {
                    data["submission_meta_event"] = JSON.stringify(submissionEventMeta);
                }

                collectAntispamData(data, formData);
            }

            function collectAntispamData(data, formData) {
                if (window.gfRequestLocks && typeof window.gfRequestLocks.updateUi === "function") {
                    window.gfRequestLocks.updateUi(String(formId), { overlay: true });
                }
                const antispamData = {};
                const activePage = $(".griffinforms-formpage-container[data-griffinforms-state='active']").first();
                let honeypotField = activePage.find("[data-gf-assist-field='1']").first();
                if (!honeypotField.length) {
                    honeypotField = $(".<?php echo esc_attr($this->antispam->getHoneypotClass()); ?>").first();
                }
                antispamData["honeypot"] = honeypotField.length ? honeypotField.val() : "";

                const captchaContext = <?php echo wp_json_encode($this->antispam->getCaptchaClientContext($this->data['position'] ?? '')); ?>;
                const providerEnabled = captchaContext && captchaContext.enabled;

                // Early exit if reCAPTCHA library (grecaptcha) is missing to prevent errors during form submission
                <?php
                $is_admin_user = is_user_logged_in() && current_user_can('manage_options') ? 'true' : 'false';
                ?>
                if (providerEnabled && captchaContext.id === "recaptcha" && typeof grecaptcha === "undefined") {
                    let recaptchaMessage = 'reCAPTCHA (' + captchaContext.version + ') library is not loaded.';
                    console.warn(recaptchaMessage);
                    <?php if ($is_admin_user === 'true') : ?>
                    console.warn('Admin Debug Info: Possible missing or delayed loading of Google reCAPTCHA API script.');
                    console.warn('Expected recaptcha version:', captchaContext.version);
                    <?php endif; ?>
                    alert('Unable to load reCAPTCHA security system (Version: ' + captchaContext.version + '). Please refresh the page or contact site administrator.');
                    btnSpinner(false);
                    unlockForm("submission", { overlay: true });
                    return;
                }

                if (providerEnabled && captchaContext.id === "recaptcha" && captchaContext.version === "v3") {
                    grecaptcha.ready(function () {
                        grecaptcha.execute(captchaContext.site_key, { action: 'submit' })
                            .then(function (token) {
                                antispamData["recaptcha"] = token;
                                setTimeout(function () {
                                    data["antispam_data"] = JSON.stringify(antispamData);
                                    proceedWithSubmission(data, formData);
                                }, 2000);
                            })
                            .catch(function (error) {
                                console.error('Error during reCAPTCHA execution:', error);
                                alert('An error occurred during reCAPTCHA execution.');
                                btnSpinner(false);
                                unlockForm("submission", { overlay: true });
                            });
                    });
                } else if (providerEnabled && captchaContext.id === "recaptcha" && captchaContext.version === "v2") {
                    let v2Token = "";
                    if (activePage.length) {
                        const widgetId = activePage.data("gfRecaptchaWidgetId");
                        if (typeof widgetId !== "undefined") {
                            v2Token = grecaptcha.getResponse(widgetId);
                        }
                    }
                    if (!v2Token) {
                        v2Token = grecaptcha.getResponse();
                    }
                    if (v2Token) {
                        antispamData["recaptcha"] = v2Token;
                    } else {
                        alert("<?php esc_html_e('Please complete the reCAPTCHA challenge.', 'griffinforms-form-builder') ?>");
                        btnSpinner(false);
                        unlockForm("submission", { overlay: true });
                        return;
                    }
                    data["antispam_data"] = JSON.stringify(antispamData);
                    proceedWithSubmission(data, formData);
                } else if (providerEnabled && captchaContext.id === "cloudflare_turnstile") {
                    let turnstileToken = "";
                    if (typeof turnstile !== "undefined" && typeof turnstile.getResponse === "function" && activePage.length) {
                        const widgetId = activePage.data("gfTurnstileWidgetId");
                        if (typeof widgetId !== "undefined") {
                            turnstileToken = turnstile.getResponse(widgetId) || "";
                        }
                    }
                    if (!turnstileToken) {
                        const turnstileField = document.querySelector('input[name=\"cf-turnstile-response\"]');
                        turnstileToken = turnstileField ? turnstileField.value.trim() : "";
                    }
                    if (!turnstileToken) {
                        alert("<?php esc_html_e('Please complete the Turnstile challenge.', 'griffinforms-form-builder'); ?>");
                        btnSpinner(false);
                        unlockForm("submission", { overlay: true });
                        return;
                    }
                    antispamData[captchaContext.token_key] = turnstileToken;
                    data["antispam_data"] = JSON.stringify(antispamData);
                    proceedWithSubmission(data, formData);
                } else if (providerEnabled && captchaContext.id === "hcaptcha") {
                    let hcaptchaToken = "";
                    if (typeof hcaptcha !== "undefined" && typeof hcaptcha.getResponse === "function" && activePage.length) {
                        const widgetId = activePage.data("gfHcaptchaWidgetId");
                        if (typeof widgetId !== "undefined") {
                            hcaptchaToken = hcaptcha.getResponse(widgetId) || "";
                        }
                    }
                    if (!hcaptchaToken) {
                        const hcaptchaField = document.querySelector('[name=\"h-captcha-response\"]');
                        hcaptchaToken = hcaptchaField ? hcaptchaField.value.trim() : "";
                    }
                    if (!hcaptchaToken) {
                        alert("<?php esc_html_e('Please complete the hCaptcha challenge.', 'griffinforms-form-builder'); ?>");
                        btnSpinner(false);
                        unlockForm("submission", { overlay: true });
                        return;
                    }
                    antispamData[captchaContext.token_key] = hcaptchaToken;
                    data["antispam_data"] = JSON.stringify(antispamData);
                    proceedWithSubmission(data, formData);
                } else {
                    data["antispam_data"] = JSON.stringify(antispamData);
                    proceedWithSubmission(data, formData);
                }
            }

            function proceedWithSubmission(data, formData) {
                $.ajax({
                    url: ajaxurl,
                    method: "POST",
                    data: data,
                    dataType: "json"
                })
                .done(function (status) {
                    accountValidationSubmitting = false;
                    btnSpinner(false);
                    unlockForm("submission", { overlay: true });
                    if (status && status.success !== false) {
                        formSessionData(formData, status);
                        pageAction(data);
                    } else {
                        const message = status && status.message ? status.message : "<?php esc_html_e('Unable to submit this page right now. Please try again.', 'griffinforms-form-builder'); ?>";
                        showErrorNotice(message);
                    }
                })
                .fail(function (jqXHR) {
                    accountValidationSubmitting = false;
                    btnSpinner(false);
                    unlockForm("submission", { overlay: true });
                    let message = "<?php esc_html_e('Unable to save progress right now. Please refresh and try again.', 'griffinforms-form-builder'); ?>";
                    console.error('griffinforms saveFormpage AJAX failed', {
                        status: jqXHR.status,
                        statusText: jqXHR.statusText,
                        responseText: jqXHR.responseText
                    });
                    if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
                        message = jqXHR.responseJSON.message;
                    } else if (jqXHR.responseText) {
                        try {
                            const parsed = JSON.parse(jqXHR.responseText);
                            if (parsed && parsed.message) {
                                message = parsed.message;
                            }
                        } catch (error) {
                            // ignore JSON parse error and use default message
                        }
                    }
                    showErrorNotice(message);
                });
            }

            function formpageData() {
                let data = {};
                let fields = JSON.parse(sessionStorage.getItem(pageFields));

                $.each(fields, function(index, field) {
                    let fieldClass = ".griffinforms-formcontrol-" + field;
                    let fieldData = {};
                    $(fieldClass).each(function(i) {
                        // Normalize user input to preserve composed UTF-8 characters (e.g. é, ’)
                        // Only apply normalize if the value is a string
                        let rawVal = $(this).val();
                        let normalized = (typeof rawVal === "string") ? rawVal.normalize("NFC") : rawVal;

                        const selectId = $(this).data("gf-phone-country-select");
                        if (selectId) {
                            const selectVal = $("#" + selectId).val() || "";
                            const valueStr = String(normalized || "").trim();
                            if (valueStr) {
                                if (valueStr.startsWith("+")) {
                                    normalized = "+" + valueStr.replace(/\\D/g, "");
                                } else if (valueStr.startsWith("00")) {
                                    normalized = "+" + valueStr.replace(/\\D/g, "").replace(/^00/, "");
                                } else if (selectVal) {
                                    const digits = valueStr.replace(/\\D/g, "");
                                    normalized = selectVal + digits;
                                }
                            }
                        }

                        fieldData[i] = normalized;
                    });
                    data[field] = fieldData;
                });

                return data;
            }

            function formSessionData(formData, status) {
                if (!status["edit"]) {
                    if (status["sub_id"]) {
                        formData["subId"] = status["sub_id"];
                    }
                    if (status["sub_token"]) {
                        formData["subToken"] = status["sub_token"];
                    }
                    formData["editSession"] = !!formData["editSession"];
                    sessionStorage.setItem("gfForm" + formId, JSON.stringify(formData));
                }
            }

            function pageAction(data) {
                page.hide();
                if (data["position"] === "single" || data["position"] === "last") {
                    if (requiresPayment) {
                        showPaymentReview();
                    } else {
                        userAction();
                    }
                } else {
                    flipPage(nextPage);
                }
            }

            function flipPage(currentPage) {
                $(".griffinforms-formpage-container").hide();
                $(".griffinforms-formpage-container").attr("data-griffinforms-state", "inactive");
                currentPage.show();
                currentPage.attr("data-griffinforms-state", "active");
                updateProgressBar(currentPage);
                window.requestAnimationFrame(function () {
                    updateProgressBarGeometry();
                });
                $(document).trigger('pageFlipped', {thePage: currentPage});
            }

            function updateProgressBar(currentPage) {
                const progressBar = $(".griffinforms-progress-bar[data-gf-progress-bar='true']").first();
                if (!progressBar.length) {
                    return;
                }
                const activePage = currentPage && currentPage.length ? currentPage : $(".griffinforms-formpage-container[data-griffinforms-state='active']").first();
                if (!activePage.length) {
                    return;
                }
                const currentStep = $(".griffinforms-formpage-container").index(activePage) + 1;
                progressBar.attr("data-gf-progress-active", String(currentStep));
                progressBar.find("[data-gf-progress-step]").each(function () {
                    const step = parseInt($(this).attr("data-gf-progress-step"), 10);
                    if (isNaN(step)) {
                        return;
                    }
                    let state = "upcoming";
                    if (step < currentStep) {
                        state = "complete";
                    } else if (step === currentStep) {
                        state = "active";
                    }
                    $(this).attr("data-gf-progress-state", state);
                });
            }

            function updateProgressBarGeometry() {
                const progressBar = $(".griffinforms-progress-bar[data-gf-progress-bar='true']").first();
                if (!progressBar.length) {
                    return;
                }

                const track = progressBar.find(".griffinforms-progress-bar__track").first();
                const connector = progressBar.find(".griffinforms-progress-bar__connector").first();
                const markers = progressBar.find(".griffinforms-progress-bar__marker");
                if (!track.length || !connector.length || markers.length < 2) {
                    connector.empty();
                    return;
                }

                const trackRect = track[0].getBoundingClientRect();
                const markerRects = markers.map(function () {
                    return this.getBoundingClientRect();
                }).get();
                const lineY = (markerRects[0].top + (markerRects[0].height / 2)) - trackRect.top;

                connector.attr("width", Math.max(trackRect.width, 1));
                connector.attr("height", Math.max(lineY * 2, markerRects[0].height, 1));
                connector.removeAttr("viewBox preserveAspectRatio");
                const svgRect = connector[0].getBoundingClientRect();
                const stateMap = progressBar.find(".griffinforms-progress-bar__step").map(function () {
                    return $(this).attr("data-gf-progress-state") || "upcoming";
                }).get();
                const segments = [];
                for (let i = 0; i < markerRects.length - 1; i++) {
                    const leftRect = markerRects[i];
                    const rightRect = markerRects[i + 1];
                    const x1 = leftRect.right - svgRect.left;
                    const x2 = rightRect.left - svgRect.left;
                    const state = stateMap[i + 1] || "upcoming";
                    const stroke = state === "upcoming" ? "var(--gf-progress-bar-muted, var(--bs-secondary, #6c757d))" : "var(--gf-progress-bar-accent, var(--bs-primary, #0d6efd))";
                    segments.push('<line class="griffinforms-progress-bar__connector-line" style="stroke:' + stroke + ';" x1="' + x1 + '" y1="' + lineY + '" x2="' + x2 + '" y2="' + lineY + '"></line>');
                }
                connector.html(segments.join(""));
            }

            initAccountRegistrationValidation();

            function parseCaptchaContext(pageEl) {
                const raw = pageEl.attr("data-gf-captcha-context");
                if (!raw) {
                    return null;
                }
                try {
                    return JSON.parse(raw);
                } catch (e) {
                    return null;
                }
            }

            function initCaptchaForPage(pageEl) {
                if (!pageEl || !pageEl.length) {
                    return;
                }
                if (pageEl.attr("data-gf-form-id") !== String(formId)) {
                    return;
                }
                const captchaContext = parseCaptchaContext(pageEl);
                if (!captchaContext || !captchaContext.enabled) {
                    return;
                }

                if (captchaContext.id === "recaptcha" && captchaContext.version === "v2") {
                    if (typeof grecaptcha === "undefined" || typeof grecaptcha.render !== "function") {
                        return;
                    }
                    const existingId = pageEl.data("gfRecaptchaWidgetId");
                    if (typeof existingId !== "undefined" && typeof grecaptcha.reset === "function") {
                        grecaptcha.reset(existingId);
                        return;
                    }
                    pageEl.find(".g-recaptcha").each(function () {
                        const el = this;
                        el.innerHTML = "";
                        const widgetId = grecaptcha.render(el, { sitekey: captchaContext.site_key });
                        pageEl.data("gfRecaptchaWidgetId", widgetId);
                    });
                    return;
                }

                if (captchaContext.id === "cloudflare_turnstile") {
                    if (typeof turnstile === "undefined" || typeof turnstile.render !== "function") {
                        return;
                    }
                    const existingId = pageEl.data("gfTurnstileWidgetId");
                    if (typeof existingId !== "undefined" && typeof turnstile.reset === "function") {
                        turnstile.reset(existingId);
                        return;
                    }
                    pageEl.find(".cf-turnstile").each(function () {
                        const el = this;
                        el.innerHTML = "";
                        const widgetId = turnstile.render(el, { sitekey: captchaContext.site_key });
                        pageEl.data("gfTurnstileWidgetId", widgetId);
                    });
                    return;
                }

                if (captchaContext.id === "hcaptcha") {
                    if (typeof hcaptcha === "undefined" || typeof hcaptcha.render !== "function") {
                        return;
                    }
                    const existingId = pageEl.data("gfHcaptchaWidgetId");
                    if (typeof existingId !== "undefined" && typeof hcaptcha.reset === "function") {
                        hcaptcha.reset(existingId);
                        return;
                    }
                    pageEl.find(".h-captcha").each(function () {
                        const el = this;
                        el.innerHTML = "";
                        const widgetId = hcaptcha.render(el, { sitekey: captchaContext.site_key });
                        pageEl.data("gfHcaptchaWidgetId", widgetId);
                    });
                }
            }

            function resetPaymentFlowState() {
                paymentFlowState.gateway = null;
                paymentFlowState.ready = false;
                paymentFlowState.busy = false;
                paymentFlowState.paypal.orderData = null;
                paymentFlowState.paypal.orderId = "";
                paymentFlowState.paypal.rendered = false;
                if (paymentFlowState.stripe.card) {
                    try {
                        paymentFlowState.stripe.card.unmount();
                    } catch (e) {}
                    paymentFlowState.stripe.card = null;
                }
                if (paymentUiContainer.length) {
                    paymentUiContainer.empty().hide();
                }
                if (reviewContinueBtn.length && reviewContinueBtn.data("defaultLabel")) {
                    setReviewContinueLabel(reviewContinueBtn.data("defaultLabel"));
                }
            }

            function setReviewContinueLabel(text) {
                if (!reviewContinueBtn.length || !text) {
                    return;
                }
                let labelNode = reviewContinueBtn.find("[data-gf-review-label]").first();
                if (!labelNode.length) {
                    reviewContinueBtn.contents().filter(function() {
                        return this.nodeType === 3;
                    }).remove();
                    labelNode = $("<span/>", {
                        "data-gf-review-label": "1",
                        "class": "griffinforms-review-btn-label"
                    });
                    const spinner = reviewContinueBtn.find(".spinner-border").first();
                    if (spinner.length) {
                        spinner.after(labelNode);
                    } else {
                        reviewContinueBtn.append(labelNode);
                    }
                }
                labelNode.text(text);
            }

            function showPaymentReview() {
                if (!reviewContainer.length) {
                    userAction();
                    return;
                }
                resetPaymentFlowState();
                populateReviewSummary();
                if (!resumeMode && reviewSummaryTarget.attr("data-gf-has-payment") !== "1") {
                    // No products selected in optional payment fields; proceed with normal submission flow.
                    userAction();
                    return;
                }
                $(".griffinforms-formpage-container").hide().attr("data-griffinforms-state", "inactive");
                reviewContainer.fadeIn().attr("data-griffinforms-state", "review");
                markReviewOpenedAt();
                sendSubmissionMetaEvent(buildPaymentEventMeta("payment_review_open"));
            }

            function populateReviewSummary() {
                if (!reviewSummaryTarget.length) {
                    return;
                }
                reviewSummaryTarget.attr("data-gf-has-payment", "0");
                if (resumeMode) {
                    renderResumeSummary();
                    return;
                }
                const summaries = $(".griffinforms-form" + formId + "-container .griffinforms-payment-field-wrapper").find("[data-gf-payment-summary]");
                if (!summaries.length) {
                    reviewSummaryTarget.html('<p class="text-muted mb-0">' + reviewEmptyText + "</p>");
                    return;
                }
                const fragments = [];
                let globalGatewayConfig = null;
                let gatewayWarning = false;
                summaries.each(function(){
                    const original = $(this);
                    const fieldLabel = original.data("summaryLabel") || original.closest(".griffinforms-payment-field-wrapper").find('[data-gf-element="label"]').text().trim();
                    const clone = original.clone();
                    clone.removeAttr("data-gf-payment-summary");
                    clone.find("[data-gf-payment-summary-items]").removeAttr("data-gf-payment-summary-items");
                    clone.find("[data-gf-payment-summary-subtotal]").removeAttr("data-gf-payment-summary-subtotal");
                    clone.find("[data-gf-payment-summary-total]").removeAttr("data-gf-payment-summary-total");
                    clone.find("[data-gf-payment-summary-coupon]").removeAttr("data-gf-payment-summary-coupon");
                    clone.find("[data-gf-payment-summary-tax]").removeAttr("data-gf-payment-summary-tax");
                    clone.find("[data-gf-payment-tax-registration]").removeAttr("data-gf-payment-tax-registration");
                    const wrapper = $("<div/>", { "class": reviewFieldClass });
                    if (fieldLabel) {
                        wrapper.append($("<div/>", { "class": "griffinforms-payment-review-heading" }).text(fieldLabel));
                    }
                    const hasItems = original.hasClass("is-active");
                    if (!hasItems) {
                        const fieldHasItems = original.closest(".griffinforms-payment-field-wrapper").find(".griffinforms-payment-field-group").attr("data-gf-has-items") === "1";
                        if (!fieldHasItems) {
                            return;
                        }
                        return;
                    }
                    reviewSummaryTarget.attr("data-gf-has-payment", "1");
                    wrapper.append(clone);

                    const paymentWrapper = original.closest(".griffinforms-payment-field-wrapper");
                    const gatewayNode = paymentWrapper.find(".griffinforms-payment-gateway-config");
                    if (!globalGatewayConfig && gatewayNode.length) {
                        let gatewayOptions = [];
                        try {
                            gatewayOptions = JSON.parse(gatewayNode.attr("data-gf-payment-gateways") || "[]") || [];
                        } catch (error) {
                            gatewayOptions = [];
                        }
                        const selectedGateway = gatewayNode.attr("data-gf-selected-gateway") || gatewayNode.attr("data-gf-gateway-default") || "";
                        const gatewayRequired = gatewayNode.attr("data-gf-gateway-required") === "1";
                        const fieldId = paymentWrapper.data("gfPaymentFieldId");
                        if (gatewayOptions.length) {
                            globalGatewayConfig = {
                                fieldId: fieldId,
                                options: gatewayOptions,
                                selected: selectedGateway,
                                required: gatewayRequired
                            };
                        } else {
                            gatewayWarning = true;
                        }
                    } else if (gatewayNode.length) {
                        const gatewayRequired = gatewayNode.attr("data-gf-gateway-required") === "1";
                        let gatewayOptions = [];
                        try {
                            gatewayOptions = JSON.parse(gatewayNode.attr("data-gf-payment-gateways") || "[]") || [];
                        } catch (error) {
                            gatewayOptions = [];
                        }
                        if (!gatewayOptions.length) {
                            gatewayWarning = true;
                        }
                    }

                    fragments.push(wrapper);
                });
                reviewSummaryTarget.empty();
                fragments.forEach(function(fragment){
                    reviewSummaryTarget.append(fragment);
                });
                if (!fragments.length) {
                    reviewSummaryTarget.html('<p class="text-muted mb-0">' + reviewEmptyText + "</p>");
                    return;
                }
                if (globalGatewayConfig) {
                    const selector = buildGlobalGatewaySelector(globalGatewayConfig);
                    reviewSummaryTarget.append(selector);
                } else if (gatewayWarning) {
                    reviewSummaryTarget.prepend($("<div/>", { "class": "alert alert-warning griffinforms-payment-gateway-warning", "role": "alert" }).text(gatewayUnavailableText));
                    reviewContinueBtn.prop("disabled", true);
                }
            }

            if (!window.gfCaptchaHandlers[formId]) {
                window.gfCaptchaHandlers[formId] = true;
                $(document).on("pageFlipped", function(event, payload) {
                    if (payload && payload.thePage) {
                        initCaptchaForPage(payload.thePage);
                    }
                });
            }
            window.gfCaptchaInitFns[formId] = initCaptchaForPage;

            if (typeof window.griffinformsRecaptchaReady !== "function") {
                window.griffinformsRecaptchaReady = function () {
                    const activePages = $(".griffinforms-formpage-container[data-griffinforms-state='active']");
                    activePages.each(function () {
                        const pageEl = $(this);
                        const pageFormId = pageEl.attr("data-gf-form-id");
                        if (pageFormId && window.gfCaptchaInitFns && window.gfCaptchaInitFns[pageFormId]) {
                            window.gfCaptchaInitFns[pageFormId](pageEl);
                        }
                    });
                };
            }

            if (!resumeMode) {
                initCaptchaForPage(firstPage);
            }

            updateProgressBarGeometry();

            if (reviewBackBtn.length) {
                reviewBackBtn.off("click.gfPaymentReview").on("click.gfPaymentReview", function(){
                    reviewContainer.hide().attr("data-griffinforms-state", "inactive");
                    resetPaymentFlowState();
                    flipPage(page);
                });
            }

            if (reviewContinueBtn.length) {
                reviewContinueBtn.off("click.gfPaymentReview").on("click.gfPaymentReview", function(){
                    if (reviewContainer.attr("data-griffinforms-state") !== "review") {
                        return;
                    }
                    if (requiresPayment) {
                        if (paymentFlowState.busy) {
                            return;
                        }
                        const hasPayment = reviewSummaryTarget.attr("data-gf-has-payment") === "1";
                        if (!hasPayment) {
                            if (resumeMode) {
                                showErrorNotice(reviewEmptyText);
                                showReviewBtnSpinner(false);
                                return;
                            }
                            // Optional payment field with no selection: complete submission without payment.
                            userAction();
                            return;
                        }
                        ensureGatewaySelection();
                if (!validateGatewaySelection()) {
                    showReviewBtnSpinner(false);
                    return;
                }
                let gatewayId = resolveReviewGatewayValue();
                if (!gatewayId && resumeMode && resumeDefaultGateway) {
                    gatewayId = resumeDefaultGateway;
                }
                if (!gatewayId) {
                    showErrorNotice(gatewayErrorText);
                    showReviewBtnSpinner(false);
                    return;
                }
                        sendSubmissionMetaEvent(buildPaymentEventMeta("payment_submit"));
                        if (window.griffinformsGatewayHandlers && window.griffinformsGatewayHandlers[gatewayId]) {
                            const _proSess = getSubmissionSession();
                            showReviewBtnSpinner(true);
                            window.griffinformsGatewayHandlers[gatewayId].pay({
                                nonce:            paymentFlowNonce,
                                form_id:          formId,
                                submission_id:    _proSess.subId    || '',
                                submission_token: _proSess.subToken || '',
                            });
                            return;
                        }
                        if (paymentFlowState.ready && paymentFlowState.gateway === gatewayId) {
                        showReviewBtnSpinner(true);
                        processGatewayPayment(gatewayId);
                        } else {
                            showReviewBtnSpinner(true);
                            initializeGatewayPayment(gatewayId);
                        }
                        return;
                    }
                    userAction();
                });
            }

            $(window).off("resize.gfProgressBar").on("resize.gfProgressBar", function () {
                updateProgressBarGeometry();
            });

            function buildGlobalGatewaySelector(config) {
                let activeValue = config.selected || "";
                if (!activeValue && config.options.length) {
                    activeValue = config.options[0].id || "";
                }
                let sectionClasses = reviewFieldClass + " mt-3";
                if (reviewFieldFallback) {
                    sectionClasses += " " + reviewFieldFallback;
                }
                const section = $("<div/>", {
                    "class": sectionClasses,
                    "data-gf-global-gateway": "1",
                    "data-gf-gateway-field": config.fieldId,
                    "data-gf-gateway-required": config.required ? "1" : "0"
                });
                section.append($("<div/>", { "class": "fw-semibold mb-2" }).text(gatewayHeadingText));
                const grid = $("<div/>", { "class": "griffinforms-payment-gateway-grid" });
                config.options.forEach(function(option){
                    const inputId = "griffinforms-payment-gateway-" + config.fieldId + "-" + option.id;
                    const item = $("<label/>", { "class": "griffinforms-payment-gateway-option d-flex align-items-start gap-3" });
                    const iconWrapper = $("<div/>", { "class": "griffinforms-payment-gateway-media" });
                    if (option.icon && option.hide_logo !== true) {
                        iconWrapper.append($("<img/>", {
                            src: option.icon,
                            alt: option.label,
                            class: "griffinforms-payment-gateway-icon",
                            style: "width:40px;height:40px;object-fit:contain;"
                        }));
                    }
                    const radio = $("<input/>", {
                        type: "radio",
                        class: "griffinforms-payment-gateway-radio",
                        name: "griffinforms-payment-gateway-" + config.fieldId,
                        value: option.id,
                        id: inputId,
                        checked: option.id === activeValue
                    });
                    const radioWrapper = $("<span/>", { "class": "griffinforms-payment-gateway-radio-wrap" }).append(radio);
                    const label = $("<div/>", { "class": "griffinforms-payment-gateway-meta flex-grow-1" });
                    label.append($("<div/>", { "class": "griffinforms-payment-gateway-label fw-semibold" }).text(option.label));
                    if (option.description) {
                        label.append($("<div/>", { "class": "griffinforms-payment-gateway-description" }).text(option.description));
                    }
                    if (iconWrapper.children().length) {
                        item.append(iconWrapper);
                    }
                    item.append(radioWrapper);
                    item.append(label);
                    grid.append(item);
                });
                section.append(grid);
                section.append($("<div/>", {
                    "class": "griffinforms-payment-gateway-error text-danger small mt-2",
                    "style": "display:none;"
                }).text(gatewayErrorText));
                section.on("change", 'input[name="griffinforms-payment-gateway-' + config.fieldId + '"]', function(){
                    setReviewGatewayValue(config.fieldId, $(this).val());
                    section.find(".griffinforms-payment-gateway-error").hide();
                    resetPaymentFlowState();
                });
                if (activeValue) {
                    setReviewGatewayValue(config.fieldId, activeValue, true);
                }
                return section;
            }

            function setReviewGatewayValue(fieldId, gatewayId, skipSync) {
                const group = $(".griffinforms-payment-field-group[data-gf-payment-field-group='" + fieldId + "']");
                if (!group.length) {
                    return;
                }
                const api = group.data("griffinformsPaymentApi");
                if (api && typeof api.setSelectedGateway === "function") {
                    api.setSelectedGateway(gatewayId, !!skipSync);
                }
            }

            function validateGatewaySelection() {
                if (resumeMode && resumeDefaultGateway) {
                    return true;
                }
                const sections = reviewContainer.find("[data-gf-gateway-field]");
                const pendingWarnings = reviewContainer.find(".griffinforms-payment-gateway-warning");
                if (!sections.length) {
                    if (pendingWarnings.length) {
                        showErrorNotice(gatewayUnavailableText);
                        showReviewBtnSpinner(false);
                        return false;
                    }
                    return true;
                }

                let valid = true;
                sections.each(function(){
                    const section = $(this);
                    const radios = section.find("input[type='radio']");
                    if (!radios.length) {
                        if (section.attr("data-gf-gateway-required") === "1") {
                            valid = false;
                        }
                        return;
                    }
                    const checked = radios.filter(":checked");
                    if (!checked.length) {
                        valid = false;
                        section.find(".griffinforms-payment-gateway-error").text(gatewayErrorText).show();
                    } else {
                        section.find(".griffinforms-payment-gateway-error").hide();
                    }
                });
                if (!valid) {
                    showErrorNotice(gatewayErrorText);
                    showReviewBtnSpinner(false);
                    console.log("GF gateway validation failed", { resumeMode: resumeMode, sections: sections.length });
                }
                return valid;
            }

            function ensureGatewaySelection() {
                const sections = reviewContainer.find("[data-gf-gateway-field]");
                console.log("GF ensureGatewaySelection sections", sections.length);
                sections.each(function(){
                    const section = $(this);
                    const radios = section.find("input[type='radio']");
                    if (!radios.length) {
                        return;
                    }
                    const checked = radios.filter(":checked");
                    if (!checked.length) {
                        const first = radios.first();
                        first.prop("checked", true);
                        const fieldId = section.attr("data-gf-gateway-field") || "";
                        if (fieldId) {
                            setReviewGatewayValue(fieldId, first.val(), true);
                        }
                        console.log("GF auto-selected gateway", first.val());
                    }
                });
            }

            function renderResumeSummary() {
                if (!reviewSummaryTarget.length) {
                    return;
                }
                let cart = {};
                try {
                    cart = resumeCart ? JSON.parse(resumeCart) : {};
                } catch (error) {
                    cart = {};
                }
                const items = Array.isArray(cart.items) ? cart.items : [];
                const subtotal = parseFloat(cart.subtotal || 0);
                const taxLines = Array.isArray(cart.tax_lines) ? cart.tax_lines : [];
                const taxTotal = parseFloat(cart.tax_total || 0);
                const total = parseFloat(cart.total || subtotal + taxTotal);
                const currency = (cart.currency || "USD").toUpperCase();

                reviewSummaryTarget.empty();
                reviewSummaryTarget.attr("data-gf-has-payment", items.length ? "1" : "0");

                const wrapper = $("<div/>", { "class": reviewFieldClass });
                if (items.length) {
                    const list = $("<div/>", { "class": "griffinforms-payment-cart-summary" });
                    items.forEach(function(entry){
                        const line = $("<div/>", { "class": "griffinforms-payment-cart-item d-flex justify-content-between" });
                        const qtyText = entry.quantity > 1 ? " × " + entry.quantity : "";
                        line.append($("<span/>").text((entry.label || '') + qtyText));
                        line.append($("<span/>").text(formatCurrencyDisplay(entry.subtotal || 0, currency)));
                        list.append(line);
                    });

                    const footer = $("<div/>", { "class": "griffinforms-payment-cart-footer mt-2" });
                    footer.append(buildSummaryRow("Subtotal", subtotal, currency));
                    if (taxLines.length) {
                        taxLines.forEach(function(tax){
                            footer.append(buildSummaryRow(tax.label || "Tax", tax.amount || 0, currency));
                        });
                    } else if (taxTotal > 0) {
                        footer.append(buildSummaryRow("Tax", taxTotal, currency));
                    }
                    footer.append(buildSummaryRow("Total", total, currency, true));
                    list.append(footer);
                    wrapper.append(list);
                } else {
                    wrapper.append($("<p/>", { "class": "text-muted mb-0" }).text(reviewEmptyText));
                }

                reviewSummaryTarget.append(wrapper);

                let gateways = [];
                try {
                    gateways = resumeGateways ? JSON.parse(resumeGateways) : [];
                } catch (error) {
                    gateways = [];
                }
                if (gateways.length) {
                    const selectorConfig = {
                        fieldId: "resume",
                        options: gateways,
                        selected: resumeDefaultGateway || gateways[0].id || "",
                        required: true
                    };
                    const selector = buildGlobalGatewaySelector(selectorConfig);
                    reviewSummaryTarget.append(selector);
                }
            }

            function buildSummaryRow(label, amount, currencyCode, isTotal) {
                const row = $("<div/>", { "class": "griffinforms-payment-summary-row d-flex justify-content-between" + (isTotal ? " fw-semibold" : "") });
                row.append($("<span/>", { "class": "griffinforms-payment-summary-label" }).text(label));
                row.append($("<span/>", { "class": "griffinforms-payment-summary-amount" }).text(formatCurrencyDisplay(amount, currencyCode)));
                return row;
            }

            function resolveReviewGatewayValue() {
                const section = reviewContainer.find("[data-gf-gateway-field]").first();
                if (!section.length) {
                    if (resumeMode && resumeDefaultGateway) {
                        console.log("GF resume gateway fallback", resumeDefaultGateway);
                        return resumeDefaultGateway;
                    }
                    return "";
                }
                const radio = section.find("input[type='radio']:checked");
                const val = radio.length ? radio.val() : "";
                console.log("GF resolveReviewGatewayValue", { val: val, resumeMode: resumeMode, resumeDefaultGateway: resumeDefaultGateway });
                return val;
            }

            function registerPaymentGatewayHandler(gatewayId, handler) {
                if (!gatewayId || !handler || typeof handler !== "object") {
                    return;
                }
                paymentGatewayHandlers[gatewayId] = handler;
            }

            function initializeGatewayPayment(gatewayId) {
                if (window.griffinformsGatewayHandlers && window.griffinformsGatewayHandlers[gatewayId]) {
                    requestGatewaySession(gatewayId)
                        .done(function(response) {
                            if (!response || response.success !== true || !response.data) {
                                showGatewayError((response && response.data && response.data.message) ? response.data.message : gatewayErrorText);
                                paymentFlowState.busy = false;
                                showReviewBtnSpinner(false);
                                return;
                            }
                            window.griffinformsGatewayHandlers[gatewayId].init(response.data);
                        })
                        .fail(function(jqXHR) {
                            let message = gatewayErrorText;
                            if (jqXHR.responseJSON && jqXHR.responseJSON.data && jqXHR.responseJSON.data.message) {
                                message = jqXHR.responseJSON.data.message;
                            }
                            showGatewayError(message);
                            paymentFlowState.busy = false;
                            showReviewBtnSpinner(false);
                        });
                    return;
                }
                const handler = paymentGatewayHandlers[gatewayId];
                if (handler && typeof handler.initialize === "function") {
                    handler.initialize();
                    return;
                }
                showGatewayError(gatewayUnavailableText);
            }

            function processGatewayPayment(gatewayId) {
                if (window.griffinformsGatewayHandlers && window.griffinformsGatewayHandlers[gatewayId]) {
                    const session = getSubmissionSession();
                    window.griffinformsGatewayHandlers[gatewayId].pay({
                        nonce:            paymentFlowNonce,
                        form_id:          formId,
                        submission_id:    session.subId    || '',
                        submission_token: session.subToken || '',
                    });
                    return;
                }
                const handler = paymentGatewayHandlers[gatewayId];
                if (handler && typeof handler.process === "function") {
                    handler.process();
                    return;
                }
                showGatewayError(gatewayUnavailableText);
            }

            function requestGatewaySession(gatewayId) {
                const session = getSubmissionSession();
                if (!paymentFlowNonce || !session || !session.subId || !session.subToken) {
                    return $.Deferred().reject({ responseJSON: { data: { message: gatewayErrorText } } }).promise();
                }

                return $.ajax({
                    url: ajaxurl,
                    method: "POST",
                    data: {
                        action: "griffinforms_create_payment_intent",
                        nonce: paymentFlowNonce,
                        form_id: formId,
                        submission_id: session.subId,
                        submission_token: session.subToken,
                        gateway: gatewayId
                    }
                });
            }

            function loadStripeLibrary() {
                if (window.Stripe) {
                    return Promise.resolve();
                }
                if (window.griffinformsStripeLoader) {
                    return window.griffinformsStripeLoader;
                }

                window.griffinformsStripeLoader = new Promise(function(resolve, reject) {
                    const script = document.createElement("script");
                    script.src = "https://js.stripe.com/v3/";
                    script.onload = resolve;
                    script.onerror = reject;
                    document.head.appendChild(script);
                });
                return window.griffinformsStripeLoader;
            }

            function startStripeIntent() {
                if (!paymentFlowNonce) {
                    showGatewayError(gatewayErrorText);
                    return;
                }

                paymentFlowState.gateway = "stripe";
                paymentFlowState.ready = false;
                paymentFlowState.busy = true;

                requestGatewaySession("stripe")
                .done(function(response) {
                    if (!response || response.success !== true || !response.data) {
                        showGatewayError((response && response.data && response.data.message) ? response.data.message : gatewayErrorText);
                        paymentFlowState.busy = false;
                        showReviewBtnSpinner(false);
                        return;
                    }
                    loadStripeLibrary()
                        .then(function(){
                            mountStripeElement(response.data);
                        })
                        .catch(function(){
                            showGatewayError(gatewayErrorText);
                        })
                        .finally(function(){
                            paymentFlowState.busy = false;
                            showReviewBtnSpinner(false);
                        });
                })
                .fail(function(jqXHR){
                    let message = gatewayErrorText;
                    if (jqXHR.responseJSON && jqXHR.responseJSON.data && jqXHR.responseJSON.data.message) {
                        message = jqXHR.responseJSON.data.message;
                    }
                    showGatewayError(message);
                    paymentFlowState.busy = false;
                    showReviewBtnSpinner(false);
                });
            }

            function mountStripeElement(data) {
                if (!window.Stripe || !data || !data.client_secret) {
                    showGatewayError(gatewayErrorText);
                    return;
                }

                if (!paymentUiContainer.length) {
                    showGatewayError(gatewayErrorText);
                    return;
                }

                if (!paymentFlowState.stripe.instance || paymentFlowState.stripe.publishableKey !== data.publishable_key) {
                    paymentFlowState.stripe.instance = window.Stripe(data.publishable_key);
                    paymentFlowState.stripe.publishableKey = data.publishable_key;
                }

                paymentFlowState.stripe.intentData = data;
                paymentFlowState.stripe.elements = paymentFlowState.stripe.instance.elements();
                if (paymentFlowState.stripe.card) {
                    try {
                        paymentFlowState.stripe.card.unmount();
                    } catch (e) {}
                    paymentFlowState.stripe.cardMounted = false;
                }

                const mountId = stripeMountIdBase + "-v" + Date.now();
                paymentFlowState.stripe.mountId = mountId;

                const controlStyle = paymentStyleTokens;
                const containerStyle = buildStripeContainerStyle(controlStyle);
                const wrapperClass = "griffinforms-payment-gateway-card-wrapper";
                const noteClass = themeIsSet ? "small mt-2" : "text-muted small mt-2";
                paymentUiContainer.show().html(
                    '<div class="griffinforms-payment-gateway-note fw-semibold mb-2">' +
                    formatCurrencyDisplay(data.display_amount || 0, data.currency || "USD") +
                    '</div>' +
                    '<div class="' + wrapperClass + '" style="' + containerStyle + '">' +
                        '<div class="griffinforms-payment-gateway-card" id="' + mountId + '"></div>' +
                    '</div>' +
                    '<div class="' + noteClass + '">' +
                    "<?php echo esc_js(__('Enter your card details and click Pay now to continue.', 'griffinforms-form-builder')); ?>" +
                    '</div>' +
                    '<div class="text-danger small mt-2" data-gf-payment-error style="display:none;"></div>'
                );

                const stripeStyle = determineStripeStyle();
                paymentFlowState.stripe.card = paymentFlowState.stripe.elements.create("card", {
                    hidePostalCode: false,
                    style: stripeStyle
                });
                paymentFlowState.stripe.card.mount("#" + paymentFlowState.stripe.mountId);
                paymentFlowState.stripe.cardMounted = true;
                paymentFlowState.stripe.clientSecret = data.client_secret;
                paymentFlowState.stripe.intentId = data.intent_id;
                paymentFlowState.stripe.billingDetails = data.customer || {};
                paymentFlowState.ready = true;
                setReviewContinueLabel("<?php echo esc_js(__('Pay now', 'griffinforms-form-builder')); ?>");
            }

            function confirmStripePayment() {
                if (!paymentFlowState.stripe.instance || !paymentFlowState.stripe.card || !paymentFlowState.stripe.clientSecret) {
                    initializeGatewayPayment("stripe");
                    return;
                }
                if (!paymentFlowState.stripe.mountId || !document.getElementById(paymentFlowState.stripe.mountId) || !paymentFlowState.stripe.cardMounted) {
                    paymentFlowState.ready = false;
                    initializeGatewayPayment("stripe");
                    return;
                }

                const session = getSubmissionSession();
                if (!session || !session.subId || !session.subToken) {
                    showGatewayError(gatewayErrorText);
                    return;
                }

                paymentFlowState.busy = true;
                showReviewBtnSpinner(true);

                const billing = paymentFlowState.stripe.billingDetails || {};
                const billingDetails = {
                    name: billing.name || "",
                    email: billing.email || "",
                    phone: billing.phone || "",
                    address: billing.address || {}
                };

                try {
                    paymentFlowState.stripe.instance.confirmCardPayment(paymentFlowState.stripe.clientSecret, {
                        payment_method: {
                            card: paymentFlowState.stripe.card,
                            billing_details: billingDetails
                        }
                    })
                    .then(function(result){
                        if (result.error) {
                            showGatewayError(result.error.message || gatewayErrorText);
                            paymentFlowState.busy = false;
                            showReviewBtnSpinner(false);
                            return;
                        }
                        if (!result.paymentIntent) {
                            showGatewayError(gatewayErrorText);
                            paymentFlowState.busy = false;
                            showReviewBtnSpinner(false);
                            return;
                        }
                        markPaymentComplete("stripe", {
                            intent_id: result.paymentIntent.id
                        });
                    })
                    .catch(function(error){
                        showGatewayError(error.message || gatewayErrorText);
                        paymentFlowState.busy = false;
                        showReviewBtnSpinner(false);
                    });
                } catch (err) {
                    showGatewayError(gatewayErrorText);
                    paymentFlowState.stripe.card = null;
                    paymentFlowState.ready = false;
                    paymentFlowState.busy = false;
                    showReviewBtnSpinner(false);
                    initializeGatewayPayment("stripe");
                }
            }

            function loadPaypalLibrary(data) {
                if (!data || !data.client_id) {
                    return Promise.reject(new Error("missing_paypal_client_id"));
                }

                const clientId = encodeURIComponent(String(data.client_id));
                const currency = encodeURIComponent(String(data.currency || "USD").toUpperCase());
                const sdkKey = [clientId, currency].join("|");

                if (window.paypal && typeof window.paypal.Buttons === "function" && window.griffinformsPaypalLoaderKey === sdkKey) {
                    return Promise.resolve();
                }

                if (window.griffinformsPaypalLoader && window.griffinformsPaypalLoaderKey === sdkKey) {
                    return window.griffinformsPaypalLoader;
                }

                window.griffinformsPaypalLoaderKey = sdkKey;
                window.griffinformsPaypalLoader = new Promise(function(resolve, reject) {
                    document.querySelectorAll("script[data-gf-paypal-sdk='1']").forEach(function(node) {
                        if (node && node.parentNode) {
                            node.parentNode.removeChild(node);
                        }
                    });

                    try {
                        delete window.paypal;
                    } catch (error) {
                        window.paypal = undefined;
                    }

                    const script = document.createElement("script");
                    script.src = "https://www.paypal.com/sdk/js?client-id=" + clientId + "&currency=" + currency + "&intent=capture&components=buttons";
                    script.async = true;
                    script.setAttribute("data-gf-paypal-sdk", "1");
                    script.onload = function() {
                        if (window.paypal && typeof window.paypal.Buttons === "function") {
                            resolve();
                            return;
                        }
                        reject(new Error("paypal_sdk_missing_buttons"));
                    };
                    script.onerror = function() {
                        reject(new Error("paypal_sdk_load_failed"));
                    };
                    document.head.appendChild(script);
                }).catch(function(error) {
                    window.griffinformsPaypalLoader = null;
                    window.griffinformsPaypalLoaderKey = "";
                    throw error;
                });

                return window.griffinformsPaypalLoader;
            }

            function startPayPalCheckout() {
                if (!paymentFlowNonce) {
                    showGatewayError(paypalInitText);
                    return;
                }

                paymentFlowState.gateway = "paypal";
                paymentFlowState.ready = false;
                paymentFlowState.busy = true;

                requestGatewaySession("paypal")
                    .done(function(response) {
                        if (!response || response.success !== true || !response.data || !response.data.order_id || !response.data.client_id) {
                            showGatewayError((response && response.data && response.data.message) ? response.data.message : paypalInitText);
                            paymentFlowState.busy = false;
                            showReviewBtnSpinner(false);
                            return;
                        }

                        loadPaypalLibrary(response.data)
                            .then(function() {
                                mountPaypalButtons(response.data);
                            })
                            .catch(function() {
                                showGatewayError(paypalInitText);
                            })
                            .finally(function() {
                                paymentFlowState.busy = false;
                                showReviewBtnSpinner(false);
                            });
                    })
                    .fail(function(jqXHR) {
                        let message = paypalInitText;
                        if (jqXHR.responseJSON && jqXHR.responseJSON.data && jqXHR.responseJSON.data.message) {
                            message = jqXHR.responseJSON.data.message;
                        }
                        showGatewayError(message);
                        paymentFlowState.busy = false;
                        showReviewBtnSpinner(false);
                    });
            }

            function mountPaypalButtons(data) {
                if (!window.paypal || typeof window.paypal.Buttons !== "function" || !data || !data.order_id) {
                    showGatewayError(paypalInitText);
                    return;
                }

                const controlStyle = paymentStyleTokens;
                const shellStyle = buildGatewayShellStyle(controlStyle);
                const wrapperClass = "griffinforms-payment-gateway-shell";
                const noteClass = themeIsSet ? "small mt-2 mb-0" : "text-muted small mt-2 mb-0";
                paymentUiContainer.show().html(
                    '<div class="griffinforms-payment-gateway-note fw-semibold mb-2">' +
                    formatCurrencyDisplay(data.display_amount || 0, data.currency || "USD") +
                    '</div>' +
                    '<div class="' + wrapperClass + '" style="' + shellStyle + '">' +
                        '<div class="griffinforms-payment-review-description mb-3">' + paypalPopupHintText + '</div>' +
                        '<div class="griffinforms-payment-gateway-paypal-wrap" data-gf-paypal-button-wrap></div>' +
                    '</div>' +
                    '<p class="' + noteClass + '">' + paypalButtonPromptText + '</p>' +
                    '<div class="text-danger small mt-2" data-gf-payment-error style="display:none;"></div>'
                );

                paymentFlowState.paypal.orderData = data;
                paymentFlowState.paypal.orderId = data.order_id;
                paymentFlowState.paypal.rendered = false;
                paymentFlowState.ready = false;

                const target = paymentUiContainer.find("[data-gf-paypal-button-wrap]").get(0);
                const buttons = window.paypal.Buttons({
                    createOrder: function() {
                        return data.order_id;
                    },
                    onClick: function() {
                        paymentUiContainer.find("[data-gf-payment-error]").hide().text("");
                        sendSubmissionMetaEvent(buildPaymentEventMeta("payment_submit"));
                    },
                    onApprove: function(approvalData) {
                        paymentFlowState.busy = true;
                        showReviewBtnSpinner(true);
                        return markPaymentComplete("paypal", {
                            order_id: approvalData && approvalData.orderID ? approvalData.orderID : data.order_id
                        });
                    },
                    onCancel: function() {
                        paymentFlowState.busy = false;
                        showReviewBtnSpinner(false);
                        showGatewayError(paypalCancelText);
                    },
                    onError: function(error) {
                        paymentFlowState.busy = false;
                        showReviewBtnSpinner(false);
                        const message = error && error.message ? error.message : paypalPopupBlockedText;
                        showGatewayError(message);
                    }
                });

                if (!buttons || (typeof buttons.isEligible === "function" && !buttons.isEligible())) {
                    showGatewayError(gatewayUnavailableText);
                    return;
                }

                buttons.render(target)
                    .then(function() {
                        paymentFlowState.paypal.rendered = true;
                        paymentFlowState.ready = true;
                        setReviewContinueLabel(paypalContinueLabelText);
                    })
                    .catch(function() {
                        showGatewayError(paypalInitText);
                    });
            }

            function confirmPayPalPayment() {
                if (!paymentFlowState.ready || paymentFlowState.gateway !== "paypal" || !paymentFlowState.paypal.rendered) {
                    initializeGatewayPayment("paypal");
                    return;
                }

                const buttonWrap = paymentUiContainer.find("[data-gf-paypal-button-wrap]").get(0);
                if (buttonWrap && typeof buttonWrap.scrollIntoView === "function") {
                    buttonWrap.scrollIntoView({ behavior: "smooth", block: "center" });
                }

                const clickableButton = paymentUiContainer.find("[data-gf-paypal-button-wrap] button").first();
                if (clickableButton.length && typeof clickableButton.get(0).click === "function") {
                    clickableButton.get(0).click();
                }

                showReviewBtnSpinner(false);
            }

            function markPaymentComplete(gatewayId, paymentData) {
                const session = getSubmissionSession();
                if (!paymentFlowNonce || !session || !session.subId || !session.subToken) {
                    showGatewayError(gatewayErrorText);
                    paymentFlowState.busy = false;
                    showReviewBtnSpinner(false);
                    return $.Deferred().reject({ message: gatewayErrorText }).promise();
                }

                showReviewBtnSpinner(true);

                return $.ajax({
                    url: ajaxurl,
                    method: "POST",
                    data: {
                        action: "griffinforms_complete_payment",
                        nonce: paymentFlowNonce,
                        form_id: formId,
                        submission_id: session.subId,
                        submission_token: session.subToken,
                        gateway: gatewayId || paymentFlowState.gateway || "",
                        payment_data: JSON.stringify(paymentData || {})
                    }
                })
                .done(function(response){
                    if (response && response.success) {
                        paymentFlowState.busy = false;
                        showReviewBtnSpinner(false);
                        sendSubmissionMetaEvent(buildPaymentEventMeta("payment_confirmed"));
                        userAction();
                        return;
                    }
                    const errorMessage = (response && response.data && response.data.message) ? response.data.message : gatewayErrorText;
                    showGatewayError(errorMessage);
                    paymentFlowState.busy = false;
                    showReviewBtnSpinner(false);
                })
                .fail(function(jqXHR){
                    let message = gatewayErrorText;
                    if (jqXHR.responseJSON && jqXHR.responseJSON.data && jqXHR.responseJSON.data.message) {
                        message = jqXHR.responseJSON.data.message;
                    }
                    showGatewayError(message);
                    paymentFlowState.busy = false;
                    showReviewBtnSpinner(false);
                });
            }

            function getSubmissionSession() {
                try {
                    const stored = sessionStorage.getItem("gfForm" + formId);
                    return stored ? JSON.parse(stored) : {};
                } catch (e) {
                    return {};
                }
            }

            function showGatewayError(message) {
                showErrorNotice(message);
                if (!paymentUiContainer.length) {
                    return;
                }
                let target = paymentUiContainer.find("[data-gf-payment-error]");
                if (!target.length) {
                    target = $("<div/>", { "data-gf-payment-error": "1", "class": "text-danger small mt-2" });
                    paymentUiContainer.append(target);
                }
                target.text(message).show();
            }

            registerPaymentGatewayHandler("stripe", {
                initialize: startStripeIntent,
                process: confirmStripePayment
            });

            registerPaymentGatewayHandler("paypal", {
                initialize: startPayPalCheckout,
                process: confirmPayPalPayment
            });

            <?php do_action('griffinforms_gateway_ui_js_register'); ?>

            function formatCurrencyDisplay(amount, currency) {
                const code = (currency || "USD").toUpperCase();
                const numericAmount = parseFloat(amount || 0);
                try {
                    return new Intl.NumberFormat(undefined, { style: "currency", currency: code }).format(numericAmount);
                } catch (error) {
                    return code + " " + numericAmount.toFixed(2);
                }
            }

            function showReviewBtnSpinner(state) {
                if (!reviewContinueBtn.length) {
                    return;
                }
                if (state) {
                    const spinner = reviewContinueBtn.find(".spinner-border").first();
                    if (spinner.length) {
                        spinner.removeClass("visually-hidden");
                    }
                    reviewContinueBtn.prop("disabled", true);
                } else {
                    reviewContinueBtn.prop("disabled", false);
                    const spinner = reviewContinueBtn.find(".spinner-border").first();
                    if (spinner.length) {
                        spinner.addClass("visually-hidden");
                    }
                }
            }

            function determineStripeStyle() {
                const controlStyle = paymentStyleTokens || getDefaultPaymentStyle();
                const placeholderColor = controlStyle.placeholder_color || (themeIsSet ? "#9ca3af" : "#6c757d");

                return {
                    base: {
                        color: controlStyle.color,
                        fontSize: controlStyle.font_size,
                        fontFamily: controlStyle.font_family,
                        fontSmoothing: "antialiased",
                        iconColor: controlStyle.color,
                        '::placeholder': { color: placeholderColor },
                        ':-webkit-autofill': { color: controlStyle.color }
                    },
                    invalid: {
                        color: "#dc2626",
                        iconColor: "#dc2626"
                    }
                };
            }

            function buildStripeContainerStyle(controlStyle) {
                return buildGatewayShellStyle(controlStyle);
            }

            function buildGatewayShellStyle(controlStyle) {
                const background = controlStyle.background_color || (themeIsSet ? "#ffffff" : "#ffffff");
                const border = controlStyle.border_color || (themeIsSet ? "#d1d5db" : "#ced4da");
                const radius = controlStyle.border_radius || (themeIsSet ? "0.5rem" : "0.375rem");
                const padding = controlStyle.padding || "0.75rem 1rem";

                return "background-color:" + background + ";border:1px solid " + border + ";border-radius:" + radius + ";padding:" + padding + ";";
            }

            function getPaymentStyleTokens() {
                if (!reviewContainer.length) {
                    return getDefaultPaymentStyle();
                }
                const raw = reviewContainer.attr("data-gf-payment-style");
                if (!raw) {
                    return getDefaultPaymentStyle();
                }
                try {
                    const parsed = JSON.parse(raw);
                    return Object.assign(getDefaultPaymentStyle(), parsed || {});
                } catch (error) {
                    return getDefaultPaymentStyle();
                }
            }

            function getDefaultPaymentStyle() {
                return {
                    color: "#2d3748",
                    font_family: "system-ui, -apple-system, 'Segoe UI', sans-serif",
                    font_size: "16px",
                    line_height: "1.5",
                    background_color: "#ffffff",
                    border_color: "#ced4da",
                    border_radius: "0.375rem",
                    padding: "0.75rem 1rem",
                    placeholder_color: themeIsSet ? "#9ca3af" : "#6c757d"
                };
            }

            function getCurrentPage() {
                return $(".griffinforms-formpage-container[data-griffinforms-state=active").first();
            }

            function btnSpinner(state) {
                if(state) {
                    $(actionBtn).find(".spinner-border").removeClass("visually-hidden");
                    $(actionBtn).find(".action-btn-label").addClass("visually-hidden");
                } else {
                    $(actionBtn).find(".spinner-border").addClass("visually-hidden");
                    $(actionBtn).find(".action-btn-label").removeClass("visually-hidden");
                }
            }

            if (typeof window.griffinformsFormRedirectBase !== "object" || window.griffinformsFormRedirectBase === null) {
                window.griffinformsFormRedirectBase = {};
            }
            if (typeof window.griffinformsFormRedirectOverrides !== "object" || window.griffinformsFormRedirectOverrides === null) {
                window.griffinformsFormRedirectOverrides = {};
            }
            if (typeof window.griffinformsFormRedirectAppends !== "object" || window.griffinformsFormRedirectAppends === null) {
                window.griffinformsFormRedirectAppends = {};
            }
            if (typeof window.griffinformsFormRedirectBase[formId] === "undefined") {
                window.griffinformsFormRedirectBase[formId] = "<?php echo esc_js($userActionUrl); ?>";
            }

            function userAction() {
                const userActionType = "<?php echo esc_js($userActionType); ?>";
                const baseMap = window.griffinformsFormRedirectBase || {};
                const overrideMap = window.griffinformsFormRedirectOverrides || {};
                const appendMap = window.griffinformsFormRedirectAppends || {};

                const baseRedirect = (baseMap[formId] || "").toString();
                const overrideEntry = overrideMap[formId] || null;
                const appendEntry = appendMap[formId] || null;

                let finalRedirect = '';
                if (overrideEntry && typeof overrideEntry.url === "string") {
                    finalRedirect = overrideEntry.url.trim();
                }

                if (!finalRedirect.length && baseRedirect.length) {
                    finalRedirect = baseRedirect;
                }

                if (appendEntry && Array.isArray(appendEntry.segments) && appendEntry.segments.length) {
                    const appendText = appendEntry.segments.map(function(segment) {
                        return (segment || '').toString();
                    }).join('');
                    if (appendText.length) {
                        finalRedirect = (finalRedirect || '') + appendText;
                    }
                }

                // Resume payment should not reload the same resume URL after a successful payment,
                // otherwise the link becomes "no longer pending" and shows an error view.
                if (resumeMode) {
                    if (finalRedirect.length) {
                        window.location.href = finalRedirect;
                        return;
                    }

                    try {
                        const url = new URL(window.location.href);
                        url.searchParams.delete('gf_payment_resume');
                        window.history.replaceState({}, document.title, url.toString());
                    } catch (e) {}

                    $(".griffinforms-form" + formId + "-container").hide();
                    reviewContainer.hide();
                    const successMessage = $(".griffinforms-form" + formId + "-success_message");
                    if (successMessage.length) {
                        successMessage.fadeIn();
                    } else {
                        showErrorNotice("<?php echo esc_js(__('Payment completed successfully.', 'griffinforms-form-builder')); ?>");
                    }
                    return;
                }

                if (finalRedirect.length) {
                    window.location.href = finalRedirect;
                    return;
                }

                if (userActionType === "show_success_message") {
                    $(".griffinforms-form" + formId + "-container").hide();
                    $(".griffinforms-form" + formId + "-success_message").fadeIn();
                    return;
                }

                if (userActionType === "redirect_user") {
                    window.location.href = "<?php echo esc_url($userActionUrl); ?>";
                    return;
                }

                if (userActionType === "do_nothing" || userActionType === "") {
                    sessionStorage.setItem('griffinformsFormSubmitted_' + formId, '1');
                    location.reload();
                }
            }

            function showErrorNotice(message) {
                let notices = [];
                if (Array.isArray(message)) {
                    notices = message;
                } else if (message && typeof message === "object") {
                    notices = Object.values(message);
                } else if (typeof message === "string" && message.trim().length) {
                    notices = [message];
                }

                if (!notices.length) {
                    console.error("Invalid response format:", message);
                    return;
                }

                const alertText = notices.map(function(text) {
                    return String(text).replace(/\s+/g, " ").trim();
                }).filter(Boolean).join("\n");

                let notice = "<div>";
                $.each(notices, function(index, text) {
                    notice += "<div>" + $("<div>").text(text).html() + "</div>";
                });
                notice += "</div>";

                let alertElement = $("#griffinforms-form-alert-danger[data-gf-form-id='" + formId + "']");

                if (alertElement.length) {
                    alertElement.html(notice).show();
                    if (typeof alertElement[0].scrollIntoView === "function") {
                        alertElement[0].scrollIntoView({ behavior: "smooth", block: "nearest" });
                    }
                    return;
                }

                const activePage = $(".griffinforms-formpage-container[data-griffinforms-state='active']").first();
                if (activePage.length && activePage.data("gfPageRole") !== "review") {
                    const pageAlert = activePage.find("[data-gf-element='error']").first();
                    if (pageAlert.length) {
                        pageAlert.html(notice).show();
                        if (typeof pageAlert[0].scrollIntoView === "function") {
                            pageAlert[0].scrollIntoView({ behavior: "smooth", block: "nearest" });
                        }
                        return;
                    }
                }

                if (paymentUiContainer && paymentUiContainer.length) {
                    let inlineErr = paymentUiContainer.find("[data-gf-payment-error]");
                    if (!inlineErr.length) {
                        inlineErr = $("<div/>", { "data-gf-payment-error": "1", "class": "text-danger small mt-2" });
                        paymentUiContainer.append(inlineErr);
                    }
                    inlineErr.html(notice).show();
                    return;
                }

                // Final fallback: inject a lightweight alert into the form container
                const formContainer = $(".griffinforms-form" + formId + "-container");
                if (formContainer.length) {
                    let fallbackAlert = formContainer.find(".griffinforms-form-fallback-alert");
                    if (!fallbackAlert.length) {
                        fallbackAlert = $('<div class="griffinforms-form-fallback-alert alert alert-danger border-0 mb-3"></div>');
                        formContainer.prepend(fallbackAlert);
                    }
                    fallbackAlert.html(notice).show();
                }
            }

            <?php $this->actionBtnJs(); ?>
            <?php $this->backBtnJs(); ?>
        });

        <?php

        // Capture the JavaScript output
        $js_content = ob_get_clean();

        // Add the JS inline in the footer
        wp_add_inline_script('griffinforms-frontend', $js_content);
    }
}
