const { createApp, ref, reactive, onMounted, onUnmounted, computed, watch, nextTick } = Vue;

const isValidEmailFormat = (value) => {
    const s = String(value ?? '').trim();
    if (!s || s.length > 254) {
        return false;
    }
    return /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(s);
};

const adjustColor = (col, amt) => {
    if (!col) return col;
    var usePound = false;
    if (col[0] == "#") {
        col = col.slice(1);
        usePound = true;
    }
    var num = parseInt(col, 16);
    var r = (num >> 16) + amt;
    if (r > 255) r = 255;
    else if (r < 0) r = 0;
    var b = ((num >> 8) & 0x00FF) + amt;
    if (b > 255) b = 255;
    else if (b < 0) b = 0;
    var g = (num & 0x0000FF) + amt;
    if (g > 255) g = 255;
    else if (g < 0) g = 0;

    const newColor = (g | (b << 8) | (r << 16)).toString(16).padStart(6, '0');
    return (usePound ? "#" : "") + newColor;
};

const BookingWizard = {
    template: '#booking-wizard-template',
    components: {},
    setup() {
        const isSidebarCollapsed = ref(false);
        const categories = ref([]);
        const services = ref([]);
        const staff = ref([]);
        const slots = ref([]);
        const selectedDate = ref('');
        const loadingSlots = ref(false);
        const loadingServices = ref(true);
        const loadingStaff = ref(false);
        const staffByServiceCache = ref({});
        const monthDisabledDates = ref([]);
        const loadingMonthAvailability = ref(false);
        const monthScanDirection = ref('forward'); // forward | backward
        const skipNextMonthWatch = ref(false);
        const slotsCacheByDate = ref({});
        // Month dates + slots are fetched once and reused on date select for 5 minutes.
        const MONTH_AVAILABILITY_TTL_MS = 5 * 60 * 1000;
        const monthAvailabilityCacheMeta = ref({
            fetchedAt: 0,
            staffId: 0,
            serviceId: 0,
            year: 0,
            month: 0,
            extrasKey: '',
        });
        let monthAvailabilityRequestId = 0;

        const currentMonthDate = ref(new Date(Date.now()));

        // Read pre-selected attributes directly from DOM for immediate state initialization
        const appEl = document.querySelector("#bookwellab_frontend_app");
        const sc_staff_id = appEl?.getAttribute('data-staff-id');
        const sc_service_id = appEl?.getAttribute('data-service-id');
        const isPopupHost = appEl?.getAttribute('data-bw-popup-host') === '1';
        const formType = (bookwellab_front_obj.appearance && bookwellab_front_obj.appearance.form_type)
            ? String(bookwellab_front_obj.appearance.form_type)
            : 'wizard';
        // Popup-button host always uses the catalog modal shell (same form as Service List popup).
        const isCatalogMode = formType === 'full_page_list' || isPopupHost;
        const appearanceFlags = bookwellab_front_obj.appearance || {};
        const hideDescription = appearanceFlags.hide_description === true
            || appearanceFlags.hide_description === 1
            || String(appearanceFlags.hide_description) === 'true'
            || String(appearanceFlags.hide_description) === '1';
        const showStaffDesignation = appearanceFlags.show_staff_designation === true
            || appearanceFlags.show_staff_designation === 1
            || String(appearanceFlags.show_staff_designation) === 'true'
            || String(appearanceFlags.show_staff_designation) === '1';

        // Catalog / full-page list state
        const catalogDialogVisible = ref(false);
        const catalogPage = ref(1);
        const catalogPageSize = 12;
        const catalogServiceLocked = ref(false);
        const forceButtonPopup = ref(false);
        const runtimeStaffId = ref(null);
        const servicesFilteredByButton = ref(false);
        const getPresetStaffId = () => {
            if (runtimeStaffId.value) {
                return runtimeStaffId.value;
            }
            if (sc_staff_id) {
                return parseInt(sc_staff_id, 10);
            }
            return null;
        };
        const showBookingPopup = computed(() => {
            return catalogDialogVisible.value && (isCatalogMode || forceButtonPopup.value);
        });

        // Determine initial step based on pre-selected attributes.
        // Full-page catalog always starts on the list (wizard opens in popup later).
        const getInitialStepSlug = () => {
            if (isCatalogMode) {
                return 'service';
            }
            const hideStaff = String(bookwellab_front_obj.general?.hide_staff_step) === 'true'
                || bookwellab_front_obj.general?.hide_staff_step === true
                || bookwellab_front_obj.general?.hide_staff_step == 1;
            if (sc_staff_id && sc_service_id) return 'datetime';
            if (sc_service_id) return hideStaff ? 'datetime' : 'staff';
            return 'service';
        };

        const currentStepSlug = ref(getInitialStepSlug());
        const wizardStepDefs = computed(() => {
            return Array.isArray(bookwellab_front_obj.wizard_steps) ? bookwellab_front_obj.wizard_steps : [];
        });

        const extrasByService = computed(() => {
            const map = bookwellab_front_obj.bootstrap?.extras_by_service;
            return map && typeof map === 'object' ? map : {};
        });

        const hasExtrasForService = (serviceId) => {
            if (!serviceId) return false;
            const list = extrasByService.value[String(serviceId)] ?? extrasByService.value[serviceId];
            return Array.isArray(list) && list.length > 0;
        };

        const showServiceStep = computed(() => {
            if (isCatalogMode || forceButtonPopup.value) {
                return !catalogServiceLocked.value;
            }
            return !sc_service_id;
        });

        const hasPresetStaff = computed(() => !!getPresetStaffId());

        const shouldIncludeWizardStep = (step) => {
            if (!step || !step.slug) return false;
            if (step.slug === 'confirmed') return false;
            if (step.slug === 'service' && !showServiceStep.value) return false;
            if (step.slug === 'staff' && (getPresetStaffId() || hideStaffStep.value)) return false;
            if (step.conditional && step.slug === 'extras') {
                return hasExtrasForService(bookingData.service_id);
            }
            return true;
        };

        const getActiveSteps = () => wizardStepDefs.value.filter(shouldIncludeWizardStep);

        const getStepIndex = (slug) => getActiveSteps().findIndex((step) => step.slug === slug);

        const isStep = (slug) => currentStepSlug.value === slug;

        const isBookingConfirmed = computed(() => currentStepSlug.value === 'confirmed');

        const isStepCompleted = (slug) => {
            if (isBookingConfirmed.value) {
                return getStepIndex(slug) >= 0;
            }
            const stepIndex = getStepIndex(slug);
            const currentIndex = getStepIndex(currentStepSlug.value);
            return stepIndex >= 0 && currentIndex >= 0 && stepIndex < currentIndex;
        };

        const goToStep = (slug) => {
            if (isBookingConfirmed.value) {
                return;
            }
            if (getStepIndex(slug) >= 0) {
                currentStepSlug.value = slug;
            }
        };

        const getNextStepSlug = () => {
            const steps = getActiveSteps();
            const currentIndex = getStepIndex(currentStepSlug.value);
            if (currentIndex < 0 || currentIndex >= steps.length - 1) {
                return null;
            }
            return steps[currentIndex + 1].slug;
        };

        const getPrevStepSlug = () => {
            const currentIndex = getStepIndex(currentStepSlug.value);
            if (currentIndex <= 0) {
                return null;
            }
            return getActiveSteps()[currentIndex - 1].slug;
        };

        const getStepAfterService = () => {
            if (hasExtrasForService(bookingData.service_id)) {
                return 'extras';
            }
            if (hideStaffStep.value || getPresetStaffId()) {
                return 'datetime';
            }
            return 'staff';
        };

        const visibleSidebarSteps = computed(() => getActiveSteps());

        const getStepLabel = (step) => {
            if (!step || !step.sidebar) {
                return '';
            }
            return labels.value[step.sidebar] || '';
        };

        const getStepTitle = (step) => {
            if (!step || !step.title) {
                return '';
            }
            return labels.value[step.title] || '';
        };

        const currentStepTitle = computed(() => {
            const step = wizardStepDefs.value.find((item) => item.slug === currentStepSlug.value);
            return step ? getStepTitle(step) : '';
        });

        const isContentStep = computed(() => currentStepSlug.value !== 'confirmed');

        const hideStaffStep = computed(() => {
            const settings = bookwellab_front_obj.general || {};
            return settings.hide_staff_step === true || settings.hide_staff_step === "true" || settings.hide_staff_step == 1;
        });

        const detailFormFields = computed(() => {
            const configured = bookwellab_front_obj.form_fields;
            if (Array.isArray(configured) && configured.length) {
                return configured;
            }

            const legacyLabels = bookwellab_front_obj.labels || {};
            const termsPrefix = legacyLabels.terms_agree_prefix || 'I agree with';
            const termsLink = legacyLabels.terms_agree_link || 'terms & conditions';
            const termsUrl = legacyLabels.terms_conditions_url || '#';

            return [
                { field_name: 'phone', label: legacyLabels.phone || 'Phone', placeholder: '', is_required: true, error_message: legacyLabels.required_fields_error || '' },
                { field_name: 'first_name', label: legacyLabels.first_name || 'First Name', placeholder: '', is_required: true, error_message: legacyLabels.required_fields_error || '' },
                { field_name: 'last_name', label: legacyLabels.last_name || 'Last Name', placeholder: '', is_required: true, error_message: legacyLabels.required_fields_error || '' },
                { field_name: 'email', label: legacyLabels.email || 'Email', placeholder: '', is_required: true, error_message: legacyLabels.invalid_email_error || '' },
                { field_name: 'note', label: legacyLabels.notes || 'Notes', placeholder: '', is_required: false, error_message: '' },
                {
                    field_name: 'terms_and_conditions',
                    label: `${termsPrefix} <a href="${termsUrl}" target="_blank" rel="noopener noreferrer">${termsLink}</a>`,
                    placeholder: '',
                    is_required: true,
                    error_message: legacyLabels.terms_agree_error || '',
                    is_hide: false
                }
            ];
        });

        const labels = computed(() => {
            return bookwellab_front_obj.labels || {};
        });

        const prefillCustomer = bookwellab_front_obj.prefill_customer || {};

        const bookingData = reactive({
            service_id: (!isCatalogMode && sc_service_id) ? parseInt(sc_service_id) : null,
            staff_id: sc_staff_id ? parseInt(sc_staff_id) : null,
            base_service_price: 0,
            service_price: 0, // Will be populated once service data is fetched
            date: null,
            time: null,
            customer: {
                first_name: prefillCustomer.first_name || '',
                last_name: prefillCustomer.last_name || '',
                email: prefillCustomer.email || '',
                phone: prefillCustomer.phone || '',
                phone_code: prefillCustomer.phone_code || '',
                country_slug: prefillCustomer.country_slug || '',
                note: ''
            },
            custom_fields: {},
            payment_gateway: '',
            extras: [],
            spam_key: bookwellab_front_obj.spam_token?.key || '',
            spam_code: bookwellab_front_obj.spam_token?.code || '',
            terms_accepted: false
        });

        if (window.BookWellABProCustomFields && typeof window.BookWellABProCustomFields.initBookingData === 'function') {
            window.BookWellABProCustomFields.initBookingData(bookingData, detailFormFields.value);
        }

        const visibleDetailFields = computed(() => {
            return detailFormFields.value.filter((field) => {
                if (field.is_hide) {
                    return false;
                }
                if (window.BookWellABProCustomFields && typeof window.BookWellABProCustomFields.isFieldVisible === 'function') {
                    return window.BookWellABProCustomFields.isFieldVisible(field, bookingData);
                }
                return true;
            });
        });

        const hasCustomerDisplayName = computed(() => {
            const first = (bookingData.customer.first_name || '').trim();
            const last = (bookingData.customer.last_name || '').trim();
            return !!(first || last);
        });

        const getDetailFieldConfig = (fieldName) => {
            return detailFormFields.value.find((field) => field.field_name === fieldName) || null;
        };

        const displayPrice = (price) => {
            const settings = bookwellab_front_obj.general || {};
            const symbol = settings.currency_symbol || '$';
            const position = settings.currency_position || 'before';

            if (position === 'after') {
                return price + symbol;
            }
            return symbol + price;
        };

        const syncBookingTotalPrice = () => {
            if (window.BookWellABProExtras?.syncBookingPrice) {
                window.BookWellABProExtras.syncBookingPrice(bookingData);
            } else {
                bookingData.service_price = parseFloat(bookingData.base_service_price) || parseFloat(bookingData.service_price) || 0;
            }
        };

        const setBaseServicePrice = (price) => {
            if (price !== undefined && price !== null && price !== '') {
                bookingData.base_service_price = parseFloat(price) || 0;
            }
            syncBookingTotalPrice();
        };

        const selectedExtrasSummary = computed(() => {
            if (window.BookWellABProExtras?.getSelectedExtrasSummary) {
                return window.BookWellABProExtras.getSelectedExtrasSummary(bookingData);
            }
            return [];
        });

        const bookingTotalPrice = computed(() => {
            const base = parseFloat(bookingData.base_service_price);
            const basePrice = Number.isNaN(base) ? (parseFloat(bookingData.service_price) || 0) : base;
            const extrasTotal = window.BookWellABProExtras?.calculateExtrasTotal?.(bookingData) ?? 0;
            return basePrice + extrasTotal;
        });

        const paymentOptions = computed(() => {
            if (bookingTotalPrice.value <= 0) {
                return [];
            }

            const options = [];
            const settings = bookwellab_front_obj.payment || {};

            if (settings.enable_offline === 'yes') {
                options.push({
                    value: 'offline',
                    label: settings.offline_label || (bookwellab_front_obj.labels?.offline_payment || 'Offline Payment')
                });
            }

            if (
                settings.enable_paypal === 'yes'
                && bookwellab_front_obj.paypal_enabled
            ) {
                options.push({
                    value: 'paypal',
                    label: settings.paypal_label || 'PayPal'
                });
            }

            if (
                settings.enable_woocommerce === 'yes'
                && bookwellab_front_obj.wc_booking_action
            ) {
                options.push({
                    value: 'woocommerce',
                    label: settings.woocommerce_label || 'Pay with WooCommerce'
                });
            }

            return options;
        });

        const showPaymentOptions = computed(() => paymentOptions.value.length > 0 && bookingTotalPrice.value > 0);

        const syncPaymentGatewayForTotal = () => {
            if (bookingTotalPrice.value <= 0) {
                bookingData.payment_gateway = '-';
                return;
            }
            if (paymentOptions.value.length > 0) {
                const current = bookingData.payment_gateway;
                const isValid = paymentOptions.value.some((option) => option.value === current);
                if (!current || current === '-' || !isValid) {
                    bookingData.payment_gateway = paymentOptions.value[0].value;
                }
            }
        };

        const activeCategory = ref('all');

        const filteredServices = computed(() => {
            if (activeCategory.value === 'all') {
                return services.value;
            }
            return services.value.filter(s => s.bw_category_id == activeCategory.value);
        });

        const errors = reactive({});
        const globalError = ref('');
        const globalErrorTimer = ref(null);
        const confirmedAppointmentId = ref(null);

        const nextStep = async () => {
            if (isBookingConfirmed.value) {
                return;
            }

            Object.keys(errors).forEach(key => delete errors[key]);
            globalError.value = '';

            if (isStep('service')) {
                if (!bookingData.service_id) {
                    showGlobalError(labels.value.select_service_error || 'Please select a service to continue.');
                    return;
                }
                if (hideStaffStep.value) {
                    const hasValidStaff = await ensureValidAutoAssignedStaff(bookingData.service_id);
                    if (!hasValidStaff) {
                        showGlobalError(labels.value.no_staff_available || 'No staff available for the selected service.');
                        return;
                    }
                    goToStep(getStepAfterService());
                    return;
                }
            } else if (isStep('staff')) {
                if (!bookingData.staff_id) {
                    showGlobalError(labels.value.select_staff_error || 'Please select a staff member to continue.');
                    return;
                }
            } else if (isStep('extras')) {
                if (window.BookWellABProExtras && typeof window.BookWellABProExtras.validateSelection === 'function') {
                    const extrasResult = window.BookWellABProExtras.validateSelection(bookingData);
                    if (extrasResult !== true) {
                        showGlobalError(extrasResult);
                        return;
                    }
                }
            } else if (isStep('datetime')) {
                if (!bookingData.date || !bookingData.time) {
                    showGlobalError(labels.value.select_date_time_error || 'Please select date and time.');
                    return;
                }
            } else if (isStep('details')) {
                let hasError = false;
                let errorMessage = '';

                visibleDetailFields.value.forEach((field) => {
                    const fieldName = field.field_name;
                    const isRequired = !!field.is_required;
                    const fieldError = field.error_message || labels.value.required_fields_error || 'Please fill in all required fields marked in red.';

                    if (fieldName === 'phone') {
                        const phoneDigits = (bookingData.customer.phone || '').toString().replace(/\D/g, '');
                        if (isRequired && !phoneDigits) {
                            errors.phone = true;
                            hasError = true;
                            if (!errorMessage) errorMessage = fieldError;
                        } else {
                            errors.phone = false;
                        }
                        return;
                    }

                    if (fieldName === 'first_name') {
                        const value = (bookingData.customer.first_name || '').trim();
                        if (isRequired && !value) {
                            errors.first_name = true;
                            hasError = true;
                            if (!errorMessage) errorMessage = fieldError;
                        } else {
                            errors.first_name = false;
                        }
                        return;
                    }

                    if (fieldName === 'last_name') {
                        const value = (bookingData.customer.last_name || '').trim();
                        if (isRequired && !value) {
                            errors.last_name = true;
                            hasError = true;
                            if (!errorMessage) errorMessage = fieldError;
                        } else {
                            errors.last_name = false;
                        }
                        return;
                    }

                    if (fieldName === 'email') {
                        const emailTrimmed = (bookingData.customer.email || '').trim();
                        if (isRequired && !emailTrimmed) {
                            errors.email = true;
                            hasError = true;
                            if (!errorMessage) errorMessage = fieldError;
                        } else if (emailTrimmed && !isValidEmailFormat(emailTrimmed)) {
                            errors.email = true;
                            hasError = true;
                            if (!errorMessage) {
                                errorMessage = labels.value.invalid_email_error || fieldError;
                            }
                        } else {
                            errors.email = false;
                        }
                        return;
                    }

                    if (fieldName === 'note') {
                        const value = (bookingData.customer.note || '').trim();
                        if (isRequired && !value) {
                            errors.note = true;
                            hasError = true;
                            if (!errorMessage) errorMessage = fieldError;
                        } else {
                            errors.note = false;
                        }
                        return;
                    }

                    if (fieldName === 'terms_and_conditions') {
                        if (isRequired && !bookingData.terms_accepted) {
                            errors.terms_accepted = true;
                            hasError = true;
                            if (!errorMessage) {
                                errorMessage = field.error_message || labels.value.terms_agree_error || 'Please accept the terms & conditions to continue.';
                            }
                        } else {
                            errors.terms_accepted = false;
                        }
                        return;
                    }

                    if (window.BookWellABProCustomFields && typeof window.BookWellABProCustomFields.validateField === 'function') {
                        const customResult = window.BookWellABProCustomFields.validateField(field, bookingData, errors);
                        if (customResult !== true) {
                            hasError = true;
                            if (!errorMessage && typeof customResult === 'string') {
                                errorMessage = customResult;
                            }
                        }
                    }
                });

                if (hasError) {
                    showGlobalError(errorMessage || labels.value.required_fields_error || 'Please fill in all required fields marked in red.');
                    return;
                }
            }

            const nextSlug = getNextStepSlug();
            if (nextSlug) {
                goToStep(nextSlug);
            }
        };

        const prevStep = () => {
            if (isBookingConfirmed.value) {
                return;
            }

            const appEl = document.querySelector("#bookwellab_frontend_app");
            const serviceIdFromShortcode = appEl?.getAttribute('data-service-id');
            const staffIdFromShortcode = appEl?.getAttribute('data-staff-id');

            if (isStep('datetime')) {
                if (staffIdFromShortcode || hideStaffStep.value) {
                    if (serviceIdFromShortcode) {
                        return;
                    }
                    if (hasExtrasForService(bookingData.service_id)) {
                        goToStep('extras');
                    } else {
                        goToStep('service');
                    }
                } else {
                    goToStep('staff');
                }
            } else if (isStep('staff')) {
                if (serviceIdFromShortcode) {
                    return;
                }
                goToStep('service');
            } else if (isStep('extras')) {
                if (serviceIdFromShortcode) {
                    return;
                }
                goToStep('service');
            } else {
                const prevSlug = getPrevStepSlug();
                if (prevSlug) {
                    goToStep(prevSlug);
                }
            }
            globalError.value = '';
        };

        const dismissGlobalError = () => {
            globalError.value = '';
        };

        const showGlobalError = (message) => {
            if (!message) return;
            if (globalError.value === message) {
                globalError.value = '';
                nextTick(() => {
                    globalError.value = message;
                });
                return;
            }
            globalError.value = message;
        };

        watch(globalError, (message) => {
            if (globalErrorTimer.value) {
                clearTimeout(globalErrorTimer.value);
                globalErrorTimer.value = null;
            }

            if (!message) {
                return;
            }

            globalErrorTimer.value = setTimeout(() => {
                globalError.value = '';
                globalErrorTimer.value = null;
            }, 2000);
        });

        onUnmounted(() => {
            if (globalErrorTimer.value) {
                clearTimeout(globalErrorTimer.value);
                globalErrorTimer.value = null;
            }
            destroyPayPalButtons();
        });

        const applyServicesPayload = (payload) => {
            if (!payload || !Array.isArray(payload.services)) {
                return false;
            }
            categories.value = payload.categories || [];
            services.value = payload.services;
            return true;
        };

        const primeStaffCache = (bootstrap) => {
            const map = bootstrap?.staff_by_service;
            if (!map || typeof map !== 'object') {
                return;
            }
            staffByServiceCache.value = { ...map };
        };

        const applyStaffForService = (serviceId) => {
            const key = String(serviceId);
            if (staffByServiceCache.value[key]) {
                staff.value = staffByServiceCache.value[key];
                return true;
            }
            return false;
        };

        const applyShortcodePreselection = async () => {
            // Catalog mode opens the list first; service is chosen when opening the popup.
            if (isCatalogMode || !sc_service_id) {
                return;
            }

            const preselectedService = services.value.find(s => s.bw_service_id == sc_service_id);
            if (!preselectedService) {
                currentStepSlug.value = 'service';
                bookingData.service_id = null;
                bookingData.staff_id = null;
                return;
            }

            setBaseServicePrice(preselectedService.bw_service_price);

            if (!staff.value.length) {
                await fetchStaff(sc_service_id);
            }

            if (sc_staff_id) {
                const preselectedStaff = staff.value.find(s => s.bw_staff_id == sc_staff_id);
                if (preselectedStaff) {
                    bookingData.staff_id = preselectedStaff.bw_staff_id;
                    if (preselectedStaff.bw_service_price) {
                        setBaseServicePrice(preselectedStaff.bw_service_price);
                    }
                }
            }

            if (hideStaffStep.value) {
                const hasValidStaff = await ensureValidAutoAssignedStaff(sc_service_id);
                if (!hasValidStaff) {
                    currentStepSlug.value = 'service';
                    showGlobalError(labels.value.no_staff_available || 'No staff available for the selected service.');
                } else {
                    goToStep(getStepAfterService());
                }
            } else if (hasExtrasForService(sc_service_id)) {
                goToStep('extras');
            } else if (sc_staff_id && hasExtrasForService(sc_service_id)) {
                goToStep('extras');
            }
        };

        const initializeBookingData = async () => {
            const bootstrap = bookwellab_front_obj.bootstrap;
            const hasBootstrapServices = bootstrap?.services && applyServicesPayload(bootstrap.services);

            if (hasBootstrapServices) {
                primeStaffCache(bootstrap);
                if (Array.isArray(bootstrap.staff)) {
                    staff.value = bootstrap.staff;
                } else if (sc_service_id) {
                    applyStaffForService(sc_service_id);
                }
                await applyShortcodePreselection();
                return;
            }

            await fetchServices();
        };

        const fetchServices = async (staffIdOverride = null, skipPreselection = false) => {
            loadingServices.value = true;
            try {
                const staffFilter = (staffIdOverride !== null && staffIdOverride !== undefined)
                    ? (parseInt(staffIdOverride, 10) || 0)
                    : (sc_staff_id || 0);
                const response = await axios.post(bookwellab_front_obj.ajax_url, Qs.stringify({
                    action: 'bookwellab_get_front_services',
                    staff_id: staffFilter,
                    nonce: bookwellab_front_obj.nonce
                }));

                if (response.data.success) {
                    applyServicesPayload(response.data.data);
                    if (!skipPreselection) {
                        await applyShortcodePreselection();
                    }
                }
            } catch (e) {
                console.error(e);
            } finally {
                loadingServices.value = false;
            }
        };

        const fetchStaff = async (serviceId) => {
            const key = String(serviceId);
            if (applyStaffForService(serviceId)) {
                return;
            }

            loadingStaff.value = true;
            try {
                const response = await axios.post(bookwellab_front_obj.ajax_url, Qs.stringify({
                    action: 'bookwellab_get_front_staff',
                    service_id: serviceId,
                    nonce: bookwellab_front_obj.nonce
                }));
                if (response.data.success && Array.isArray(response.data.data?.staff)) {
                    staff.value = response.data.data.staff;
                    staffByServiceCache.value[key] = staff.value;
                }
            } catch (e) {
                console.error(e);
            } finally {
                loadingStaff.value = false;
            }
        };

        const ensureValidAutoAssignedStaff = async (serviceId) => {
            if (!serviceId) return false;
            if (!staff.value.length) {
                await fetchStaff(serviceId);
            }
            if (!staff.value.length) {
                bookingData.staff_id = null;
                return false;
            }

            const currentStaff = staff.value.find(s => s.bw_staff_id == bookingData.staff_id);
            if (currentStaff) {
                if (currentStaff.bw_service_price) {
                    setBaseServicePrice(currentStaff.bw_service_price);
                }
                return true;
            }

            const firstStaff = staff.value[0];
            bookingData.staff_id = firstStaff.bw_staff_id;
            if (firstStaff.bw_service_price) {
                setBaseServicePrice(firstStaff.bw_service_price);
            }
            return true;
        };

        const selectService = async (service, isAuto = false) => {
            if (bookingData.service_id !== service.bw_service_id) {
                bookingData.service_id = service.bw_service_id;
                setBaseServicePrice(service.bw_service_price);

                bookingData.staff_id = null;
                bookingData.date = null;
                bookingData.time = null;
                bookingData.extras = [];
                selectedDate.value = '';
                slots.value = [];
                currentMonthDate.value = new Date();
                monthScanDirection.value = 'forward';

                await fetchStaff(service.bw_service_id);

                // If staff_id is pre-selected, select it automatically and skip step 2
                if (getPresetStaffId()) {
                    const preselectedStaff = staff.value.find(s => s.bw_staff_id == getPresetStaffId());
                    if (preselectedStaff) {
                        bookingData.staff_id = preselectedStaff.bw_staff_id;
                        if (preselectedStaff.bw_service_price) {
                            setBaseServicePrice(preselectedStaff.bw_service_price);
                        }
                        goToStep(getStepAfterService());
                        return;
                    }
                }

                if (hideStaffStep.value) {
                    const hasValidStaff = await ensureValidAutoAssignedStaff(service.bw_service_id);
                    if (!hasValidStaff) {
                        showGlobalError(labels.value.no_staff_available || 'No staff available for the selected service.');
                        return;
                    }
                    goToStep(getStepAfterService());
                    return;
                }
            } else {
                if (getPresetStaffId()) {
                    goToStep(getStepAfterService());
                    return;
                }

                if (hideStaffStep.value) {
                    const hasValidStaff = await ensureValidAutoAssignedStaff(service.bw_service_id);
                    if (!hasValidStaff) {
                        showGlobalError(labels.value.no_staff_available || 'No staff available for the selected service.');
                        return;
                    }
                    goToStep(getStepAfterService());
                    return;
                }
            }
            if (!isAuto) {
                await nextStep();
            }
        };

        const selectStaff = (staffMember) => {
            if (bookingData.staff_id !== staffMember.bw_staff_id) {
                bookingData.staff_id = staffMember.bw_staff_id;
                if (staffMember.bw_service_price) {
                    setBaseServicePrice(staffMember.bw_service_price);
                }
                bookingData.date = null;
                bookingData.time = null;
                selectedDate.value = '';
                slots.value = [];
                invalidateMonthAvailabilityCache();
                currentMonthDate.value = new Date();
                monthScanDirection.value = 'forward';
            }
            nextStep();
        };

        const getTodayStart = () => {
            const today = new Date();
            today.setHours(0, 0, 0, 0);
            return today;
        };

        const clearSelectedDateIfOutsideMonth = (year, month) => {
            if (!bookingData.date) {
                return;
            }
            const parts = String(bookingData.date).split('-');
            if (parts.length !== 3) {
                return;
            }
            const selectedYear = parseInt(parts[0], 10);
            const selectedMonth = parseInt(parts[1], 10);
            if (selectedYear !== year || selectedMonth !== month) {
                bookingData.date = null;
                bookingData.time = null;
                selectedDate.value = '';
                slots.value = [];
            }
        };

        const getExtrasPayload = () => {
            return Array.isArray(bookingData.extras) ? bookingData.extras : [];
        };

        const getExtrasCacheKey = () => {
            try {
                return JSON.stringify(getExtrasPayload());
            } catch (e) {
                return '';
            }
        };

        const invalidateMonthAvailabilityCache = () => {
            slotsCacheByDate.value = {};
            monthDisabledDates.value = [];
            monthAvailabilityCacheMeta.value = {
                fetchedAt: 0,
                staffId: 0,
                serviceId: 0,
                year: 0,
                month: 0,
                extrasKey: '',
            };
        };

        const isMonthAvailabilityCacheFresh = (year, month) => {
            const meta = monthAvailabilityCacheMeta.value;
            if (!meta.fetchedAt) {
                return false;
            }
            if ((Date.now() - meta.fetchedAt) > MONTH_AVAILABILITY_TTL_MS) {
                return false;
            }
            if (Number(meta.staffId) !== Number(bookingData.staff_id)) {
                return false;
            }
            if (Number(meta.serviceId) !== Number(bookingData.service_id)) {
                return false;
            }
            if (Number(meta.year) !== Number(year) || Number(meta.month) !== Number(month)) {
                return false;
            }
            if (meta.extrasKey !== getExtrasCacheKey()) {
                return false;
            }
            return true;
        };

        const fetchMonthAvailability = async () => {
            if (!bookingData.staff_id || !bookingData.service_id) {
                return;
            }

            const requestId = ++monthAvailabilityRequestId;
            loadingMonthAvailability.value = true;
            loadingSlots.value = true;
            const scanDirection = monthScanDirection.value;
            monthScanDirection.value = 'forward';
            const extrasKey = getExtrasCacheKey();

            try {
                const response = await axios.post(bookwellab_front_obj.ajax_url, Qs.stringify({
                    action: 'bookwellab_resolve_calendar_month',
                    start_year: currentMonthDate.value.getFullYear(),
                    start_month: currentMonthDate.value.getMonth() + 1,
                    scan_direction: scanDirection,
                    staff_id: bookingData.staff_id,
                    service_id: bookingData.service_id,
                    extras: JSON.stringify(getExtrasPayload()),
                    nonce: bookwellab_front_obj.nonce
                }));

                if (requestId !== monthAvailabilityRequestId) {
                    return;
                }

                if (response.data.success) {
                    const data = response.data.data || {};
                    const year = parseInt(data.year, 10);
                    const month = parseInt(data.month, 10);
                    const resolvedMonth = new Date(year, month - 1, 1);

                    skipNextMonthWatch.value = true;
                    currentMonthDate.value = resolvedMonth;
                    monthDisabledDates.value = data.disabled_dates || [];
                    slotsCacheByDate.value = data.time_slots_by_date || {};
                    monthAvailabilityCacheMeta.value = {
                        fetchedAt: Date.now(),
                        staffId: bookingData.staff_id,
                        serviceId: bookingData.service_id,
                        year: year,
                        month: month,
                        extrasKey: extrasKey,
                    };
                    clearSelectedDateIfOutsideMonth(year, month);

                    if (bookingData.date && Array.isArray(slotsCacheByDate.value[bookingData.date])) {
                        slots.value = slotsCacheByDate.value[bookingData.date];
                    } else if (bookingData.date) {
                        slots.value = [];
                    }
                }
            } catch (e) {
                console.error(e);
            } finally {
                if (requestId === monthAvailabilityRequestId) {
                    loadingMonthAvailability.value = false;
                    loadingSlots.value = false;
                }
            }
        };

        const ensureMonthAvailability = async (force = false) => {
            if (!bookingData.staff_id || !bookingData.service_id) {
                return;
            }
            const year = currentMonthDate.value.getFullYear();
            const month = currentMonthDate.value.getMonth() + 1;
            if (!force && isMonthAvailabilityCacheFresh(year, month)) {
                return;
            }
            await fetchMonthAvailability();
        };

        const applySlotsForDate = (dateStr) => {
            const cached = slotsCacheByDate.value[dateStr];
            if (Array.isArray(cached)) {
                slots.value = cached;
                loadingSlots.value = false;
                return true;
            }
            return false;
        };

        watch([() => bookingData.staff_id, () => bookingData.service_id], () => {
            skipNextMonthWatch.value = true;
            currentMonthDate.value = new Date();
            monthScanDirection.value = 'forward';
            invalidateMonthAvailabilityCache();
            slots.value = [];
            // Fetch only once the user is on Date & Time (includes selected extras).
            if (isStep('datetime') && bookingData.staff_id && bookingData.service_id) {
                fetchMonthAvailability();
            }
        }, { immediate: true });

        watch(() => currentMonthDate.value, () => {
            if (skipNextMonthWatch.value) {
                skipNextMonthWatch.value = false;
                return;
            }
            if (bookingData.staff_id && bookingData.service_id && isStep('datetime')) {
                fetchMonthAvailability();
            }
        });

        watch(() => getExtrasCacheKey(), () => {
            // Extras change duration/slots — drop month cache so datetime refetch includes them.
            invalidateMonthAvailabilityCache();
            if (isStep('datetime') && bookingData.staff_id && bookingData.service_id) {
                fetchMonthAvailability();
            }
        });

        watch(currentStepSlug, async (slug) => {
            if (slug !== 'datetime' || !bookingData.staff_id || !bookingData.service_id) {
                return;
            }
            await ensureMonthAvailability();
            if (bookingData.date) {
                applySlotsForDate(bookingData.date);
            }
        }, { immediate: true });

        const scrollToSlotsOnMobile = () => {
            if (window.innerWidth <= 768) {
                nextTick(() => {
                    const stepContainer = document.querySelector('.bw-step-datetime');
                    const slotsWrapper = document.querySelector('.bw-slots-wrapper');
                    if (stepContainer && slotsWrapper) {
                        const targetTop = slotsWrapper.offsetTop - stepContainer.offsetTop;
                        stepContainer.scrollTo({
                            top: Math.max(0, targetTop),
                            behavior: 'smooth'
                        });
                    }
                });
            }
        };

        const onDateChange = async (val) => {
            if (!val) return;
            bookingData.date = val;
            selectedDate.value = val;
            bookingData.time = null;

            scrollToSlotsOnMobile();

            const parts = String(val).split('-');
            const year = parseInt(parts[0], 10);
            const month = parseInt(parts[1], 10);

            // Prefer month cache (5 min). Refresh whole month when stale — no per-date AJAX.
            if (!isMonthAvailabilityCacheFresh(year, month)) {
                if (
                    Number(currentMonthDate.value.getFullYear()) !== year
                    || Number(currentMonthDate.value.getMonth() + 1) !== month
                ) {
                    skipNextMonthWatch.value = true;
                    currentMonthDate.value = new Date(year, month - 1, 1);
                }
                await fetchMonthAvailability();
            }

            if (!applySlotsForDate(val)) {
                slots.value = [];
                loadingSlots.value = false;
            }
        };

        const selectSlot = (slot) => {
            bookingData.time = slot;
            if (isStep('datetime')) {
                nextStep();
            }
        };

        const checkIsDateDisabled = (dayObj) => {
            if (!dayObj.isCurrentMonth) return true;

            const yyyy = dayObj.date.getFullYear();
            const mm = String(dayObj.date.getMonth() + 1).padStart(2, '0');
            const dd = String(dayObj.date.getDate()).padStart(2, '0');
            const dateStr = `${yyyy}-${mm}-${dd}`;

            if (monthDisabledDates.value.includes(dateStr)) return true;

            const siteToday = bookwellab_front_obj.site_today || '';
            if (siteToday && dateStr < siteToday) {
                return true;
            }

            return false;
        };

        const selectCustomDate = (dayObj) => {
            if (dayObj.disabled || checkIsDateDisabled(dayObj)) return;

            const yyyy = dayObj.date.getFullYear();
            const mm = String(dayObj.date.getMonth() + 1).padStart(2, '0');
            const dd = String(dayObj.date.getDate()).padStart(2, '0');
            const formatted = `${yyyy}-${mm}-${dd}`;

            onDateChange(formatted);
        };

        const nextMonth = () => {
            monthScanDirection.value = 'forward';
            currentMonthDate.value = new Date(currentMonthDate.value.getFullYear(), currentMonthDate.value.getMonth() + 1, 1);
        };

        const prevMonth = () => {
            const todayMonthStart = new Date(getTodayStart().getFullYear(), getTodayStart().getMonth(), 1);
            const previousMonth = new Date(currentMonthDate.value.getFullYear(), currentMonthDate.value.getMonth() - 1, 1);
            if (previousMonth < todayMonthStart) {
                return;
            }
            monthScanDirection.value = 'backward';
            currentMonthDate.value = previousMonth;
        };

        const canGoToPreviousMonth = computed(() => {
            const todayMonthStart = new Date(getTodayStart().getFullYear(), getTodayStart().getMonth(), 1);
            const previousMonth = new Date(currentMonthDate.value.getFullYear(), currentMonthDate.value.getMonth() - 1, 1);
            return previousMonth >= todayMonthStart;
        });

        const formatMonthYear = computed(() => {
            return currentMonthDate.value.toLocaleDateString('default', { month: 'long', year: 'numeric' });
        });

        const morningSlots = computed(() => {
            if (!slots.value || slots.value.length === 0) return [];
            return slots.value.filter(slot => {
                const hour = parseInt(slot.split(':')[0]);
                return hour < 12;
            });
        });

        const afternoonSlots = computed(() => {
            if (!slots.value || slots.value.length === 0) return [];
            return slots.value.filter(slot => {
                const hour = parseInt(slot.split(':')[0]);
                return hour >= 12;
            });
        });

        const disabledDate = (time) => {
            const date = new Date(time);
            return date.getTime() < Date.now() - 8.64e7;
        };

        const submitting = ref(false);
        const paypalButtonLoading = ref(false);
        const pendingPayPalAppointmentId = ref(null);
        const pendingPayPalAppointmentHash = ref(null);
        let paypalButtonsInstance = null;

        const isPayPalSelected = computed(() => bookingTotalPrice.value > 0 && bookingData.payment_gateway === 'paypal');
        let iti = null;
        let lastAutofillLookupKey = '';

        const getNormalizedPhoneData = () => {
            let phoneCode = '';
            let localPhone = (bookingData.customer.phone || '').toString();

            let countrySlug = (bookingData.customer.country_slug || '').toString().toLowerCase();

            if (iti) {
                const countryData = iti.getSelectedCountryData();
                if (countryData && countryData.dialCode) {
                    phoneCode = '+' + countryData.dialCode;
                }
                if (countryData && countryData.iso2) {
                    countrySlug = String(countryData.iso2).toLowerCase();
                }
            }

            localPhone = localPhone.replace(/\D+/g, '');
            return {
                phone_code: phoneCode,
                phone: localPhone,
                country_slug: countrySlug
            };
        };

        const prepareBookingPayload = () => {
            syncBookingTotalPrice();
            bookingData.service_price = bookingTotalPrice.value;
            const phoneData = getNormalizedPhoneData();
            bookingData.customer.phone = phoneData.phone;
            bookingData.customer.phone_code = phoneData.phone_code;
            bookingData.customer.country_slug = phoneData.country_slug;
        };

        const refreshSpamToken = async () => {
            try {
                const response = await axios.post(bookwellab_front_obj.ajax_url, Qs.stringify({
                    action: 'bookwellab_refresh_spam_token',
                    nonce: bookwellab_front_obj.nonce
                }));

                if (response.data.success && response.data.data.spam_token) {
                    bookingData.spam_key = response.data.data.spam_token.key || '';
                    bookingData.spam_code = response.data.data.spam_token.code || '';
                }
            } catch (e) {
                console.error(e);
            }
        };

        let paypalSdkLoadPromise = null;

        const getPayPalApi = () => {
            const api = window.paypal;
            return api && typeof api.Buttons === 'function' ? api : null;
        };

        const loadPayPalSdk = () => {
            const existing = getPayPalApi();
            if (existing) {
                return Promise.resolve(existing);
            }

            if (paypalSdkLoadPromise) {
                return paypalSdkLoadPromise;
            }

            const clientId = bookwellab_front_obj.paypal_client_id || '';
            const currency = bookwellab_front_obj.paypal_currency || 'USD';
            if (!clientId) {
                return Promise.resolve(null);
            }

            paypalSdkLoadPromise = new Promise((resolve) => {
                const script = document.createElement('script');
                script.src = 'https://www.paypal.com/sdk/js'
                    + '?client-id=' + encodeURIComponent(clientId)
                    + '&currency=' + encodeURIComponent(currency)
                    + '&components=buttons'
                    + '&disable-funding=credit,card';
                script.async = true;
                script.onload = () => resolve(getPayPalApi());
                script.onerror = () => resolve(null);
                document.head.appendChild(script);
            });

            return paypalSdkLoadPromise;
        };

        const waitForPayPalContainer = async (maxAttempts = 20) => {
            for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
                await nextTick();
                const container = document.getElementById('bw-paypal-button-container');
                if (container) {
                    return container;
                }
                await new Promise((resolve) => setTimeout(resolve, 50));
            }
            return null;
        };

        const destroyPayPalButtons = () => {
            const container = document.getElementById('bw-paypal-button-container');
            if (container) {
                container.innerHTML = '';
            }
            paypalButtonsInstance = null;
        };

        const cancelPendingPayPalAppointment = async () => {
            const appointmentId = pendingPayPalAppointmentId.value;
            const appointmentHash = pendingPayPalAppointmentHash.value;
            if (!appointmentId || !appointmentHash) {
                return;
            }

            pendingPayPalAppointmentId.value = null;
            pendingPayPalAppointmentHash.value = null;

            try {
                await axios.post(bookwellab_front_obj.ajax_url, Qs.stringify({
                    action: 'bookwellab_paypal_cancel_order',
                    appointment_id: appointmentId,
                    appointment_hash: appointmentHash,
                    nonce: bookwellab_front_obj.nonce,
                }));
            } catch (e) {
                console.error(e);
            }
        };

        const confirmPayPalOrderWithRetry = async (orderId, maxAttempts = 3) => {
            let lastMessage = labels.value.unexpected_error || 'An unexpected error occurred. Please try again.';

            for (let attempt = 1; attempt <= maxAttempts; attempt++) {
                try {
                    const response = await axios.post(bookwellab_front_obj.ajax_url, Qs.stringify({
                        action: 'bookwellab_paypal_confirm_order',
                        order_id: orderId,
                        nonce: bookwellab_front_obj.nonce,
                    }));

                    if (response.data.success) {
                        return response;
                    }

                    lastMessage = (response.data.data && response.data.data.msg)
                        ? response.data.data.msg
                        : lastMessage;
                } catch (error) {
                    console.error(error);
                }

                if (attempt < maxAttempts) {
                    await new Promise((resolve) => setTimeout(resolve, 1000 * attempt));
                }
            }

            throw new Error(lastMessage);
        };

        const handlePayPalPaymentAborted = async (showCancelledMessage = false) => {
            await cancelPendingPayPalAppointment();
            await refreshSpamToken();
            if (showCancelledMessage) {
                showGlobalError(labels.value.paypal_payment_cancelled || 'PayPal payment was cancelled.');
            }
        };

        const renderPayPalButtons = async () => {
            destroyPayPalButtons();

            if (!isPayPalSelected.value || !isStep('summary')) {
                paypalButtonLoading.value = false;
                return;
            }

            if (!bookwellab_front_obj.paypal_enabled) {
                paypalButtonLoading.value = false;
                return;
            }

            paypalButtonLoading.value = true;

            const paypalApi = await loadPayPalSdk();
            if (!paypalApi) {
                paypalButtonLoading.value = false;
                showGlobalError(labels.value.paypal_unavailable || 'PayPal is not available for this booking.');
                return;
            }

            const container = await waitForPayPalContainer();
            if (!container) {
                paypalButtonLoading.value = false;
                return;
            }

            paypalButtonLoading.value = false;

            const footerActions = document.querySelector('.bw-nav-footer-actions');
            const footerBtnHeight = footerActions
                ? parseInt(window.getComputedStyle(footerActions).getPropertyValue('--bw-footer-btn-height'), 10)
                : 46;

            try {
                paypalButtonsInstance = paypalApi.Buttons({
                    style: {
                        shape: 'rect',
                        layout: 'horizontal',
                        height: Number.isFinite(footerBtnHeight) ? footerBtnHeight : 46,
                        tagline: false,
                    },
                    createOrder: async () => {
                        prepareBookingPayload();
                        const response = await axios.post(bookwellab_front_obj.ajax_url, Qs.stringify({
                            action: 'bookwellab_paypal_create_order',
                            data: JSON.stringify(bookingData),
                            nonce: bookwellab_front_obj.nonce,
                        }));

                        if (!response.data.success) {
                            const msg = (response.data.data && response.data.data.msg)
                                ? response.data.data.msg
                                : (labels.value.unexpected_error || 'An unexpected error occurred. Please try again.');
                            showGlobalError(msg);
                            await refreshSpamToken();
                            throw new Error(msg);
                        }

                        pendingPayPalAppointmentId.value = response.data.data.appointment_id || null;
                        pendingPayPalAppointmentHash.value = response.data.data.appointment_hash || null;
                        return response.data.data.order_id;
                    },
                    onApprove: async (data, actions) => {
                        submitting.value = true;
                        try {
                            await actions.order.capture();
                            const response = await confirmPayPalOrderWithRetry(data.orderID);

                            pendingPayPalAppointmentId.value = null;
                            pendingPayPalAppointmentHash.value = null;
                            confirmedAppointmentId.value = response.data.data.appointment_id;
                            currentStepSlug.value = 'confirmed';
                        } catch (e) {
                            console.error(e);
                            const msg = (e && e.message)
                                ? e.message
                                : (labels.value.unexpected_error || 'An unexpected error occurred. Please try again.');
                            showGlobalError(msg);
                        } finally {
                            submitting.value = false;
                        }
                    },
                    onCancel: async () => {
                        await handlePayPalPaymentAborted(true);
                    },
                    onError: async (err) => {
                        console.error(err);
                        await handlePayPalPaymentAborted(false);
                    },
                });

                if (paypalButtonsInstance.isEligible && !paypalButtonsInstance.isEligible()) {
                    showGlobalError(labels.value.paypal_unavailable || 'PayPal is not available for this booking.');
                    return;
                }

                await paypalButtonsInstance.render(container);
            } catch (e) {
                console.error(e);
                paypalButtonLoading.value = false;
                showGlobalError(labels.value.paypal_unavailable || 'PayPal is not available for this booking.');
            }
        };

        const stripPhoneToDigits = (raw) => String(raw ?? '').replace(/\D/g, '');
        const MAX_PHONE_DIGITS = 15;

        const attachPhoneDigitsOnlyGuards = (phoneInputEl) => {
            const syncFromDom = () => {
                let digits = stripPhoneToDigits(phoneInputEl.value);
                if (digits.length > MAX_PHONE_DIGITS) {
                    digits = digits.slice(0, MAX_PHONE_DIGITS);
                }
                if (phoneInputEl.value !== digits) {
                    phoneInputEl.value = digits;
                }
                bookingData.customer.phone = digits;
                errors.phone = false;
            };

            phoneInputEl.addEventListener('input', () => {
                queueMicrotask(syncFromDom);
            });
            phoneInputEl.addEventListener('beforeinput', (e) => {
                if (e.isComposing) {
                    return;
                }
                if (e.inputType === 'insertText' && e.data && /\D/.test(e.data)) {
                    e.preventDefault();
                }
            });
            phoneInputEl.addEventListener('paste', (e) => {
                e.preventDefault();
                const paste = stripPhoneToDigits((e.clipboardData || window.clipboardData).getData('text'));
                const start = phoneInputEl.selectionStart ?? 0;
                const end = phoneInputEl.selectionEnd ?? 0;
                const cur = String(phoneInputEl.value ?? '');
                const merged = stripPhoneToDigits(cur.slice(0, start) + paste + cur.slice(end)).slice(0, MAX_PHONE_DIGITS);
                phoneInputEl.value = merged;
                bookingData.customer.phone = merged;
                errors.phone = false;
                nextTick(() => {
                    try {
                        const pos = merged.length;
                        phoneInputEl.setSelectionRange(pos, pos);
                    } catch (ignore) {
                        /* ignore */
                    }
                });
            });
            phoneInputEl.addEventListener('keydown', (e) => {
                if (e.isComposing) {
                    return;
                }
                if (e.ctrlKey || e.metaKey || e.altKey) {
                    return;
                }
                const k = typeof e.key === 'string' ? e.key : '';
                if (k === 'Backspace' || k === 'Delete' || k === 'Tab' || k === 'Escape' || k === 'Enter') {
                    return;
                }
                if (k === 'ArrowLeft' || k === 'ArrowRight' || k === 'ArrowUp' || k === 'ArrowDown' || k === 'Home' || k === 'End') {
                    return;
                }
                if (k !== '' && k.length === 1 && /\D/.test(k)) {
                    e.preventDefault();
                }
            });
        };

        watch(
            () => bookingData.customer.phone,
            (v) => {
                const d = stripPhoneToDigits(v);
                if (String(v ?? '') !== d) {
                    bookingData.customer.phone = d;
                }
            }
        );

        // Disabled for privacy/security compliance:
        // do not auto-fetch customer personal data by phone number.
        const autofillCustomerByPhone = async () => {
            return;
        };

        const syncFrontendPhoneInput = () => {
            const phoneInputEl = document.querySelector("#bw-phone-input");
            if (!phoneInputEl || typeof window.intlTelInput !== "function") {
                return;
            }

            const initOptions = {
                separateDialCode: true,
                initialCountry: prefillCustomer.country_slug || bookwellab_front_obj.default_country_iso || "us"
            };

            const applyPrefillNumber = () => {
                const prefillPhone = (prefillCustomer.phone || '').toString().trim();
                if (!prefillPhone || phoneInputEl.dataset.bwPrefilled) {
                    return;
                }
                phoneInputEl.dataset.bwPrefilled = '1';
                if (prefillCustomer.country_slug) {
                    iti.setCountry(prefillCustomer.country_slug);
                }
                iti.setNumber(prefillPhone);
            };

            const runInit = () => {
                if (!iti) {
                    iti = window.intlTelInput(phoneInputEl, initOptions);
                    if (!phoneInputEl.dataset.bwDigitsGuard) {
                        attachPhoneDigitsOnlyGuards(phoneInputEl);
                        phoneInputEl.dataset.bwDigitsGuard = '1';
                    }
                    phoneInputEl.addEventListener('countrychange', () => {
                        lastAutofillLookupKey = '';
                        //autofillCustomerByPhone();
                    });
                    applyPrefillNumber();
                    return;
                }

                applyPrefillNumber();

                const selected = iti.getSelectedCountryData();
                if (selected && selected.iso2) {
                    iti.setCountry(selected.iso2);
                }
            };

            requestAnimationFrame(() => {
                requestAnimationFrame(runInit);
            });
        };

        watch(currentStepSlug, (newVal) => {
            if (newVal === 'details') {
                nextTick(syncFrontendPhoneInput);
            }
        });

        onMounted(async () => {
            loadingServices.value = true;
            try {
                await initializeBookingData();
            } finally {
                loadingServices.value = false;
            }

            syncPaymentGatewayForTotal();

            watch(bookingTotalPrice, () => {
                syncPaymentGatewayForTotal();
            });

            const appearance = bookwellab_front_obj.appearance || {};

            // Google Font is now enqueued from PHP for better reliability

            const primaryColor = appearance.primary_color || '#409eff';
            const primaryHover = adjustColor(primaryColor, -10);

            let fontFamily = 'inherit';
            if (appearance.font_family) {
                const fontName = appearance.font_family.split(',')[0].replace(/['"]/g, '').trim();
                fontFamily = `'${fontName}', sans-serif`;
            }

            const titleColor = appearance.title_color || '#2c3e50';
            const detailColor = appearance.detail_text_color || '#64748b';
            const titleFontSize = (appearance.title_font_size || 20) + 'px';
            const detailFontSize = (appearance.detail_text_size || 14) + 'px';
            const subtitleColor = appearance.subtitle_color || titleColor;
            const subtitleColorDown = adjustColor(subtitleColor, 70);

            const sidebarTitleColor = appearance.sidebar_title_color || '#ffffff';
            const sidebarTextColor = appearance.sidebar_text_color || '#ffffff';
            const sidebarTitleSize = (appearance.sidebar_title_size || 16) + 'px';
            const sidebarTextSize = (appearance.sidebar_text_size || 12) + 'px';
            const sectionTitleSize = (appearance.section_title_size || 24) + 'px';

            const hexToRgb = (hex) => {
                const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
                return result ? `${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}` : "64, 158, 255";
            };

            const primaryColorRgb = hexToRgb(primaryColor);

            const style = document.createElement('style');
            style.innerHTML = `
                :root {
                    --bw-primary-color: ${primaryColor};
                    --bw-primary-color-rgb: ${primaryColorRgb};
                    --bw-primary-hover: ${primaryHover};
                    --bw-font-family: ${fontFamily};
                    --bw-button-font-family: ${fontFamily};
                    --bw-title-color: ${titleColor};
                    --bw-detail-color: ${detailColor};
                    --bw-title-font-size: ${titleFontSize};
                    --bw-detail-font-size: ${detailFontSize};
                    --bw-sidebar-title-color: ${sidebarTitleColor};
                    --bw-sidebar-text-color: ${sidebarTextColor};
                    --bw-sidebar-title-size: ${sidebarTitleSize};
                    --bw-sidebar-text-size: ${sidebarTextSize};
                    --bw-section-title-size: ${sectionTitleSize};
                    --bw-category-title-color: ${appearance.category_title_color || titleColor};
                    --bw-category-title-size: ${appearance.category_title_size ? appearance.category_title_size + 'px' : titleFontSize};
                    --bw-price-badge-color: ${appearance.price_badge_color || '#ffffff'};
                    --bw-price-badge-size: ${(appearance.price_badge_size || 14) + 'px'};
                    --bw-button-text-color: ${appearance.button_text_color || '#ffffff'};
                    --bw-button-font-size: ${(appearance.button_font_size || 16) + 'px'};
                    --bw-subtitle-color: ${subtitleColor};
                    --bw-subtitle-color-down-color: ${subtitleColorDown};
                    --bw-subtitle-size: ${appearance.subtitle_size ? appearance.subtitle_size + 'px' : titleFontSize};
                    --bw-content-text-color: ${appearance.content_text_color || detailColor};
                    --bw-content-text-size: ${appearance.content_text_size ? appearance.content_text_size + 'px' : detailFontSize};
                    --bw-panel-bg-color: ${appearance.panel_bg_color || '#f7f3ed'};
                    --bw-border-color: ${appearance.border_color || '#d4cdc9'};
                    --bw-button-secondary-bg: ${appearance.button_text_color || '#ffffff'};
                    --bw-button-secondary-text: ${primaryColor};
                    --bw-button-secondary-border: ${primaryColor};
                    --el-border-color: var(--bw-border-color);
                    --el-input-border-color: var(--bw-border-color);
                    --el-input-hover-border-color: var(--bw-primary-color);
                    --el-input-focus-border-color: var(--bw-primary-color);
                }
                .bw-wizard-content .bw-bp-service-card, .bw-wizard-content .bw-bp-staff-card, .bw-wizard-content .bw-booking-calendar,
                .bw-wizard-content .bw-cal-header, .bw-wizard-content .bw-cal-weekdays, .bw-wizard-content .bw-cal-day, .bw-wizard-content .bw-date-select-first, 
                .bw-wizard-content .bw-payment-method-item, .bw-wizard-content .bw-radio-outer, .bw-wizard-content .bw-summary-item-v2, .bw-wizard-content .bw-summary-card-v2,
                .bw-wizard-content .bw-slots-wrapper, .bw-wizard-content .bw-slots-inner, .bw-wizard-content .bw-slot-btn, .bw-wizard-content #bw-phone-input {
                    border-color: var(--bw-border-color) !important;
                }
                .bw-slots-wrapper, .bw-slot-btn, .bw-custom-calendar {
                    background-color: #fff !important;
                }
                .bw-cal-day.bw-cal-disabled {
                    opacity: 0.3;
                    cursor: not-allowed;
                    color: var(--bw-subtitle-color-down-color) !important;
                }    
                .bw-wizard-content .bw-sidebar-step {
                    border-bottom-color: var(--bw-border-color) !important;
                }
                .bw-wizard-content .bw-wizard-content-header,
                .bw-wizard-content .bw-bp-category-filter, 
                .bw-wizard-content .bw-category-filter {
                    border-bottom: 1px solid var(--bw-border-color) !important;
                }
                .bw-wizard-content .bw-bp-nav-footer {
                    border-top: 1px solid var(--bw-border-color) !important;
                }
                .bw-wizard-content .el-input__wrapper, .bw-wizard-content .el-textarea__inner {
                    border-color: var(--bw-border-color) !important;
                    box-shadow: none !important;
                }
                .bw-wizard-content .el-input__wrapper:hover,
                .bw-wizard-content .el-input__wrapper.is-focus,
                .bw-wizard-content .el-textarea__inner:hover,
                .bw-wizard-content .el-textarea__inner:focus {
                    border-color: var(--bw-primary-color) !important;
                    box-shadow: none !important;
                }
                #bookwellab_frontend_app .el-input__inner {
                    border: none !important;
                    box-shadow: none !important;
                    outline: none !important;
                    background: transparent !important;
                }
                .bw-wizard-content .iti {
                    border-color: var(--bw-border-color) !important;
                }
                .bw-slot-group h4{
                    text-transform: uppercase !important;
                }    
                .bw-sidebar-step-icon svg {
                    display: block;
                    color: var(--bw-sidebar-title-color, #ffffff);
                }
                .bw-sidebar-step-icon svg path[stroke],
                .bw-sidebar-step-icon svg rect[stroke],
                .bw-sidebar-step-icon svg circle[stroke] {
                    stroke: currentColor !important;
                    fill: none !important;
                }
                .bw-sidebar-step-icon svg path[fill]:not([fill="none"]),
                .bw-sidebar-step-icon svg circle[fill]:not([fill="none"]) {
                    fill: currentColor !important;
                    stroke: none !important;
                }    
                #bookwellab_frontend_app,
                #bookwellab_frontend_app .bw-wizard-content,
                #bookwellab_frontend_app .bw-fp-catalog,
                #bookwellab_frontend_app .bw-fp-service-card {
                    font-family: var(--bw-font-family);
                }
                #bookwellab_frontend_app button:not(.iti__selected-country),
                #bookwellab_frontend_app .bw-btn-continue,
                #bookwellab_frontend_app .bw-btn-back,
                #bookwellab_frontend_app .bw-bp-design .bw-bp-category-filter button,
                #bookwellab_frontend_app .bw-slot-btn,
                #bookwellab_frontend_app .bw-fp-service-book-btn {
                    font-family: var(--bw-button-font-family) !important;
                }
                #bookwellab_frontend_app .bw-phone-container .iti__selected-country,
                #bookwellab_frontend_app .bw-phone-container .iti__selected-country-primary {
                    background: none !important;
                    background-color: transparent !important;
                    border: 0 !important;
                    box-shadow: none !important;
                }
                #bookwellab_frontend_app .bw-phone-container .iti__selected-country-primary:hover,
                #bookwellab_frontend_app .bw-phone-container .iti__selected-country:focus .iti__selected-country-primary {
                    background-color: rgba(0, 0, 0, 0.05) !important;
                }
                .bw-wizard-content {
                    background-color: var(--bw-panel-bg-color) !important;
                }
                .bw-wizard-sidebar { background-color: var(--bw-primary-color) !important; }
                .bw-btn-continue, .bw-sidebar-step.active .bw-sidebar-step-icon { background-color: var(--bw-primary-color) !important; color: var(--bw-button-text-color) !important; font-family: var(--bw-button-font-family) !important; font-size: var(--bw-button-font-size) !important; }
                .bw-btn-back { background-color: var(--bw-button-secondary-bg) !important; color: var(--bw-button-secondary-text) !important; border: 1px solid var(--bw-button-secondary-border) !important; font-family: var(--bw-button-font-family) !important; font-size: var(--bw-button-font-size) !important; }
                .bw-btn-back:hover { background-color: rgba(var(--bw-primary-color-rgb), 0.05) !important; border-color: var(--bw-primary-hover) !important; }
                .bw-slot-btn.selected { background-color: var(--bw-primary-color) !important; color: var(--bw-button-text-color) !important; }
                .bw-bp-price-badge { background-color: var(--bw-primary-color) !important; color: var(--bw-price-badge-color) !important; font-size: var(--bw-price-badge-size) !important; }
                .bw-bp-design .bw-bp-category-filter button {
                    color: var(--bw-category-title-color) !important;
                    font-size: var(--bw-category-title-size) !important;
                    font-family: var(--bw-font-family) !important;
                }
                .bw-bp-design .bw-bp-category-filter button.active {
                    background-color: var(--bw-primary-color) !important;
                    border-color: var(--bw-primary-color) !important;
                    color: var(--bw-button-text-color) !important;
                }
                .bw-fp-catalog-filters button,
                .bw-category-filter.bw-fp-catalog-filters button {
                    color: var(--bw-category-title-color) !important;
                    font-size: var(--bw-category-title-size) !important;
                    font-family: var(--bw-font-family) !important;
                    border-color: var(--bw-border-color) !important;
                    background: var(--bw-panel-bg-color) !important;
                }
                .bw-fp-catalog-filters button.active,
                .bw-category-filter.bw-fp-catalog-filters button.active {
                    background-color: var(--bw-primary-color) !important;
                    border-color: var(--bw-primary-color) !important;
                    color: var(--bw-button-text-color) !important;
                }
                .bw-fp-catalog-filters button:hover:not(.active),
                .bw-category-filter.bw-fp-catalog-filters button:hover:not(.active) {
                    border-color: var(--bw-primary-color) !important;
                    color: var(--bw-primary-color) !important;
                }
                .el-radio.is-checked .el-radio__inner { background-color: var(--bw-primary-color) !important; border-color: var(--bw-primary-color) !important; }
                .el-radio.is-checked .el-radio__label { color: var(--bw-primary-color) !important; }
                .el-radio__inner:hover { border-color: var(--bw-primary-color) !important; }
                .el-checkbox__input.is-checked .el-checkbox__inner,
                .el-checkbox__input.is-indeterminate .el-checkbox__inner {
                    background-color: var(--bw-primary-color) !important;
                    border-color: var(--bw-primary-color) !important;
                }
                .el-checkbox__input.is-checked + .el-checkbox__label,
                .el-checkbox__input.is-indeterminate + .el-checkbox__label {
                    color: var(--bw-primary-color) !important;
                }
                .el-checkbox__input.is-focus .el-checkbox__inner,
                .el-checkbox__inner:hover {
                    border-color: var(--bw-primary-color) !important;
                }

                /* Element Plus Button Overrides */
                .el-button--primary {
                    background-color: var(--bw-primary-color) !important;
                    border-color: var(--bw-primary-color) !important;
                    color: var(--bw-button-text-color) !important;
                    font-family: var(--bw-button-font-family) !important;
                    font-size: var(--bw-button-font-size) !important;
                }
                .el-button--primary:hover {
                    background-color: var(--bw-primary-hover) !important;
                    border-color: var(--bw-primary-hover) !important;
                }
                .el-button:not(.el-button--primary):hover {
                    color: var(--bw-primary-color) !important;
                    border-color: var(--bw-primary-hover) !important;
                    background-color: rgba(var(--bw-primary-color-rgb), 0.05) !important;
                }
                
                /* Title Styles */
                .bw-summary-title { 
                    color: var(--bw-title-color) !important; 
                    font-size: var(--bw-title-font-size) !important; 
                }

                .bw-bp-staff-name {
                    color: var(--bw-title-color) !important;
                    font-size: var(--bw-subtitle-size) !important;
                    background-color: #fff;
                }
                .bw-bp-service-name {
                    color: var(--bw-subtitle-color) !important;
                    font-size: var(--bw-subtitle-size) !important; 
                }
                .bw-bp-staff-card {
                    border-color: var(--bw-border-color) !important;
                }
                .bw-bp-staff-card:hover,
                .bw-bp-staff-card.selected {
                    border-color: var(--bw-primary-color) !important;
                }

                /* Section Title Style */
                .bw-content-title {
                    color: var(--bw-title-color) !important; 
                    font-size: var(--bw-section-title-size) !important;
                }

                /* Calendar, Subtitles & Form Labels (Month header, Time Slot title, Form labels, etc.) */
                .bw-cal-title, .bw-slots-title, .bw-bp-section-title, .bw-payment-title, .bw-slot-group h4, .el-form-item__label {
                    color: var(--bw-subtitle-color) !important;
                    font-size: var(--bw-subtitle-size) !important;
                    font-weight: 600 !important;
                }

                /* Sidebar Title Styles */
                .bw-step-title {
                    color: var(--bw-sidebar-title-color) !important;
                    font-size: var(--bw-sidebar-title-size) !important;
                }

                /* Confirmation Step Styles */
                .el-result__title p {
                    color: var(--bw-title-color) !important;
                    font-size: var(--bw-section-title-size) !important;
                }
                .el-result__subtitle p {
                    color: var(--bw-subtitle-color) !important;
                    font-size: var(--bw-subtitle-size) !important;
                }
                .el-result__icon svg {
                    color: var(--bw-primary-color) !important;
                }
                .bw-success-confirm-title {
                    color: var(--bw-title-color) !important;
                    font-family: var(--bw-font-family) !important;
                }
                .bw-success-confirm-message {
                    color: var(--bw-detail-color) !important;
                    font-family: var(--bw-font-family) !important;
                }
                .bw-success-check {
                    color: var(--bw-primary-color) !important;
                }
                .bw-add-to-calendar {
                    border-top-color: var(--bw-border-color) !important;
                }
                .bw-add-to-calendar__heading {
                    color: var(--bw-title-color) !important;
                    font-family: var(--bw-font-family) !important;
                    font-size: var(--bw-subtitle-size) !important;
                }
                .bw-add-to-calendar__btn {
                    border-color: var(--bw-border-color) !important;
                    color: var(--bw-content-text-color) !important;
                    font-family: var(--bw-button-font-family) !important;
                    font-size: var(--bw-content-text-size) !important;
                    box-sizing: border-box !important;
                    max-width: 100% !important;
                }
                @media (max-width: 767px) {
                    .bw-add-to-calendar__btn,
                    a.bw-add-to-calendar__btn,
                    button.bw-add-to-calendar__btn {
                        display: flex !important;
                        width: 100% !important;
                        max-width: 100% !important;
                        box-sizing: border-box !important;
                    }
                }
                .bw-add-to-calendar__btn:hover,
                .bw-add-to-calendar__btn:focus {
                    border-color: var(--bw-primary-color) !important;
                    color: var(--bw-primary-color) !important;
                    background: rgba(var(--bw-primary-color-rgb), 0.06) !important;
                    box-shadow: 0 0 0 1px rgba(var(--bw-primary-color-rgb), 0.12) !important;
                }
                .bw-add-to-calendar__icon {
                    color: var(--bw-title-color) !important;
                }
                
                /* Detail Styles (Global fallback) */
                .bw-item-meta { 
                    color: var(--bw-detail-color) !important; 
                    font-size: var(--bw-detail-font-size) !important; 
                }

                /* Sidebar Subtitle Styles */
                .bw-step-subtitle {
                    color: var(--bw-sidebar-text-color) !important;
                    font-size: var(--bw-sidebar-text-size) !important;
                }

                /* Content Text Styling (Specific Overrides - Must be last) */
                .bw-slot-btn, .el-radio__label, .bw-summary-value, .bw-bp-staff-email, .bw-bp-meta-duration, .bw-bp-service-description, .bw-fp-service-description, .bw-fp-service-duration, .bw-summary-label, .el-input__inner, .bw-step-details #bw-phone-input, .bw-step-details .iti__selected-dial-code, .bw-step-details .el-textarea__inner, .bw-step-details .bw-terms-agree__prefix, .bw-step-details .el-checkbox__label, .bw-step-details .el-select__wrapper, .bw-step-details .el-select__selected-item, .bw-step-details .el-select__placeholder, .bw-step-details .el-select__input, .bw-step-details .el-date-editor .el-input__inner, .bw-step-details .el-date-editor .el-input__wrapper {
                    color: var(--bw-content-text-color) !important;
                    font-size: var(--bw-content-text-size) !important;
                    font-family: var(--bw-font-family) !important;
                }

                .bw-booking-field-popper .el-select-dropdown__item,
                .bw-booking-field-popper .el-picker-panel,
                body.bw-fp-booking-modal-open .el-select-dropdown__item {
                    font-size: var(--bw-content-text-size) !important;
                    font-family: var(--bw-font-family) !important;
                }

                .bw-fp-service-card {
                    border-color: var(--bw-border-color) !important;
                    font-family: var(--bw-font-family) !important;
                }

                .bw-fp-service-card:hover {
                    border-color: var(--bw-primary-color) !important;
                }

                .bw-fp-service-title {
                    color: var(--bw-title-color) !important;
                    font-size: var(--bw-subtitle-size) !important;
                    font-family: var(--bw-font-family) !important;
                }

                .bw-fp-service-price {
                    background-color: var(--bw-primary-color) !important;
                    color: var(--bw-price-badge-color) !important;
                    font-size: var(--bw-price-badge-size) !important;
                    font-family: var(--bw-font-family) !important;
                }

                .bw-fp-service-book-btn {
                    background-color: var(--bw-primary-color) !important;
                    color: var(--bw-button-text-color) !important;
                    font-size: var(--bw-button-font-size) !important;
                    font-family: var(--bw-button-font-family, var(--bw-font-family)) !important;
                }

                .bw-fp-service-book-btn:hover,
                .bw-fp-service-card:hover .bw-fp-service-book-btn {
                    background-color: var(--bw-primary-hover) !important;
                }

                .bw-fp-service-meta--bordered {
                    border-bottom: 1px solid var(--bw-border-color) !important;
                }

                .bw-fp-service-description {
                    border: 0 !important;
                    border-top: none !important;
                }

                .bw-step-details .bw-terms-agree__link {
                    color: var(--bw-primary-color) !important;
                    font-size: var(--bw-content-text-size) !important;
                }

                .bw-step-details .bw-terms-agree-item .el-checkbox__input.is-checked .el-checkbox__inner {
                    background-color: var(--bw-primary-color) !important;
                    border-color: var(--bw-primary-color) !important;
                }

                .bw-step-details .bw-terms-agree-item .el-checkbox__input.is-focus .el-checkbox__inner,
                .bw-step-details .bw-terms-agree-item .el-checkbox__inner:hover {
                    border-color: var(--bw-primary-color) !important;
                }

                .bw-step-details .el-input__inner {
                    padding-left: 0px !important;
                }

                .bw-sidebar-step.active .bw-step-title, .bw-sidebar-step.active .bw-step-subtitle { color: #fff !important; }
                .bw-bp-service-card.selected { border-color: var(--bw-primary-color) !important; }
                .bw-cal-selected { background-color: var(--bw-primary-color) !important; color: #fff !important; border-color: var(--bw-primary-color) !important; }
                .bw-btn-continue:hover { background-color: var(--bw-primary-hover) !important; }
                .bw-bp-service-card:hover, .bw-bp-staff-card:hover, .bw-slot-btn:hover { border-color: var(--bw-primary-color) !important; }
                #bookwellab_frontend_app .bw-bp-extra-learn-more,
                #bookwellab_frontend_app .bw-bp-extra-learn-more:hover,
                #bookwellab_frontend_app .bw-bp-extra-learn-more:focus,
                #bookwellab_frontend_app .bw-bp-extra-learn-more:active,
                #bookwellab_frontend_app .bw-bp-extra-learn-more:focus-visible {
                    background: transparent !important;
                    background-color: transparent !important;
                    color: var(--bw-primary-color) !important;
                    outline: none !important;
                    box-shadow: none !important;
                    border: none !important;
                }
                #bookwellab_frontend_app .bw-bp-extra-qty-btn,
                #bookwellab_frontend_app .bw-bp-extra-qty-btn:hover,
                #bookwellab_frontend_app .bw-bp-extra-qty-btn:focus,
                #bookwellab_frontend_app .bw-bp-extra-qty-btn:active,
                #bookwellab_frontend_app .bw-bp-extra-qty-btn:focus-visible {
                    background: #fff !important;
                    background-color: #fff !important;
                    color: var(--bw-title-color) !important;
                    outline: none !important;
                    box-shadow: none !important;
                }
                .bw-cal-day:not(.bw-cal-disabled):not(.bw-cal-selected):hover { color: var(--bw-primary-color) !important; }
                .bw-slot-btn:hover { background-color: var(--bw-primary-color) !important; color: #fff !important; }
                .bw-cal-day:not(.bw-cal-disabled):not(.bw-cal-selected):hover { background-color: rgba(var(--bw-primary-color-rgb), 0.1) !important; }

                /* Summary Redesign Styles */
                .bw-summary-split {
                    display: grid;
                    grid-template-columns: 1fr 1fr;
                    gap: 30px;
                    margin-top: 10px;
                }
                .bw-summary-card-v2 {
                    background: #fff;
                    border: 1px solid var(--bw-border-color, #eef2f7);
                    border-radius: 16px;
                    padding: 30px;
                    box-shadow: 0 4px 20px rgba(0,0,0,0.03);
                }
                .bw-summary-item-v2 {
                    display: flex;
                    align-items: center;
                    gap: 15px;
                    margin-bottom: 25px;
                }
                .bw-summary-item-v2:last-child { margin-bottom: 0; }
                .bw-summary-item-icon {
                    width: 44px;
                    height: 44px;
                    border-radius: 50%;
                    border: 1px solid var(--bw-detail-color);
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    color: var(--bw-detail-color);
                    flex-shrink: 0;
                }
                .bw-summary-item-label {
                    font-size: var(--bw-detail-font-size);
                    color: var(--bw-detail-color);
                    margin-bottom: 4px;
                }
                .bw-summary-item-value {
                    font-size: var(--bw-title-font-size);
                    color: var(--bw-title-color);
                    font-weight: 600;
                }
                .bw-summary-item-subtext {
                    font-size: 13px;
                    color: var(--bw-detail-color);
                    margin-top: 2px;
                }

                .bw-payment-title-v2 {
                    font-size: var(--bw-subtitle-size);
                    color: var(--bw-subtitle-color);
                    margin-bottom: 15px;
                    font-weight: 600;
                }
                .bw-payment-methods-grid {
                    display: flex;
                    flex-direction: column;
                    gap: 12px;
                }
                .bw-payment-method-card {
                    display: flex;
                    align-items: center;
                    gap: 12px;
                    padding: 12px 16px;
                    border: 1px solid var(--bw-detail-color);
                    border-radius: 12px;
                    cursor: pointer;
                    transition: all 0.2s ease;
                    background-color: #fff;
                }
                .bw-payment-method-card:hover {
                    border-color: var(--bw-primary-color);
                }
                .bw-payment-method-card.selected {
                    border-color: var(--bw-primary-color);
                    background-color: rgba(var(--bw-primary-color-rgb), 0.05);
                }
                .bw-payment-method-icon {
                    color: var(--bw-primary-color);
                    display: flex;
                }
                .bw-payment-method-label {
                    flex-grow: 1;
                    font-size: var(--bw-title-font-size);
                    color: var(--bw-title-color);
                }
                .bw-radio-outer {
                    width: 18px;
                    height: 18px;
                    border: 1px solid #dcdfe6;
                    border-radius: 50%;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    background: #fff;
                }
                .bw-payment-method-card.selected .bw-radio-outer {
                    border-color: var(--bw-primary-color);
                }
                .bw-radio-inner {
                    width: 10px;
                    height: 10px;
                    background-color: var(--bw-primary-color);
                    border-radius: 50%;
                }

                .bw-summary-total-section {
                    margin-top: 20px;
                    border-top: 1px solid var(--bw-border-color);
                    padding-top: 20px;
                }
                .bw-total-label-row {
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                }
                .bw-total-text {
                    font-size: var(--bw-section-title-size) !important;
                    color: var(--bw-title-color) !important;
                    font-weight: 600;
                }
                .bw-total-amount {
                    font-size: var(--bw-section-title-size) !important;
                    color: var(--bw-title-color) !important;
                    font-weight: 700;
                }
                .bw-cal-title,
                .bw-slots-title,
                .bw-slot-btn {
                    font-weight: 600 !important;
                }   
                .bw-wizard-content .bw-bp-category-filter, .bw-wizard-content .bw-category-filter {
                    border: none !important;
                    border-bottom: none !important;
                }   
                @media (max-width: 768px) {
                    .bw-summary-split { grid-template-columns: 1fr; }
                    .bw-summary-total-section { margin-top: 10px; }
                }

                .bw-dots-loader {
                    display: inline-flex;
                    align-items: center;
                    gap: 3px;
                }

                .bw-dots-loader span {
                    width: 7px;
                    height: 7px;
                    background-color: var(--bw-button-text-color);
                    border-radius: 50%;
                    display: inline-block;
                    animation: bw-dots 1.4s infinite ease-in-out both;
                }

                .bw-dots-loader span:nth-child(1) { animation-delay: -0.32s; }
                .bw-dots-loader span:nth-child(2) { animation-delay: -0.16s; }

                @keyframes bw-dots {
                    0%, 80%, 100% { transform: scale(0); }
                    40% { transform: scale(1.0); }
                }

                .bw-btn-continue.is-loading {
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    cursor: not-allowed;
                    opacity: 0.8;
                    min-width: 160px;
                }
            `;

            document.head.appendChild(style);

            notifyWizardReady();
        });

        watch(() => bookingData.payment_gateway, (newVal, oldVal) => {
            if (oldVal === 'paypal' && newVal !== 'paypal' && pendingPayPalAppointmentId.value) {
                handlePayPalPaymentAborted(false);
            }
            if (isStep('summary')) {
                nextTick(() => renderPayPalButtons());
            }
        }, { flush: 'post' });

        watch(isPayPalSelected, (selected) => {
            if (selected && isStep('summary')) {
                renderPayPalButtons();
            } else if (!selected) {
                destroyPayPalButtons();
            }
        }, { flush: 'post' });

        watch(currentStepSlug, (slug) => {
            if (slug === 'summary') {
                syncPaymentGatewayForTotal();
                if (isPayPalSelected.value) {
                    nextTick(() => renderPayPalButtons());
                } else {
                    destroyPayPalButtons();
                }
            } else {
                destroyPayPalButtons();
            }
        });

        const submitBooking = async () => {
            submitting.value = true;
            prepareBookingPayload();
            try {
                const bookingAction = (bookingData.payment_gateway === 'woocommerce' && bookwellab_front_obj.wc_booking_action)
                    ? bookwellab_front_obj.wc_booking_action
                    : 'bookwellab_process_booking';
                const response = await axios.post(bookwellab_front_obj.ajax_url, Qs.stringify({
                    action: bookingAction,
                    data: JSON.stringify(bookingData),
                    nonce: bookwellab_front_obj.nonce,
                }));

                if (response.data.success && response.data.data && response.data.data.redirect_url) {
                    window.location.href = response.data.data.redirect_url;
                    return;
                }

                if (response.data.success) {
                    confirmedAppointmentId.value = response.data.data.appointment_id;
                    currentStepSlug.value = 'confirmed';
                } else {
                    showGlobalError(response.data.data.msg);
                    if (response.data.data.msg && response.data.data.msg.indexOf('Security verification failed') !== -1) {
                        refreshSpamToken();
                    }
                }
            } catch (e) {
                console.error(e);
                showGlobalError(labels.value.unexpected_error || 'An unexpected error occurred. Please try again.');
            } finally {
                submitting.value = false;
            }
        };

        const applyPrefillCustomer = () => {
            bookingData.customer.first_name = prefillCustomer.first_name || '';
            bookingData.customer.last_name = prefillCustomer.last_name || '';
            bookingData.customer.email = prefillCustomer.email || '';
            bookingData.customer.phone = prefillCustomer.phone || '';
            bookingData.customer.phone_code = prefillCustomer.phone_code || '';
            bookingData.customer.country_slug = prefillCustomer.country_slug || '';
            bookingData.customer.note = '';

            const phoneInputEl = document.querySelector('#bw-phone-input');
            if (phoneInputEl) {
                delete phoneInputEl.dataset.bwPrefilled;
            }

            if (!iti) {
                return;
            }

            const prefillPhone = (prefillCustomer.phone || '').toString().trim();
            if (prefillPhone) {
                if (prefillCustomer.country_slug) {
                    iti.setCountry(prefillCustomer.country_slug);
                }
                iti.setNumber(prefillPhone);
                if (phoneInputEl) {
                    phoneInputEl.dataset.bwPrefilled = '1';
                }
            } else {
                iti.setNumber('');
            }
        };

        const reset = async () => {
            bookingData.date = null;
            bookingData.time = null;
            bookingData.payment_gateway = '-';
            bookingData.extras = [];
            bookingData.custom_fields = {};
            bookingData.base_service_price = 0;

            applyPrefillCustomer();
            bookingData.terms_accepted = false;
            if (window.BookWellABProCustomFields && typeof window.BookWellABProCustomFields.initBookingData === 'function') {
                window.BookWellABProCustomFields.initBookingData(bookingData, detailFormFields.value);
            }
            bookingData.spam_key = '';
            bookingData.spam_code = '';

            selectedDate.value = '';
            slots.value = [];
            monthDisabledDates.value = [];
            currentMonthDate.value = new Date();
            monthScanDirection.value = 'forward';
            activeCategory.value = 'all';
            confirmedAppointmentId.value = null;
            globalError.value = '';

            if (isCatalogMode) {
                bookingData.service_id = null;
                bookingData.staff_id = null;
                bookingData.service_price = 0;
                bookingData.base_service_price = 0;
                bookingData.extras = [];
                catalogServiceLocked.value = false;
                catalogDialogVisible.value = false;
                forceButtonPopup.value = false;
                runtimeStaffId.value = null;
                document.body.classList.remove('bw-fp-booking-modal-open');
                currentStepSlug.value = 'service';
                if (servicesFilteredByButton.value) {
                    servicesFilteredByButton.value = false;
                    await fetchServices(sc_staff_id || 0, true);
                }
            } else if (forceButtonPopup.value) {
                bookingData.service_id = sc_service_id ? parseInt(sc_service_id, 10) : null;
                bookingData.staff_id = sc_staff_id ? parseInt(sc_staff_id, 10) : null;
                catalogServiceLocked.value = false;
                catalogDialogVisible.value = false;
                forceButtonPopup.value = false;
                runtimeStaffId.value = null;
                document.body.classList.remove('bw-fp-booking-modal-open');
                currentStepSlug.value = getInitialStepSlug();
                if (servicesFilteredByButton.value) {
                    servicesFilteredByButton.value = false;
                    await fetchServices(sc_staff_id || 0, true);
                }
            } else if (sc_service_id) {
                bookingData.service_id = parseInt(sc_service_id, 10);
                bookingData.staff_id = sc_staff_id ? parseInt(sc_staff_id, 10) : null;

                const preselectedService = services.value.find(s => s.bw_service_id == sc_service_id);
                if (preselectedService) {
                    setBaseServicePrice(preselectedService.bw_service_price || 0);
                }

                if (sc_staff_id) {
                    const preselectedStaff = staff.value.find(s => s.bw_staff_id == sc_staff_id);
                    if (preselectedStaff && preselectedStaff.bw_service_price) {
                        setBaseServicePrice(preselectedStaff.bw_service_price);
                    }
                } else if (hideStaffStep.value) {
                    await ensureValidAutoAssignedStaff(bookingData.service_id);
                }

                currentStepSlug.value = getInitialStepSlug();
            } else {
                bookingData.service_id = null;
                bookingData.staff_id = null;
                bookingData.service_price = 0;
                bookingData.base_service_price = 0;
                bookingData.extras = [];
                currentStepSlug.value = 'service';
            }

            await refreshSpamToken();
            syncPaymentGatewayForTotal();

            if (bookingData.staff_id && bookingData.service_id) {
                await fetchMonthAvailability();
            }
        };

        const computedServices = computed(() => {
            if (activeCategory.value === 'all') return services.value;
            return services.value.filter(s => s.bw_category_id == activeCategory.value);
        });

        const catalogTotalItems = computed(() => computedServices.value.length);
        const catalogTotalPages = computed(() => Math.max(1, Math.ceil(catalogTotalItems.value / catalogPageSize)));
        const paginatedCatalogServices = computed(() => {
            const start = (catalogPage.value - 1) * catalogPageSize;
            return computedServices.value.slice(start, start + catalogPageSize);
        });

        const setCatalogCategory = (categoryId) => {
            activeCategory.value = categoryId;
            catalogPage.value = 1;
        };

        const onCatalogPageChange = (page) => {
            catalogPage.value = page || 1;
            if (typeof window !== 'undefined') {
                window.scrollTo({ top: 0, behavior: 'smooth' });
            }
        };

        const openCatalogBooking = async (service) => {
            if (!service || !service.bw_service_id) {
                return;
            }

            // Re-apply logged-in / stored customer details each time the popup opens.
            applyPrefillCustomer();

            catalogServiceLocked.value = true;
            catalogDialogVisible.value = true;
            document.body.classList.add('bw-fp-booking-modal-open');

            // Ensure selectService treats this as a fresh selection.
            if (bookingData.service_id === service.bw_service_id) {
                bookingData.service_id = null;
            }

            await selectService(service, true);

            if (hideStaffStep.value && !bookingData.staff_id) {
                catalogServiceLocked.value = false;
                catalogDialogVisible.value = false;
                forceButtonPopup.value = false;
                runtimeStaffId.value = null;
                document.body.classList.remove('bw-fp-booking-modal-open');
                return;
            }

            // isAuto skips nextStep — always move past the service step in popup mode.
            if (currentStepSlug.value === 'service') {
                goToStep(getStepAfterService());
            }

            await nextTick();
            syncFrontendPhoneInput();
        };

        const openEmptyBookingPopup = async () => {
            applyPrefillCustomer();

            bookingData.service_id = null;
            bookingData.staff_id = null;
            bookingData.date = null;
            bookingData.time = null;
            bookingData.extras = [];
            bookingData.service_price = 0;
            bookingData.base_service_price = 0;
            selectedDate.value = '';
            slots.value = [];
            invalidateMonthAvailabilityCache();
            globalError.value = '';

            catalogServiceLocked.value = false;
            catalogDialogVisible.value = true;
            document.body.classList.add('bw-fp-booking-modal-open');
            currentStepSlug.value = 'service';

            await nextTick();
            syncFrontendPhoneInput();
        };

        const openBookingByIds = async (serviceId, staffId = null) => {
            let tries = 0;
            while (loadingServices.value && tries < 60) {
                await new Promise((resolve) => setTimeout(resolve, 100));
                tries += 1;
            }

            runtimeStaffId.value = staffId ? parseInt(staffId, 10) : null;
            if (!isCatalogMode) {
                forceButtonPopup.value = true;
            }

            const sid = parseInt(serviceId, 10);
            if (!sid) {
                // Staff-only → that staff's active services; neither → all active services.
                const filterStaff = runtimeStaffId.value || 0;
                if (filterStaff > 0 || servicesFilteredByButton.value) {
                    await fetchServices(filterStaff, true);
                }
                servicesFilteredByButton.value = filterStaff > 0;
                activeCategory.value = 'all';
                catalogPage.value = 1;
                await openEmptyBookingPopup();
                return true;
            }

            // Ensure the target service is in the loaded list (undo prior staff filter if needed).
            let service = services.value.find((item) => parseInt(item.bw_service_id, 10) === sid);
            if (!service || servicesFilteredByButton.value) {
                await fetchServices(runtimeStaffId.value || 0, true);
                servicesFilteredByButton.value = !!runtimeStaffId.value;
                service = services.value.find((item) => parseInt(item.bw_service_id, 10) === sid);
            }

            if (!service) {
                showGlobalError(labels.value.service_not_found || 'Selected service is not available.');
                forceButtonPopup.value = false;
                runtimeStaffId.value = null;
                if (servicesFilteredByButton.value) {
                    servicesFilteredByButton.value = false;
                    await fetchServices(sc_staff_id || 0, true);
                }
                return false;
            }

            await openCatalogBooking(service);

            if (runtimeStaffId.value && parseInt(bookingData.staff_id, 10) !== runtimeStaffId.value) {
                const member = staff.value.find((item) => parseInt(item.bw_staff_id, 10) === runtimeStaffId.value);
                if (member) {
                    bookingData.staff_id = member.bw_staff_id;
                    if (member.bw_service_price) {
                        setBaseServicePrice(member.bw_service_price);
                    }
                    if (currentStepSlug.value === 'staff') {
                        goToStep(getStepAfterService());
                    }
                }
            }

            return true;
        };

        const closeCatalogBooking = async () => {
            await reset();
        };

        const getServiceName = (id) => {
            const s = services.value.find(x => x.bw_service_id == id);
            return s ? s.bw_service_name : '';
        };

        const getStaffName = (id) => {
            const s = staff.value.find(x => x.bw_staff_id == id);
            return s ? s.bw_staff_name : '';
        };

        const getStaffImageSrc = (member) => {
            const fallback = bookwellab_front_obj.default_staff_image || '';
            const src = member && member.bw_staff_image ? String(member.bw_staff_image) : '';
            if (!src || /(?:default-avatar|staff-placeholder)\.svg/i.test(src)) {
                return fallback || src;
            }
            return src;
        };

        const isStaffPlaceholderImage = (member) => {
            const src = member && member.bw_staff_image ? String(member.bw_staff_image) : '';
            return !src || /(?:default-avatar|staff-placeholder)\.svg/i.test(src);
        };

        const getServiceImageSrc = (service) => {
            const fallback = bookwellab_front_obj.default_service_image || '';
            const src = service && service.bw_service_image ? String(service.bw_service_image) : '';
            if (!src || /service-placeholder\.svg/i.test(src)) {
                return fallback || src;
            }
            return src;
        };

        const isServicePlaceholderImage = (service) => {
            const src = service && service.bw_service_image ? String(service.bw_service_image) : '';
            return !src || /service-placeholder\.svg/i.test(src);
        };

        const applyExternalConfirmation = (data) => {
            if (!data || !data.booking) {
                return false;
            }

            const booking = data.booking;
            bookingData.service_id = booking.service_id || null;
            bookingData.staff_id = booking.staff_id || null;
            bookingData.date = booking.date || null;
            bookingData.time = booking.time || null;
            bookingData.extras = Array.isArray(booking.extras) ? booking.extras : [];
            if (booking.base_service_price !== undefined && booking.base_service_price !== null) {
                bookingData.base_service_price = parseFloat(booking.base_service_price) || 0;
            } else {
                bookingData.base_service_price = parseFloat(booking.service_price) || 0;
            }
            syncBookingTotalPrice();

            if (booking.customer) {
                Object.assign(bookingData.customer, booking.customer);
            }

            if (data.service && data.service.bw_service_id) {
                const serviceExists = services.value.find((item) => item.bw_service_id == data.service.bw_service_id);
                if (!serviceExists) {
                    services.value.push(data.service);
                }
            }

            if (data.staff && data.staff.bw_staff_id) {
                const staffExists = staff.value.find((item) => item.bw_staff_id == data.staff.bw_staff_id);
                if (!staffExists) {
                    staff.value.push(data.staff);
                }
            }

            confirmedAppointmentId.value = data.appointment_id || null;
            currentStepSlug.value = 'confirmed';
            return true;
        };

        const onExtrasChanged = () => {
            syncBookingTotalPrice();
            bookingData.date = null;
            bookingData.time = null;
            selectedDate.value = '';
            slots.value = [];
            invalidateMonthAvailabilityCache();
            // Month slots refetch when entering Date & Time (with updated extras).
        };

        const notifyWizardReady = () => {
            const detail = {
                applyConfirmation: applyExternalConfirmation,
                onExtrasChanged,
                bookingData,
                hasExtrasForService,
                goToStep,
                openCatalogBooking,
                openBookingByIds,
            };
            if (typeof window !== 'undefined') {
                window.BookWellABBooking = {
                    open: async (opts = {}) => openBookingByIds(opts.serviceId || opts.service_id, opts.staffId || opts.staff_id),
                    openCatalogBooking,
                    openBookingByIds,
                };
            }
            document.dispatchEvent(new CustomEvent('bookwellab:wizard-ready', { detail }));
        };

        const formatDuration = (val, unit) => {
            const v = parseInt(val);
            const labels = bookwellab_front_obj.labels || {};
            let label = '';
            if (unit === 'h') {
                label = v === 1 ? (labels.hour_label || 'Hour') : (labels.hours_label || 'Hours');
            } else {
                label = v === 1 ? (labels.minute_label || 'Minute') : (labels.minutes_label || 'Minutes');
            }
            return `${v} ${label}`;
        };

        const truncateCatalogDescription = (text, maxLength = 500) => {
            if (!text) {
                return '';
            }
            const plain = String(text).replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').trim();
            if (plain.length <= maxLength) {
                return plain;
            }
            return plain.slice(0, maxLength).trimEnd() + '…';
        };

        const getServiceDurationMinutes = (serviceId) => {
            const service = services.value.find((item) => item.bw_service_id == serviceId);
            if (!service) {
                return 60;
            }
            let minutes = parseInt(service.bw_service_duration_val, 10) || 60;
            if (service.bw_service_duration_unit === 'h') {
                minutes *= 60;
            }
            return Math.max(1, minutes);
        };

        const normalizeBookingTime = (time) => {
            const raw = String(time || '').trim();
            if (!raw) {
                return '00:00:00';
            }
            if (/^\d{2}:\d{2}:\d{2}$/.test(raw)) {
                return raw;
            }
            if (/^\d{1,2}:\d{2}:\d{2}$/.test(raw)) {
                const parts = raw.split(':');
                return String(parts[0]).padStart(2, '0') + ':' + parts[1] + ':' + parts[2];
            }
            if (/^\d{2}:\d{2}$/.test(raw)) {
                return raw + ':00';
            }
            if (/^\d{1,2}:\d{2}$/.test(raw)) {
                const parts = raw.split(':');
                return String(parts[0]).padStart(2, '0') + ':' + parts[1] + ':00';
            }
            return '00:00:00';
        };

        const pad2 = (value) => String(value).padStart(2, '0');

        const getWpTimezoneConfig = () => {
            const tzObj = bookwellab_front_obj.timezone || {};
            let timezone = String(tzObj.string || bookwellab_front_obj.timezone_string || '').trim();
            if (!timezone || /^UTC([+-]|$)/i.test(timezone) || /^[+-]\d{2}/.test(timezone)) {
                timezone = '';
            }

            let offset = String(tzObj.offset || bookwellab_front_obj.timezone_offset || '').trim();
            if (!/^[+-]\d{2}:\d{2}$/.test(offset)) {
                const seconds = parseInt(tzObj.offset_seconds, 10);
                if (!isNaN(seconds)) {
                    const sign = seconds < 0 ? '-' : '+';
                    const abs = Math.abs(seconds);
                    offset = sign + pad2(Math.floor(abs / 3600)) + ':' + pad2(Math.floor((abs % 3600) / 60));
                } else {
                    const gmt = parseFloat(tzObj.gmt_offset);
                    if (!isNaN(gmt)) {
                        const sign = gmt < 0 ? '-' : '+';
                        const abs = Math.abs(gmt);
                        const hours = Math.floor(abs);
                        const minutes = Math.round((abs - hours) * 60);
                        offset = sign + pad2(hours) + ':' + pad2(minutes);
                    } else {
                        offset = '+00:00';
                    }
                }
            }

            return { timezone, offset };
        };

        /**
         * Add minutes to a site wall-clock date/time without browser timezone shifts.
         */
        const addMinutesToWallClock = (dateStr, timeStr, addMinutes) => {
            const dateParts = String(dateStr).split('-').map((part) => parseInt(part, 10));
            const timeParts = String(timeStr).split(':').map((part) => parseInt(part, 10));
            if (dateParts.length !== 3 || timeParts.length < 2) {
                return null;
            }
            const total = Date.UTC(
                dateParts[0],
                dateParts[1] - 1,
                dateParts[2],
                timeParts[0] || 0,
                timeParts[1] || 0,
                timeParts[2] || 0
            ) + (addMinutes * 60000);
            const end = new Date(total);
            return {
                date: end.getUTCFullYear() + '-' + pad2(end.getUTCMonth() + 1) + '-' + pad2(end.getUTCDate()),
                time: pad2(end.getUTCHours()) + ':' + pad2(end.getUTCMinutes()) + ':' + pad2(end.getUTCSeconds()),
            };
        };

        const toLocalCalendarStamp = (dateStr, timeStr) => {
            return String(dateStr).replace(/-/g, '') + 'T' + String(timeStr).replace(/:/g, '');
        };

        const toOffsetIso = (dateStr, timeStr, offset) => {
            const safeOffset = offset && /^[+-]\d{2}:\d{2}$/.test(offset) ? offset : '+00:00';
            return String(dateStr) + 'T' + String(timeStr) + safeOffset;
        };

        /**
         * Convert WordPress site wall-clock time to a real UTC Date.
         */
        const wallClockToUtcDate = (dateStr, timeStr, timezone, fallbackOffset) => {
            // Prefer explicit WP offset ISO — reliable and matches Settings → General timezone.
            const offset = fallbackOffset && /^[+-]\d{2}:\d{2}$/.test(fallbackOffset) ? fallbackOffset : '+00:00';
            const fromOffset = new Date(toOffsetIso(dateStr, timeStr, offset));
            if (!isNaN(fromOffset.getTime())) {
                return fromOffset;
            }

            if (timezone && typeof Intl !== 'undefined' && typeof Intl.DateTimeFormat === 'function') {
                const [year, month, day] = String(dateStr).split('-').map((part) => parseInt(part, 10));
                const [hour, minute, second] = String(timeStr).split(':').map((part) => parseInt(part, 10));
                const desiredAsUtcMs = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0);
                const dtf = new Intl.DateTimeFormat('en-US', {
                    timeZone: timezone,
                    year: 'numeric',
                    month: '2-digit',
                    day: '2-digit',
                    hour: '2-digit',
                    minute: '2-digit',
                    second: '2-digit',
                    hourCycle: 'h23',
                });

                const wallClockMs = (ms) => {
                    const map = {};
                    dtf.formatToParts(new Date(ms)).forEach((part) => {
                        if (part.type !== 'literal') {
                            map[part.type] = part.value;
                        }
                    });
                    const localHour = parseInt(map.hour, 10) % 24;
                    return Date.UTC(
                        parseInt(map.year, 10),
                        parseInt(map.month, 10) - 1,
                        parseInt(map.day, 10),
                        localHour,
                        parseInt(map.minute, 10),
                        parseInt(map.second, 10)
                    );
                };

                let utcMs = desiredAsUtcMs;
                for (let i = 0; i < 3; i++) {
                    utcMs += desiredAsUtcMs - wallClockMs(utcMs);
                }
                const result = new Date(utcMs);
                if (!isNaN(result.getTime())) {
                    return result;
                }
            }

            return new Date(toOffsetIso(dateStr, timeStr, '+00:00'));
        };

        const toUtcCalendarStamp = (dateObj) => {
            return dateObj.toISOString().replace(/[-:]/g, '').replace(/\.\d{3}/, '');
        };

        const getCalendarEventTimes = () => {
            const date = String(bookingData.date || '').trim();
            const time = normalizeBookingTime(bookingData.time);
            if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
                return null;
            }

            const endWall = addMinutesToWallClock(date, time, getServiceDurationMinutes(bookingData.service_id));
            if (!endWall) {
                return null;
            }

            const { timezone, offset } = getWpTimezoneConfig();
            const startUtc = wallClockToUtcDate(date, time, timezone, offset);
            const endUtc = wallClockToUtcDate(endWall.date, endWall.time, timezone, offset);
            if (isNaN(startUtc.getTime()) || isNaN(endUtc.getTime())) {
                return null;
            }

            return {
                startDate: date,
                startTime: time,
                endDate: endWall.date,
                endTime: endWall.time,
                startUtc,
                endUtc,
                offset,
                timezone,
                // Wall-clock + WP offset (e.g. 2026-07-21T17:00:00+05:30)
                startIso: toOffsetIso(date, time, offset),
                endIso: toOffsetIso(endWall.date, endWall.time, offset),
                // Absolute UTC for consumers that need Zulu time
                startUtcStamp: toUtcCalendarStamp(startUtc),
                endUtcStamp: toUtcCalendarStamp(endUtc),
                // Floating local stamps (NO Z) — same numbers as booking UI
                startLocalStamp: toLocalCalendarStamp(date, time),
                endLocalStamp: toLocalCalendarStamp(endWall.date, endWall.time),
            };
        };

        const escapeIcsText = (value) => {
            return String(value || '')
                .replace(/\\/g, '\\\\')
                .replace(/;/g, '\\;')
                .replace(/,/g, '\\,')
                .replace(/\r?\n/g, '\\n');
        };

        const getCalendarEventMeta = () => {
            const times = getCalendarEventTimes();
            if (!times) {
                return null;
            }

            const serviceName = getServiceName(bookingData.service_id) || (labels.value.service_label || 'Appointment');
            const staffName = hideStaffStep.value ? '' : getStaffName(bookingData.staff_id);
            const detailsParts = [];
            if (staffName) {
                detailsParts.push((labels.value.staff_label || 'Staff') + ': ' + staffName);
            }
            if (confirmedAppointmentId.value) {
                detailsParts.push((labels.value.appointment_id_label || 'Appointment ID') + ': #' + confirmedAppointmentId.value);
            }
            const customerName = [bookingData.customer.first_name, bookingData.customer.last_name]
                .map((part) => String(part || '').trim())
                .filter(Boolean)
                .join(' ');
            if (customerName) {
                detailsParts.push((labels.value.customer_label || 'Customer') + ': ' + customerName);
            }

            return {
                title: serviceName,
                details: detailsParts.join('\n'),
                ...times,
            };
        };

        const canAddToCalendar = computed(() => {
            return !!(bookingData.date && bookingData.time && bookingData.service_id);
        });

        const googleCalendarUrl = computed(() => {
            const event = getCalendarEventMeta();
            if (!event) {
                return '#';
            }
            // Floating local times (no Z) keep 5:00 PM as 5:00 PM.
            // ctz tells Google which WordPress timezone that wall-clock belongs to.
            const params = new URLSearchParams({
                action: 'TEMPLATE',
                text: event.title,
                dates: event.startLocalStamp + '/' + event.endLocalStamp,
            });
            if (event.timezone) {
                params.set('ctz', event.timezone);
            }
            if (event.details) {
                params.set('details', event.details);
            }
            return 'https://calendar.google.com/calendar/render?' + params.toString();
        });

        const outlookCalendarUrl = computed(() => {
            const event = getCalendarEventMeta();
            if (!event) {
                return '#';
            }
            // Outlook: omit Z/offset so time is treated as the viewer's local wall-clock
            // (same numbers as the booking UI). Docs: without Z = user's timezone.
            // See https://interactiondesignfoundation.github.io/add-event-to-calendar-docs/services/outlook-web.html
            const params = new URLSearchParams({
                path: '/calendar/action/compose',
                rru: 'addevent',
                subject: event.title,
                startdt: event.startDate + 'T' + event.startTime,
                enddt: event.endDate + 'T' + event.endTime,
            });
            if (event.details) {
                params.set('body', event.details);
            }
            return 'https://outlook.live.com/calendar/0/deeplink/compose?' + params.toString();
        });

        const downloadIcalCalendar = () => {
            const event = getCalendarEventMeta();
            if (!event) {
                return;
            }

            const nowStamp = toUtcCalendarStamp(new Date());
            const uid = 'bookwell-' + (confirmedAppointmentId.value || Date.now()) + '@' + (window.location.hostname || 'bookwell');
            const lines = [
                'BEGIN:VCALENDAR',
                'VERSION:2.0',
                'PRODID:-//BookWellNow//Appointment Booking//EN',
                'CALSCALE:GREGORIAN',
                'METHOD:PUBLISH',
                'BEGIN:VEVENT',
                'UID:' + uid,
                'DTSTAMP:' + nowStamp,
            ];

            if (event.timezone) {
                lines.push('DTSTART;TZID=' + event.timezone + ':' + event.startLocalStamp);
                lines.push('DTEND;TZID=' + event.timezone + ':' + event.endLocalStamp);
            } else if (event.offset && event.offset !== '+00:00') {
                // Manual WP offset: store absolute UTC derived from WP offset.
                lines.push('DTSTART:' + event.startUtcStamp);
                lines.push('DTEND:' + event.endUtcStamp);
            } else {
                // Last resort: floating local (same clock time as booking UI).
                lines.push('DTSTART:' + event.startLocalStamp);
                lines.push('DTEND:' + event.endLocalStamp);
            }

            lines.push('SUMMARY:' + escapeIcsText(event.title));
            lines.push('DESCRIPTION:' + escapeIcsText(event.details));
            lines.push('END:VEVENT');
            lines.push('END:VCALENDAR');

            const blob = new Blob([lines.join('\r\n')], { type: 'text/calendar;charset=utf-8' });
            const url = URL.createObjectURL(blob);
            const link = document.createElement('a');
            link.href = url;
            link.download = 'bookwell-appointment.ics';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
            URL.revokeObjectURL(url);
        };

        const getServiceDuration = (id) => {
            const s = services.value.find(x => x.bw_service_id == id);
            if (!s) return '';
            return formatDuration(s.bw_service_duration_val, s.bw_service_duration_unit);
        };

        const formatPHPDate = (dateObj, format) => {
            if (!(dateObj instanceof Date) || isNaN(dateObj)) return "";

            const months = bookwellab_front_obj.month_names || ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];

            const replacements = {
                'F': months[dateObj.getMonth()],
                'j': dateObj.getDate(),
                'Y': dateObj.getFullYear(),
                'm': String(dateObj.getMonth() + 1).padStart(2, '0'),
                'd': String(dateObj.getDate()).padStart(2, '0'),
                'h': String(dateObj.getHours() % 12 || 12).padStart(2, '0'),
                'g': String(dateObj.getHours() % 12 || 12),
                'H': String(dateObj.getHours()).padStart(2, '0'),
                'G': String(dateObj.getHours()),
                'i': String(dateObj.getMinutes()).padStart(2, '0'),
                's': String(dateObj.getSeconds()).padStart(2, '0'),
                'a': dateObj.getHours() >= 12 ? 'pm' : 'am',
                'A': dateObj.getHours() >= 12 ? 'PM' : 'AM'
            };

            return format.replace(/[FjYmdhgGHisaA]/g, (match) => {
                return replacements[match] !== undefined ? replacements[match] : match;
            });
        };

        const formatDate = (dateStr) => {
            if (!dateStr) return "";
            const d = new Date(dateStr + "T00:00:00"); // Ensure local time parsing
            if (isNaN(d)) return dateStr;
            const format = bookwellab_front_obj.date_format || 'F j, Y';
            return formatPHPDate(d, format);
        };

        const formatTime = (timeStr) => {
            if (!timeStr) return "";
            const d = new Date("1970-01-01T" + timeStr);
            if (isNaN(d)) return timeStr;
            const format = bookwellab_front_obj.time_format || 'h:i a';
            return formatPHPDate(d, format);
        };

        const formatSlotRange = (timeStr) => {
            if (!timeStr) return "";
            let duration = 60;
            if (bookingData.staff_id && staff.value.length > 0) {
                const currentStaff = staff.value.find(s => s.bw_staff_id == bookingData.staff_id);
                if (currentStaff) {
                    let val = parseInt(currentStaff.bw_service_duration_val);
                    if (currentStaff.bw_service_duration_unit == 'h') val *= 60;
                    if (!isNaN(val) && val > 0) duration = val;
                }
            }
            const extrasDuration = window.BookWellABProExtras?.calculateExtrasDurationMinutes?.(bookingData) ?? 0;
            duration += extrasDuration;
            const start = new Date("1970-01-01T" + timeStr);
            const end = new Date(start.getTime() + duration * 60000);

            const format = bookwellab_front_obj.time_format || 'h:i a';
            return `${formatPHPDate(start, format)} - ${formatPHPDate(end, format)}`;
        };

        return {
            currentStepSlug, currentStepTitle, isStep, isStepCompleted, isBookingConfirmed, goToStep, visibleSidebarSteps, getStepLabel, getStepTitle, isContentStep,
            categories, services, staff, bookingData, paymentOptions, showPaymentOptions, activeCategory, filteredServices,
            selectService, selectStaff, nextStep, prevStep, errors, globalError, dismissGlobalError,
            selectedDate, slots, loadingSlots, loadingServices, loadingStaff, onDateChange, selectSlot, currentMonthDate, calendarWeekdayLabels: Array.isArray(bookwellab_front_obj.week_day_labels) && bookwellab_front_obj.week_day_labels.length === 7
                ? bookwellab_front_obj.week_day_labels
                : ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
            calendarDays: computed(() => {
                const year = currentMonthDate.value.getFullYear();
                const month = currentMonthDate.value.getMonth();
                const firstDay = new Date(year, month, 1).getDay();
                const daysInMonth = new Date(year, month + 1, 0).getDate();
                const daysInPrevMonth = new Date(year, month, 0).getDate();
                let weekStartsOn = parseInt(bookwellab_front_obj.week_starts_on, 10);
                if (isNaN(weekStartsOn) || weekStartsOn < 0 || weekStartsOn > 6) {
                    weekStartsOn = 1;
                }
                let startOffset = (firstDay - weekStartsOn + 7) % 7;
                const days = [];
                for (let i = startOffset - 1; i >= 0; i--) {
                    const d = new Date(year, month - 1, daysInPrevMonth - i);
                    days.push({ date: d, dayNo: d.getDate(), isCurrentMonth: false, disabled: true });
                }
                for (let i = 1; i <= daysInMonth; i++) {
                    const d = new Date(year, month, i);
                    days.push({ date: d, dayNo: i, isCurrentMonth: true, disabled: false });
                }
                const remaining = 42 - days.length;
                for (let i = 1; i <= remaining; i++) {
                    const d = new Date(year, month + 1, i);
                    days.push({ date: d, dayNo: i, isCurrentMonth: false, disabled: true });
                }
                return days;
            }), nextMonth, prevMonth, canGoToPreviousMonth, loadingMonthAvailability, selectCustomDate, isDateDisabled: checkIsDateDisabled, formatMonthYear,
            morningSlots, afternoonSlots, disabledDate, submitBooking, submitting, paypalButtonLoading, isPayPalSelected, getServiceName, getStaffName, getStaffImageSrc, isStaffPlaceholderImage, getServiceImageSrc, isServicePlaceholderImage, getServiceDuration,
            formatDuration,
            truncateCatalogDescription,
            canAddToCalendar,
            googleCalendarUrl,
            outlookCalendarUrl,
            downloadIcalCalendar,
            labels: bookwellab_front_obj.labels || {},
            computedServices,
            hasExtrasForService, getExtrasPayload, onExtrasChanged, bookingTotalPrice, selectedExtrasSummary,
            confirmedAppointmentId,
            displayPrice, reset, formatDate, formatTime, formatSlotRange, applyExternalConfirmation,
            isSidebarCollapsed,
            appearance: bookwellab_front_obj.appearance || {},
            hideCategories: String(bookwellab_front_obj.general?.hide_categories) === 'true',
            hideStaffStep: String(bookwellab_front_obj.general?.hide_staff_step) === 'true' || bookwellab_front_obj.general?.hide_staff_step === true || bookwellab_front_obj.general?.hide_staff_step == 1,
            hideDescription,
            showStaffDesignation,
            visibleDetailFields,
            hasCustomerDisplayName,
            getDetailFieldConfig,
            sc_service_id,
            sc_staff_id,
            isCatalogMode,
            isPopupHost,
            forceButtonPopup,
            showBookingPopup,
            hasPresetStaff,
            showServiceStep,
            catalogDialogVisible,
            catalogPage,
            catalogPageSize,
            catalogTotalItems,
            catalogTotalPages,
            paginatedCatalogServices,
            setCatalogCategory,
            onCatalogPageChange,
            openCatalogBooking,
            openBookingByIds,
            closeCatalogBooking
        };
    }
};

