let flowClicked = false;
let currentSubscription = null;

// Fallbacks for every customizable string. The resolved texts normally arrive
// on churn_ajax_obj.texts, but a browser holding a cached copy of this file
// from before an upgrade would not have them, so never render undefined.
const CHURN_SOLUTION_DEFAULT_TEXTS = {
    button_skip: 'Skip this %s',
    button_pause: 'Pause Subscription',
    button_resume: 'Resume Subscription Now',
    button_cancel: 'Cancel Subscription',
    button_manage: 'Manage Subscription',
    invoice_after_discount: 'after discount',
    invoice_after_taxes: 'after taxes',
    invoice_paused_until: 'Paused until',
    error_cancel_failed: 'Failed to cancel. Please contact support.'
};

/**
 * Resolves a customizable string, substituting the single %s placeholder when
 * a replacement is given. A merchant who removed the placeholder simply gets
 * their string back unchanged.
 */
function churnText(key, replacement) {
    const texts = (typeof churn_ajax_obj !== 'undefined' && churn_ajax_obj.texts) || {};
    const value = texts[key] || CHURN_SOLUTION_DEFAULT_TEXTS[key] || '';

    if (replacement === undefined) {
        return value;
    }

    return value.replace('%s', replacement);
}

/**
 * Escapes merchant-supplied text for the few places where it has to be
 * interpolated into an HTML string rather than set as a text node.
 */
function escapeChurnHtml(value) {
    return String(value).replace(/[&<>"']/g, function (char) {
        return {'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'}[char];
    });
}

function handleChurnSolutionRewriteCancelButton($) {
    var elements = $('a[href*="levelstocancel"], a.woocommerce-button.cancel');
    if (elements.length > 0) {
        elements.each(function () {
            var element = $(this);
            element.on('click', blockDefaultCancellation)
        });
        checkConnection($, elements);
    }
}

function applyMediaQueryStylesToWrapper(buttonEl, wrapperEl, excludeProps = []) {
    const excluded = new Set(excludeProps.map(p => p.toLowerCase()));
    const sheets = Array.from(document.styleSheets);

    for (const sheet of sheets) {
        let rules;
        try {
            rules = Array.from(sheet.cssRules || []);
        } catch {
            continue;
        }

        for (const rule of rules) {
            if (rule instanceof CSSMediaRule) {
                const mediaRules = Array.from(rule.cssRules);
                const matchingStyles = mediaRules
                    .filter(r => r instanceof CSSStyleRule && buttonEl.matches(r.selectorText))
                    .map(r => r.style);

                if (matchingStyles.length === 0) continue;

                const styleLines = matchingStyles.flatMap(style =>
                    Array.from(style)
                        .filter(prop => !excluded.has(prop.toLowerCase()))
                        .map(prop => `  ${prop}: ${style.getPropertyValue(prop)};`)
                );

                if (styleLines.length === 0) continue;
                if (!wrapperEl.dataset.mqId) {
                    wrapperEl.dataset.mqId = `wcs-wrapper-${Math.random().toString(36).slice(2, 8)}`;
                }

                const selector = `[data-mq-id="${wrapperEl.dataset.mqId}"]`;
                const newRule = `@media ${rule.conditionText} {\n${selector} {\n${styleLines.join('\n')}\n}\n}`;

                let styleTag = document.getElementById('wcs-mq-overrides');
                if (!styleTag) {
                    styleTag = document.createElement('style');
                    styleTag.id = 'wcs-mq-overrides';
                    document.head.appendChild(styleTag);
                }

                styleTag.textContent += newRule + '\n';
            }
        }
    }
}

