/*!
 * Copyright 2024-2025 Simply.IN Sp. z o.o.
 *
 * NOTICE OF LICENSE
 * Licensed under the EUPL-1.2 or later.
 * You may not use this work except in compliance with the Licence.
 * Copy of the Licence is available at: https://joinup.ec.europa.eu/software/page/eupl
 * It is bundled with this package in the file LICENSE.txt
 *
 * @author    Simply.IN Sp. z o.o.
 * @copyright 2024-2025 Simply.IN Sp. z o.o.
 * @license   https://joinup.ec.europa.eu/software/page/eupl
 */

const $ = window.jQuery;
const simplyin = window.simplyin;

// The pay-again surface's own submit control and the form that holds it. Named once so the
// template's class names appear in one place on this side of the boundary.
const RETRY_FORM = '.simplyin-retry-form';
const RETRY_BUTTON = '.simplyin-retry-button';
const SURFACE_ORDER_RETRY = 'order-retry';

// Narration for a still-open attempt, keyed by the exact status the live poll returns - never by
// how long the poll has been running. A property lookup rather than an equality check against the
// status on purpose, so this outcome is never written as a literal branch condition here - see
// PaymentPollerStatusContractTest, which pins that it is never read as terminal. It stays exactly
// as non-terminal as any other unrecognised status; only the copy beside the spinner changes.
const PENDING_STATUS_MESSAGES = {
    PENDING: 'Waiting for the bank to confirm your payment...',
    AWAITING_CONFIRMATION: 'Confirm the payment in your banking app to finish.',
};

// Copy for a definitive failure, one entry per reason this plugin has safe, non-fraud shopper
// copy for - kept in step with payment_return.tpl's own {if}/{elseif} chain, which renders the
// same four for the page's first load. Every other reason - known to SIN.CORE or not - falls
// through to the generic sentence below, exactly as an unrecognised value would.
const REJECT_REASON_MESSAGES = {
    BLIK_INVALID_CODE: 'The BLIK code you entered was incorrect.',
    CARD_THREE_DS_FAILED: 'Your bank did not confirm the card.',
    AMOUNT_LIMIT_EXCEEDED: 'This payment exceeds an allowed transaction limit.',
    TRANSACTION_TIMEOUT: 'The payment took too long and timed out.',
};

