/**
 * ConvesioPay – Unified Classic Checkout
 *
 * Purpose:
 * - Provide a clean, modular checkout integration for ConvesioPay.
 * - Preserve WooCommerce’s native checkout lifecycle and event flow.
 * - Rely on backend-calculated totals and fees (server-authoritative model).
 * - Support multiple payment methods (Card, Apple Pay, Google Pay, BTCPay, etc.)
 *   through a single, unified runtime state.
 *
 * Design Principles:
 * - Backend-first fee and total calculation (no frontend fee mirroring).
 * - Idempotent event handling to avoid duplicate execution on AJAX refreshes.
 * - Compatible with WooCommerce checkout re-rendering and fragment updates.
 * - Safe coexistence with checkout builders (e.g. FunnelKit / similar tools).
 *
 * Notes:
 * - This file is a refactor target intended to replace legacy production logic.
 * - The existing production script remains untouched until feature parity
 *   and behavioral equivalence are fully validated.
 * - All checkout state synchronization must be resilient to multiple
 *   `update_checkout` and `updated_checkout` cycles.
 */
(function ($) {
    'use strict';

    /**
     * Immutable configuration/constants for Classic checkout.
     */
    const CONFIG = Object.freeze({
        gatewayId: 'peachpay_convesiopay_unified',
        containerId: 'convesiopay-unified-payment-container',
        mountNodeId: 'convesiopay-persistent-sdk-mount',
        selectedMethodField: 'convesiopay_selected_method',
        sessionAction: 'peachpay_store_convesiopay_method',
        checkoutFormSelector: 'form.checkout',
        paymentMethodSelector: 'input[name="payment_method"]',
        defaultMethod: 'card',
        allowedMethods: ['card', 'applepay', 'googlepay', 'btcpay'],
        methodIntentSuppressionMs: 1800,
        checkoutRefreshDebounceMs: 220,
        customerContextDebounceMs: 450,
        overlayClearFastMs: 80,
        overlayClearSlowMs: 220,
        peachpay_is_advanced_logging_enabled: false
    });

    /**
     * Runtime state holder.
     * Keep mutations centralized so event flows are easier to reason about.
     */
    const state = {
        initialized: false,
        mounted: false,
        component: null,
        mountNode: null,
        currentMethod: null,
        paymentConfirmedData: null,
        cardComponentValid: false,
        cardComponentErrors: {},
        btcSession: null,
        btcSessionSignature: '',
        btcListenerBound: false,
        lastSyncedMethod: '',
        lastKnownCartTotal: 0,
        lastKnownAmountCurrencySignature: '',
        applePaySessionPrimed: false,
        applePaySessionSignature: '',
        googlePaySessionSignature: '',
        googlePaySessionPrimed: false,
        googlePayUiLockUntilMs: 0,
        lastKnownFeeAmount: 0,
        lastCustomerSignature: '',
        lastKnownCustomerEmail: '',
        refreshingSessions: false,
        pendingSessionRefreshReason: '',
        methodSwitchInProgress: false,
        methodSwitchTarget: '',
        methodSwitchFallbackUnlock: null,
        explicitMethodIntent: '',
        explicitMethodIntentAtMs: 0,
        suppressCardComponentChangeUntilMs: 0,
        submitInProgress: false,
        allowNativeSubmitOnce: false,
        nativeSubmitBypassCount: 0,
        lastSubmitPreflightError: '',
        submitOverlayLocked: false,
        submitOverlayFailsafeTimer: null,
        overlayCleanupBound: false,
        overlayObserver: null,
        timers: {
            checkoutRefresh: null,
            customerContext: null,
            overlaySweep: null,
            nativeSubmitHandoff: null
        }
    };

    /**
     * Read localized unified configuration from WordPress.
     *
     * @returns {Object}
     */
    function getConfig() {
        return window.peachpay_convesiopay_unified_data || {};
    }

    /**
     * Resolve active checkout form selector for classic checkout and order-pay.
     *
     * @returns {string}
     */
    function getCheckoutFormSelector() {
        if (document.querySelector('form.checkout')) {
            return 'form.checkout';
        }
        if (document.querySelector('form#order_review')) {
            return 'form#order_review';
        }
        return CONFIG.checkoutFormSelector;
    }

    /**
     * Resolve active checkout form jQuery object.
     *
     * @returns {jQuery}
     */
    function getCheckoutForm() {
        return $(getCheckoutFormSelector());
    }

    /**
     * Whether current request context is order-pay.
     *
     * @returns {boolean}
     */
    function isOrderPayContext() {
        const config = getConfig();
        if (typeof config.is_order_pay !== 'undefined') {
            return Boolean(config.is_order_pay);
        }
        return window.location.pathname.includes('/order-pay/');
    }

    /**
     * Whether advanced debug logging is enabled from localized PHP config.
     *
     * @returns {boolean}
     */
    function isAdvancedLoggingEnabled() {
        try {
            const raw = getConfig().peachpay_is_advanced_logging_enabled;
            if (typeof raw === 'string') {
                const normalized = raw.trim().toLowerCase();
                return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
            }
            if (typeof raw === 'number') {
                return raw === 1;
            }
            if (typeof raw === 'boolean') {
                return raw;
            }
            return Boolean(CONFIG.peachpay_is_advanced_logging_enabled);
        } catch (error) {
            // Logging guard must never break checkout behavior.
            return false;
        }
    }

    /**
     * Safely prepare debug context for console logging.
     * Guards against circular references and exotic values.
     *
     * @param {*} context
     * @returns {*}
     */
    function getSafeDebugContext(context) {
        if (typeof context === 'undefined') {
            return undefined;
        }
        if (context === null) {
            return null;
        }
        const contextType = typeof context;
        if (contextType === 'string' || contextType === 'number' || contextType === 'boolean') {
            return context;
        }
        if (context instanceof Error) {
            return {
                name: context.name,
                message: context.message,
                stack: context.stack
            };
        }
        try {
            return JSON.parse(JSON.stringify(context));
        } catch (error) {
            return {
                note: 'Unserializable debug context',
                contextType: Object.prototype.toString.call(context)
            };
        }
    }

    /**
     * Print debug logs only when advanced logging is enabled.
     *
     * @param {string} message
     * @param {Object=} context
     */
    function debugLog(message, context) {
        try {
            if (!isAdvancedLoggingEnabled()) {
                return;
            }
            const safeMessage = String(message || '');
            if (typeof context === 'undefined') {
                console.log('[ConvesioPay][ClassicV2]', safeMessage);
                return;
            }
            console.log('[ConvesioPay][ClassicV2]', safeMessage, getSafeDebugContext(context));
        } catch (error) {
            // Debug logging must be no-op on any unexpected runtime issue.
        }
    }

    /**
     * Defer Google Pay session refreshes while the PaymentRequest sheet is opening/open.
     *
     * @param {string=} source
     */
    function lockGooglePayUiRefresh(source) {
        state.googlePayUiLockUntilMs = Date.now() + 15000;
        debugLog('Google Pay UI refresh lock set', {
            source: source || 'unspecified',
            lockedUntil: state.googlePayUiLockUntilMs
        });
    }

    /**
     * Check whether Google Pay session refresh should be deferred.
     *
     * @returns {boolean}
     */
    function isGooglePayUiRefreshLocked() {
        return state.googlePayUiLockUntilMs > Date.now();
    }

    /**
     * Determine if BTCPay/crypto method is enabled in configuration.
     *
     * @returns {boolean}
     */
    function isBtcpayEnabled() {
        const activeMethods = getConfig().active_methods || [];
        if (!Array.isArray(activeMethods) || activeMethods.length === 0) {
            return false;
        }
        return activeMethods.includes('crypto') || activeMethods.includes('btcpay');
    }

    /**
     * Determine if Apple Pay is enabled in configuration.
     *
     * @returns {boolean}
     */
    function isApplePayEnabled() {
        const activeMethods = getConfig().active_methods || [];
        if (!Array.isArray(activeMethods) || activeMethods.length === 0) {
            return false;
        }
        return activeMethods.includes('applepay') || activeMethods.includes('applePay');
    }

    /**
     * Determine if Google Pay is enabled in configuration.
     *
     * @returns {boolean}
     */
    function isGooglePayEnabled() {
        const activeMethods = getConfig().active_methods || [];
        if (!Array.isArray(activeMethods) || activeMethods.length === 0) {
            return false;
        }
        return activeMethods.includes('googlepay') || activeMethods.includes('googlePay') || activeMethods.includes('google-pay');
    }

    /**
     * Determine if Card is enabled in configuration.
     *
     * Backward compatibility:
     * if active_methods is missing/empty, assume card is enabled.
     *
     * @returns {boolean}
     */
    function isCardEnabled() {
        const activeMethods = getConfig().active_methods || [];
        if (!Array.isArray(activeMethods) || activeMethods.length === 0) {
            return true;
        }
        return activeMethods.includes('card');
    }

    /**
     * Validate whether a given internal method is currently enabled.
     *
     * @param {string} method
     * @returns {boolean}
     */
    function isMethodEnabled(method) {
        const normalized = String(method || '').toLowerCase();
        if (normalized === 'applepay') {
            return isApplePayEnabled();
        }
        if (normalized === 'googlepay') {
            return isGooglePayEnabled();
        }
        if (normalized === 'btcpay') {
            return isBtcpayEnabled();
        }
        if (normalized === 'card') {
            return isCardEnabled();
        }
        return false;
    }

    /**
     * Get native checkout disabled method flags for the ConvesioPay SDK.
     *
     * @returns {{cards: boolean, applePay: boolean, googlePay: boolean}}
     */
    function getDisabledPaymentMethodsConfig() {
        const disabled = getConfig().disabled_payment_methods || {};
        return {
            cards: Boolean(disabled.cards),
            applePay: Boolean(typeof disabled.applePay !== 'undefined' ? disabled.applePay : disabled.applepay),
            googlePay: Boolean(typeof disabled.googlePay !== 'undefined' ? disabled.googlePay : disabled.googlepay)
        };
    }

    /**
     * Get/create persistent mount node reference used by SDK.
     *
     * @returns {HTMLElement}
     */
    function getOrCreateMountNode() {
        // If the mount node is not created, create it.
        if (!state.mountNode) {
            // Create the mount node.
            const node = document.createElement('div');
            node.id = CONFIG.mountNodeId;
            // // Set the style of the mount node to avoid layout shifts.
            // node.style.minHeight = '300px';
            // node.style.minWidth = '100%';
            // // Set the width of the mount node to 100%.
            // node.style.width = '100%';
            // Set the mount node to the state.
            state.mountNode = node;
        }
        // Return the mount node.
        return state.mountNode;
    }

    /**
     * Attach mount node to current unified container.
     *
     * @returns {boolean}
     */
    function attachMountNodeToContainer() {
        const container = document.getElementById(CONFIG.containerId);
        if (!container) {
            return false;
        }
        const mountNode = getOrCreateMountNode();
        if (mountNode.parentNode !== container) {
            if (mountNode.parentNode) {
                mountNode.parentNode.removeChild(mountNode);
            }
            container.innerHTML = '';
            container.appendChild(mountNode);
        }
        return true;
    }

    /**
     * Parse a price string (with optional locale separators) to integer cents.
     *
     * @param {string} orderTotal - Raw price text (e.g. "$1,234.56").
     * @returns {number} Amount in cents, or 0 if unparseable.
     */
    function parsePriceToCents(orderTotal) {
        try {
            if (!orderTotal || typeof orderTotal !== 'string') {
                return 0;
            }
            const decimalSep = String(window.wc_checkout_params?.currency_format_decimal_sep || '.');
            const thousandSep = String(window.wc_checkout_params?.currency_format_thousand_sep || ',');
            const raw = String(orderTotal).replace(/[^\d.,-]/g, '').trim();
            if (!raw) {
                return 0;
            }
            let normalized = raw;
            if (thousandSep) {
                const escapedThousand = thousandSep.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
                normalized = normalized.replace(new RegExp(escapedThousand, 'g'), '');
            }
            if (decimalSep && decimalSep !== '.') {
                const escapedDecimal = decimalSep.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
                normalized = normalized.replace(new RegExp(escapedDecimal, 'g'), '.');
            }
            if (!/^-?\d+(\.\d+)?$/.test(normalized)) {
                const lastDot = normalized.lastIndexOf('.');
                const lastComma = normalized.lastIndexOf(',');
                const splitAt = Math.max(lastDot, lastComma);
                if (splitAt >= 0) {
                    const integerPart = normalized.slice(0, splitAt).replace(/[.,]/g, '');
                    const decimalPart = normalized.slice(splitAt + 1).replace(/[.,]/g, '');
                    normalized = integerPart + (decimalPart ? '.' + decimalPart : '');
                } else {
                    normalized = normalized.replace(/[.,]/g, '');
                }
            }
            const amount = parseFloat(normalized);
            if (Number.isNaN(amount)) {
                return 0;
            }
            return Math.round(amount * 100);
        } catch (e) {
            debugLog('[ConvesioPay Classic] Error parsing price to cents', { error: e });
            return 0;
        }
    }

    /**
     * Get current rendered order total as integer cents.
     * Tries DOM (WooCommerce and FunnelKit selectors) then backend cart_total
     * so Apple Pay/BTCPay sessions work when the review table is missing or delayed.
     *
     * @returns {number}
     */
    function getCurrentCartTotalCents() {
        const orderPayData = getConfig().order_pay || {};
        if (isOrderPayContext() && orderPayData && orderPayData.order_id) {
            const serverTotal = Number(orderPayData.total || 0);
            if (Number.isFinite(serverTotal) && serverTotal > 0) {
                return Math.round(serverTotal * 100);
            }
        }

        const primarySelector = '.woocommerce-checkout-review-order-table .order-total .amount';
        const $primaryAmounts = $(primarySelector);
        if ($primaryAmounts.length > 1) {
            debugLog('[ConvesioPay Classic] Multiple primary total amount nodes detected; using first match', {
                count: $primaryAmounts.length
            });
        }
        let cents = parsePriceToCents($primaryAmounts.first().text());
        if (cents > 0) {
            return cents;
        }
        const fallbackSelectors = [
            '.shop_table .order-total .amount',
            '.fkwcs-place-order-total .amount',
            '.fkwcs-place-order-total',
            '.cart-subtotal .amount',
            '.order_total .amount',
            '.order-total .amount'
        ];
        for (let i = 0; i < fallbackSelectors.length; i++) {
            const text = $(fallbackSelectors[i]).first().text();
            cents = parsePriceToCents(text);
            if (cents > 0) {
                return cents;
            }
        }
        const configTotal = getConfig().cart_total != null ? getConfig().cart_total : (window.convesiopayCartTotal != null ? window.convesiopayCartTotal : 0);
        return Math.round(Number(configTotal) * 100);
    }

    /**
     * Retrieve the checkout order identifier (real or temporary).
     *
     * Determines the appropriate order reference to use during checkout.
     * The function follows a fallback strategy:
     *
     * 1. If on the order-received page, return the real WooCommerce order ID
     *    from the URL query parameter (?order=).
     * 2. If on the checkout page before order creation, return the temporary
     *    session ID provided by backend configuration.
     * 3. If no temporary ID is available, generate a runtime fallback
     *    temporary identifier.
     *
     * This ensures that every payment attempt has a unique and traceable
     * reference even before WooCommerce creates the real order.
     *
     * @returns {string} Order identifier (real order ID or temporary session ID).
     */
    function getOrderNumber() {
        const orderPayData = getConfig().order_pay || {};
        if (isOrderPayContext() && orderPayData && orderPayData.order_id) {
            return String(orderPayData.order_id);
        }

        const urlParams = new URLSearchParams(window.location.search);
        const orderId = urlParams.get('order');
        if (orderId) {
            return orderId;
        }
        const tempSessionId = getConfig().temp_session_id;
        if (tempSessionId) {
            return tempSessionId;
        }
        return 'TEMP-' + Date.now() + '-' + Math.floor(Math.random() * 10000);
    }

    /**
     * Collect and validate checkout order data.
     *
     * Retrieves:
     * - Order amount
     * - Currency
     * - Order number (real or temporary)
     *
     * Ensures required values exist before allowing
     * token processing or checkout submission.
     *
     * @returns {Object} Order data object.
     * @returns {boolean} returns.isValid - Whether required fields are present.
     * @returns {number} returns.amount - Order total amount.
     * @returns {string} returns.currency - Order currency code.
     * @returns {string} returns.orderNumber - Order reference.
     */
    function getOrderData() {
        const orderPayData = getConfig().order_pay || {};
        const amount = getCurrentCartTotalCents();
        let first = String($('#billing_first_name').val() || '').trim();
        let last = String($('#billing_last_name').val() || '').trim();
        let email = String($('#billing_email').val() || '').trim();
        let currency = (getConfig().currency && String(getConfig().currency).trim()) || 'USD';

        if (isOrderPayContext() && orderPayData && orderPayData.order_id) {
            first = first || String(orderPayData.billing_first_name || '').trim();
            last = last || String(orderPayData.billing_last_name || '').trim();
            email = email || String(orderPayData.billing_email || '').trim();
            currency = String(orderPayData.currency || currency || 'USD').trim();
        }

        return {
            isValid: amount > 0,
            orderNumber: getOrderNumber(),
            amount: amount,
            currency: currency,
            email: email,
            name: (first + ' ' + last).trim() || 'Customer'
        };
    }

    /**
     * Build a compact amount/currency signature for runtime update guards.
     *
     * @param {Object=} orderData
     * @returns {string}
     */
    function getAmountCurrencySignature(orderData) {
        const data = orderData || getOrderData();
        const amount = Number.isFinite(Number(data?.amount)) ? String(Math.round(Number(data.amount))) : '0';
        const currency = String(data?.currency || '').trim().toUpperCase();
        return amount + '|' + currency;
    }

    /**
     * Update component transaction amount/currency without remounting.
     * Uses SDK updateAmount() when available, otherwise returns unsupported.
     *
     * @param {string} reason
     * @param {boolean=} force
     * @returns {Promise<{supported: boolean, ok: boolean, skipped: boolean, signature: string}>}
     */
    async function applyRuntimeAmountUpdate(reason, force) {
        const shouldForce = Boolean(force);
        if (!state.component || typeof state.component.updateAmount !== 'function') {
            console.error('[ConvesioPay Classic v2] component.updateAmount not found');
            return { supported: false, ok: false, skipped: false, signature: '' };
        }

        const orderData = getOrderData();
        if (!orderData.isValid) {
            debugLog('[ConvesioPay Classic] Skipping runtime amount update: invalid order data', {
                reason: reason || 'unspecified',
                amount: orderData.amount,
                currency: orderData.currency
            });
            console.error('[ConvesioPay Classic v2] Skipping runtime amount update: invalid order data');
            return { supported: true, ok: false, skipped: false, signature: '' };
        }

        const signature = getAmountCurrencySignature(orderData);
        if (!shouldForce && signature && signature === state.lastKnownAmountCurrencySignature) {
            return { supported: true, ok: true, skipped: true, signature: signature };
        }

        try {
            // Update the amount/currency in-place using the component.updateAmount method to charge accurate amounts by cards/Apple Pay/BtcPay
            await state.component.updateAmount({
                amount: orderData.amount,
                currency: orderData.currency
            });

            state.lastKnownAmountCurrencySignature = signature;
            state.lastKnownCartTotal = orderData.amount;
            debugLog('[ConvesioPay Classic] Runtime amount updated via updateAmount', {
                reason: reason || 'unspecified',
                amount: orderData.amount,
                currency: orderData.currency,
                signature: signature
            });
            return { supported: true, ok: true, skipped: false, signature: signature };
        } catch (objectCallError) {
            try {
                // Update the amount/currency in-place using the component.updateAmount method to charge accurate amounts by cards/Apple Pay/BtcPay
                await state.component.updateAmount(orderData.amount, orderData.currency);
                
                state.lastKnownAmountCurrencySignature = signature;
                state.lastKnownCartTotal = orderData.amount;
                debugLog('[ConvesioPay Classic] Runtime amount updated via updateAmount(args)', {
                    reason: reason || 'unspecified',
                    amount: orderData.amount,
                    currency: orderData.currency,
                    signature: signature
                });
                return { supported: true, ok: true, skipped: false, signature: signature };
            } catch (argsCallError) {
                debugLog('[ConvesioPay Classic] Runtime amount update failed; falling back to session rebuild', {
                    reason: reason || 'unspecified',
                    amount: orderData.amount,
                    currency: orderData.currency,
                    objectCallError: objectCallError,
                    argsCallError: argsCallError
                });
                return { supported: true, ok: false, skipped: false, signature: signature };
            }
        }
    }

    /**
     * Update component customer email without remounting.
     * Uses SDK updateEmail() when available. Only updates when email value actually changes.
     *
     * @param {string=} newEmail - Optional email; if omitted, reads from #billing_email.
     * @returns {{ supported: boolean, ok: boolean, skipped: boolean }}
     */
    function applyRuntimeEmailUpdate(newEmail) {
        if (!state.component || typeof state.component.updateEmail !== 'function') {
            return { supported: false, ok: false, skipped: false };
        }
        const email = String(
            (newEmail !== undefined && newEmail !== null ? newEmail : ($('#billing_email').val() || ''))
        ).trim();
        if (email === state.lastKnownCustomerEmail) {
            return { supported: true, ok: true, skipped: true };
        }
        try {
            state.component.updateEmail(email);
            state.lastKnownCustomerEmail = email;
            debugLog('[ConvesioPay Classic] Runtime email updated via updateEmail', { email: email });
            return { supported: true, ok: true, skipped: false };
        } catch (error) {
            debugLog('[ConvesioPay Classic] Runtime email update failed', { email: email, error: error });
            return { supported: true, ok: false, skipped: false };
        }
    }

    /**
     * Detect whether Apple Pay session error indicates readiness race.
     *
     * @param {*} error
     * @returns {boolean}
     */
    function isApplePaySessionNotReadyError(error) {
        const message = String(error?.message || error || '').toLowerCase();
        return (
            message.includes('createapplepaysession') ||
            message.includes('not initialized') ||
            message.includes('not available')
        );
    }

    /**
     * Create Apple Pay session with bounded retries for SDK readiness races.
     *
     * @param {Object} params
     * @param {Object=} options
     * @returns {Promise<*>}
     */
    async function createApplePaySessionWithRetry(params, options) {
        const maxAttempts = Number(options?.maxAttempts || 3);
        const delayMs = Number(options?.delayMs || 450);
        let attempt = 0;
        let lastError = null;
        while (attempt < maxAttempts) {
            try {
                return await state.component.createApplePaySession(params);
            } catch (error) {
                lastError = error;
                const isRetryable = isApplePaySessionNotReadyError(error);
                if (!isRetryable || attempt >= (maxAttempts - 1)) {
                    throw error;
                }
                await new Promise(function (resolve) {
                    setTimeout(resolve, delayMs);
                });
            }
            attempt += 1;
        }
        throw lastError || new Error('Apple Pay session creation failed');
    }

    /**
     * Detect retryable Google Pay session readiness failures.
     *
     * @param {*} error
     * @returns {boolean}
     */
    function isGooglePaySessionNotReadyError(error) {
        const message = String(error?.message || error || '').toLowerCase();
        return (
            message.includes('creategooglepaysession') ||
            message.includes('not initialized') ||
            message.includes('not available') ||
            message.includes('required to use this method')
        );
    }

    /**
     * Create Google Pay session with bounded retries for SDK readiness races.
     *
     * @param {Object} params
     * @param {Object=} options
     * @returns {Promise<*>}
     */
    async function createGooglePaySessionWithRetry(params, options) {
        const maxAttempts = Number(options?.maxAttempts || 3);
        const delayMs = Number(options?.delayMs || 450);
        let attempt = 0;
        let lastError = null;
        while (attempt < maxAttempts) {
            try {
                return await state.component.createGooglePaySession(params);
            } catch (error) {
                lastError = error;
                const isRetryable = isGooglePaySessionNotReadyError(error);
                if (!isRetryable || attempt >= (maxAttempts - 1)) {
                    throw error;
                }
                await new Promise(function (resolve) {
                    setTimeout(resolve, delayMs);
                });
            }
            attempt += 1;
        }
        throw lastError || new Error('Google Pay session creation failed');
    }

    /**
     * Clear stale Apple Pay authorization when amount/currency signature changes.
     *
     * @param {string} reason
     * @param {string=} nextSignature
     */
    function clearApplePayAuthorizationIfSet(reason, nextSignature) {
        const method = String(state.paymentConfirmedData?.paymentMethod || '').toLowerCase();
        if (method !== 'applepay') {
            return;
        }
        debugLog('[ConvesioPay Classic] Clearing stale Apple Pay authorization', {
            reason: reason || 'unspecified',
            previousSignature: state.lastKnownAmountCurrencySignature,
            nextSignature: nextSignature || ''
        });
        state.paymentConfirmedData = null;
        state.currentMethod = resolveMethod();
        window.convesiopayPaymentToken = null;
        window.convesiopayApplePayPaymentData = null;
    }

    /**
     * Clear stale Google Pay authorization when amount/currency signature changes.
     *
     * @param {string} reason
     * @param {string=} nextSignature
     */
    function clearGooglePayAuthorizationIfSet(reason, nextSignature) {
        const method = String(state.paymentConfirmedData?.paymentMethod || '').toLowerCase();
        if (method !== 'googlepay') {
            return;
        }
        debugLog('[ConvesioPay Classic] Clearing stale Google Pay authorization', {
            reason: reason || 'unspecified',
            previousSignature: state.lastKnownAmountCurrencySignature,
            nextSignature: nextSignature || ''
        });
        state.paymentConfirmedData = null;
        state.currentMethod = resolveMethod();
        window.convesiopayPaymentToken = null;
        window.convesiopayGooglePayPaymentData = null;
    }

    /**
     * Ensure a stable transaction ID exists for current checkout lifecycle.
     *
     * @returns {string}
     */
    function getOrCreateTransactionId() {
        if (window.peachpayTransactionId) {
            return window.peachpayTransactionId;
        }
        window.peachpayTransactionId = 'convesio-tx-' + Date.now() + '-' + Math.random().toString(36).substring(2, 11);
        return window.peachpayTransactionId;
    }

    /**
     * Write/replace hidden field on checkout form.
     *
     * @param {string} name
     * @param {string} value
     */
    function addHiddenField(name, value) {
        const $form = getCheckoutForm();
        if (!$form.length) {
            return;
        }
        const selector = 'input[name="' + name + '"]';
        const $existing = $form.find(selector);
        if ($existing.length) {
            if ($existing.val() !== String(value)) {
                $existing.val(value);
            }
            return;
        }

        $('<input>').attr('type', 'hidden').attr('name', name).attr('value', value).prependTo($form);
    }

    /**
     * Persist the generated payment token in internal state.
     *
     * This allows the token to be reused during checkout submission
     * without regenerating it.
     *
     * @param {string} token - Secure payment token from SDK.
     * @returns {void}
     */
    function storePaymentToken(token) {
        if (!token) {
            return;
        }
        window.convesiopayPaymentToken = token;
        addHiddenField('convesiopay_payment_token', token); // Add the payment token to the hidden field.
    }

    /**
     * Build non-sensitive token debug metadata.
     *
     * @param {string} token
     * @returns {Object}
     */
    function getTokenDebugMetadata(token) {
        const normalizedToken = String(token || '').trim();
        return {
            tokenPrefix: normalizedToken.substring(0, 8),
            tokenLength: normalizedToken.length
        };
    }

    /**
     * Check whether card confirmation data belongs to the current amount/currency context.
     *
     * @param {Object|null} paymentData
     * @param {Object} orderData
     * @returns {boolean}
     */
    function isMatchingCardPaymentContext(paymentData, orderData) {
        if (!paymentData || String(paymentData.paymentMethod || '').toLowerCase() !== 'card') {
            return false;
        }

        const token = String(paymentData.token || '').trim();
        const paymentAmount = Number(paymentData.amount);
        const orderAmount = Number(orderData?.amount);
        const paymentCurrency = String(paymentData.currency || '').trim().toUpperCase();
        const orderCurrency = String(orderData?.currency || '').trim().toUpperCase();

        return Boolean(
            token &&
            Number.isFinite(paymentAmount) &&
            Number.isFinite(orderAmount) &&
            Math.round(paymentAmount) === Math.round(orderAmount) &&
            paymentCurrency &&
            paymentCurrency === orderCurrency
        );
    }

    /**
     * Remove stale card token state when amount/currency no longer matches.
     *
     * @param {string} reason
     * @param {Object=} orderData
     * @returns {void}
     */
    function clearStaleCardPaymentToken(reason, orderData) {
        const confirmedMethod = String(state.paymentConfirmedData?.paymentMethod || '').toLowerCase();
        const currentToken = String(window.convesiopayPaymentToken || '').trim();
        const shouldClearGlobal = confirmedMethod === 'card' || resolveMethod() === 'card';

        if (confirmedMethod === 'card') {
            debugLog('[ConvesioPay Classic] Clearing stale card token context', Object.assign({
                reason: reason || 'unspecified',
                previousAmount: state.paymentConfirmedData?.amount || '',
                previousCurrency: state.paymentConfirmedData?.currency || '',
                nextAmount: orderData?.amount || '',
                nextCurrency: orderData?.currency || '',
                source: 'paymentConfirmedData'
            }, getTokenDebugMetadata(state.paymentConfirmedData?.token)));
            state.paymentConfirmedData = null;
        }

        if (shouldClearGlobal) {
            window.convesiopayPaymentToken = null;
            getCheckoutForm().find('input[name="convesiopay_payment_token"]').remove();
            if (currentToken) {
                debugLog('[ConvesioPay Classic] Cleared card token global cache', Object.assign({
                    reason: reason || 'unspecified',
                    source: 'window.convesiopayPaymentToken'
                }, getTokenDebugMetadata(currentToken)));
            }
        }
    }

    /**
     * Resolve a reusable card token only when it is tied to the current amount/currency.
     *
     * @param {Object} orderData
     * @returns {{ token: string, source: string }}
     */
    function getReusableCardToken(orderData) {
        if (isMatchingCardPaymentContext(state.paymentConfirmedData, orderData)) {
            const stateToken = String(state.paymentConfirmedData.token || '').trim();
            debugLog('[ConvesioPay Classic] Card token context match', Object.assign({
                amount: orderData.amount,
                currency: orderData.currency,
                source: 'paymentConfirmedData'
            }, getTokenDebugMetadata(stateToken)));
            return { token: stateToken, source: 'paymentConfirmedData' };
        }

        if (String(state.paymentConfirmedData?.paymentMethod || '').toLowerCase() === 'card') {
            clearStaleCardPaymentToken('card_token_context_mismatch', orderData);
        }

        const globalToken = String(window.convesiopayPaymentToken || '').trim();
        if (globalToken) {
            debugLog('[ConvesioPay Classic] Ignoring unbacked card token global cache', Object.assign({
                amount: orderData.amount,
                currency: orderData.currency,
                source: 'window.convesiopayPaymentToken'
            }, getTokenDebugMetadata(globalToken)));
            clearStaleCardPaymentToken('unbacked_card_token_global_cache', orderData);
        }

        return { token: '', source: '' };
    }

    /**
     * Persist non-sensitive token invalidation metadata for the next checkout submit.
     *
     * @param {string} reason
     * @param {string} token
     * @returns {void}
     */
    function addTokenInvalidationMetadata(reason, token) {
        addHiddenField('convesiopay_token_invalidated_reason', String(reason || ''));
        addHiddenField('convesiopay_token_invalidated_at', String(Date.now()));
        addHiddenField('convesiopay_previous_token_prefix', String(token || '').trim().substring(0, 8));
    }

    /**
     * Read token invalidation metadata that should travel with payment_method_data.
     *
     * @returns {Object}
     */
    function getTokenInvalidationMetadata() {
        const $form = getCheckoutForm();
        const reason = String($form.find('input[name="convesiopay_token_invalidated_reason"]').val() || '').trim();
        if (!reason) {
            return {};
        }

        return {
            convesiopay_token_invalidated_reason: reason,
            convesiopay_token_invalidated_at: String($form.find('input[name="convesiopay_token_invalidated_at"]').val() || '').trim(),
            convesiopay_previous_token_prefix: String($form.find('input[name="convesiopay_previous_token_prefix"]').val() || '').trim().substring(0, 8)
        };
    }

    /**
     * Remove one-shot token invalidation metadata after it is copied to payment_method_data.
     *
     * @returns {void}
     */
    function clearTokenInvalidationMetadata() {
        getCheckoutForm().find('input[name="convesiopay_token_invalidated_reason"], input[name="convesiopay_token_invalidated_at"], input[name="convesiopay_previous_token_prefix"]').remove();
    }

    /**
     * Clear card token data after Woo rejects the payment request as invalid.
     *
     * @param {string=} reason
     * @returns {void}
     */
    function clearCardPaymentTokenAfterInvalidRequest(reason) {
        const confirmedMethod = String(state.paymentConfirmedData?.paymentMethod || '').toLowerCase();
        if (confirmedMethod === 'card') {
            state.paymentConfirmedData = null;
        }
        const currentToken = String(window.convesiopayPaymentToken || '').trim();
        if (reason) {
            addTokenInvalidationMetadata(reason, currentToken);
        }
        window.convesiopayPaymentToken = null;
        const $form = getCheckoutForm();
        $form.find('input[name="convesiopay_payment_token"]').remove();
        $form.find('input[name="payment_method_data"]').each(function () {
            const value = String($(this).val() || '');
            let removeField = false;
            try {
                const payload = JSON.parse(value);
                const payloadMethod = String(payload?.paymentMethod || '').toLowerCase();
                const payloadToken = String(payload?.convesiopay_payment_token || '').trim();
                removeField = payloadMethod === 'card' || (currentToken && payloadToken === currentToken);
            } catch (_error) {
                removeField = value.indexOf('"paymentMethod":"card"') !== -1 || (currentToken && value.indexOf(currentToken) !== -1);
            }
            if (removeField) {
                $(this).remove();
            }
        });
        if (reason) {
            debugLog('[ConvesioPay Classic] Cleared card token data after invalid payment request', {
                reason: reason
            });
        }
    }

    const RETRYABLE_CARD_TOKEN_USER_MESSAGE = 'We couldn’t validate your card details. Please review your card information and try again.';

    /**
     * Check whether plain text contains Woo's technical invalid payment request error.
     *
     * @param {string} message
     * @returns {boolean}
     */
    function hasInvalidPaymentRequest503Text(message) {
        return String(message || '').indexOf('ConvesioPay API error (HTTP 503)') !== -1 && String(message || '').indexOf('Invalid payment request') !== -1;
    }

    /**
     * Check whether plain text contains ConvesioPay's generic failed card payment status.
     *
     * @param {string} message
     * @returns {boolean}
     */
    function hasPaymentStatusErrorText(message) {
        return String(message || '').indexOf('Payment failed with status: Error') !== -1;
    }

    /**
     * Check whether checkout is currently using ConvesioPay card.
     *
     * @returns {boolean}
     */
    function isConvesioPayCardCheckoutActive() {
        const $form = getCheckoutForm();
        const $paymentMethods = $form.find('input[name="payment_method"]');
        const selectedGateway = String($paymentMethods.filter(':checked').val() || ($paymentMethods.length === 1 ? $paymentMethods.val() : '') || '').trim();
        const selectedMethod = resolveMethod();

        return selectedGateway === CONFIG.gatewayId && selectedMethod === 'card';
    }

    /**
     * Detect retryable Woo checkout errors caused by a ConvesioPay card payment token.
     *
     * @param {*} errorMessage
     * @returns {string}
     */
    function getRetryableCardTokenInvalidationReason(errorMessage) {
        if (!isConvesioPayCardCheckoutActive()) {
            return '';
        }

        const message = $('<div>').html(String(errorMessage || '')).text()
            || getCheckoutForm().find('.woocommerce-NoticeGroup-checkout, .woocommerce-error').text();

        if (hasInvalidPaymentRequest503Text(message)) {
            return 'checkout_error_http_503_invalid_payment_request';
        }
        if (hasPaymentStatusErrorText(message)) {
            return 'checkout_error_payment_status_error';
        }
        return '';
    }

    /**
     * Check whether plain text contains a retryable ConvesioPay card token error.
     *
     * @param {string} message
     * @returns {boolean}
     */
    function hasRetryableCardTokenErrorText(message) {
        return hasInvalidPaymentRequest503Text(message) || hasPaymentStatusErrorText(message);
    }

    /**
     * Replace technical retryable card-token checkout notices with a user-friendly message.
     *
     * @param {*} errorMessage
     * @returns {void}
     */
    function replaceRetryableCardTokenNotice(errorMessage) {
        const eventMessage = $('<div>').html(String(errorMessage || '')).text();
        const eventHasRetryableCardTokenError = hasRetryableCardTokenErrorText(eventMessage);

        const replaceNoticeText = function () {
            const $form = getCheckoutForm();
            if (!$form.length) {
                return;
            }

            const $errorNotices = $form.find('.woocommerce-NoticeGroup-checkout .woocommerce-error, .woocommerce-error');
            const $listItems = $errorNotices.find('li');
            let replaced = false;

            $listItems.each(function () {
                if (hasRetryableCardTokenErrorText(this.textContent || '')) {
                    this.textContent = RETRYABLE_CARD_TOKEN_USER_MESSAGE;
                    replaced = true;
                }
            });

            if (!replaced && eventHasRetryableCardTokenError && $listItems.length === 1) {
                $listItems.get(0).textContent = RETRYABLE_CARD_TOKEN_USER_MESSAGE;
                replaced = true;
            }

            if (replaced) {
                return;
            }

            $errorNotices.each(function () {
                const $notice = $(this);
                if ($notice.find('li').length) {
                    return;
                }
                if (hasRetryableCardTokenErrorText(this.textContent || '') || (eventHasRetryableCardTokenError && $errorNotices.length === 1)) {
                    this.textContent = RETRYABLE_CARD_TOKEN_USER_MESSAGE;
                }
            });
        };

        replaceNoticeText();
        setTimeout(replaceNoticeText, 0);
    }

    /**
     * Inject payment data into the WooCommerce checkout form.
     *
     * Adds hidden input fields containing:
     * - Payment token
     * - Order number
     * - Payment method
     * - Any additional metadata required by backend
     *
     * Ensures the server receives all necessary data
     * during form submission.
     *
     * @returns {void}
     */
    function addPaymentDataToForm() {
        const txId = getOrCreateTransactionId(); // Get the transaction id.
        addHiddenField('peachpay_transaction_id', txId); // Add the transaction id to the hidden field.

        const method = state.paymentConfirmedData?.paymentMethod || state.currentMethod || CONFIG.defaultMethod;
        const payload = {
            peachpay_transaction_id: String(txId), // Add the transaction id to the payload.
            payment_method_type: String(method) // Add the payment method type to the payload.
        };

        if (state.paymentConfirmedData) {
            payload.paymentMethod = String(state.paymentConfirmedData.paymentMethod || ''); // Add the payment method to the payload.
            payload.status = String(state.paymentConfirmedData.status || ''); // Add the status to the payload.
            payload.paymentId = String(state.paymentConfirmedData.paymentId || ''); // Add the payment id to the payload.
            if (state.paymentConfirmedData.token) {
                payload.convesiopay_payment_token = String(state.paymentConfirmedData.token); // Add the payment token to the payload.
                storePaymentToken(state.paymentConfirmedData.token); // Store the payment token.
            }
            if (method === 'applepay') {
                addHiddenField('applepay_amount', String(state.paymentConfirmedData.amount || ''));
                addHiddenField('applepay_currency', String(state.paymentConfirmedData.currency || ''));
            } else if (method === 'googlepay') {
                payload.googlepay_amount = String(state.paymentConfirmedData.amount || '');
                payload.googlepay_currency = String(state.paymentConfirmedData.currency || '');
                addHiddenField('googlepay_amount', String(state.paymentConfirmedData.amount || ''));
                addHiddenField('googlepay_currency', String(state.paymentConfirmedData.currency || ''));
            }
        } else if (window.convesiopayPaymentToken && resolveMethod() !== 'card') {
            payload.convesiopay_payment_token = String(window.convesiopayPaymentToken);
            storePaymentToken(window.convesiopayPaymentToken);
        }

        const tokenInvalidationMetadata = getTokenInvalidationMetadata();
        Object.assign(payload, tokenInvalidationMetadata);

        addHiddenField('payment_method_data', JSON.stringify(payload)); // Add the payment method data to the hidden field.
        addHiddenField(CONFIG.selectedMethodField, resolveMethod()); // Add the selected method field to the hidden field.

        if (tokenInvalidationMetadata.convesiopay_token_invalidated_reason) {
            clearTokenInvalidationMetadata();
        }
    }

    /**
     * Enable checkout submit button.
     */
    function enableSubmitButton() {
        getCheckoutForm().find('#place_order').prop('disabled', false);
    }

    /**
     * Disable checkout submit button.
     */
    function disableSubmitButton() {
        getCheckoutForm().find('#place_order').prop('disabled', true);
    }

    /**
     * Determine whether mounted component appears available for checkout.
     *
     * @returns {boolean}
     */
    function hasPaymentMethodsAvailable() {
        if (!state.mounted || !state.component || !isUnifiedGatewaySelected()) {
            return false;
        }
        const container = document.getElementById(CONFIG.containerId);
        return Boolean(container && container.innerHTML && container.innerHTML.trim().length > 50);
    }

    /**
     * Update the checkout submit button state.
     *
     * Enables or disables the checkout button based on:
     * - Payment method selection
     * - Token availability
     * - Component validation state
     *
     * Prevents order submission unless payment is ready.
     *
     * @returns {void}
     */
    function updateSubmitButtonState() {
        const selected = getSelectedGatewayId();
        if (!selected) {
            return;
        }
        if (selected !== CONFIG.gatewayId) {
            enableSubmitButton();
            return;
        }
        if (hasPaymentMethodsAvailable()) {
            enableSubmitButton();
        } else {
            disableSubmitButton();
        }
    }

    /**
     * Get selected Woo gateway id from radio group.
     *
     * @returns {string}
     */
    function getSelectedGatewayId() {
        return $(CONFIG.paymentMethodSelector + ':checked').val() || '';
    }

    /**
     * Check whether unified gateway is currently selected.
     *
     * @returns {boolean}
     */
    function isUnifiedGatewaySelected() {
        return getSelectedGatewayId() === CONFIG.gatewayId;
    }

    /**
     * Resolve AJAX URL for session sync calls.
     *
     * @returns {string}
     */
    function getAjaxUrl() {
        return window.peachpay_convesiopay_unified_data?.ajax_url || window.wc_checkout_params?.ajax_url || '/wp-admin/admin-ajax.php';
    }

    /**
     * Ensure hidden method field exists and matches provided value.
     *
     * @param {string} method
     */
    function setSelectedMethodField(method) {
        const $form = getCheckoutForm();
        if (!$form.length) {
            return;
        }

        const selector = 'input[name="' + CONFIG.selectedMethodField + '"]';
        const $existing = $form.find(selector);
        if ($existing.length) {
            $existing.val(method);
            return;
        }

        $('<input>')
            .attr('type', 'hidden')
            .attr('name', CONFIG.selectedMethodField)
            .val(method)
            .prependTo($form);
    }

    /**
     * Resolve internal method from runtime + hidden field fallback.
     *
     * @returns {'card'|'applepay'|'googlepay'|'btcpay'}
     */
    function resolveMethod() {
        const runtime = String(state.currentMethod || '').toLowerCase();
        if (CONFIG.allowedMethods.includes(runtime) && isMethodEnabled(runtime)) {
            return runtime;
        }

        const hidden = String($('input[name="' + CONFIG.selectedMethodField + '"]').val() || '').toLowerCase();
        if (CONFIG.allowedMethods.includes(hidden) && isMethodEnabled(hidden)) {
            return hidden;
        }

        const confirmed = String(state.paymentConfirmedData?.paymentMethod || '').toLowerCase();
        if (CONFIG.allowedMethods.includes(confirmed) && isMethodEnabled(confirmed)) {
            return confirmed;
        }

        if (isMethodEnabled(CONFIG.defaultMethod)) {
            return CONFIG.defaultMethod;
        }
        return 'card';
    }

    /**
     * On Classic page load, normalize initial method to card to match
     * component default behavior and prevent stale session wallet selection.
     */
    function normalizeInitialMethodToCard() {
        if (!isUnifiedGatewaySelected()) {
            return;
        }
        let initialMethod = '';
        if (isCardEnabled()) {
            initialMethod = 'card';
        } else if (isApplePayEnabled()) {
            initialMethod = 'applepay';
        } else if (isGooglePayEnabled()) {
            initialMethod = 'googlepay';
        } else if (isBtcpayEnabled()) {
            initialMethod = 'btcpay';
        }
        if (!initialMethod) {
            return;
        }
        state.currentMethod = initialMethod;
        setSelectedMethodField(initialMethod);
        debugLog('[ConvesioPay Classic] Initial method normalized on load', {
            method: initialMethod
        });
    }

    /**
     * Trigger WooCommerce checkout refresh in a debounced way.
     * Backend remains source of truth for totals and fees.
     */
    function scheduleCheckoutRefresh() {
        if (!isUnifiedGatewaySelected()) {
            return;
        }

        if (state.timers.checkoutRefresh) {
            clearTimeout(state.timers.checkoutRefresh);
        }

        // Schedule the checkout refresh.
        state.timers.checkoutRefresh = setTimeout(function () {
            try {
                debugLog('[ConvesioPay Classic] Triggering debounced update_checkout', {
                    method: resolveMethod(),
                    debounceMs: CONFIG.checkoutRefreshDebounceMs
                });
                $(document.body).trigger('update_checkout');
            } catch (error) {
                console.warn('[ConvesioPay] Failed to trigger update_checkout. error: ' + error);
            }
        }, CONFIG.checkoutRefreshDebounceMs);
    }

    /**
     * Trigger immediate Woo checkout refresh.
     */
    function triggerCheckoutRefreshNow() {
        if (!isUnifiedGatewaySelected()) {
            return;
        }
        if (state.timers.checkoutRefresh) {
            clearTimeout(state.timers.checkoutRefresh);
            state.timers.checkoutRefresh = null;
        }
        try {
            debugLog('[ConvesioPay Classic] Triggering immediate update_checkout', { method: resolveMethod() });
            $(document.body).trigger('update_checkout');
        } catch (error) {
            console.warn('[ConvesioPay] Failed to trigger immediate update_checkout. error: ' + error);
        }
    }

    /**
     * Persist selected internal method in Woo session.
     *
     * @param {'card'|'applepay'|'btcpay'} method
     * @returns {Promise<boolean>}
     */
    function syncMethodToSession(method) {
        if (!method || !CONFIG.allowedMethods.includes(method) || !isMethodEnabled(method)) {
            return Promise.resolve(false);
        }

        let formData;
        try {
            setSelectedMethodField(method);
            formData = new FormData();
            formData.append('action', CONFIG.sessionAction);
            formData.append(CONFIG.selectedMethodField, method);
        } catch (error) {
            debugLog('[ConvesioPay Classic] Method sync setup failed', { method: method, error: error });
            return Promise.resolve(false);
        }

        return fetch(getAjaxUrl(), {
            method: 'POST',
            credentials: 'same-origin',
            body: formData
        })
            .then((response) => {
                return response.json().catch(function () {
                    debugLog('[ConvesioPay Classic] Method sync response JSON parse failed', { method: method });
                    return {};
                });
            })
            .then((payload) => {
                const ok = !(payload && payload.success === false);
                if (!ok) {
                    console.warn('[ConvesioPay] Method session sync returned unsuccessful payload.');
                }
                return ok;
            })
            .catch(() => {
                console.warn('[ConvesioPay] Method session sync request failed.');
                return false;
            });
    }

    /**
     * Sync method only when changed and schedule backend recalculation.
     *
     * @param {'card'|'applepay'|'btcpay'} method
     */
    function syncMethodIfChanged(method) {
        if (!method || method === state.lastSyncedMethod || !isMethodEnabled(method)) {
            return;
        }

        debugLog('[ConvesioPay Classic] Syncing internal method to session', {
            from: state.lastSyncedMethod || null,
            to: method
        });
        syncMethodToSession(method).then((success) => {
            if (success) {
                state.lastSyncedMethod = method;
                debugLog('[ConvesioPay Classic] Method sync succeeded', { method: method });

                // Schedule the checkout refresh.
                scheduleCheckoutRefresh();
            }
        });
    }

    /**
     * Inject the cpay spinner keyframe stylesheet once into the document head.
     */
    function ensureCpaySpinnerStyles() {
        if (document.getElementById('cpay-spin-style')) {
            return;
        }
        const s = document.createElement('style');
        s.id = 'cpay-spin-style';
        s.textContent = '@keyframes cpaySpin{to{transform:rotate(360deg)}}';
        document.head.appendChild(s);
    }

    /**
     * Lock/unlock in-component interaction while method change is recalculating.
     *
     * @param {boolean} locked
     * @param {string=} method
     */
    function setMethodInteractionLock(locked, method) {
        // order-pay uses a pre-created order and does not require
        // checkout total/fee recalculation lock during method changes.
        if (isOrderPayContext()) {
            return;
        }

        const container = document.getElementById(CONFIG.containerId);
        if (!container) {
            return;
        }
        const lockId = CONFIG.containerId + '-interaction-lock';
        let lockEl = document.getElementById(lockId);
        if (locked) {
            ensureCpaySpinnerStyles();
            if (!lockEl) {
                lockEl = document.createElement('div');
                lockEl.id = lockId;
                lockEl.style.position = 'absolute';
                lockEl.style.inset = '0';
                lockEl.style.zIndex = '20';
                lockEl.style.background = 'rgba(255,255,255,0.6)';
                lockEl.style.pointerEvents = 'all';
                lockEl.style.display = 'flex';
                lockEl.style.alignItems = 'center';
                lockEl.style.justifyContent = 'center';
                lockEl.style.borderRadius = '4px';
                lockEl.setAttribute('aria-hidden', 'true');
                lockEl.innerHTML = '<div style="width:24px;height:24px;border:3px solid #ddd;border-top-color:#555;border-radius:50%;animation:cpaySpin .65s linear infinite;"></div>';
            }
            const style = window.getComputedStyle(container);
            if (style.position === 'static') {
                container.dataset.cpayLockPositionAdjusted = '1';
                container.style.position = 'relative';
            }
            if (!container.contains(lockEl)) {
                container.appendChild(lockEl);
            }
            disableSubmitButton();
            return;
        }
        if (lockEl && lockEl.parentNode) {
            lockEl.parentNode.removeChild(lockEl);
        }
        if (container.dataset.cpayLockPositionAdjusted === '1') {
            container.style.position = '';
            delete container.dataset.cpayLockPositionAdjusted;
        }
        updateSubmitButtonState();
    }

    /**
     * End method-switch lock if session refresh queue is settled.
     *
     * @param {string} reason
     */
    function maybeCompleteMethodSwitch(reason) {
        if (!state.methodSwitchInProgress) {
            return;
        }
        if (state.refreshingSessions || state.pendingSessionRefreshReason) {
            setTimeout(function () {
                maybeCompleteMethodSwitch(reason);
            }, 100);
            return;
        }
        if (state.methodSwitchFallbackUnlock) {
            clearTimeout(state.methodSwitchFallbackUnlock);
            state.methodSwitchFallbackUnlock = null;
        }
        debugLog('[ConvesioPay Classic] Method switch completed', {
            method: state.methodSwitchTarget,
            reason: reason || 'unspecified'
        });
        state.methodSwitchInProgress = false;
        state.methodSwitchTarget = '';
        setMethodInteractionLock(false);
    }

    /**
     * Handle internal method switch flow with checkout recalculation guard.
     * Prevents fast-click races where wallet/card uses stale totals.
     *
     * @param {'card'|'applepay'|'googlepay'|'btcpay'|null} method
     * @param {string} source
     */
    function startInternalMethodSwitchFlow(method, source) {
        if (!method || !CONFIG.allowedMethods.includes(method) || !isMethodEnabled(method)) {
            return;
        }
        const previous = String(resolveMethod() || '').toLowerCase();
        if (previous === method && !state.methodSwitchInProgress) {
            return;
        }

        state.currentMethod = method;
        setSelectedMethodField(method);
        mirrorServerTotals();

        if (state.methodSwitchInProgress && state.methodSwitchTarget === method) {
            return;
        }

        state.methodSwitchInProgress = true;
        state.methodSwitchTarget = method;
        setMethodInteractionLock(true, method);
        debugLog('[ConvesioPay Classic] Method switch started', { previous: previous, next: method, source: source });

        if (state.methodSwitchFallbackUnlock) {
            clearTimeout(state.methodSwitchFallbackUnlock);
        }
        state.methodSwitchFallbackUnlock = setTimeout(function () {
            debugLog('[ConvesioPay Classic] Method switch fallback unlock fired', { target: state.methodSwitchTarget });
            state.methodSwitchInProgress = false;
            state.methodSwitchTarget = '';
            setMethodInteractionLock(false);
        }, 9000);

        syncMethodToSession(method).then(function (success) {
            if (!success) {
                debugLog('[ConvesioPay Classic] Method switch aborted: sync failed', { method: method });
                state.methodSwitchInProgress = false;
                state.methodSwitchTarget = '';
                setMethodInteractionLock(false);
                return;
            }
            state.lastSyncedMethod = method;

            // order-pay does not rely on update_checkout fee/total refresh.
            // Complete the method switch immediately to keep interaction smooth.
            if (isOrderPayContext()) {
                state.methodSwitchInProgress = false;
                state.methodSwitchTarget = '';
                setMethodInteractionLock(false);
                updateSubmitButtonState();
                return;
            }

            triggerCheckoutRefreshNow();
        }).catch(function (error) {
            debugLog('[ConvesioPay Classic] Method switch failed unexpectedly', { method: method, error: error });
            state.methodSwitchInProgress = false;
            state.methodSwitchTarget = '';
            setMethodInteractionLock(false);
        });
    }

    /**
     * Capture explicit user method intent from direct UI interactions.
     * This is used to prevent immediate SDK "card" change bounces
     * right after an explicit wallet selection.
     *
     * @param {'card'|'applepay'|'googlepay'|'btcpay'|null} method
     */
    function markExplicitMethodIntent(method) {
        if (!method || !CONFIG.allowedMethods.includes(method)) {
            return;
        }
        state.explicitMethodIntent = method;
        state.explicitMethodIntentAtMs = Date.now();
        if (method === 'applepay' || method === 'googlepay') {
            state.suppressCardComponentChangeUntilMs = Date.now() + CONFIG.methodIntentSuppressionMs;
            return;
        }
        state.suppressCardComponentChangeUntilMs = 0;
    }

    /**
     * Whether card component-change should be ignored temporarily because
     * explicit wallet intent is still within suppression window.
     *
     * @returns {boolean}
     */
    function shouldSuppressCardComponentChange() {
        const now = Date.now();
        const explicitIntent = String(state.explicitMethodIntent || '').toLowerCase();
        const effectiveTarget = String(state.methodSwitchTarget || state.currentMethod || '').toLowerCase();
        const withinWindow = now <= Number(state.suppressCardComponentChangeUntilMs || 0);
        return (
            withinWindow &&
            (explicitIntent === 'applepay' || explicitIntent === 'googlepay') &&
            effectiveTarget === explicitIntent
        );
    }

    /**
     * Parse numeric amount from rendered price string.
     *
     * @param {string} value
     * @returns {number}
     */
    function parseAmount(value) {
        return parseFloat(String(value || '').replace(/[^0-9.-]/g, '')) || 0;
    }

    /**
     * Get canonical order review table row amount element.
     *
     * @param {Element|null} row
     * @returns {Element|null}
     */
    function getAmountElement(row) {
        if (!row) {
            return null;
        }
        return row.querySelector('.woocommerce-Price-amount bdi') ||
            row.querySelector('.woocommerce-Price-amount') ||
            row.querySelector('.amount bdi') ||
            row.querySelector('.amount') ||
            row.querySelector('td:last-child');
    }

    /**
     * Visibility guard for mirrored cloned/template checkout rows.
     *
     * @param {Element|null} el
     * @returns {boolean}
     */
    function isVisible(el) {
        if (!el || !(el instanceof Element) || el.offsetParent === null) {
            return false;
        }

        const hiddenAncestor = el.closest('[hidden], [aria-hidden="true"], .hidden, .d-none, .sr-only, template');
        if (hiddenAncestor) {
            return false;
        }

        const style = window.getComputedStyle(el);
        return Boolean(style && style.display !== 'none' && style.visibility !== 'hidden' && parseFloat(style.opacity || '1') > 0);
    }

    /**
     * Presentation-only mirror from canonical Woo totals to visible clones.
     * No fee calculation is performed here.
     */
    function mirrorServerTotals() {
        const table = document.querySelector('.woocommerce-checkout-review-order-table');
        if (!table) {
            return;
        }

        const canonicalFeeRow = table.querySelector('tr.fee');
        const canonicalTotalRow = table.querySelector('tr.order-total');
        const canonicalFeeLabel = canonicalFeeRow?.querySelector('th')?.textContent?.trim() || '';
        const canonicalFeeAmount = getAmountElement(canonicalFeeRow)?.textContent?.trim() || '';
        const canonicalTotal = getAmountElement(canonicalTotalRow)?.textContent?.trim() || '';

        state.lastKnownFeeAmount = canonicalFeeAmount ? parseAmount(canonicalFeeAmount) : 0;

        if (canonicalFeeAmount) {
            Array.from(document.querySelectorAll('tr.fee, .fee')).forEach((row) => {
                if (!row || row === canonicalFeeRow || !isVisible(row)) {
                    return;
                }

                // Never mutate fee rows that belong to canonical Woo review table.
                if (row.closest('.woocommerce-checkout-review-order-table')) {
                    return;
                }

                const label = row.querySelector('th');
                const amount = getAmountElement(row);
                const labelText = String(label?.textContent || '').trim().toLowerCase();

                // Keep wallet/store-credit rows untouched; they are independent fee lines.
                if (
                    labelText.includes('wallet') ||
                    labelText.includes('store credit') ||
                    labelText.includes('via wallet')
                ) {
                    return;
                }

                if (label && canonicalFeeLabel) {
                    label.textContent = canonicalFeeLabel;
                }
                if (amount) {
                    amount.textContent = canonicalFeeAmount;
                }
                row.style.display = '';
            });
        }

        if (canonicalTotal) {
            Array.from(document.querySelectorAll('tr.order-total, .order-total')).forEach((row) => {
                if (!row || row === canonicalTotalRow || !isVisible(row)) {
                    return;
                }
                const amount = getAmountElement(row);
                if (amount) {
                    amount.textContent = canonicalTotal;
                }
            });
        }
    }

    /**
     * Remove stale blockUI overlays in payment wrapper.
     */
    function clearPaymentOverlay() {
        try {
            // Only clear inside payment box.
            const $payment = $('#payment, .woocommerce-checkout-payment');
            if ($payment.length && typeof $payment.unblock === 'function') {
                $payment.unblock();
            }
            // Remove blockUI nodes only from payment container.
            $payment.find('.blockUI, .blockOverlay, .blockMsg').remove();
            // Keep Woo form/review-order processing state untouched.
            $payment.removeClass('processing');
            $payment.each(function () {
                this.style.removeProperty('position');
                this.style.removeProperty('overflow');
                this.style.removeProperty('min-height');
                this.removeAttribute('aria-busy');
            });

        } catch (error) {
            console.warn('[ConvesioPay] Failed to clear stale checkout overlay.');
        }
    }

    /**
     * Centralized checkout overlay lock/unlock control with safety fallback.
     *
     * @param {boolean} locked
     * @param {string=} reason
     */
    function setCheckoutProcessingOverlay(locked, reason) {
        try {
            const $form = getCheckoutForm();
            const $payment = $('#payment, .woocommerce-checkout-payment');

            if (locked) {
                state.submitOverlayLocked = true;
                if (state.submitOverlayFailsafeTimer) {
                    clearTimeout(state.submitOverlayFailsafeTimer);
                    state.submitOverlayFailsafeTimer = null;
                }

                disableSubmitButton();
                $form.addClass('processing');
                $(document.body).addClass('processing');

                if ($payment.length && typeof $payment.block === 'function') {
                    $payment.block({
                        message: null,
                        overlayCSS: {
                            background: '#fff',
                            opacity: 0.6
                        }
                    });
                }

                state.submitOverlayFailsafeTimer = setTimeout(function () {
                    if (!state.submitInProgress && !state.submitOverlayLocked) {
                        return;
                    }
                    debugLog('[ConvesioPay Classic] Submit overlay failsafe unlock fired', {
                        reason: reason || 'unspecified'
                    });
                    state.submitInProgress = false;
                    state.allowNativeSubmitOnce = false;
                    state.nativeSubmitBypassCount = 0;
                    if (state.timers.nativeSubmitHandoff) {
                        clearTimeout(state.timers.nativeSubmitHandoff);
                        state.timers.nativeSubmitHandoff = null;
                    }
                    setCheckoutProcessingOverlay(false, 'failsafe_unlock');
                    updateSubmitButtonState();
                }, 15000);
                return;
            }

            state.submitOverlayLocked = false;
            if (state.submitOverlayFailsafeTimer) {
                clearTimeout(state.submitOverlayFailsafeTimer);
                state.submitOverlayFailsafeTimer = null;
            }
            clearPaymentOverlay();
            setTimeout(clearPaymentOverlay, CONFIG.overlayClearFastMs);
            $form.removeClass('processing');
            $(document.body).removeClass('processing');
        } catch (_error) {
            clearPaymentOverlay();
        }
    }

    /**
     * Bind persistent overlay cleanup guards for late-injected blockUI overlays.
     */
    function bindOverlayCleanupGuards() {
        if (state.overlayCleanupBound) {
            return;
        }
        state.overlayCleanupBound = true;

        $(document)
            .off('ajaxStop.convesiopayClassicOverlay ajaxComplete.convesiopayClassicOverlay')
            .on('ajaxStop.convesiopayClassicOverlay ajaxComplete.convesiopayClassicOverlay', function () {
                if (!isUnifiedGatewaySelected()) {
                    return;
                }
                clearPaymentOverlay();
                if (state.timers.overlaySweep) {
                    clearTimeout(state.timers.overlaySweep);
                }
                state.timers.overlaySweep = setTimeout(clearPaymentOverlay, 140);
            });

        try {
            const observedRoot = document.querySelector(getCheckoutFormSelector()) || document.body;
            if (observedRoot && typeof MutationObserver !== 'undefined') {
                state.overlayObserver = new MutationObserver(function (mutations) {
                    if (!isUnifiedGatewaySelected()) {
                        return;
                    }
                    let hasOverlay = false;
                    for (let i = 0; i < mutations.length; i += 1) {
                        const mutation = mutations[i];
                        if (
                            mutation.type === 'attributes' &&
                            mutation.target &&
                            mutation.target.nodeType === 1 &&
                            mutation.target.classList &&
                            mutation.target.classList.contains('blockUI')
                        ) {
                            hasOverlay = true;
                            break;
                        }
                        if (!mutation.addedNodes || !mutation.addedNodes.length) {
                            continue;
                        }
                        for (let j = 0; j < mutation.addedNodes.length; j += 1) {
                            const node = mutation.addedNodes[j];
                            if (!node || node.nodeType !== 1) {
                                continue;
                            }
                            const className = String(node.className || '');
                            if (
                                className.includes('blockUI') ||
                                className.includes('blockOverlay') ||
                                (typeof node.querySelector === 'function' && node.querySelector('.blockUI.blockOverlay'))
                            ) {
                                hasOverlay = true;
                                break;
                            }
                        }
                        if (hasOverlay) {
                            break;
                        }
                    }
                    if (hasOverlay) {
                        clearPaymentOverlay();
                        if (state.timers.overlaySweep) {
                            clearTimeout(state.timers.overlaySweep);
                        }
                        state.timers.overlaySweep = setTimeout(clearPaymentOverlay, 120);
                        setTimeout(clearPaymentOverlay, 260);
                    }
                });
                state.overlayObserver.observe(observedRoot, {
                    childList: true,
                    subtree: true,
                    attributes: true,
                    attributeFilter: ['style', 'class']
                });
            }
        } catch (_error) {
            // Guard only; should never interrupt checkout.
        }
    }

    /**
     * Create BTCPay session through existing WordPress AJAX endpoint.
     *
     * @returns {Promise<{success:boolean, session?:string, message?:string, disabled?:boolean}>}
     */
    function createBTCPaySession() {
        if (!isBtcpayEnabled()) {
            return Promise.resolve({ success: false, disabled: true, message: 'BTCPay disabled' });
        }

        const orderData = getOrderData();
        if (!orderData.isValid) {
            return Promise.resolve({ success: false, message: 'Invalid order data' });
        }
        const currentSignature = getAmountCurrencySignature(orderData);
        if (
            state.btcSession &&
            state.btcSessionSignature &&
            currentSignature &&
            state.btcSessionSignature === currentSignature
        ) {
            return Promise.resolve({ success: true, session: state.btcSession });
        }

        return new Promise((resolve) => {
            try {
                $.ajax({
                    url: window.peachpay_convesiopay_unified_data?.ajax_url || getAjaxUrl(),
                    method: 'POST',
                    data: {
                        action: 'peachpay_convesiopay_create_btcpay_session',
                        nonce: window.peachpay_convesiopay_unified_data?.nonce || '',
                        integration: getConfig().integration_name,
                        returnUrl: window.location.href,
                        orderNumber: orderData.orderNumber,
                        amount: orderData.amount,
                        currency: orderData.currency,
                        email: orderData.email,
                        name: orderData.name
                    },
                    success: function (response) {
                        if (response.success && response.data?.session) {
                            state.btcSession = response.data.session;
                            state.btcSessionSignature = currentSignature;
                            resolve({ success: true, session: state.btcSession });
                            return;
                        }
                        const message = response.data?.message || response.message || 'Failed to create BTCPay session';
                        resolve({
                            success: false,
                            message: message,
                            disabled: message.toLowerCase().includes('disabled')
                        });
                    },
                    error: function (_xhr, _status, error) {
                        resolve({ success: false, message: String(error || 'AJAX request failed') });
                    }
                });
            } catch (error) {
                console.warn('[ConvesioPay] BTCPay session AJAX setup failed. error: ' + error);
                resolve({ success: false, message: 'BTCPay AJAX setup failed' });
            }
        });
    }

    /**
     * Bind BTCPay postMessage listener once.
     */
    function setupBtcPayMessageListener() {
        if (state.btcListenerBound) {
            return;
        }
        state.btcListenerBound = true;

        window.addEventListener('message', (event) => {
                try {
                if (!isAllowedPaymentMessageOrigin(event.origin)) {
                    return;
                }

                let data = event.data;
                if (typeof data === 'string') {
                    try {
                        data = JSON.parse(data);
                    } catch (error) {
                        console.warn('[ConvesioPay] BTCPay message parsing failed. error: ' + error);
                        return;
                    }
                }

                if (data?.status !== 'Processing') {
                    return;
                }

                const orderData = getOrderData();
                state.paymentConfirmedData = {
                    paymentId: data.invoiceId || '',
                    invoiceId: data.invoiceId || '',
                    status: 'Processing',
                    paymentMethod: 'btcpay',
                    amount: orderData.amount,
                    currency: orderData.currency,
                    orderNumber: orderData.orderNumber,
                    token: 'btc-session-' + (state.btcSession || '')
                };
                state.currentMethod = 'btcpay';
                addPaymentDataToForm();
                setTimeout(function () {
                    autoClickPlaceOrderButton();
                }, 500);
                } catch (error) {
                console.warn('[ConvesioPay] BTCPay postMessage handling failed. error: ' + error);
            }
        });
    }

    /**
     * Parse origin string safely.
     *
     * @param {string} value
     * @returns {string}
     */
    function getOriginSafe(value) {
        try {
            if (!value) {
                return '';
            }
            return new URL(value, window.location.origin).origin;
        } catch (error) {
            console.warn('[ConvesioPay] Error parsing origin. error: ' + error);
            return '';
        }
    }

    /**
     * Validate postMessage origin using strict allowlist.
     *
     * Sources:
     * - `peachpay_convesiopay_unified_data.btcpay_allowed_origins` (preferred)
     * - static ConvesioPay hosts and configured API URL origin
     *
     * @param {string} origin
     * @returns {boolean}
     */
    function isAllowedPaymentMessageOrigin(origin) {
        if (!origin) {
            return false;
        }

        const configured = getConfig().btcpay_allowed_origins;
        const configOrigins = Array.isArray(configured)
            ? configured.map((item) => getOriginSafe(String(item))).filter(Boolean)
            : [];

        const defaultOrigins = [
            'https://checkout.convesiopay.com',
            'https://api.convesiopay.com',
            'https://api-qa.convesiopay.com',
            getOriginSafe(getConfig().convesiopay_config?.api_url)
        ].filter(Boolean);

        const allowlist = configOrigins.length > 0 ? configOrigins : defaultOrigins;
        return allowlist.includes(origin);
    }

    /**
     * Safely read className from HTML/SVG nodes.
     *
     * @param {Element|null} element
     * @returns {string}
     */
    function getClassNameSafe(element) {
        if (!element) {
            return '';
        }
        const className = element.className;
        if (typeof className === 'string') {
            return className;
        }
        if (className && typeof className.baseVal === 'string') {
            return className.baseVal;
        }
        return '';
    }

    /**
     * Detect internal method from accordion header click context.
     *
     * @param {Element|null} accordionHeader
     * @returns {'card'|'applepay'|'googlepay'|'btcpay'|null}
     */
    function detectMethodFromAccordionHeader(accordionHeader) {
        if (!accordionHeader) {
            return null;
        }

        const accordionItem = accordionHeader.closest('.accordion-item');
        if (accordionItem) {
            const itemClassName = getClassNameSafe(accordionItem).toLowerCase();
            const itemHtml = String(accordionItem.outerHTML || '').toLowerCase().substring(0, 500);

            if (
                itemClassName.includes('btc-pay') ||
                itemClassName.includes('btcpay') ||
                itemClassName.includes('crypto') ||
                itemHtml.includes('btc-pay') ||
                itemHtml.includes('btcpay')
            ) {
                return 'btcpay';
            }

            if (
                itemClassName.includes('apple-pay') ||
                itemClassName.includes('applepay') ||
                itemHtml.includes('apple-pay') ||
                itemHtml.includes('applepay')
            ) {
                return 'applepay';
            }

            if (
                itemClassName.includes('google-pay') ||
                itemClassName.includes('googlepay') ||
                itemHtml.includes('google-pay') ||
                itemHtml.includes('googlepay')
            ) {
                return 'googlepay';
            }

            if (
                (itemClassName.includes('new-card') ||
                    itemClassName.includes('card-container') ||
                    itemClassName.includes('card')) &&
                !itemClassName.includes('apple') &&
                !itemClassName.includes('btc')
            ) {
                return 'card';
            }
        }

        const headerText = String(accordionHeader.textContent || '').toLowerCase();
        if (headerText.includes('crypto') || headerText.includes('bitcoin') || headerText.includes('btc')) {
            return 'btcpay';
        }
        if (headerText.includes('apple')) {
            return 'applepay';
        }
        if (headerText.includes('google')) {
            return 'googlepay';
        }
        if (
            (headerText.includes('card') || headerText.includes('credit') || headerText.includes('debit')) &&
            !headerText.includes('apple') &&
            !headerText.includes('google') &&
            !headerText.includes('crypto') &&
            !headerText.includes('bitcoin')
        ) {
            return 'card';
        }

        return null;
    }

    /**
     * Apply detected method to runtime and trigger fee/session sync.
     *
     * @param {'card'|'applepay'|'btcpay'|null} method
     */
    function applyDetectedInternalMethod(method) {
        markExplicitMethodIntent(method);
        startInternalMethodSwitchFlow(method, 'accordion_click');
    }

    /**
     * Bind delegated click listener for internal payment method tabs/accordion.
     * Some SDK UI method switches are not consistently emitted via change events.
     */
    function bindInternalMethodSelectionEvents() {
        const container = document.getElementById(CONFIG.containerId);
        if (!container) {
            return;
        }
        if (container.dataset.methodBindingAdded === 'true') {
            return;
        }
        container.dataset.methodBindingAdded = 'true';

        container.addEventListener(
            'click',
            function (event) {
                const target = event.target;
                if (!target || typeof target.closest !== 'function') {
                    return;
                }
                if (
                    resolveMethod() === 'googlepay' &&
                    target.closest(
                        'button, [role="button"], iframe, .adyen-checkout__button, .adyen-checkout__googlepay, .adyen-checkout__googlepay__button, .google-pay-button'
                    )
                ) {
                    lockGooglePayUiRefresh('googlepay_action_click');
                }
                const accordionHeader = target.closest('.accordion-header');
                if (!accordionHeader) {
                    return;
                }
                const detectedMethod = detectMethodFromAccordionHeader(accordionHeader); // Detect the method from the accordion header.
                applyDetectedInternalMethod(detectedMethod); // Apply the detected internal method.
            },
            true
        );
    }

    /**
     * Refresh Apple Pay/Google Pay/BTCPay sessions without unmounting component.
     *
     * @param {string} reason
     */
    async function refreshSessionsInPlace(reason) {
        if (state.refreshingSessions) {
            if (reason) {
                state.pendingSessionRefreshReason = reason;
            }
            return;
        }

        if (!state.mounted || !state.component || !isUnifiedGatewaySelected()) {
            return;
        }

        try {
            state.refreshingSessions = true;

            const orderData = getOrderData();
            debugLog('[ConvesioPay Classic] Refreshing sessions in-place', {
                reason: reason || 'unspecified',
                amount: orderData.amount,
                currency: orderData.currency,
                isValid: orderData.isValid,
                method: resolveMethod()
            });
            if (!orderData.isValid) {
                debugLog('[ConvesioPay Classic] Skipping session refresh: invalid order data', {
                    reason: reason || 'unspecified',
                    amount: orderData.amount,
                    currency: orderData.currency
                });
                return;
            }

            const previousAmountCurrencySignature = state.lastKnownAmountCurrencySignature;
            const runtimeAmountResult = await applyRuntimeAmountUpdate(reason || 'session_refresh', false);
            const amountCurrencySignature = runtimeAmountResult.signature || getAmountCurrencySignature(orderData);
            const isUpdatedCheckoutAmountCurrencyChange = reason === 'updated_checkout_amount_currency_change';
            const amountCurrencyChanged = Boolean(
                amountCurrencySignature &&
                amountCurrencySignature !== previousAmountCurrencySignature
            );

            if (isApplePayEnabled()) {
                const shouldRebuildApplePaySession = (
                    !runtimeAmountResult.supported ||
                    !runtimeAmountResult.ok ||
                    !state.applePaySessionPrimed ||
                    !state.applePaySessionSignature ||
                    state.applePaySessionSignature !== amountCurrencySignature ||
                    (isUpdatedCheckoutAmountCurrencyChange && amountCurrencyChanged)
                );
                if (!shouldRebuildApplePaySession) {
                    debugLog('[ConvesioPay Classic] Skipping Apple Pay session rebuild: amount/currency unchanged', {
                        reason: reason || 'unspecified',
                        signature: amountCurrencySignature,
                        previousSignature: previousAmountCurrencySignature,
                        sessionSignature: state.applePaySessionSignature,
                        skipped: runtimeAmountResult.skipped,
                        applePaySessionPrimed: state.applePaySessionPrimed
                    });
                } else {
                    try {
                        // Create the Apple Pay session if it's enabled.
                        await createApplePaySessionWithRetry({
                            integration: getConfig().integration_name || 'PeachPay',
                            returnUrl: window.location.href,
                            amount: orderData.amount,
                            currency: orderData.currency,
                            email: orderData.email,
                            name: orderData.name
                        });
                        state.applePaySessionPrimed = true;
                        state.applePaySessionSignature = amountCurrencySignature;
                    } catch (error) {
                        state.applePaySessionPrimed = false;
                        state.applePaySessionSignature = '';
                        console.warn('[ConvesioPay] Apple Pay session refresh failed. error: ' + error);
                    }
                }
            } else {
                state.applePaySessionPrimed = false;
                state.applePaySessionSignature = '';
            }

            if (isGooglePayEnabled()) {
                try {
                    const googlePayUiLocked = isGooglePayUiRefreshLocked();
                    const shouldRebuildGooglePaySession = (
                        !runtimeAmountResult.supported ||
                        !runtimeAmountResult.ok ||
                        !state.googlePaySessionPrimed ||
                        !state.googlePaySessionSignature ||
                        state.googlePaySessionSignature !== amountCurrencySignature
                    );
                    if (googlePayUiLocked) {
                        debugLog('Skipping Google Pay session refresh while UI lock is active', {
                            reason: reason || 'unspecified',
                            signature: amountCurrencySignature,
                            previousSignature: state.googlePaySessionSignature,
                            primed: state.googlePaySessionPrimed
                        });
                    } else if (shouldRebuildGooglePaySession) {
                        await createGooglePaySessionWithRetry({
                            integration: getConfig().integration_name || 'PeachPay',
                            returnUrl: window.location.href,
                            orderNumber: orderData.orderNumber,
                            amount: orderData.amount,
                            currency: orderData.currency,
                            email: orderData.email,
                            name: orderData.name
                        });
                    }
                    if (!googlePayUiLocked) {
                        state.googlePaySessionPrimed = true;
                        state.googlePaySessionSignature = amountCurrencySignature;
                    }
                } catch (error) {
                    state.googlePaySessionSignature = '';
                    state.googlePaySessionPrimed = false;
                    console.warn('[ConvesioPay] Google Pay session refresh failed. error: ' + error);
                }
            } else {
                state.googlePaySessionSignature = '';
                state.googlePaySessionPrimed = false;
            }

            if (isBtcpayEnabled()) {
                try {
                    // Create the BTCPay session if it's enabled.
                    const btc = await createBTCPaySession();
                    if (btc.success && btc.session) {
                        state.btcSession = btc.session;
                        try {
                            // Create the BTCPay intent if it's enabled.
                            await state.component.createBTCPayIntent({ session: state.btcSession });
                            setupBtcPayMessageListener();
                        } catch (intentError) {
                            console.warn('[ConvesioPay] BTCPay intent refresh failed. error: ' + intentError);
                        }
                    }
                } catch (error) {
                    console.warn('[ConvesioPay] BTCPay session refresh failed. error: ' + error);
                }
            } else {
                // If the BTC Pay is not enabled, set the BTC session to null.
                state.btcSession = null;
                state.btcSessionSignature = '';
            }

            state.lastKnownAmountCurrencySignature = getAmountCurrencySignature(orderData);

        } finally {
            state.refreshingSessions = false;

            if (state.pendingSessionRefreshReason) {
                const nextReason = state.pendingSessionRefreshReason;
                state.pendingSessionRefreshReason = '';
                setTimeout(function () {
                    refreshSessionsInPlace(nextReason);
                }, 0);
            }
        }
    }

    /**
     * Handles card component change events (no tokenization on change).
     *
     * This function is triggered whenever the card component emits a change event.
     * It only updates submit button state. Card token is created once when the user
     * clicks Place order (in runSubmitPreflightFlow) to avoid 429 rate limits from
     * repeated createToken() calls while typing (e.g. cardholder name).
     *
     * @param {Object} event - Card component change event.
     * @param {boolean} event.isValid - Indicates whether the card details are fully valid.
     *
     * @returns {void}
     */
    function handleCardComponentChange(event) {
        state.cardComponentValid = event?.isValid === true;
        state.cardComponentErrors = event?.errors || {};
        if (!event?.isValid || !state.component) {
            return;
        }
        updateSubmitButtonState();
    }

    /**
     * Handle Apple Pay successful authorization event.
     *
     * @param {Object} event
     */
    function handleApplePaySuccess(event) {
        if (!isApplePayEnabled()) {
            return;
        }

        const token = event?.token ?? event?.paymentData?.token ?? event?.data?.token ?? null;
        if (!token) {
            return;
        }

        const orderData = getOrderData(); // Get the order data.
        if (!orderData.isValid) {
            return;
        }

        state.paymentConfirmedData = {
            paymentId: null,
            status: 'authorized',
            paymentMethod: 'applepay',
            amount: event.paymentData?.amount || orderData.amount,
            currency: event.paymentData?.currency || orderData.currency,
            orderNumber: orderData.orderNumber,
            token: token
        };
        state.currentMethod = 'applepay'; // Update the current method in the state.
        state.btcSession = null;
        state.btcSessionSignature = '';
        storePaymentToken(token); // Store the payment token.
        addPaymentDataToForm(); // Add the payment data to the form.

        setTimeout(function () {
            autoClickPlaceOrderButton(); // Auto-click the place order button.
        }, 500);
    }

    /**
     * Handle Google Pay successful authorization event.
     *
     * @param {Object} event
     */
    function handleGooglePaySuccess(event) {
        if (!isGooglePayEnabled()) {
            return;
        }

        const token = event?.token ?? event?.paymentData?.token ?? event?.data?.token ?? null;
        if (!token) {
            return;
        }

        const orderData = getOrderData();
        if (!orderData.isValid) {
            return;
        }

        state.paymentConfirmedData = {
            paymentId: event?.paymentId ?? event?.paymentData?.paymentId ?? event?.id ?? null,
            status: String(event?.status || event?.paymentData?.status || 'authorized'),
            paymentMethod: 'googlepay',
            amount: event?.paymentData?.amount || event?.amount || orderData.amount,
            currency: event?.paymentData?.currency || event?.currency || orderData.currency,
            orderNumber: orderData.orderNumber,
            token: token
        };
        state.currentMethod = 'googlepay';
        state.btcSession = null;
        state.btcSessionSignature = '';
        storePaymentToken(token);
        window.convesiopayGooglePayPaymentData = state.paymentConfirmedData;
        addPaymentDataToForm();

        setTimeout(function () {
            autoClickPlaceOrderButton();
        }, 500);
    }

    /**
     * Main SDK change event dispatcher.
     *
     * @param {Object} event
     */
    function handleComponentChange(event) {
        updateSubmitButtonState(); // Update the submit button state.
        if (event?.type === 'card' && shouldSuppressCardComponentChange()) {
            debugLog('[ConvesioPay Classic] Suppressed card component change due to explicit Apple Pay intent', {
                currentMethod: state.currentMethod,
                methodSwitchTarget: state.methodSwitchTarget,
                explicitMethodIntent: state.explicitMethodIntent,
                suppressUntil: state.suppressCardComponentChangeUntilMs
            });
            return;
        }

        if (
            event?.type &&
            CONFIG.allowedMethods.includes(event.type) &&
            isMethodEnabled(event.type) &&
            event.type !== state.currentMethod
        ) {
            const effectiveMethod = String(state.methodSwitchTarget || state.currentMethod || '').toLowerCase();
            const isCardBounceOverWallet = event.type === 'card' && (effectiveMethod === 'applepay' || effectiveMethod === 'googlepay');
            if (!isCardBounceOverWallet) {
                startInternalMethodSwitchFlow(event.type, 'component_change');
            }
        }

        if (event?.type === 'card') {
            handleCardComponentChange(event); // Handle the card component change.
        } else if (event?.type === 'applepay' && isApplePayEnabled() && event?.isSuccessful) {
            handleApplePaySuccess(event); // Handle the Apple Pay success.
        } else if (event?.type === 'googlepay' && isGooglePayEnabled() && event?.isSuccessful) {
            handleGooglePaySuccess(event);
        }
    }

    /**
     * Mount component and initialize in-component handlers.
     */
    async function mountComponent() {
        debugLog('[ConvesioPay Classic] Mounting Classic v2 component');
        try {
            // Attach the mount node to the container.
            const attached = attachMountNodeToContainer();

            // If the mount node is not attached, return.
            if (!attached) {
                return;
            }

            // Get the mount node.
            const mountNode = getOrCreateMountNode();

            // Do not inject custom loader text; rely on Woo/theme loader visuals.
            mountNode.innerHTML = '';

            // Get the configuration.
            const config = getConfig();
            const apiKey = config.convesiopay_config?.apiKey || config.apiKey;
            const clientKey = config.convesiopay_config?.clientKey || config.clientKey;
            const integration = config.integration_name || config.integrationName;
            if (!apiKey || !integration) {
                mountNode.innerHTML = '<p style="color:red;">ConvesioPay configuration is missing.</p>';
                return;
            }

            // Create the ConvesioPay instance.
            const cpay = window.ConvesioPay(apiKey);

            // Get customer email (no fallback; only attach when we have one, like classic).
            const rawEmail = $('#billing_email').val();
            const customerEmail = rawEmail && String(rawEmail).trim() !== '' ? String(rawEmail).trim() : null;

            const componentConfig = {
                environment: config.convesiopay_config?.api_url?.includes('qa') ? 'test' : 'live',
                clientKey: clientKey,
                integration: integration,
                theme: config.checkout_theme || 'light',
                disabledPaymentMethods: getDisabledPaymentMethodsConfig()
            };
            if (customerEmail) {
                componentConfig.customerEmail = customerEmail;
            }

            // Create the component.
            const component = cpay.component(componentConfig);
            // Mount the component to the mount node.
            component.mount('#' + CONFIG.mountNodeId);

            // Bind the change event to the component.
            component.on('change', handleComponentChange);

            // Set the component to the state.
            state.component = component;
            state.lastKnownCustomerEmail = customerEmail !== null ? customerEmail : '';

            // Set the mounted flag to true.
            state.mounted = true;
            debugLog('[ConvesioPay Classic] Component mounted', {
                environment: config.convesiopay_config?.api_url?.includes('qa') ? 'test' : 'live',
                integration: integration
            });

            // Bind internal method tab/accordion detection for reliable method switch sync.
            bindInternalMethodSelectionEvents();

            // Refresh the sessions.
            await refreshSessionsInPlace('initial_mount');

            // Update the submit button state.
            updateSubmitButtonState();

            // Return the component.
            return component;
        } catch (error) {
            console.warn('[ConvesioPay] Component mount failed. error: ' + error);
            state.mounted = false;
            state.component = null;
        }
    }

    /**
     * Ensure mounted component exists for current checkout container.
     */
    function ensureComponentMounted() {
        try {
            // If the unified gateway is not selected, do not mount the component.
            if (!isUnifiedGatewaySelected()) {
                return;
            }

            // If the ConvesioPay library is not loaded, wait for it to load and try again.
            if (!window.ConvesioPay) {
                setTimeout(ensureComponentMounted, 100);
                return;
            }

            // Get the container element.
            const container = document.getElementById(CONFIG.containerId);
            // If the container element is not found, do not mount the component.
            if (!container) {
                console.error(`[ConvesioPay Classic v2] #${CONFIG.containerId} Container not found`);
                return;
            }

            // If the component is already mounted, do not mount it again.
            if (state.mounted && state.component) {
                // Get the mount node.
                const mountNode = getOrCreateMountNode();

                // If the mount node is already in the container, do not mount it again.
                if (mountNode.parentNode === container) {
                    bindInternalMethodSelectionEvents();

                    // Refresh the sessions.
                    refreshSessionsInPlace('already_mounted_refresh');
                    return;
                }

                // Container changed. Do not move mounted iframe DOM between parents.
                // Try to unmount the component.
                try {
                    if (typeof state.component.unmount === 'function') {
                        state.component.unmount();

                        // Set the mounted flag to false.
                        state.mounted = false;

                        // Set the component to null.
                        state.component = null;

                        // Set the mount node to null.
                        state.mountNode = null;
                    }
                } catch (error) {
                    console.warn('[ConvesioPay] Component unmount failed during remount path. error: ' + error);
                }
                // Set the mounted flag to false.
                state.mounted = false;

                // Set the component to null.
                state.component = null;

                // Set the mount node to null.
                state.mountNode = null;
            }

            // Mount the component.
            mountComponent().catch((error) => {
                // Keep checkout operational even if mount fails.
                console.warn('[ConvesioPay] Component mount failed. error: ' + error);
            });
        } catch (error) {
            console.warn('[ConvesioPay] ensureComponentMounted failed. error: ' + error);
        }
    }

    /**
     * Unmount and clear transient unified component state.
     *
     * @param {string=} reason
     */
    function resetMountedComponentState(reason) {
        try {
            if (state.component && typeof state.component.unmount === 'function') {
                state.component.unmount();
            }
        } catch (error) {
            console.warn('[ConvesioPay] Component unmount failed during cleanup. error: ' + error);
        }
        state.component = null;
        state.mounted = false;
        state.mountNode = null;
        state.paymentConfirmedData = null;
        state.cardComponentValid = false;
        state.cardComponentErrors = {};
        state.btcSession = null;
        state.btcSessionSignature = '';
        state.applePaySessionPrimed = false;
        state.applePaySessionSignature = '';
        state.googlePaySessionPrimed = false;
        state.googlePaySessionSignature = '';
        state.googlePayUiLockUntilMs = 0;
        window.convesiopayPaymentToken = null;
        window.convesiopayApplePayPaymentData = null;
        window.convesiopayGooglePayPaymentData = null;
        if (reason) {
            debugLog('[ConvesioPay Classic] Mounted component state reset', {
                reason: reason
            });
        }
    }

    /**
     * Trigger Woo submit handoff after external method confirmation.
     * Uses the same guarded async preflight path as manual submit.
     */
    function autoClickPlaceOrderButton() {
        if (!state.paymentConfirmedData) {
            return;
        }
        runSubmitPreflightFlow('auto_click_after_confirmation');
    }

    /**
     * Map internal submit-preflight reason to a user-visible checkout message.
     *
     * @param {string} reason
     * @returns {string}
     */
    function getSubmitPreflightErrorMessage(reason) {
        const code = String(reason || '').toLowerCase();
        const messageMap = {
            method_switch_in_progress: 'Payment method update is still in progress. Please wait a moment and try again.',
            runtime_amount_update_failed: 'Checkout total is still updating. Please wait a moment and try again.',
            card_token_missing: 'Card authorization is not ready. Please check card details and try again.',
            card_token_timeout: 'Card authorization timed out. Please try again.',
            payment_not_confirmed: 'Payment authorization is not ready. Please try again.',
            invalid_order_data: 'Checkout data is incomplete. Please review your billing details and try again.',
            checkout_form_not_found: 'Checkout form is not available. Please refresh and try again.',
            checkout_form_invalid: 'Please complete all required checkout fields before placing your order.',
            card_component_invalid: 'Card authorization is not ready. Please check card details and try again.',
            checkout_form_submit_unavailable: 'Checkout submission is temporarily unavailable. Please refresh and try again.'
        };
        return messageMap[code] || reason;
    }

    /**
     * Show a standard Woo checkout error notice.
     *
     * @param {string} message
     * @returns {void}
     */
    function showCheckoutErrorNotice(message) {
        const $form = getCheckoutForm();
        if (!$form.length) {
            return;
        }
        const safeMessage = $('<div>').text(String(message || '')).html();
        const noticeHtml = '<div class="woocommerce-NoticeGroup woocommerce-NoticeGroup-checkout convesiopay-checkout-notice"><ul class="woocommerce-error" role="alert"><li>' + safeMessage + '</li></ul></div>';
        $form.find('.convesiopay-checkout-notice').remove();
        $form.find('.woocommerce-NoticeGroup-checkout, .woocommerce-error').first().remove();
        $form.prepend(noticeHtml);
        try {
            $('html, body').animate({ scrollTop: Math.max($form.offset().top - 120, 0) }, 220);
        } catch (_error) {
            // UX guard only.
        }
    }

    /**
     * Perform strictly sequential submit preflight and handoff to Woo submit.
     *
     * Order is guaranteed:
     * runtime amount update -> payment data readiness -> hidden data injection -> Woo submit.
     *
     * @param {string=} source
     * @returns {Promise<boolean>}
     */
    async function runSubmitPreflightFlow(source) {
        if (!isUnifiedGatewaySelected()) {
            return true;
        }

        // If the submit is already in progress, return false.
        if (state.submitInProgress) {
            return false;
        }

        // Set the submit in progress flag to true.
        state.submitInProgress = true;
        setCheckoutProcessingOverlay(true, source || 'submit_preflight');

        try {
            // If the method switch is in progress, throw an error.
            if (state.methodSwitchInProgress) {
                throw new Error('method_switch_in_progress');
            }

            // If the component is mounted and the update amount function exists, apply the runtime amount update.
            if (state.component && typeof state.component.updateAmount === 'function') {
                const runtimeUpdate = await applyRuntimeAmountUpdate('submit_preflight', true);
                if (runtimeUpdate.supported && !runtimeUpdate.ok) {
                    throw new Error('runtime_amount_update_failed');
                }
            }

            const currentMethod = resolveMethod();
            const orderData = getOrderData();
            if (!orderData.isValid) {
                throw new Error('invalid_order_data');
            }

            if (
                currentMethod === 'card' &&
                String(state.paymentConfirmedData?.paymentMethod || '').toLowerCase() === 'card' &&
                !isMatchingCardPaymentContext(state.paymentConfirmedData, orderData)
            ) {
                clearStaleCardPaymentToken('submit_preflight_card_context_mismatch', orderData);
            }

            if (!state.paymentConfirmedData) {
                if (currentMethod === 'card' && state.component) {
                    if (state.cardComponentValid !== true) {
                        debugLog('[ConvesioPay Classic] Blocking card tokenization: component invalid', {
                            source: source || 'submit_preflight',
                            amount: orderData.amount,
                            currency: orderData.currency,
                            errors: state.cardComponentErrors
                        });
                        throw new Error('card_component_invalid');
                    }

                    debugLog('[ConvesioPay Classic] Submit preflight card token fallback started', {
                        source: source || 'submit_preflight',
                        amount: orderData.amount,
                        currency: orderData.currency
                    });
                    const reusableToken = getReusableCardToken(orderData);
                    const token = reusableToken.token
                        ? reusableToken.token
                        : await Promise.race([
                            Promise.resolve().then(function () {
                                // Create the card token.
                                const tokenResult = state.component.createToken({
                                    amount: orderData.amount,
                                    currency: orderData.currency
                                });
                                return (tokenResult && typeof tokenResult.then === 'function')
                                    ? tokenResult
                                    : tokenResult;
                            }),
                            new Promise(function (_resolve, reject) {
                                setTimeout(function () {
                                    reject(new Error('card_token_timeout'));
                                }, 10000);
                            })
                        ]);
                    debugLog('[ConvesioPay Classic] Submit preflight card token fallback resolved', {
                        source: source || 'submit_preflight',
                        amount: orderData.amount,
                        currency: orderData.currency,
                        tokenSource: reusableToken.source || 'createToken',
                        usedExistingToken: Boolean(reusableToken.token),
                        hasToken: Boolean(token),
                        tokenPrefix: String(token || '').trim().substring(0, 8),
                        tokenLength: String(token || '').trim().length
                    });
                    if (!token) {
                        throw new Error('card_token_missing');
                    }
                    state.paymentConfirmedData = {
                        paymentId: 'card_' + Date.now(),
                        status: 'succeeded',
                        paymentMethod: 'card',
                        amount: orderData.amount,
                        currency: orderData.currency,
                        orderNumber: orderData.orderNumber,
                        token: token,
                        createdAt: Date.now()
                    };
                    state.currentMethod = 'card';

                    // Store the payment token.
                    storePaymentToken(token);
                } else {
                    throw new Error('payment_not_confirmed');
                }
            }

            // Add the payment data to the form.
            addPaymentDataToForm();

            // Get the form element.
            const formEl = getCheckoutForm().get(0);

            // If the form element is not found, throw an error.
            if (!formEl) {
                throw new Error('checkout_form_not_found');
            }

            // If the form element is not valid, throw an error.
            if (typeof formEl.reportValidity === 'function' && !formEl.reportValidity()) {
                throw new Error('checkout_form_invalid');
            }

            // Allow Woo's submit to run: when we trigger('submit') below, Woo may re-fire checkout_place_order.
            // Allow exactly those two native passes, then block any further clicks.
            state.allowNativeSubmitOnce = true;
            state.nativeSubmitBypassCount = 2;
            setCheckoutProcessingOverlay(false, 'submit_handoff');
            // Woo-standard: keep Place Order disabled until redirect. Re-enable only on error (catch / releaseSubmitLockIfActive).
            $(formEl).trigger('submit');

            state.lastSubmitPreflightError = '';
            window.convesiopayClassicLastSubmitPreflightError = '';

            // Keep submit lock active during native handoff to block multi-click submissions.
            // If Woo submit pipeline does not progress, this timer safely releases the lock.
            if (state.timers.nativeSubmitHandoff) {
                clearTimeout(state.timers.nativeSubmitHandoff);
            }
            state.timers.nativeSubmitHandoff = setTimeout(function () {
                if (!state.submitInProgress) {
                    return;
                }

                // Release the submit lock if the submit is in progress.
                releaseSubmitLockIfActive('native_submit_handoff_timeout');
            }, 8000);

            return true;
        } catch (error) {
            const reason = error?.message || String(error || 'unknown_error');
            state.lastSubmitPreflightError = reason;
            window.convesiopayClassicLastSubmitPreflightError = reason;
            debugLog('[ConvesioPay Classic] Submit preflight failed', {
                source: source || 'submit_preflight',
                reason: reason
            });
            debugLog('[ConvesioPay Classic] Submit preflight reason code', {
                reasonCode: reason,
                method: resolveMethod(),
                source: source || 'submit_preflight'
            });
            state.submitInProgress = false;
            state.allowNativeSubmitOnce = false;
            state.nativeSubmitBypassCount = 0;
            if (state.timers.nativeSubmitHandoff) {
                clearTimeout(state.timers.nativeSubmitHandoff);
                state.timers.nativeSubmitHandoff = null;
            }
            setCheckoutProcessingOverlay(false, 'submit_preflight_error');

            // Woo-standard: re-enable Place Order on error so the user can fix and try again.
            updateSubmitButtonState();

            // Show the checkout error notice.
            showCheckoutErrorNotice(getSubmitPreflightErrorMessage(reason));
            return false;
        }
    }

    /**
     * Release submit re-entry lock when Woo keeps user on checkout.
     *
     * @param {string=} reason
     */
    function releaseSubmitLockIfActive(reason) {
        if (!state.submitInProgress && !state.allowNativeSubmitOnce && !state.submitOverlayLocked) {
            return;
        }
        debugLog('[ConvesioPay Classic] Releasing submit lock', {
            reason: reason || 'unspecified',
            submitInProgress: state.submitInProgress,
            allowNativeSubmitOnce: state.allowNativeSubmitOnce
        });
        state.submitInProgress = false;
        state.allowNativeSubmitOnce = false;
        state.nativeSubmitBypassCount = 0;
        if (state.timers.nativeSubmitHandoff) {
            clearTimeout(state.timers.nativeSubmitHandoff);
            state.timers.nativeSubmitHandoff = null;
        }

        // Set the checkout processing overlay to false.
        setCheckoutProcessingOverlay(false, reason || 'submit_lock_release');

        // Update the submit button state.
        updateSubmitButtonState();
    }

    /**
     * Synchronous checkout event gate for unified flow.
     * Delegates all async submit work to runSubmitPreflightFlow().
     *
     * @param {Event} event
     * @returns {boolean}
     */
    function handleCheckoutSubmission(event) {
        if (!isUnifiedGatewaySelected()) {
            return true;
        }
        if (state.allowNativeSubmitOnce && state.nativeSubmitBypassCount > 0) {
            state.nativeSubmitBypassCount -= 1;
            if (state.nativeSubmitBypassCount <= 0) {
                state.allowNativeSubmitOnce = false;
                state.nativeSubmitBypassCount = 0;
            }
            return true;
        }
        if (state.submitInProgress) {
            if (event && typeof event.preventDefault === 'function') {
                event.preventDefault();
            }
            if (event && typeof event.stopPropagation === 'function') {
                event.stopPropagation();
            }
            if (event && typeof event.stopImmediatePropagation === 'function') {
                event.stopImmediatePropagation();
            }
            return false;
        }

        if (event && typeof event.preventDefault === 'function') {
            event.preventDefault();
        }
        if (event && typeof event.stopPropagation === 'function') {
            event.stopPropagation();
        }
        if (event && typeof event.stopImmediatePropagation === 'function') {
            event.stopImmediatePropagation();
        }

        // Woo-standard: disable Place Order immediately on first click to prevent double-submit and show processing state.
        disableSubmitButton();

        // Run the submit preflight flow.
        runSubmitPreflightFlow('checkout_submit_event');
        return false;
    }

    /**
     * Keep basic method + fee mirror state aligned with current selection.
     */
    function reconcileMethodAndMirror() {
        if (!isUnifiedGatewaySelected()) {
            return;
        }
        const method = resolveMethod(); // Get the current selected method inside the cpay unified [card, applepay, btcpay]
        state.currentMethod = method; // Update the current method in the state.
        setSelectedMethodField(method); // Set the selected method field in the form.
        syncMethodIfChanged(method); // Sync the method if changed.
        mirrorServerTotals(); // Mirror the server totals.
        updateSubmitButtonState(); // Update the submit button state.
    }

    /**
     * Compute customer signature used for debounced context refresh checks.
     *
     * @returns {string}
     */
    function getCustomerSignature() {
        const email = String($('#billing_email').val() || '').trim().toLowerCase();
        const first = String($('#billing_first_name').val() || '').trim();
        const last = String($('#billing_last_name').val() || '').trim();
        return [email, first, last].join('|');
    }

    /**
     * Unified updated_checkout orchestrator.
     */
    function onUpdatedCheckout() {
        if (state.submitInProgress && !state.submitOverlayLocked) {
            releaseSubmitLockIfActive('updated_checkout_after_submit');
        }
        clearPaymentOverlay(); // Clear the payment overlay.
        setTimeout(clearPaymentOverlay, CONFIG.overlayClearFastMs);
        setTimeout(clearPaymentOverlay, CONFIG.overlayClearSlowMs);
        if (!isUnifiedGatewaySelected()) {
            resetMountedComponentState('updated_checkout_gateway_deselected');
            return;
        }
        ensureComponentMounted();
        reconcileMethodAndMirror(); // Keep the method and fee mirror state aligned with current selection.

        const orderData = getOrderData();
        const total = orderData.amount; // Get the current cart total.
        const amountCurrencySignature = getAmountCurrencySignature(orderData);
        debugLog('[ConvesioPay Classic] updated_checkout orchestrator', {
            total: total,
            lastKnownTotal: state.lastKnownCartTotal,
            amountCurrencySignature: amountCurrencySignature,
            lastKnownAmountCurrencySignature: state.lastKnownAmountCurrencySignature,
            method: resolveMethod()
        });
        if (
            amountCurrencySignature &&
            amountCurrencySignature !== state.lastKnownAmountCurrencySignature
        ) {
            state.lastKnownCartTotal = total; // Update the last known cart total.
            clearStaleCardPaymentToken('updated_checkout_amount_currency_change', orderData);
            clearApplePayAuthorizationIfSet('updated_checkout_amount_currency_change', amountCurrencySignature);
            clearGooglePayAuthorizationIfSet('updated_checkout_amount_currency_change', amountCurrencySignature);
            if (orderData.isValid) {
                refreshSessionsInPlace('updated_checkout_amount_currency_change'); // Refresh/update runtime amount in place.
            } else {
                state.lastKnownAmountCurrencySignature = amountCurrencySignature;
                state.applePaySessionPrimed = false;
                state.applePaySessionSignature = '';
                state.googlePaySessionPrimed = false;
                state.googlePaySessionSignature = '';
                state.btcSession = null;
                state.btcSessionSignature = '';
                debugLog('[ConvesioPay Classic] Invalidated payment sessions for zero/invalid checkout total', {
                    reason: 'updated_checkout_amount_currency_change',
                    amount: orderData.amount,
                    currency: orderData.currency,
                    signature: amountCurrencySignature
                });
            }
        }

        if (state.methodSwitchInProgress) {
            const targetMethod = state.methodSwitchTarget;
            if (targetMethod === 'applepay' || targetMethod === 'googlepay' || targetMethod === 'btcpay') {
                refreshSessionsInPlace('method_switch_finalize_' + targetMethod).finally(function () {
                    maybeCompleteMethodSwitch('updated_checkout_finalize_' + targetMethod);
                });
            } else {
                maybeCompleteMethodSwitch('updated_checkout_finalize_card');
            }
        }
    }

    /**
     * Register Woo checkout event handlers once.
     */
    function bindCheckoutEvents() {
        bindOverlayCleanupGuards();

        // Bind the change event to the payment method selector. E.g When the user changes the payment method.
        $(document.body)
            .off('change.convesiopayClassicGateway', CONFIG.paymentMethodSelector)
            .on('change.convesiopayClassicGateway', CONFIG.paymentMethodSelector, function () {
                const selected = $(this).val();
                if (selected === CONFIG.gatewayId) {
                    reconcileMethodAndMirror(); // Keep the method and fee mirror state aligned with current selection.
                    setTimeout(function () {
                        ensureComponentMounted(); // Ensure the component is mounted after Woo updates the payment box.
                    }, 0);
                } else {
                    resetMountedComponentState('payment_method_change_gateway_deselected');
                }
                setTimeout(clearPaymentOverlay, CONFIG.overlayClearFastMs);
                setTimeout(clearPaymentOverlay, CONFIG.overlayClearSlowMs);
            });

        // Bind the updated_checkout event to the checkout form. E.g When the user updates the checkout form.
        $(document.body)
            .off('updated_checkout.convesiopayClassic')
            .on('updated_checkout.convesiopayClassic', function () {
                onUpdatedCheckout(); // Trigger the updated checkout event.
            });

        $(document.body)
            .off('checkout_error.convesiopayClassic')
            .on('checkout_error.convesiopayClassic', function (_event, errorMessage) {
                const tokenInvalidationReason = getRetryableCardTokenInvalidationReason(errorMessage);
                if (tokenInvalidationReason) {
                    clearCardPaymentTokenAfterInvalidRequest(tokenInvalidationReason);
                    replaceRetryableCardTokenNotice(errorMessage);
                }
                releaseSubmitLockIfActive('checkout_error');
            });

        // Bind the input event to the billing email, first name, and last name. E.g When the user updates the billing email, first name, or last name.
        $(document)
            .off('input.convesiopayClassicContext change.convesiopayClassicContext', '#billing_email, #billing_first_name, #billing_last_name')
            .on('input.convesiopayClassicContext change.convesiopayClassicContext', '#billing_email, #billing_first_name, #billing_last_name', function () {
                if (state.timers.customerContext) {
                    clearTimeout(state.timers.customerContext);
                }
                state.timers.customerContext = setTimeout(function () {
                    const signature = getCustomerSignature();
                    if (!signature || signature === state.lastCustomerSignature) {
                        return;
                    }
                    state.lastCustomerSignature = signature;
                    // Update component email only when billing email has changed (no-op if only name changed).
                    applyRuntimeEmailUpdate();
                    reconcileMethodAndMirror(); // Keep the method and fee mirror state aligned with current selection.
                    refreshSessionsInPlace('customer_context_change'); // Refresh the sessions in place.
                }, CONFIG.customerContextDebounceMs);
            });

        /**
         * Bind WooCommerce checkout submission events.
         *
         * Woo triggers two possible JS events when the user clicks "Place order":
         *
         * 1. checkout_place_order_{gateway_id}
         *    → Fired only when this specific payment gateway is selected.
         *
         * 2. checkout_place_order
         *    → Generic event fired for all gateways.
         *
         * We bind to BOTH events for maximum compatibility because:
         * - Some themes/plugins only trigger the generic event.
         * - Some Woo flows trigger the gateway-specific event.
         * - AJAX reloads can behave inconsistently across environments.
         *
         * Both events route to the same handler (handleCheckoutSubmission),
         * which contains internal guards to prevent duplicate processing.
         *
         * The `.off()` call ensures we remove any previously attached handlers
         * (important because Woo checkout reloads via AJAX and could otherwise
         * stack duplicate listeners).
         */
        const $checkoutForm = getCheckoutForm();
        if ($checkoutForm.is('form.checkout')) {
            $checkoutForm
                .off('checkout_place_order_' + CONFIG.gatewayId + '.convesiopayClassic')
                .on('checkout_place_order_' + CONFIG.gatewayId + '.convesiopayClassic', handleCheckoutSubmission);

            $checkoutForm
                .off('checkout_place_order.convesiopayClassic')
                .on('checkout_place_order.convesiopayClassic', handleCheckoutSubmission);
        }

        // order-pay path does not consistently fire checkout_place_order hooks.
        // Bind native submit so preflight + hidden field injection still run.
        $(document)
            .off('submit.convesiopayClassic', 'form.checkout, form#order_review')
            .on('submit.convesiopayClassic', 'form.checkout, form#order_review', handleCheckoutSubmission);

    }

    // Add CSS to hide payment method label when it's the only payment method
    function addClassicCheckoutStyles() {
        const styleId = 'convesiopay-unified-classic-styles';
        if (!document.getElementById(styleId)) {
            const style = document.createElement('style');
            style.id = styleId;
            style.textContent = `
                /* Hide payment method label when ConvesioPay is the only payment method */
                ul.wc_payment_methods li.payment_method_peachpay_convesiopay_unified:only-child > label {
                    display: none !important;
                }
                /* Remove padding when it's the only payment method */
                ul.wc_payment_methods li.payment_method_peachpay_convesiopay_unified:only-child .payment_box {
                    padding: 0 !important;
                    margin: 0 !important;
                }

                /* Fixed Card Holder Name Input height and border */
                ul.wc_payment_methods li.payment_method_peachpay_convesiopay_unified input[name='holderName'] {
                    min-height: auto !important;
                    border: none !important;
                }

                /* Fixed Card Holder Name Input Margin */
                #payment ul li input[type=text][name='holderName'] {
                    margin: 0 !important;
                }

                /* Fixed Adyen Checkbox label display */
                ul.wc_payment_methods li.payment_method_peachpay_convesiopay_unified .adyen-checkout__store-details .adyen-checkout__checkbox::before {
                    display: block !important;
                }

                /* Keep ConvesioPay SDK OTP inputs from inheriting broad checkout text input styles. */
                #payment ul.wc_payment_methods li.payment_method_peachpay_convesiopay_unified #convesiopay-persistent-sdk-mount #convesiopay-checkout .two-factor-auth-section .code-input.verification-code-input {
                    display: flex !important;
                    flex-direction: row !important;
                    justify-content: center !important;
                    flex-wrap: nowrap !important;
                    gap: 8px !important;
                }

                #payment ul.wc_payment_methods li.payment_method_peachpay_convesiopay_unified #convesiopay-persistent-sdk-mount #convesiopay-checkout .two-factor-auth-section .code-input.verification-code-input input.code-input__digit[type="text"] {
                    width: 48px !important;
                    min-width: 48px !important;
                    max-width: 48px !important;
                    height: 48px !important;
                    padding: 0 !important;
                    margin: 0 !important;
                    box-sizing: border-box !important;
                    text-align: center !important;
                    flex: 0 0 48px !important;
                }

                @media (max-width: 400px) {
                    #payment ul.wc_payment_methods li.payment_method_peachpay_convesiopay_unified #convesiopay-persistent-sdk-mount #convesiopay-checkout .two-factor-auth-section .code-input.verification-code-input {
                        gap: 6px !important;
                    }

                    #payment ul.wc_payment_methods li.payment_method_peachpay_convesiopay_unified #convesiopay-persistent-sdk-mount #convesiopay-checkout .two-factor-auth-section .code-input.verification-code-input input.code-input__digit[type="text"] {
                        width: 40px !important;
                        min-width: 40px !important;
                        max-width: 40px !important;
                        height: 40px !important;
                        flex: 0 0 40px !important;
                    }
                }

                /*
                #convesiopay-unified-payment-container .loading-input__spinner {
                    display: none !important;
                }
                */
                #convesiopay-unified-payment-container.loading-in-progress {
                    min-height: 300px !important;
                    min-width: 100% !important;
                    display: flex !important;
                    align-items: center !important;
                    justify-content: center !important;
                }

                #convesiopay-unified-payment-container.loading-in-progress .adyen-checkout,
                #convesiopay-unified-payment-container.loading-in-progress [class*="adyen-checkout"] {
                    min-height: 280px !important;
                    min-width: 100% !important;
                    display: flex !important;
                    align-items: center !important;
                    justify-content: center !important;
                }
            `;
            document.head.appendChild(style);
        }
    }

    /**
     * Initialize refactor architecture.
     * Safe to call multiple times; binding is namespaced and idempotent.
     */
    function init() {
        if (state.initialized) {
            debugLog('[ConvesioPay Classic] init called after initialization; running onUpdatedCheckout only');
            onUpdatedCheckout(); // Trigger initial checkout update.
            return;
        }

        state.initialized = true;
        const initialOrderData = getOrderData();
        state.lastKnownCartTotal = initialOrderData.amount;
        state.lastKnownAmountCurrencySignature = getAmountCurrencySignature(initialOrderData);
        state.lastCustomerSignature = getCustomerSignature();
        getOrCreateTransactionId(); // Ensure transaction ID exists before any submit.
        debugLog('[ConvesioPay Classic] Initializing Classic v2', {
            advancedLogging: isAdvancedLoggingEnabled(),
            initialTotal: state.lastKnownCartTotal,
            activeMethods: getConfig().active_methods || []
        });
        normalizeInitialMethodToCard(); // Keep CPay + Woo selected method aligned to card on refresh.
        bindCheckoutEvents(); // Bind all checkout events.
        onUpdatedCheckout(); // Trigger initial checkout update.
        ensureComponentMounted(); // Ensure the component is mounted.
    }

    // Bootstrap only for classic checkout pages.
    $(document).ready(function () {
        const isBlocksCheckout = document.querySelector('.wc-block-checkout') ||
            document.querySelector('.wp-block-woocommerce-checkout') ||
            document.querySelector('.wc-convesiopay-unified-blocks-content');

        if (isBlocksCheckout) {
            return;
        }

        // Add CSS to hide payment method label when it's the only payment method
        addClassicCheckoutStyles();

        // Entry point for the script.
        init();
    });
})(jQuery);