window.BookingWizard = BookingWizard;

const mountBookingApp = () => {
    const mountEl = document.getElementById('bookwellab_frontend_app');
    if (!mountEl || typeof bookwellab_front_obj === 'undefined') {
        return;
    }
    if (mountEl.__bw_mounted) {
        return;
    }
    mountEl.__bw_mounted = true;

    const app = createApp({
        components: { 'booking-wizard': BookingWizard },
        setup() {
            return { appearance: bookwellab_front_obj.appearance || {}, adjustColor };
        },
    });

    app.use(ElementPlus);
    app.mount(mountEl);
};

const bindBookingPopupButtons = () => {
    if (typeof document === 'undefined' || document.__bw_popup_btn_bound) {
        return;
    }
    document.__bw_popup_btn_bound = true;

    document.addEventListener('click', async (event) => {
        const btn = event.target.closest('[data-bw-booking-popup]');
        if (!btn) {
            return;
        }
        event.preventDefault();

        const serviceId = btn.getAttribute('data-service-id');
        const staffId = btn.getAttribute('data-staff-id');

        const runOpen = async () => {
            if (window.BookWellABBooking && typeof window.BookWellABBooking.open === 'function') {
                btn.disabled = true;
                try {
                    await window.BookWellABBooking.open({ serviceId, staffId });
                } finally {
                    btn.disabled = false;
                }
                return true;
            }
            return false;
        };

        if (await runOpen()) {
            return;
        }

        // Wait briefly for the wizard to finish mounting/loading.
        let attempts = 0;
        const timer = window.setInterval(async () => {
            attempts += 1;
            if (await runOpen() || attempts >= 40) {
                window.clearInterval(timer);
                btn.disabled = false;
            }
        }, 150);
    });
};

if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', () => {
        // Defer one tick so Pro extension scripts can register wizard child components first.
        window.setTimeout(mountBookingApp, 0);
        bindBookingPopupButtons();
    });
} else {
    window.setTimeout(mountBookingApp, 0);
    bindBookingPopupButtons();
}