function replaceCancellationButton($, subscription, query, appId, element) {
    if (subscription.status === "cancelled") return;
    // can_pause is false for a suspended subscription that has never taken a payment.
    // Both Skip and Pause run as a pause behind the scenes, so both have to go — the
    // backend would reject them anyway, and offering an action that always fails is
    // worse than not offering it. Older plugin versions omit the flag, so treat a
    // missing value as allowed.
    const canPause = subscription.metadata.can_pause !== false;

    const actions = [
        {
            visible: canPause && !subscription.metadata.churnsolution_pause && !subscription.metadata.scheduled_to_be_cancelled,
            title: churnText('button_skip', subscription.billing_interval == 1 ? subscription.billing_period : "period"),
            callback: () => triggerChurnSolutionFlow($, subscription, query, appId, "skip")
        },
        {
            visible: canPause && !subscription.metadata.churnsolution_pause && !subscription.metadata.scheduled_to_be_cancelled,
            title: churnText('button_pause'),
            callback: () => triggerChurnSolutionFlow($, subscription, query, appId, "pause")
        },
        {
            visible: subscription.metadata.churnsolution_pause,
            title: churnText('button_resume'),
            callback: () => triggerChurnSolutionFlow($, subscription, query, appId, "resume")
        },
        {
            visible: !subscription.metadata.scheduled_to_be_cancelled,
            title: churnText('button_cancel'),
            callback: () => triggerChurnSolutionFlow($, subscription, query, appId)
        },
    ];
    const visibleActions = actions.filter(({ visible }) => visible);

    if (visibleActions.length === 0) {
        element.remove();
        return;
    }

    const classes = element.attr('class').split(/\s+/).filter(cls => !["cancel", "wcs_block_ui_on_click"].includes(cls));

    if (visibleActions.length === 1) {
        const [action] = visibleActions;
        const button = $('<button type="button"></button>').text(action.title).addClass(classes);
        button.on('click', function (e) {
            e.preventDefault();
            e.stopPropagation();
            action.callback();
        });
        element.replaceWith(button);
        return;
    }

    const chevronSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>';
    const wrapper = $('<div class="wcs-manage-wrapper"></div>');
    // Titles are merchant-supplied, so they are set as text nodes rather than
    // interpolated into the markup.
    const button = $('<a href="#" role="button" class="wcs-manage-button"></a>')
        .text(churnText('button_manage'))
        .append(chevronSvg)
        .addClass(classes);
    const dropdown = $('<div class="wcs-manage-dropdown" role="menu"></div>');

    visibleActions.forEach(({ title }) => {
        dropdown.append(
            $('<button type="button" class="wcs-manage-dropdown__item" role="menuitem"></button>').text(title)
        );
    });
    wrapper.css("margin", element.css("margin")).append(button).append(dropdown);
    element.replaceWith(wrapper);

    dropdown.find('.wcs-manage-dropdown__item').each(function (index) {
        $(this).on('click', function (e) {
            e.preventDefault();
            e.stopPropagation();
            wrapper.removeClass('wcs-open');
            visibleActions[index].callback();
        });
    });

    button.on('click', function (e) {
        e.preventDefault();
        e.stopPropagation();
        wrapper.toggleClass('wcs-open');
    });

    applyMediaQueryStylesToWrapper(button[0], wrapper[0], [
        "padding-left",
        "padding-right",
        "padding-top",
        "padding-bottom",
    ]);
}

function checkConnection($, elements) {
    $.ajax({
        url: `${churn_ajax_obj.base_url}/wp-json/churnsolution/v1/connection`,
        type: 'GET',
        dataType: 'json',
        beforeSend: function (xhr) {
            xhr.setRequestHeader('X-WP-Nonce', churn_ajax_obj.nonce);
        },
        success: function (response) {
            if (response?.app_id) {
                clickFlow($, elements, response.app_id);
            } else {
                console.error("Response does not contain appId");
                revealCancelButtons();
            }
        },
        error: function (jqXHR, textStatus, errorThrown) {
            console.error(jqXHR);
            elements.each(function () {
                var element = $(this);
                var href = element.attr('href');
                if (href?.includes('subscription_id')) {
                    element.addClass("wcs_block_ui_on_click");
                }
            });
            revealCancelButtons();
        },
        complete: function () {
            elements.each(function () {
                var element = $(this);
                element.off('click', blockDefaultCancellation);
            });
        }
    });
}