$(function () {
    window.SimplyInPayment = {
        errorMsgBlik: null,
        successMsgBlik: null,
        cc: null,
        successMsg: null,
        errorMsg: null,
        ott_corect: false,
        blik_corect: false,
        inputs: [],
        newImg: false,
        card_interval: null,
        duplicated_card: null,
        // The control that submits the payment. In the checkout it is the theme's own confirmation
        // button; on the pay-again surface it is that surface's own submit button. Every bind and
        // unbind resolves it through here, so both surfaces drive one flow rather than one of them
        // growing a near-copy of the card and BLIK handling.
        confirmationSelector: '#payment-confirmation button',
        confirmationButton: function () {
            return document.querySelector(this.confirmationSelector);
        },
        // The method-selection/BLIK/card-tokenize requests below authenticate with THESE, not with
        // `simplyin.ajax_token`/`simplyin.cart_token` directly - init() and initRetry() each point
        // them at the right pair (checkout-scoped or retry-scoped) before wiring anything. On the
        // order-confirmation page the plain `simplyin.cart_token` is already spoken for by the live
        // payment-status poll, which is cart-specific and must not be touched by the retry form
        // sharing the page - reading through here is what keeps the two from fighting over one slot.
        ajaxToken: null,
        cartToken: null,
        _formInputsWired: false,
        _retryRevealChecked: false,
        // The card/BLIK/method-selection listeners below bind through $(document).on(...) - calling
        // this a second time would double-bind them, not replace the first binding. Today only
        // initRetry() ever calls it (directly, or via initOrderConfirmation()) and the dispatcher
        // below never runs it twice on one page - but the guard lives here, not "don't call twice"
        // left to every caller, so that stays true if a future caller is added.
        wireFormInputs: function () {
            if (this._formInputsWired) return;
            this._formInputsWired = true;
            this.setupCardForm();
            this.setupBlikInputs();
            this.setupPaymentOptionChange();
            this.setupBlikEvents();
        },
        init: function () {
            this.ajaxToken = simplyin.ajax_token;
            this.cartToken = simplyin.cart_token;
            this.inlinePaymentOptionLogo();
            this.wireFormInputs();
            this.bindPaymentMethodChecker();
            this.autoSelectPayment();
        },
        // The pay-again surface: the same paywall, so the same card, BLIK and method-selection
        // behaviour, and none of the rest.
        //
        // What is left out is left out because there is nothing for it to act on: no theme payment
        // options to pull a logo into or to watch for a switch away from Simply.IN. No status poll
        // either - that belongs to init()/initOrderConfirmation(), and is wired separately when the
        // order-confirmation page runs both surfaces side by side.
        //
        // The selection guard is bound up front and released the moment a method is chosen, exactly
        // as in the checkout, so the button cannot submit a form with nothing selected.
        initRetry: function () {
            // Defensive only - the dispatcher owns the decision, from what the server declared.
            if (!document.querySelector(RETRY_FORM)) return;

            this.ajaxToken = simplyin.retry_ajax_token;
            this.cartToken = simplyin.retry_cart_token;
            this.confirmationSelector = RETRY_BUTTON;
            this.wireFormInputs();
            this.bindGeneralConfirmationEvent();
        },
        // The order-confirmation page: the live payment-status poll this hook has always driven, and
        // - now - the pay-again form, rendered only when the order still allows a retry. The two are
        // independent (the poll reads the order's cart-specific token, the form reads the retry
        // token above) so wiring one is never a reason to skip the other; initRetry() itself is a
        // no-op when the form is not in the page's markup.
        initOrderConfirmation: function () {
            this.setupOrderStatusCheck();
            this.initRetry();
        },
        // PaymentOption::setLogo() lets the theme place the logo where it wants — classic wraps it
        // into a second <label> that breaks onto its own line below the caption. Pull it inline in
        // front of the caption (the pre-setLogo look) wherever the theme put it.
        inlinePaymentOptionLogo: function () {
            $('input[type="radio"][data-module-name^="simplyin"]').each(function () {
                const $paymentOption = $(this).closest('.payment-option');
                if (!$paymentOption.length) return;

                const $label = $paymentOption.find('label').first()
                    .css({ display: 'flex', alignItems: 'center', gap: '8px' });
                const $themeLogo = $paymentOption.find('img[src*="/simplyin/views/img/"]').first();
                if (!$themeLogo.length) return;

                $themeLogo.css({ maxHeight: '20px', width: 'auto' });
                if ($label.find('img').length) return;

                const $logoLabel = $themeLogo.closest('label');
                $label.prepend($themeLogo);
                if ($logoLabel.length && !$logoLabel.is($label) && $logoLabel.children().length === 0 && $logoLabel.text().trim() === '') {
                    $logoLabel.remove();
                }
            });
        },
        bindGeneralConfirmationEvent: function () {
            const self = this;
            const button = self.confirmationButton();
            if (!button) return;

            const handler = (e) => {
                const selected = $('.simplyin-payment-option:checked');
                const errorContainer = $('#simplyin-payment-error');

                if (!selected.length) {
                    e.preventDefault();
                    e.stopPropagation();
                    e.stopImmediatePropagation();

                    if (errorContainer) {
                        errorContainer.html(self.l('Please select a payment method'));
                        errorContainer.fadeIn();
                        $('.simplyin-payment-option-item').addClass('select-method');
                    }
                    return false;
                }

                if (errorContainer) {
                    errorContainer.fadeOut();
                }
            };

            if (button._simplyinGeneralHandler) {
                button.removeEventListener('click', button._simplyinGeneralHandler, { capture: true });
            }

            button.addEventListener('click', handler, { capture: true });
            button._simplyinGeneralHandler = handler;
        },

        unbindGeneralConfirmationEvent: function () {
            const button = this.confirmationButton();
            if (button && button._simplyinGeneralHandler) {
                button.removeEventListener('click', button._simplyinGeneralHandler, { capture: true });
                delete button._simplyinGeneralHandler;
            }
        },

        bindPaymentMethodChecker: function () {
            const self = this;
            $(document).on('change', 'input[name=payment-option]', function () {
                if ($(this).data('module-name') && $(this).data('module-name').startsWith('simplyin')) {
                    if ($('.simplyin-payment-option:checked').length == 0) {
                        self.bindGeneralConfirmationEvent();
                        return true;
                    }
                } else {
                    self.unbindGeneralConfirmationEvent();
                    return true;
                }
            });
        },
        l: function (string) {
            if (typeof simplyin.labels !== 'undefined' && simplyin.labels[string]) {
                return simplyin.labels[string];
            }
            return string;
        },
        setupCardForm: function () {
            const self = this;
            if (self.duplicated_card) {
                clearTimeout(self.duplicated_card);
            }
            self.duplicated_card = setTimeout(function () {
                if ($('#simplyin-add-card-payment-method input[name="simplyin-payment-option"]').length && $('#card-number').find('div').length == 0) {
                    let iframe_url = $('#simplyin-add-card-payment-method input[name="simplyin-payment-option"]').data('iframe-url');
                    let integrator_id = $('#simplyin-add-card-payment-method input[name="simplyin-payment-option"]').data('integrator-id');
                    const integratorId = integrator_id;
                    const iframeUrl = iframe_url;

                    const script = document.createElement('script');
                    script.src = iframeUrl;
                    script.onload = () => self.initCardForm(integratorId);
                    document.head.appendChild(script);

                    self.watchCardFocus();
                }
            }, 150);
        },

        watchCardFocus: function () {
            var self = this;
            if (this._cardFocusObserver) {
                this._cardFocusObserver.disconnect();
            }
            var roots = document.querySelectorAll('#card-holder, #card-number, #expiry-date, #verification-code');
            if (!roots.length) {
                return;
            }
            this._cardFocusObserver = new MutationObserver(function () {
                self.hideCardErrors();
            });
            for (var i = 0; i < roots.length; i++) {
                this._cardFocusObserver.observe(roots[i], { attributes: true, attributeFilter: ['class'], subtree: true });
            }
        },
        hideCardErrors: function () {
            if ($('.ccIframe-component.has-focus').length) {
                $(this.errorMsg).hide();
            }
        },
        initCardForm: function (integratorId) {
            try {
                this.cc = CCIframe({ integratorId: integratorId });

                const styleConfig = {
                    fonts: [{
                        family: 'Inter',
                        src: "url(https://fonts.gstatic.com/s/inter/v18/UcCo3FwrK3iLTcviYwYZ8UA3.woff2) format('woff2')",
                        style: 'normal',
                        weight: '100 900',
                    }],
                    styles: {
                        base: {
                            color: 'rgba(0, 0, 0, 0.8)',
                            fontFamily: 'Inter',
                            fontSize: '14px',
                        },
                    },
                };

                this.cc.createComponent('cardHolder', '#card-holder', styleConfig);
                this.cc.createComponent('cardNumber', '#card-number', styleConfig);
                this.cc.createComponent('cardExp', '#expiry-date', styleConfig);
                this.cc.createComponent('cardCvv', '#verification-code', styleConfig);

                this.successMsg = '#card-success-message-1';
                this.errorMsg = '#form-error-1';
            } catch (e) {
            }
        },

        setupBlikInputs: function () {
            this.successMsgBlik = '#blik-success-message-1';
            this.errorMsgBlik = '#blik-form-error-1';
            this.inputs = '.simplyin-blik-digit-input';

            const self = this;

            $(document).on('click', this.inputs, function () {
                if (!$('input[name="simplyin-payment-option"][data-payment-type="blik"]').is(':checked')) {
                    $('input[name="simplyin-payment-option"][data-payment-type="blik"]').prop('checked', true).trigger('change');
                }
            });
            $(document).on('input', this.inputs, function () {
                let $input = $(this);
                const $inputs = $(self.inputs);
                let index = $inputs.index(this);

                let value = $input.val().replace(/\D/g, '');
                $input.val(value.substring(0, 1));

                if (value && index < $inputs.length - 1) {
                    $($inputs[index + 1]).focus();
                }
            });

            $(document).on('keydown', this.inputs, function (e) {
                if (e.key === 'Backspace') {
                    const $input = $(this);
                    const $inputs = $(self.inputs);
                    let index = $inputs.index(this);

                    if (!$input.val() && index > 0) {
                        $($inputs[index - 1]).focus();
                    }
                }
            });

            $(document).on('paste', this.inputs, function (e) {
                e.preventDefault();
                const $inputs = $(self.inputs);
                const $input = $(this);
                let index = $inputs.index(this);

                const paste = (e.originalEvent.clipboardData || window.clipboardData).getData('text').replace(/\D/g, '');
                if (!paste) return;

                [...paste].slice(0, $inputs.length).forEach((digit, i) => {
                    $($inputs[i]).val(digit);
                });

                const lastIndex = Math.min(paste.length, $inputs.length) - 1;
                $($inputs[lastIndex]).focus();
                $($inputs[$inputs.length - 1]).trigger('change');
            });
        },

        getBlikCode: function () {
            return $(this.inputs).map(function () {
                return $(this).val();
            }).get().join('');
        },

        checkBlikCode: function () {
            const self = this;
            let blik_code = this.getBlikCode();
            let result = (blik_code.length === 6);
            return result;
        },
        setPaymentsTerm: function (target) {
            const self = this;
            let regulationsUrl = target.data('regulationsurl');
            let gdprUrl = target.data('gdprurl');
            let provider = target.data('payment-provider-name');

            let terms_string = self.l('By paying, I accept the terms and [conditions] and [privacy policy] of %s');

            terms_string = terms_string.replace(/\[(.*?)\]/, `<a class="simplyin-payment-provider-url" href="${regulationsUrl}" target="_blank">$1</a>`);
            terms_string = terms_string.replace(/\[(.*?)\]/, `<a class="simplyin-payment-provider-url" href="${gdprUrl}" target="_blank">$1</a>`);

            terms_string = terms_string.replace('%s', provider);

            $('.simplyin-payment-provider-footer').html(
                `<p>${terms_string}</p>`
            );
        },
        setupPaymentOptionChange: function () {
            const self = this;
            $(document).on('change', '#simplyin-save-card', function () {
                if ($(this).is(':checked')) {
                    $('.simplyin-card-name').fadeIn();
                } else {
                    $('.simplyin-card-name').fadeOut();
                }
            });
            $(document).on('change', 'input[name="simplyin-payment-option"]', (e) => {
                let target = $(e.currentTarget);
                let payment_type = target.data('payment-type');
                let params = {
                    action: 'selectPaymentMethod',
                    ajax: 1,
                    ajax_token: self.ajaxToken,
                    cart_token: self.cartToken,
                    payment_id: target.val(),
                    payment_type: payment_type,
                    payment_provider: target.data('payment-provider-name'),
                    provider_image: target.data('provider-image'),
                    logo: target.data('logo'),
                };
                self.setPaymentsTerm(target);
                if (payment_type === 'card') {
                    params.card_name = target.data('card-name');
                    params.last_digits = target.data('last-digits');
                    params.card_provider = target.data('card-provider');
                    this.unbindCardConfirmationEvents();
                    this.unbindBlikConfirmationEvents();
                } else if (payment_type === 'blik') {
                    this.unbindCardConfirmationEvents();
                    this.bindBlikConfirmationEvents();
                } else if (payment_type === 'add-card') {
                    this.bindCardConfirmationEvents();
                    this.unbindBlikConfirmationEvents();
                }

                $.ajax({
                    url: simplyin.controllers.payment_ajax,
                    method: 'POST',
                    data: params,
                    dataType: 'json',
                });
                self.unbindGeneralConfirmationEvent();
                $('#simplyin-payment-error').hide();
                $('.simplyin-payment-option-item').removeClass('select-method');
            });
        },

        setupBlikEvents: function () {
            const self = this;
            $(document).on('focus', '.simplyin-blik-digit-input', () => {
                $('.simplyin-blik-digit-input').removeClass('valid error');
                $('.simplyin-blik-error').hide();
            });

            $(document).on('change', 'input[name="simplyin-payment-option"]', function () {
                if ($(this).data('payment-type') !== 'blik') {
                    $('.simplyin-blik-container').hide();
                    $('.simplyin-blik-digit-input').removeClass('valid error').val('');
                    $('#blik-form-error-1').hide();
                } else {
                    $('.simplyin-blik-container').show();
                }
            });

            $(document).on('keyup', '.simplyin-blik-digit-input', () => {
                let blik_code = this.getBlikCode();
                self.checkBlikCode();
                if (blik_code.length === 6) {
                    let params = {
                        action: 'enterBlikCode',
                        ajax: 1,
                        ajax_token: self.ajaxToken,
                        cart_token: self.cartToken,
                        blik_code: blik_code,
                    };
                    $('.simplyin-blik-digit-input').addClass('valid');
                    $('.simplyin-blik-error').hide();
                    $.ajax({
                        url: simplyin.controllers.payment_ajax,
                        method: 'POST',
                        data: params,
                        dataType: 'json',
                    }).done(data => {
                        if (data.status === 'success') {
                            $('.simplyin-blik-error').hide();
                        }
                    });
                }
            });

            $(document).on('focusout', '.simplyin-blik-inputs-grid', (e) => {
                if ($(e.relatedTarget).closest('.simplyin-blik-inputs-grid').length) {
                    return;
                }
                if (this.checkBlikCode() === false) {
                    $('.simplyin-blik-digit-input').removeClass('valid').addClass('error');
                    $('.simplyin-blik-error').show();
                } else {
                    $('.simplyin-blik-digit-input').removeClass('error').addClass('valid');
                    $('.simplyin-blik-error').hide();
                }
            });
        },

        bindCardConfirmationEvents: function () {
            const self = this;
            const button = self.confirmationButton();
            if (!button) return;
            const handler = (e) => {
                if (this.ott_corect) return true;
                e.preventDefault();
                e.stopPropagation();
                e.stopImmediatePropagation();

                $(this.errorMsg).hide();
                $(this.successMsg).hide();

                this.cc.createToken().then((res) => {
                    if (res && res.oneTimeToken) {
                        $(this.successMsg).show();
                        let params = {
                            action: 'selectPaymentMethod',
                            ajax: 1,
                            ajax_token: self.ajaxToken,
                            cart_token: self.cartToken,
                            payment_id: $('input[name="simplyin-payment-option"]:checked').val(),
                            payment_type: 'add-card',
                            payment_provider: $('input[name="simplyin-payment-option"]:checked').data('payment-provider-name'),
                            provider_image: $('input[name="simplyin-payment-option"]:checked').data('provider-image'),
                            logo: $('input[name="simplyin-payment-option"]:checked').data('logo'),
                        };
                        if ($('#simplyin-save-card').is(':checked')) {
                            params.card_name = $('#card-name').val();
                            params.card_save = '1';
                        } else {
                            params.card_name = '';
                            params.card_save = '0';
                        }
                        params.last_digits = res.last4digits;
                        params.card_provider = res.organization;
                        params.ott = res.oneTimeToken;
                        this.unbindCardConfirmationEvents();
                        $.ajax({
                            url: simplyin.controllers.payment_ajax,
                            method: 'POST',
                            data: params,
                            dataType: 'json',
                        }).done(() => {
                            this.ott_corect = true;
                            setTimeout(() => {
                                // Explicit null check rather than `?.`: this bundle ships
                                // untranspiled, so syntax a browser cannot parse takes the WHOLE
                                // payment script down - no card iframe, no BLIK, no method
                                // selection - instead of failing just this line. No shipped bundle
                                // in this module uses optional chaining; keep it that way.
                                const button = self.confirmationButton();
                                if (button) {
                                    button.click();
                                }
                            }, 300);
                        });
                    } else {
                        $(this.errorMsg).text(self.l('Invalid tokenization response'));
                        $(this.errorMsg).show();
                    }
                }).catch((err) => {
                    $(this.errorMsg).text(self.l('Problem processing card'));
                    $(this.errorMsg).show();
                });
            };

            button.addEventListener('click', handler, { capture: true });
            button._simplyinHandler = handler;
        },

        unbindCardConfirmationEvents: function () {
            const button = this.confirmationButton();
            if (button && button._simplyinHandler) {
                button.removeEventListener('click', button._simplyinHandler, { capture: true });
                delete button._simplyinHandler;
            }
        },

        bindBlikConfirmationEvents: function () {
            const self = this;
            const button = self.confirmationButton();
            if (!button) return;

            const handler = (e) => {
                if (this.blik_corect) return true;
                e.preventDefault();
                e.stopPropagation();
                e.stopImmediatePropagation();

                $(this.errorMsgBlik).hide();
                $(this.successMsgBlik).hide();

                if (!this.checkBlikCode()) {
                    $(this.errorMsgBlik).text(self.l('Invalid BLIK code'));
                    $(this.errorMsgBlik).show();
                    return;
                }

                let blik_code = this.getBlikCode();

                let params = {
                    action: 'selectPaymentMethod',
                    ajax: 1,
                    ajax_token: self.ajaxToken,
                    cart_token: self.cartToken,
                    payment_id: $('input[name="simplyin-payment-option"]:checked').val(),
                    payment_type: 'blik',
                    blik_code: blik_code,
                    payment_provider: $('input[name="simplyin-payment-option"]:checked').data('payment-provider-name'),
                    provider_image: $('input[name="simplyin-payment-option"]:checked').data('provider-image'),
                    logo: $('input[name="simplyin-payment-option"]:checked').data('logo'),
                };

                this.unbindBlikConfirmationEvents();

                $.ajax({
                    url: simplyin.controllers.payment_ajax,
                    method: 'POST',
                    data: params,
                    dataType: 'json',
                }).done((data) => {
                    if (data.success === true) {
                        this.blik_corect = true;
                        setTimeout(() => {
                            // Explicit null check, for the reason given at the card-confirmation
                            // site above: no optional chaining in a bundle that ships as written.
                            const button = self.confirmationButton();
                            if (button) {
                                button.click();
                            }
                        }, 300);
                    } else {
                        $(this.errorMsgBlik).text(self.l('BLIK code rejected'));
                        $(this.errorMsgBlik).show();
                    }
                }).fail(() => {
                    $(this.errorMsgBlik).text(self.l('Problem with BLIK processing'));
                    $(this.errorMsgBlik).show();
                });
            };

            button.addEventListener('click', handler, { capture: true });
            button._simplyinBlikHandler = handler;
        },

        unbindBlikConfirmationEvents: function () {
            const button = this.confirmationButton();
            if (button && button._simplyinBlikHandler) {
                button.removeEventListener('click', button._simplyinBlikHandler, { capture: true });
                delete button._simplyinBlikHandler;
            }
        },

        setupOrderStatusCheck: function () {
            var self = this;
            if (typeof (simplyin.order_sended) !== 'undefined' && !simplyin.order_status_paid) {
                self.setupConfirmationHeading();
                if (simplyin.payment_status == 'PAID') {
                    $('#status-success').show().css('display', 'flex');
                    $('#status-pending').hide();
                    self.applyConfirmationHeading(true);
                    return;
                }
                $('#status-pending').show().css('display', 'flex');
                self.applyConfirmationHeading(false);
                var checkCount = 0;
                var maxChecks = 100;
                // Giving up after maxChecks is not a definitive outcome - it is the poll admitting it
                // does not know. The pay-again surface must never be revealed on that basis, only on
                // an actual REJECTED/FAILED verdict below, so this branch stays exactly what it was:
                // the error box shown, nothing asked about a retry.
                let check_status_interval = setInterval(() => {
                    checkCount++;
                    if (checkCount > maxChecks) {
                        clearInterval(check_status_interval);
                        $('#status-error').show().css('display', 'flex');
                        $('#status-pending').hide();
                        return;
                    }
                    let params = {
                        id_cart: simplyin.id_cart,
                        ajax: 1,
                        ajax_token: simplyin.ajax_token,
                        cart_token: simplyin.cart_token,
                        action: 'checkOrderStatus',
                        id_module: simplyin.id_module,
                        payment_check_token: simplyin.payment_check_token,
                    };
                    $.ajax({
                        url: simplyin.controllers.payment_ajax,
                        method: 'POST',
                        data: params,
                        dataType: 'json',
                    }).done(data => {
                        if (data.status === 'PAID') {
                            $('#status-success').show().css('display', 'flex');
                            $('#status-pending').hide();
                            self.applyConfirmationHeading(true);
                            clearInterval(check_status_interval);
                        } else if (data.status == 'REJECTED' || data.status == 'FAILED' || data.status == 'FAILD') {
                            $('#status-error').show().css('display', 'flex');
                            $('#status-pending').hide();
                            clearInterval(check_status_interval);
                            self.showRejectReason(data.reject_reason);
                            self.checkRetryReveal();
                        } else {
                            self.showPendingMessage(data.status);
                        }
                    });
                }, 3000);
            }
        },
        // The order-confirmation heading: "your order is confirmed", with a tick, is a
        // claim about payment - one the theme makes unconditionally, before the order is actually
        // paid. Driven by the exact same state as the status widget above (unpaid → "order placed",
        // no tick; paid → back to the theme's own confirmed wording), never a second source of truth.
        //
        // Merchant-toggleable (default ON) and fail-safe on the theme's markup: only classic's own
        // known shape - one `.card-title` inside `#content-hook_order_confirmation`, holding one icon
        // element and one plain-text label - is touched. Anything else (a customised theme, a
        // renamed block, more than one heading) is left untouched rather than guessed at; a stale
        // "confirmed" heading on an unpaid order is a copy problem, mangling a merchant's theme
        // markup is a support ticket.
        setupConfirmationHeading: function () {
            this._headingIcon = null;
            this._headingTextNode = null;
            if (!simplyin.confirmation_heading_override) return;

            try {
                var container = document.getElementById('content-hook_order_confirmation');
                if (!container) return;

                var headings = container.querySelectorAll('.card-title');
                if (headings.length !== 1) return;

                var heading = headings[0];
                var textNode = null;
                for (var i = 0; i < heading.childNodes.length; i++) {
                    var node = heading.childNodes[i];
                    if (node.nodeType === 3 && node.textContent.trim() !== '') {
                        textNode = node;
                        break;
                    }
                }
                if (!textNode) return;

                this._headingIcon = heading.querySelector('i.material-icons') || null;
                this._headingTextNode = textNode;
            } catch (e) {
                // Never let a heading lookup break the confirmation page.
                this._headingIcon = null;
                this._headingTextNode = null;
            }
        },
        applyConfirmationHeading: function (paid) {
            if (!simplyin.confirmation_heading_override || !this._headingTextNode) return;

            try {
                if (this._headingIcon) {
                    this._headingIcon.style.display = paid ? '' : 'none';
                }
                this._headingTextNode.textContent = paid
                    ? this.l('Order confirmed')
                    : this.l('Order placed');
            } catch (e) {
                // Never let this break the confirmation page.
            }
        },

        // While the attempt is still open: PENDING and AWAITING_CONFIRMATION are the two the poll
        // can tell apart from the status alone; any other status (including one this list has not
        // caught up with) leaves the existing text as it is rather than blanking it.
        showPendingMessage: function (status) {
            var self = this;
            var message = PENDING_STATUS_MESSAGES[status];
            if (message) {
                $('#blik-status-message').text(self.l(message));
            }
        },

        // On a definitive failure: the reason beside the red marker when it is one of the few this
        // plugin has safe copy for, the existing generic sentence otherwise. Targets the static
        // fallback only - when the pay-again surface rendered its own copy instead, this id is not
        // in the page at all and the lookup is a silent no-op.
        showRejectReason: function (reason) {
            var self = this;
            var message = (reason && REJECT_REASON_MESSAGES[reason])
                ? REJECT_REASON_MESSAGES[reason]
                : 'This order has not been paid.';
            $('#simplyin-status-error-message').text(self.l(message));
        },

        // Reveals the pay-again surface once the poll above has learned of a DEFINITIVE failure -
        // never called from the timeout branch. Does not decide anything itself: it re-asks the
        // server for the same verdict OrderRetryRenderer/OrderPaymentRetry::decisionFor() already
        // renders everywhere else, and only ever displays what that answer says.
        //
        // Idempotent by construction, not by a second flag layered on top of it: `_retryRevealChecked`
        // stops this firing more than once per page load, and the check for an already-present
        // RETRY_FORM stops it firing at all when the decision was already `retry_allowed` at render
        // time - wireFormInputs()'s own guard is what actually prevents a double-bind if this ever
        // ran twice regardless.
        //
        // Fails silent everywhere: no container, no tokens, a network error or a malformed response
        // all leave the page exactly as it was before this ran.
        checkRetryReveal: function () {
            var self = this;
            if (self._retryRevealChecked) return;
            self._retryRevealChecked = true;

            try {
                var $container = $('.simplyin-retry');
                if (!$container.length || $(RETRY_FORM).length) {
                    return;
                }

                var idOrder = $container.data('order-id');
                var retryToken = $container.data('retry-token');
                if (!idOrder || !retryToken) {
                    return;
                }

                $.ajax({
                    url: simplyin.controllers.payment_ajax,
                    method: 'POST',
                    data: {
                        action: 'checkRetryDecision',
                        ajax: 1,
                        ajax_token: simplyin.retry_ajax_token,
                        cart_token: simplyin.retry_cart_token,
                        id_order: idOrder,
                        retry_token: retryToken,
                    },
                    dataType: 'json',
                }).done(function (data) {
                    try {
                        if (data && typeof data.html === 'string' && data.html !== '') {
                            $container.replaceWith(data.html);
                            self.initRetry();
                        }
                    } catch (e) {
                        // Malformed response - leave the page exactly as it was.
                    }
                });
            } catch (e) {
                // Never let a reveal attempt break the confirmation page.
            }
        },

        autoSelectPayment: function () {
            const self = this;
            if ($('input[type="radio"][data-module-name^="simplyin"]').length) {
                $('input[type="radio"][data-module-name^="simplyin"]').prop('checked', true).trigger('change');
            }
        },
    };

    // Which surface this is comes from the server, which already decided it when it chose what to
    // render and what to emit here - not from the markup, where a renamed class would silently
    // disable the page's behaviour with no error. The checkout initialization is not run on the
    // order page or the order-confirmation page: neither has the theme's own confirmation button
    // this branch's init() targets.
    if (!simplyin) {
        // No header payload reached the page; nothing below can be wired. Leaving the surface
        // server-rendered and inert is the safe direction - the shopper still sees the order.
    } else if (simplyin.surface === SURFACE_ORDER_RETRY) {
        window.SimplyInPayment.initRetry();
    } else if (simplyin.controller === 'orderconfirmation') {
        window.SimplyInPayment.initOrderConfirmation();
    } else if (simplyin.checkout == 'classic' || simplyin.checkout == 'hummingbird') {
        window.SimplyInPayment.init();
    }
});