function clickFlow($, elements, appId) {
    elements.each(function () {
        const element = $(this);
        const href = element.attr('href'); // Get the href attribute
        const tagName = element.prop("tagName").toLowerCase();

        if (tagName !== 'a' || (href && !href?.includes('levelstocancel') && !href?.includes('subscription_id'))) {
            return;
        }

        const urlParams = new URLSearchParams(element.prop("search"));
        const levelstocancel = urlParams.get('levelstocancel');
        const subscriptionId = urlParams.get('subscription_id');
        const query = getRequestQuery({
            levelId: levelstocancel,
            subscriptionId,
        })

        element.on('click', function (e) {
            e.preventDefault();
            element.removeClass("wcs_block_ui_on_click");

            if (!query) {
                console.error("There is no level value to cancel");
                return;
            }

            if (flowClicked) return;
            flowClicked = true;

            if (currentSubscription) {
                triggerChurnSolutionFlow($, currentSubscription, query, appId);
                return;
            }

            checkSubscription($, query, appId, href, {
                success: ($, response, query, appId) => triggerChurnSolutionFlow($, response, query, appId),
                error: () => { flowClicked = false },
            }, $(this));
        });

        const isWooCommeerce = ["woocommerce-button", "cancel"].every(cls => element.hasClass(cls));
        if (isWooCommeerce) {
            checkSubscription($, query, appId, href, {
                success: ($, subscription) => {
                    currentSubscription = subscription;
                    replaceCancellationButton($, subscription, query, appId, element);
                },
                complete: () => revealCancelButtons(),
            }, $(this));
        } else {
            revealCancelButtons();
        }
    });
}

function checkSubscription($, query, appId, clickedUrl, callback, clickedElement) {
    $.ajax({
        url: `${churn_ajax_obj.base_url}/wp-json/churnsolution/v1/subscription/${query}`,
        type: 'GET',
        dataType: 'json',
        beforeSend: function (xhr) {
            xhr.setRequestHeader('X-WP-Nonce', churn_ajax_obj.nonce);
        },
        success: function (response) {
            if (response?.external_id && response?.status !== 'cancelled') {
                callback?.success?.($, response, query, appId, clickedElement);
            } else {
                console.error("Response does not contain subscription Id");
                if (clickedUrl) {
                    window.location.href = clickedUrl;
                }
            }
        },
        error: function (jqXHR, textStatus, errorThrown) {
            console.error(jqXHR);
            callback?.error?.(jqXHR, textStatus, errorThrown);
        },
        complete: function (jqXHR, textStatus) {
            callback?.complete?.(jqXHR, textStatus);
        }
    });
}

function triggerChurnSolutionFlow($, subscription, query, appId, method = "run") {
    const subscriptionId = subscription.external_id;
    // An on-hold subscription's next payment date is the moment its charge failed, so
    // it is in the past. The modal uses this to show when the pause would resume from,
    // and the backend anchors the pause on max(now, next payment) — so quoting the stale
    // date would promise a resume date that has already been and gone. Use now instead.
    const nextPaymentDate = subscription.status === "on-hold"
        ? new Date().toISOString()
        : (subscription.next_payment_date
            ? `${subscription.next_payment_date}Z`.replace(/Z+$/g, "Z")
            : null);
    const subscriptionEndDate =  (subscription.end_date
        ? `${subscription.end_date}Z`.replace(/Z+$/g, "Z")
        : null);
    let pauseSettings = null;
    nextPaymentDate && (pauseSettings = { ...pauseSettings, nextPaymentDate });
    subscriptionEndDate && (pauseSettings = { ...pauseSettings, subscriptionEndDate });
    $.ajax({
        url: churn_ajax_obj.ajax_url,
        type: 'POST',
        data: {
            action: 'churnsolution_get_hmac_hash',
            nonce: churn_ajax_obj.nonce,
            subscription_id: subscriptionId
        },
        beforeSend: function (xhr) {
            xhr.setRequestHeader('X-WP-Nonce', churn_ajax_obj.nonce);
        },
        success: function (response) {
            window.churnSolution?.[method](
                response,
                subscriptionId,
                appId,
                {
                    ...getChurnSolutionCallbacks($, query),
                    // No customTexts here: the SDK resolves modal labels as
                    // `chrTranslations.CF_MODAL_X || chrGlobalText.X`, and every
                    // CF_MODAL_* default is non-empty, so customTexts can never
                    // win. Those labels live in the Churn Solution app under
                    // Settings > Configuration > Multilingual and Text Overrides.
                    ...(["run", "pause"].includes(method) && pauseSettings && {
                        pauseSettings,
                    })
                }
            );
        },
        error: function (xhr, status, error) {
            // Deliberately no fallback to the account page's own cancel link. That link cancels on
            // GET with no confirmation, so following it would turn a failed launch into the
            // unattended cancellation this product exists to intercept. Releasing the click lets
            // the customer retry.
            console.error(xhr.responseText);
            flowClicked = false;
        }
    });
}

function getChurnSolutionCallbacks($, query) {
    const isWooCommerce = typeof query === "string" && query.includes("subscription_id");
    return {
        handleCancel: isWooCommerce ? async () => {} : async () => {
            await new Promise((resolve, reject) => {
                handleSubscriptionCancel($, query, resolve, reject);
            });
        },
        handleClose: () => {
            flowClicked = false;
        },
        handleDone: () => {
            flowClicked = false;
            syncSubscription($, () => {
                location.reload();
            });
        },
        handleAbandon: () => {
            churnSolution.clearState();
        },
        handlePause: async ({pausePeriod}, customer) => {
            // await new Promise((resolve, reject) => {
            //     handlePaidMembershipProPause($, query, pausePeriod, resolve, reject);
            // });
        },
        handleSwitchPlan: () => {
        },
        handleTrialExtension: async ({trialExtensionDays}, customer) => {
            // await new Promise((resolve, reject) => {
            //     handlePaidMembershipProExtend($, query, trialExtensionDays, resolve, reject);
            // });
        },
        handleCoupon: async ({code}, customer) => {
            // await new Promise((resolve, reject) => {
            //     handlePaidMembershipProApplyCoupon($, query, code, resolve, reject);
            // });
        },
        autoCancel: isWooCommerce ? true : false,
        autoPause: true,
        autoSwitchPlan: true,
        autoTrialExtension: true,
        autoCoupon: true
    };
}

function handleSubscriptionCancel($, query, resolve, reject) {
    $.ajax({
        url: `${churn_ajax_obj.base_url}/wp-json/churnsolution/v1/subscription/cancel/${query}`,
        type: 'POST',
        data: {},
        beforeSend: function (xhr) {
            xhr.setRequestHeader('X-WP-Nonce', churn_ajax_obj.nonce);
        },
        success: function (response, textStatus, xhr) {
            if (resolve) {
                resolve({message: ''});
            }
        },
        error: function (xhr, status, error) {
            console.error(xhr);
            if (xhr.status === 200 && xhr.responseText === "" && resolve) {
                resolve({message: ''});
            } else {
                if (reject) {
                    reject({message: churnText('error_cancel_failed')});
                }
            }
        }
    });
}

function handlePaidMembershipProApplyCoupon($, query, code, resolve, reject) {
    $.ajax({
        url: `${churn_ajax_obj.base_url}/wp-json/churnsolution/v1/subscription/apply-coupon/${query}`,
        type: 'POST',
        data: {
            coupon_code: code
        },
        beforeSend: function (xhr) {
            xhr.setRequestHeader('X-WP-Nonce', churn_ajax_obj.nonce);
        },
        success: function (response, textStatus, xhr) {
            if (resolve) {
                resolve({message: ''});
            }
        },
        error: function (xhr, status, error) {
            console.error(xhr);
            if (xhr.status === 200 && xhr.responseText === "" && resolve) {
                resolve({message: ''});
            } else {
                if (reject) {
                    reject({message: 'Failed to apply coupon. Please contact support.'});
                }
            }
        }
    });
}

function handlePaidMembershipProPause($, query, pausePeriod, resolve, reject) {
    $.ajax({
        url: `${churn_ajax_obj.base_url}/wp-json/churnsolution/v1/subscription/pause/${query}`,
        type: 'POST',
        data: {
            number_of_months: pausePeriod
        },
        beforeSend: function (xhr) {
            xhr.setRequestHeader('X-WP-Nonce', churn_ajax_obj.nonce);
        },
        success: function (response, textStatus, xhr) {
            if (resolve) {
                resolve({message: ''});
            }
        },
        error: function (xhr, status, error) {
            console.error(xhr);
            if (xhr.status === 200 && xhr.responseText === "" && resolve) {
                resolve({message: ''});
            } else {
                if (reject) {
                    reject({message: 'Failed to apply coupon. Please contact support.'});
                }
            }
        }
    });
}

function handlePaidMembershipProExtend($, query, trialExtensionDays, resolve, reject) {
    $.ajax({
        url: `${churn_ajax_obj.base_url}/wp-json/churnsolution/v1/subscription/extend-free-trial/${query}`,
        type: 'POST',
        data: {
            number_of_days: trialExtensionDays
        },
        beforeSend: function (xhr) {
            xhr.setRequestHeader('X-WP-Nonce', churn_ajax_obj.nonce);
        },
        success: function (response, textStatus, xhr) {
            if (resolve) {
                resolve({message: ''});
            }
        },
        error: function (xhr, status, error) {
            console.error(xhr);
            if (xhr.status === 200 && xhr.responseText === "" && resolve) {
                resolve({message: ''});
            } else {
                if (reject) {
                    reject({message: 'Failed to apply coupon. Please contact support.'});
                }
            }
        }
    });
}

function syncSubscription($, resolve) {
    return $.ajax({
        url: `${churn_ajax_obj.base_url}/wp-json/churnsolution/v1/subscription/sync-subscription`,
        type: 'POST',
        data: {},
        beforeSend: function (xhr) {
            xhr.setRequestHeader('X-WP-Nonce', churn_ajax_obj.nonce);
        },
        success: function (response, textStatus, xhr) {
            if (resolve) {
                resolve();
            }
        },
        error: function (xhr, status, error) {
            console.error(xhr);
        }
    });
}

function handleUpcomingInvoice($) {
    $('div[id*=pmpro_account-membership-]').each(function () {

        const cancelLink = $(this).find('a[href*="levelstocancel"]');

        if (cancelLink.length) {
            const urlParams = new URLSearchParams(new URL(cancelLink.attr('href')).search);
            const levelToCancel = urlParams.get('levelstocancel');
            const query = getRequestQuery({ levelId: levelToCancel });
            checkSubscription($, query, '', null, {success: replaceUpcomingInvoiceAndNextPaymentDate}, $(this));
        }
    });
}

function replaceUpcomingInvoiceAndNextPaymentDate($, subscription, query, appId, el) {
    const upcomingAmount = subscription?.upcoming_invoice?.amount_due;
    const upcomingAmountBeforeTaxes = subscription?.upcoming_invoice?.total_excluding_tax;
    const tax = subscription?.upcoming_invoice?.tax;
    const totalDiscounts = subscription?.upcoming_invoice?.total_discounts;
    const pauseCollection = subscription?.pause_collection;
    const currencySymbol = subscription?.upcoming_invoice?.currency_symbol;
    if (el && upcomingAmount >= 0) {
        const itemLabels = el.find(".pmpro_list_item_label");
        itemLabels.each(function () {
            const itemLabel = $(this);

            if (itemLabel.text() == "Subscription") {
                const itemValue = itemLabel.siblings(".pmpro_list_item_value");
                const valueText = itemValue.text();
                const valueTextMatch = valueText.match(/\d+(\.\d+)?/);

                if (valueTextMatch) {
                    const originalAmount = parseFloat(valueTextMatch[0]);
                    const newAmount = parseFloat((parseFloat(upcomingAmount)).toFixed(2));
                    const newAmountBeforeTaxes = parseFloat((parseFloat(upcomingAmountBeforeTaxes)).toFixed(2));

                    const isDiscounted = Boolean(totalDiscounts);
                    const isTaxed = Boolean(tax);
                    const isSameFinalPrice = originalAmount === newAmount;

                    if (!isSameFinalPrice && ((isDiscounted && newAmount < originalAmount) || isTaxed )) {
                        let textTemplate = `${originalAmount.toFixed(2)}`;

                        if(isDiscounted){
                            textTemplate = `<s>${textTemplate}</s>`;
                        }

                        if (isDiscounted) {
                            textTemplate +=
                                `<div></div><strong>${currencySymbol}${newAmountBeforeTaxes}</strong> <span class="chr-sub-price-detail">${escapeChurnHtml(churnText('invoice_after_discount'))}</span><div></div>`;
                        }

                        if (isTaxed) {
                            textTemplate += `<div></div><strong>${currencySymbol}${newAmount} <span class="chr-sub-price-detail">${escapeChurnHtml(churnText('invoice_after_taxes'))}</span></strong><div></div>`;
                        }

                        const updatedText = valueText.replace(valueTextMatch[0], textTemplate);

                        itemValue.html(updatedText);
                    }else if(!isSameFinalPrice){
                        let textTemplate = `<strong>${newAmount}</strong>`;
                        const updatedText = valueText.replace(valueTextMatch[0], textTemplate);

                        itemValue.html(updatedText);
                    }
                }

            } else if (pauseCollection?.resumes_at && itemLabel.text() == "Next payment on") {
                const itemValue = itemLabel.siblings(".pmpro_list_item_value");
                const valueText = itemValue.text();

                if (valueText && pauseCollection?.resumes_at) {
                    itemLabel.text(churnText('invoice_paused_until'));
                    itemValue.html(formatTimestamp(pauseCollection.resumes_at));
                }
            }
        });

    }
}

function formatTimestamp(seconds) {
    const date = new Date(seconds * 1000); // Convert seconds to milliseconds
    return date.toLocaleDateString('en-US', {month: 'long', day: 'numeric', year: 'numeric'});
}

function getRequestQuery(params = { levelId: null, subscriptionId: null }) {
    Object.keys(params).forEach((key) => {
        if (params[key] === null) {
            delete params[key];
        }
    });
    let query = "";
    if (params.levelId) {
        query = `?level_id=${params.levelId}`
    } else if (params.subscriptionId) {
        query = `?subscription_id=${String(params.subscriptionId)}`
    }
    return query;
}

function blockDefaultCancellation(e) {
    e.preventDefault();
    e.target.classList.remove("wcs_block_ui_on_click");
}

function revealCancelButtons() {
    document.documentElement.classList.add('wcs-ready');
}

jQuery(document).ready(function ($) {
    // Call the custom JavaScript function
    handleChurnSolutionRewriteCancelButton($);
    // Call the update invoice logic
    handleUpcomingInvoice($);

    // Close any open manage-subscription dropdown when clicking elsewhere on the page.
    $(document).on("click", function () {
        $(".wcs-manage-wrapper.wcs-open").removeClass('wcs-open');
    });
});
