/* Yatoon: scoped i18n wrapper -- keeps __() out of the global namespace. */
(function () {
/**
 * Yatoon i18n bridge.
 *
 * Every user-facing literal in this file is wrapped in __(). This resolves it
 * against the WordPress JS translation catalog registered for this handle via
 * wp_set_script_translations(), so a locale's .json file translates the
 * interactive booking flow -- not just the PHP-rendered chrome around it.
 *
 * The fallback matters: if wp-i18n is unavailable for any reason (an
 * aggressive JS optimizer, a very old core, a hand-rolled enqueue) __() must
 * still return the original English rather than throwing and taking the whole
 * booking form down with it. A missing translation is a cosmetic problem; a
 * dead booking form is a lost sale.
 */
var __ = (function () {
    var d = 'yatoon-booking-system';
    if (typeof window !== 'undefined' && window.wp && window.wp.i18n && typeof window.wp.i18n.__ === 'function') {
        return function (s) { return window.wp.i18n.__(s, d); };
    }
    return function (s) { return s; };
})();

/* Yatoon Customer Portal - Frontend JS */
(function ($) {
    'use strict';

    if (typeof yatoonPortal === 'undefined') return;

    const cfg   = yatoonPortal;
    const $wrap = $('#sbs-portal-wrap');
    if (!$wrap.length) return;

    /* Apply brand colors */
    document.documentElement.style.setProperty('--portal-primary', cfg.primaryColor);
    if (cfg.bannerBg)   document.documentElement.style.setProperty('--portal-banner-bg',   cfg.bannerBg);
    if (cfg.bannerText) document.documentElement.style.setProperty('--portal-banner-text', cfg.bannerText);

    /* ── State ────────────────────────────────────────────── */
    let currentCustomer = null;
    let currentTab      = 'upcoming';
    let allData         = null;        // cached response from get_bookings
    let rescheduleBookingId = null;

    /* ── Helpers ──────────────────────────────────────────── */
    function ajax(action, data, done, fail) {
        $.post(cfg.ajaxurl, Object.assign({ action, nonce: cfg.nonce }, data))
            .done(function (r) {
                if (r.success) done(r.data);
                else (fail || defaultFail)(r.data);
            })
            .fail(function () { (fail || defaultFail)({ message: __('Request failed') }); });
    }

    function defaultFail(d) {
        showMsg($wrap.find('.sbs-portal-msg'), d && d.message ? d.message : __('Something went wrong.'), 'error');
    }

    function showMsg($el, msg, type) {
        $el.text(msg).removeClass('success error').addClass(type || '');
    }

    function loading(show) {
        $wrap.find('.sbs-portal-loading').toggle(show);
    }

    function parseMonth(dateStr) {
        // dateStr e.g. "Tuesday, February 24, 2026"
        const parts = dateStr.split(' ');
        return { month: parts[1].slice(0, 3).toUpperCase(), day: parseInt(parts[2]) };
    }

    function statusClass(status) {
        const map = {
            ACCEPTED: 'confirmed', PENDING: 'pending',
            CANCELLED_BY_CUSTOMER: 'cancelled', CANCELLED_BY_SELLER: 'cancelled',
            DECLINED: 'cancelled', NO_SHOW: 'no_show',
            confirmed: 'confirmed', pending: 'pending', completed: 'confirmed',
            cancelled: 'cancelled', no_show: 'no_show'
        };
        return 'sbs-status-' + (map[status] || 'pending');
    }

    function moneyLabel(value) {
        return value || '-';
    }

    function freshBookingUrl(url) {
        if (!url) return '';
        try {
            const target = new URL(url, window.location.href);
            target.searchParams.set('yatoon_session_refresh', String(Date.now()));
            return target.toString();
        } catch (e) {
            return url;
        }
    }

    function reloadAfterLogout() {
        try {
            const target = new URL(window.location.href);
            target.searchParams.set('yatoon_signed_out', String(Date.now()));
            window.location.replace(target.toString());
        } catch (e) {
            window.location.reload();
        }
    }

    function googleMark() {
        return '<span class="sbs-google-signin-mark" aria-hidden="true"><svg width="18" height="18" style="display:block;width:18px!important;height:18px!important;max-width:18px!important;max-height:18px!important" viewBox="0 0 18 18" focusable="false"><path fill="#4285F4" d="M17.64 9.205c0-.638-.057-1.252-.164-1.841H9v3.481h4.844a4.14 4.14 0 0 1-1.797 2.716v2.258h2.909c1.702-1.567 2.684-3.874 2.684-6.614Z"/><path fill="#34A853" d="M9 18c2.43 0 4.468-.806 5.956-2.181l-2.909-2.258c-.806.54-1.835.859-3.047.859-2.344 0-4.328-1.585-5.037-3.714H.956v2.333A9 9 0 0 0 9 18Z"/><path fill="#FBBC05" d="M3.963 10.706A5.41 5.41 0 0 1 3.682 9c0-.592.102-1.167.281-1.706V4.961H.956A9 9 0 0 0 0 9c0 1.452.347 2.827.956 4.039l3.007-2.333Z"/><path fill="#EA4335" d="M9 3.58c1.322 0 2.508.455 3.441 1.346l2.581-2.581C13.464.892 11.426 0 9 0A9 9 0 0 0 .956 4.961l3.007 2.333C4.672 5.165 6.656 3.58 9 3.58Z"/></svg></span>';
    }

    function appleMark() {
        return '<span class="sbs-apple-signin-mark" aria-hidden="true"><svg width="17" height="20" viewBox="0 0 17 20" focusable="false"><path fill="currentColor" d="M14.18 10.57c.02-2.19 1.79-3.24 1.87-3.29a4.02 4.02 0 0 0-3.17-1.72c-1.33-.14-2.63.8-3.31.8-.7 0-1.75-.79-2.89-.76a4.2 4.2 0 0 0-3.54 2.16c-1.53 2.64-.39 6.52 1.08 8.65.74 1.04 1.6 2.2 2.74 2.16 1.11-.05 1.53-.7 2.87-.7 1.33 0 1.72.7 2.88.68 1.2-.02 1.95-1.04 2.66-2.09a8.64 8.64 0 0 0 1.22-2.48 3.79 3.79 0 0 1-2.41-3.41ZM12 4.15A3.83 3.83 0 0 0 12.88 1a3.92 3.92 0 0 0-2.54 1.5 3.66 3.66 0 0 0-.9 3.05A3.24 3.24 0 0 0 12 4.15Z"/></svg></span>';
    }

    function facebookMark() {
        return '<span class="sbs-facebook-signin-mark" aria-hidden="true"><svg width="19" height="19" viewBox="0 0 24 24" focusable="false"><circle cx="12" cy="12" r="12" fill="#1877F2"/><path fill="#fff" d="M13.55 21v-8.2h2.75l.41-3.2h-3.16V7.56c0-.93.26-1.56 1.59-1.56h1.69V3.14A22.8 22.8 0 0 0 14.37 3c-2.44 0-4.11 1.49-4.11 4.21V9.6H7.5v3.2h2.76V21h3.29Z"/></svg></span>';
    }

    /* Passwordless sign-in: verified email or SMS, plus Google when enabled. */
    function renderPhoneStep() {
        const requestedChannel = cfg.initialAuth === 'sms' && cfg.smsLoginEnabled ? 'sms' : (cfg.initialAuth === 'email' && cfg.emailLoginEnabled ? 'email' : '');
        const defaultChannel = requestedChannel || (cfg.emailLoginEnabled ? 'email' : 'sms');
        let activeChannel = defaultChannel;
        const hasCodeLogin = cfg.emailLoginEnabled || cfg.smsLoginEnabled;
        $wrap.find('#sbs-portal-inner').html(`
            <div class="sbs-portal-card sbs-portal-login">
                ${cfg.returnUrl ? `<div class="sbs-portal-login-back-wrap"><a href="${escHtml(cfg.returnUrl)}" class="sbs-portal-login-back" aria-label="${__('Back to booking')}"><span aria-hidden="true">←</span> ${__('Back to booking')}</a></div>` : ''}
                <div class="sbs-portal-login-icon">💅</div>
                <h2>${cfg.strings.loginTitle}</h2>
                ${cfg.strings.loginBrand ? `<p class="brand-subtitle">${escHtml(cfg.strings.loginBrand)}</p>` : ''}
                <p class="subtitle">${cfg.strings.loginSubtitle}</p>
                ${(cfg.appleUrl || cfg.googleUrl || cfg.facebookUrl) ? `<div class="sbs-portal-social-signin" aria-label="${__('Quick sign in')}">
                    ${cfg.googleUrl ? `<a href="${escHtml(cfg.googleUrl)}" class="sbs-google-signin sbs-social-icon-btn" aria-label="${__('Continue with Google')}" title="${__('Continue with Google')}">${googleMark()}<span class="screen-reader-text">${__('Continue with Google')}</span></a>` : ''}
                    ${cfg.appleUrl ? `<a href="${escHtml(cfg.appleUrl)}" class="sbs-apple-signin sbs-social-icon-btn" aria-label="${__('Continue with Apple')}" title="${__('Continue with Apple')}">${appleMark()}<span class="screen-reader-text">${__('Continue with Apple')}</span></a>` : ''}
                    ${cfg.facebookUrl ? `<a href="${escHtml(cfg.facebookUrl)}" class="sbs-facebook-signin sbs-social-icon-btn" aria-label="${__('Continue with Facebook')}" title="${__('Continue with Facebook')}">${facebookMark()}<span class="screen-reader-text">${__('Continue with Facebook')}</span></a>` : ''}
                </div>${hasCodeLogin ? `<div class="sbs-portal-login-divider"><span>${__('or use your appointment details')}</span></div>` : ''}` : ''}
                ${hasCodeLogin ? `<div class="sbs-portal-auth-tabs">
                    ${cfg.emailLoginEnabled ? `<button type="button" class="sbs-portal-auth-tab ${defaultChannel === 'email' ? 'active' : ''}" data-channel="email">${__('Email me a code')}</button>` : ''}
                    ${cfg.smsLoginEnabled ? `<button type="button" class="sbs-portal-auth-tab ${defaultChannel === 'sms' ? 'active' : ''}" data-channel="sms">${__('Text me a code')}</button>` : ''}
                </div>
                <input type="${defaultChannel === 'email' ? 'email' : 'tel'}" id="sbs-portal-identifier" class="sbs-portal-input"
                       data-channel="${defaultChannel}" placeholder="${defaultChannel === 'email' ? cfg.strings.emailPlaceholder : cfg.strings.phonePlaceholder}" autocomplete="${defaultChannel === 'email' ? 'email' : 'tel'}">
                <button id="sbs-portal-send-otp" class="sbs-portal-btn">${cfg.strings.sendCode}</button>` : `<p class="subtitle">${__('Code sign-in is currently disabled. Please use Apple, Google, or contact the salon.')}</p>`}
                <p class="sbs-portal-msg" id="sbs-portal-phone-msg"></p>
            </div>
        `);

        $('.sbs-portal-auth-tab').on('click', function () {
            $('.sbs-portal-auth-tab').removeClass('active');
            $(this).addClass('active');
            const channel = $(this).data('channel');
            activeChannel = channel;
            $('#sbs-portal-identifier').attr({
                type: channel === 'email' ? 'email' : 'tel',
                'data-channel': channel,
                placeholder: channel === 'email' ? cfg.strings.emailPlaceholder : cfg.strings.phonePlaceholder,
                autocomplete: channel === 'email' ? 'email' : 'tel'
            }).val('').focus();
        });

        $('#sbs-portal-identifier').on('keydown', function (e) {
            if (e.key === 'Enter') $('#sbs-portal-send-otp').trigger('click');
        });

        $('#sbs-portal-send-otp').on('click', function () {
            const $input = $('#sbs-portal-identifier');
            const identifier = $input.val().trim();
            const looksLikeEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(identifier);
            const looksLikePhone = identifier.replace(/\D/g, '').length >= 6 && identifier.indexOf('@') === -1;
            const channel = looksLikeEmail ? 'email' : (looksLikePhone && cfg.smsLoginEnabled ? 'sms' : activeChannel);
            if (!identifier) { showMsg($('#sbs-portal-phone-msg'), __('Please enter your appointment contact information.'), 'error'); return; }
            const $btn = $(this).prop('disabled', true).text(cfg.strings.sending);
            showMsg($('#sbs-portal-phone-msg'), '', '');

            ajax('yatoon_portal_send_otp', { channel, identifier, purpose: 'login' }, function (data) {
                $btn.prop('disabled', false).text(cfg.strings.sendCode);
                renderCodeStep(identifier, channel, data && data.masked_destination ? data.masked_destination : identifier, 'login');
            }, function (d) {
                $btn.prop('disabled', false).text(cfg.strings.sendCode);
                showMsg($('#sbs-portal-phone-msg'), d && d.message ? d.message : 'Error', 'error');
            });
        });
    }

    /* ══════════════════════════════════════════════════════
       RENDER: CODE ENTRY (step 2)
    ══════════════════════════════════════════════════════ */
    function renderCodeStep(identifier, channel, maskedDestination, purpose) {
        const hint = maskedDestination || identifier;
        $wrap.find('#sbs-portal-inner').html(`
            <div class="sbs-portal-card sbs-portal-login">
                ${cfg.returnUrl ? `<div class="sbs-portal-login-back-wrap"><a href="${escHtml(cfg.returnUrl)}" class="sbs-portal-login-back"><span aria-hidden="true">←</span> ${__('Back to booking')}</a></div>` : ''}
                <div class="sbs-portal-login-icon">${channel === 'sms' ? '💬' : '📧'}</div>
                <h2>${cfg.strings.loginTitle}</h2>
                <p class="subtitle">${channel === 'sms' ? cfg.strings.smsCodeSent : cfg.strings.emailCodeSent}<br>
                    <small style="color:#9ca3af">${hint}</small>
                </p>
                <input type="text" id="sbs-portal-code" class="sbs-portal-input"
                       placeholder="${cfg.strings.codePlaceholder}" maxlength="6"
                       inputmode="numeric" pattern="[0-9]*" autocomplete="one-time-code">
                <button id="sbs-portal-verify" class="sbs-portal-btn">${cfg.strings.verify}</button>
                <p class="sbs-portal-msg" id="sbs-portal-code-msg"></p>
                <p style="text-align:center;margin-top:12px;">
                    <button class="sbs-portal-link" id="sbs-portal-resend">${cfg.strings.resendCode}</button>
                    &nbsp;·&nbsp;
                    <button class="sbs-portal-link" id="sbs-portal-change-phone">${__('Use another method')}</button>
                </p>
            </div>
        `);

        $('#sbs-portal-code').focus().on('keydown', function (e) {
            if (e.key === 'Enter') $('#sbs-portal-verify').trigger('click');
        });

        $('#sbs-portal-change-phone').on('click', renderPhoneStep);

        $('#sbs-portal-resend').on('click', function () {
            const $btn = $(this).prop('disabled', true).text(__('Sending...'));
            ajax('yatoon_portal_send_otp', { channel, identifier, purpose }, function () {
                showMsg($('#sbs-portal-code-msg'), channel === 'sms' ? cfg.strings.smsCodeSent : cfg.strings.emailCodeSent, 'success');
                $btn.prop('disabled', false).text(cfg.strings.resendCode);
            }, function (d) {
                $btn.prop('disabled', false).text(cfg.strings.resendCode);
                showMsg($('#sbs-portal-code-msg'), d && d.message ? d.message : 'Error', 'error');
            });
        });

        $('#sbs-portal-verify').on('click', function () {
            const code = $('#sbs-portal-code').val().replace(/\D/g, '');
            if (code.length !== 6) { showMsg($('#sbs-portal-code-msg'), __('Please enter the 6-digit code.'), 'error'); return; }
            const $btn = $(this).prop('disabled', true).text(cfg.strings.verifying);

            ajax('yatoon_portal_verify_otp', { code }, function (data) {
                currentCustomer = data.customer;
                if (cfg.returnUrl) {
                    window.location.assign(cfg.returnUrl);
                    return;
                }
                loadDashboard();
            }, function (d) {
                $btn.prop('disabled', false).text(cfg.strings.verify);
                showMsg($('#sbs-portal-code-msg'), d && d.message ? d.message : __('Invalid code'), 'error');
            });
        });
    }

    /* ══════════════════════════════════════════════════════
       LOAD DASHBOARD DATA
    ══════════════════════════════════════════════════════ */
    function loadDashboard() {
        renderLoading();
        ajax('yatoon_portal_get_bookings', {}, function (data) {
            allData         = data;
            currentCustomer = data.customer;
            currentTab      = data.upcoming && data.upcoming.length ? 'upcoming' : 'past';
            renderDashboard();
        }, function (d) {
            if (d && d.code === 'not_logged_in') { renderPhoneStep(); return; }
            $wrap.find('#sbs-portal-inner').html('<p style="color:red;text-align:center;padding:24px;">' + (d && d.message ? d.message : __('Error loading data.')) + '</p>');
        });
    }

    function renderLoading() {
        $wrap.find('#sbs-portal-inner').html('<div class="sbs-portal-loading" style="display:flex;"><span class="sbs-portal-spinner"></span></div>');
    }

    /* ══════════════════════════════════════════════════════
       RENDER: DASHBOARD
    ══════════════════════════════════════════════════════ */
    function renderDashboard() {
        const d    = allData;
        const name = d.customer.name || 'there';
        const next = d.upcoming && d.upcoming.length ? d.upcoming[0] : null;

        const html = `
            <div class="sbs-portal-card">
                <div class="sbs-portal-header">
                    <div>
                        <h2>👋 ${__('Hi,')} ${escHtml(name.split(' ')[0])}!</h2>
                        <div class="greeting">${escHtml(d.customer.email || d.customer.phone)}</div>
                    </div>
                    <div class="sbs-portal-account-actions" aria-label="${__('Account actions')}">
                        <button id="sbs-portal-switch-account-btn" class="sbs-portal-btn-sm sbs-portal-btn-outline">↻ ${cfg.strings.switchAccount}</button>
                        <button id="sbs-portal-logout-btn" class="sbs-portal-btn-sm sbs-portal-signout-btn">${cfg.strings.logout}</button>
                    </div>
                </div>

                ${(d.customer.needs_account_link || d.customer.needs_square_link) ? `
                <div class="sbs-portal-link-account">
                    <div><strong>${__('Connect your existing appointments')}</strong><p>${__('Verify the email address or phone number used when booking. This securely connects your Apple or Google sign-in to your appointment history.')}</p></div>
                    ${(cfg.emailLoginEnabled || cfg.smsLoginEnabled) ? `<div class="sbs-portal-auth-tabs sbs-link-auth-tabs">
                        ${cfg.emailLoginEnabled ? `<button type="button" class="sbs-portal-auth-tab active" data-channel="email">${__('Email code')}</button>` : ''}
                        ${cfg.smsLoginEnabled ? `<button type="button" class="sbs-portal-auth-tab ${cfg.emailLoginEnabled ? '' : 'active'}" data-channel="sms">${__('Text code')}</button>` : ''}
                    </div><div class="sbs-portal-link-account-form"><input type="${cfg.emailLoginEnabled ? 'email' : 'tel'}" id="sbs-link-account-id" data-channel="${cfg.emailLoginEnabled ? 'email' : 'sms'}" class="sbs-portal-input" placeholder="${cfg.emailLoginEnabled ? cfg.strings.emailPlaceholder : cfg.strings.phonePlaceholder}" autocomplete="${cfg.emailLoginEnabled ? 'email' : 'tel'}"><button type="button" id="sbs-link-account-send" class="sbs-portal-btn-sm">${__('Send code')}</button></div>` : `<p>${__('Verification is unavailable. Please contact the salon to connect your appointments.')}</p>`}
                    <p class="sbs-portal-msg" id="sbs-link-square-msg"></p>
                </div>` : ''}

                <!-- Stats -->
                <div class="sbs-portal-stats">
                    <div class="sbs-portal-stat">
                        <div class="value">${d.visit_count}</div>
                        <div class="label">${cfg.strings.visits}</div>
                    </div>
                    <div class="sbs-portal-stat">
                        <div class="value">${d.upcoming.length}</div>
                        <div class="label">${__('Upcoming')}</div>
                    </div>
                    <div class="sbs-portal-stat">
                        <div class="value">${escHtml(d.total_spent || '$0.00')}</div>
                        <div class="label">${cfg.strings.totalSpent}</div>
                    </div>
                </div>

                ${next ? `
                <div class="sbs-portal-next">
                    <div>
                        <span class="sbs-portal-kicker">${__('Next appointment')}</span>
                        <strong>${escHtml(next.services)}</strong>
                        <span>${escHtml(next.date)} · ${escHtml(next.time)}</span>
                    </div>
                    <span class="sbs-status-badge ${statusClass(next.status)}">${escHtml(next.status_label)}</span>
                </div>` : ''}

                <!-- Tabs -->
                <div class="sbs-portal-tabs">
                    <button class="sbs-portal-tab ${currentTab==='upcoming'?'active':''}" data-tab="upcoming">
                        ${cfg.strings.upcoming} (${d.upcoming.length})
                    </button>
                    <button class="sbs-portal-tab ${currentTab==='past'?'active':''}" data-tab="past">
                        ${cfg.strings.past}
                    </button>
                    <button class="sbs-portal-tab ${currentTab==='wallet'?'active':''}" data-tab="wallet">
                        ${__('Wallet')}
                    </button>
                    <button class="sbs-portal-tab ${currentTab==='profile'?'active':''}" data-tab="profile">
                        ${__('Profile')}
                    </button>
                </div>

                <!-- Booking list -->
                <div id="sbs-portal-booking-list">
                    ${currentTab === 'wallet' ? renderWallet(d) : (currentTab === 'profile' ? renderProfile(d) : renderBookingList(currentTab === 'upcoming' ? d.upcoming : d.past, currentTab))}
                </div>

                <!-- Book new -->
                ${cfg.bookNewUrl ? `
                <div class="sbs-portal-book-new">
                    <a href="${escHtml(freshBookingUrl(cfg.bookNewUrl))}" class="sbs-portal-btn-sm sbs-portal-btn-outline"
                       style="display:inline-block;text-decoration:none;padding:9px 20px;">
                        ✨ ${cfg.strings.bookNew}
                    </a>
                </div>` : ''}
            </div>
        `;

        $wrap.find('#sbs-portal-inner').html(html);
        bindDashboardEvents();

        $('.sbs-link-auth-tabs .sbs-portal-auth-tab').on('click', function () {
            $('.sbs-link-auth-tabs .sbs-portal-auth-tab').removeClass('active');
            $(this).addClass('active');
            const channel = $(this).data('channel');
            $('#sbs-link-account-id').attr({ type: channel === 'email' ? 'email' : 'tel', placeholder: channel === 'email' ? cfg.strings.emailPlaceholder : cfg.strings.phonePlaceholder, autocomplete: channel === 'email' ? 'email' : 'tel' }).data('channel', channel).val('').focus();
        });

        $('#sbs-link-account-send').on('click', function () {
            const identifier = $('#sbs-link-account-id').val().trim();
            const channel = $('#sbs-link-account-id').data('channel') || 'email';
            const $btn = $(this).prop('disabled', true).text(cfg.strings.sending);
            ajax('yatoon_portal_send_otp', { channel: channel, identifier: identifier, purpose: 'link' }, function (data) {
                $btn.prop('disabled', false).text(__('Send code'));
                renderCodeStep(identifier, channel, data.masked_destination || identifier, 'link');
            }, function (d) {
                $btn.prop('disabled', false).text(__('Send code'));
                showMsg($('#sbs-link-square-msg'), d && d.message ? d.message : __('Could not send code.'), 'error');
            });
        });
    }

    function renderBookingList(bookings, tab) {
        if (!bookings.length) {
            return `<div class="sbs-portal-empty">
                <div class="icon">${tab === 'upcoming' ? '📅' : '🗓'}</div>
                ${tab === 'upcoming' ? cfg.strings.noUpcoming : cfg.strings.noPast}
            </div>`;
        }

        return bookings.map(function (b) {
            const { month, day } = parseMonth(b.date);
            const statusCls = statusClass(b.status);
            const isCancelled = b.is_cancelled;

            const rebook = cfg.bookNewUrl && tab === 'past' && b.rebook && (b.rebook.services || []).length
                ? `<button type="button" class="sbs-portal-btn-sm sbs-portal-btn-outline" data-action="rebook" data-id="${escHtml(b.id)}">${cfg.strings.rebook}</button>`
                : '';
            const payNow = b.pay_url ? `<a class="sbs-portal-btn-sm sbs-portal-btn-primary" href="${escHtml(b.pay_url)}">${__('Pay now')}</a>` : '';
            const invoice = b.invoice_url ? `<a class="sbs-portal-btn-sm sbs-portal-btn-outline" href="${escHtml(b.invoice_url)}" target="_blank" rel="noopener">${__('Invoice')}</a>` : '';
            const actions = b.can_cancel || b.can_reschedule || b.can_change_service || rebook || payNow || invoice ? `
                <div class="sbs-portal-booking-actions">
                    ${payNow}
                    ${b.can_reschedule ? `<button class="sbs-portal-btn-sm" style="background:${cfg.primaryColor};color:#fff;"
                        data-action="reschedule" data-id="${b.id}">${cfg.strings.reschedule}</button>` : ''}
                    ${b.can_change_service ? `<button class="sbs-portal-btn-sm sbs-portal-btn-outline" data-action="change-service" data-id="${b.id}">${cfg.strings.changeService}</button>` : ''}
                    ${b.can_cancel ? `<button class="sbs-portal-btn-sm" style="background:#fee2e2;color:#991b1b;"
                        data-action="cancel" data-id="${b.id}">${cfg.strings.cancel}</button>` : ''}
                    ${(cfg.aiRescheduleEnabled && (b.can_reschedule || b.can_cancel)) ? `<button type="button" class="sbs-portal-btn-sm sbs-portal-btn-outline" data-action="ai-assistant" data-id="${b.id}">✨ ${__('Ask')}</button>` : ''}
                    ${rebook}
                    ${invoice}
                </div>` : '';

            return `
                <div class="sbs-portal-booking ${b.is_upcoming ? 'upcoming' : 'past'} ${isCancelled ? 'cancelled' : ''}"
                     data-booking-id="${b.id}">
                    <div class="sbs-portal-date-badge">
                        <div class="month">${month}</div>
                        <div class="day">${day}</div>
                    </div>
                    <div class="sbs-portal-booking-info">
                        <div class="service-name">${escHtml(b.services)}</div>
                        <div class="staff-name">👤 ${escHtml(b.staff || __('Any Staff'))}</div>
                        <div class="time">⏰ ${escHtml(b.time)}</div>
                        <div class="sbs-portal-booking-meta">
                            <span class="sbs-status-badge ${statusCls}">${escHtml(b.status_label)}</span>
                            <span class="sbs-portal-mini-pill">${escHtml(b.duration_min || 0)} ${__('min')}</span>
                            <span class="sbs-portal-mini-pill">${escHtml(moneyLabel(b.price))}</span>
                            ${b.payment_label ? `<span class="sbs-portal-mini-pill">${escHtml(b.payment_label)}</span>` : ''}
                        </div>
                        ${actions}
                    </div>
                </div>`;
        }).join('');
    }

    function renderWallet(data) {
        const next = data.upcoming && data.upcoming.length ? data.upcoming[0] : null;
        const wallet = data.wallet || {};
        const gifts = wallet.gift_cards || { enabled: false, balance: '$0.00', count: 0, items: [] };
        const packages = wallet.packages || { enabled: false, remaining: 0, items: [], history: [] };
        const membership = wallet.membership || { enabled: false, items: [], history: [] };
        const loyalty = wallet.loyalty || { enabled: false, points: 0, next_reward: '', rewards: [] };
        const giftDetail = gifts.items && gifts.items.length
            ? gifts.items.map(item => `${escHtml(item.label)} ${escHtml(item.balance)}${item.expires ? ` · ${__('expires')} ${escHtml(item.expires)}` : ''}`).join('<br>')
            : (gifts.enabled ? __('No active gift cards on this account.') : __('Gift cards are not enabled by this business.'));
        const packageDetail = packages.items && packages.items.length
            ? packages.items.map(item => `${escHtml(item.name)} · ${escHtml(item.remaining)} ${Number(item.remaining) === 1 ? __('visit left') : __('visits left')}${item.expires ? ` · ${__('expires')} ${escHtml(item.expires)}` : ''}`).join('<br>')
            : (packages.enabled ? __('No active service packages.') : __('Packages are not enabled by this business.'));
        const membershipDetail = membership.items && membership.items.length
            ? membership.items.map(item => `${escHtml(item.name)}${item.expires ? ` · ${__('through')} ${escHtml(item.expires)}` : ''}`).join('<br>')
            : (membership.enabled ? __('No active membership.') : __('Memberships are not enabled by this business.'));
        const loyaltyDetail = loyalty.enabled
            ? (loyalty.next_reward ? `${__('Next reward:')} ${escHtml(loyalty.next_reward)}` : __('Your available rewards will appear here.'))
            : __('Rewards are not enabled by this business.');
        const cleanSeparator = value => String(value || '').replace(/路/g, '&middot;');
        const paymentRows = (wallet.payment_history || []).map(item => `
            <div class="sbs-portal-wallet-history-row"><div><strong>${escHtml(item.status)}</strong><br><span>${escHtml(item.date)}</span></div><strong>${escHtml(item.amount)}</strong></div>`).join('');
        const rewardRows = (loyalty.rewards || []).map(reward => `
            <div class="sbs-portal-reward-row">
                <div><strong>${escHtml(reward.name)}</strong><p>${escHtml(reward.points)} ${__('points')} &middot; ${escHtml(reward.value)} ${__('value')}</p></div>
                <button type="button" class="sbs-portal-btn-sm ${reward.eligible ? 'sbs-portal-btn-primary' : 'sbs-portal-btn-outline'}" data-action="redeem-reward" data-reward-id="${escHtml(reward.id)}" ${reward.eligible ? '' : 'disabled'}>${reward.eligible ? __('Redeem') : `${Math.max(0, Number(reward.points) - Number(loyalty.points || 0))} ${__('more')}`}</button>
            </div>`).join('');
        const packageHistory = (packages.history || []).slice(0, 8).map(item => `
            <div class="sbs-portal-wallet-history-row"><div><strong>${escHtml(item.name)}</strong><br><span>${escHtml(item.status)}${item.purchased ? ` &middot; ${escHtml(item.purchased)}` : ''}</span></div><strong>${escHtml(item.remaining)}/${escHtml(item.total)}</strong></div>`).join('');
        return `
            <section class="sbs-portal-wallet" aria-labelledby="sbs-wallet-title">
                <div class="sbs-portal-wallet-head">
                    <div>
                        <span class="sbs-portal-kicker">${__('Your salon account')}</span>
                        <h3 id="sbs-wallet-title">${__('Wallet')}</h3>
                        <p>${__('Visits, spending, gift cards, packages, and rewards in one place.')}</p>
                    </div>
                    <div class="sbs-portal-wallet-mark" aria-hidden="true">Y</div>
                </div>
                <div class="sbs-portal-wallet-grid">
                    <div><span>${__('Gift card balance')}</span><strong>${escHtml(gifts.balance || '$0.00')}</strong></div>
                    <div><span>${__('Package visits left')}</span><strong>${escHtml(packages.remaining || 0)}</strong></div>
                    <div><span>${__('Balance due')}</span><strong>${escHtml(wallet.balance_due || '$0.00')}</strong></div>
                </div>
                <div class="sbs-portal-wallet-assets">
                    <div class="sbs-portal-wallet-asset"><div><strong>${__('Gift cards')}</strong><p>${cleanSeparator(giftDetail)}</p></div><strong class="sbs-portal-wallet-asset-value">${escHtml(gifts.balance || '$0.00')}</strong></div>
                    <div class="sbs-portal-wallet-asset"><div><strong>${__('Packages')}</strong><p>${cleanSeparator(packageDetail)}</p></div><strong class="sbs-portal-wallet-asset-value">${escHtml(packages.remaining || 0)} ${__('left')}</strong></div>
                    <div class="sbs-portal-wallet-asset"><div><strong>${__('Membership')}</strong><p>${cleanSeparator(membershipDetail)}</p></div><strong class="sbs-portal-wallet-asset-value">${membership.items && membership.items.length ? __('Active') : __('None')}</strong></div>
                    <div class="sbs-portal-wallet-asset"><div><strong>${__('Rewards')}</strong><p>${loyaltyDetail}</p></div><strong class="sbs-portal-wallet-asset-value">${escHtml(loyalty.points || 0)} ${__('points')}</strong></div>
                </div>
                ${rewardRows ? `<div class="sbs-portal-rewards"><h4>${__('Available rewards')}</h4>${rewardRows}<p class="sbs-portal-reward-message" role="status" aria-live="polite"></p></div>` : ''}
                <div class="sbs-portal-wallet-note"><span aria-hidden="true">&#10003;</span><div><strong>${escHtml(data.total_spent || '$0.00')} ${__('spent across')} ${escHtml(data.visit_count || 0)} ${Number(data.visit_count) === 1 ? __('completed visit') : __('completed visits')}</strong><p>${__('Balances and benefits are read from your verified salon account.')}</p></div></div>
                ${packageHistory ? `<div class="sbs-portal-wallet-history"><h4>${__('Package history')}</h4>${packageHistory}</div>` : ''}
                ${paymentRows ? `<div class="sbs-portal-wallet-history"><h4>${__('Payment history')}</h4>${paymentRows}</div>` : ''}
                ${next ? `<p class="sbs-portal-wallet-next"><span>${__('Next visit')}</span><strong>${escHtml(next.date)} · ${escHtml(next.time)}</strong></p>` : ''}
                ${cfg.bookNewUrl ? `<a href="${escHtml(freshBookingUrl(cfg.bookNewUrl))}" class="sbs-portal-btn sbs-portal-wallet-cta">${__('Book an appointment')}</a>` : ''}
            </section>`;
    }

    function renderProfile(data) {
        const profile = (data.wallet && data.wallet.profile) || data.customer || {};
        return `
            <section class="sbs-portal-profile" aria-labelledby="sbs-profile-title">
                <span class="sbs-portal-kicker">${__('Verified account')}</span>
                <h3 id="sbs-profile-title">${__('Profile')}</h3>
                <p class="sbs-portal-profile-intro">${__('Keep your display name and appointment preferences up to date. Contact details stay read-only because they protect your sign-in.')}</p>
                <div class="sbs-portal-profile-grid">
                    <label><span>${__('Name')}</span><input type="text" id="sbs-profile-name" class="sbs-portal-input" maxlength="120" value="${escHtml(profile.name || '')}"></label>
                    <label><span>${__('Email')}</span><input type="email" class="sbs-portal-input" value="${escHtml(profile.email || '')}" readonly aria-readonly="true"></label>
                    <label><span>${__('Phone')}</span><input type="tel" class="sbs-portal-input" value="${escHtml(profile.phone || '')}" readonly aria-readonly="true"></label>
                    <label class="sbs-portal-profile-wide"><span>${__('Appointment preferences')}</span><textarea id="sbs-profile-preferences" class="sbs-portal-input" rows="4" maxlength="1000" placeholder="${__('Examples: preferred shape, sensitivities, or accessibility needs')}">${escHtml(profile.preferences || '')}</textarea></label>
                </div>
                <div class="sbs-portal-profile-actions"><button type="button" id="sbs-profile-save" class="sbs-portal-btn">${__('Save profile')}</button><p id="sbs-profile-message" role="status" aria-live="polite"></p></div>
                <div class="sbs-portal-privacy-note"><strong>${__('Privacy')}</strong><p>${__('Your profile is used only to support your appointments. Sign out when using a shared device.')}</p></div>
            </section>`;
    }

    function bindDashboardEvents() {
        $(document).off('click.sbsPortalRebook', '[data-action="rebook"]').on('click.sbsPortalRebook', '[data-action="rebook"]', function () {
            const bookingId = String($(this).data('id') || '');
            const bookings = [].concat((allData && allData.upcoming) || [], (allData && allData.past) || []);
            const booking = bookings.find(function (item) { return String(item.id || '') === bookingId; });
            const request = booking && booking.rebook;

            if (request && Array.isArray(request.services) && request.services.length) {
                try {
                    sessionStorage.removeItem('yatoon_booking_state');
                    sessionStorage.setItem('yatoon_rebook_request', JSON.stringify(Object.assign({}, request, {
                        createdAt: Date.now()
                    })));
                } catch (e) {}
            }

            window.location.href = freshBookingUrl(cfg.bookNewUrl);
        });

        /* Tab switch */
        $(document).off('click.sbsPortalTab', '.sbs-portal-tab').on('click.sbsPortalTab', '.sbs-portal-tab', function () {
            currentTab = $(this).data('tab');
            $('.sbs-portal-tab').removeClass('active');
            $(this).addClass('active');
            if (currentTab === 'wallet') {
                $('#sbs-portal-booking-list').html(renderWallet(allData));
                return;
            }
            if (currentTab === 'profile') {
                $('#sbs-portal-booking-list').html(renderProfile(allData));
                return;
            }
            const bookings = currentTab === 'upcoming' ? allData.upcoming : allData.past;
            $('#sbs-portal-booking-list').html(renderBookingList(bookings, currentTab));
        });

        $(document).off('click.sbsPortalProfile', '#sbs-profile-save').on('click.sbsPortalProfile', '#sbs-profile-save', function () {
            const $button = $(this).prop('disabled', true).text(__('Saving...'));
            const $message = $('#sbs-profile-message').removeClass('success error').text('');
            ajax('yatoon_portal_update_profile', {
                name: $('#sbs-profile-name').val().trim(),
                preferences: $('#sbs-profile-preferences').val().trim()
            }, function (result) {
                if (allData.wallet) {
                    allData.wallet.profile.name = $('#sbs-profile-name').val().trim();
                    allData.wallet.profile.preferences = $('#sbs-profile-preferences').val().trim();
                }
                allData.customer.name = $('#sbs-profile-name').val().trim();
                $button.prop('disabled', false).text(__('Save profile'));
                $message.addClass('success').text(result.message || __('Profile saved.'));
            }, function (error) {
                $button.prop('disabled', false).text(__('Save profile'));
                $message.addClass('error').text(error && error.message ? error.message : __('Could not save profile.'));
            });
        });

        $(document).off('click.sbsPortalReward', '[data-action="redeem-reward"]').on('click.sbsPortalReward', '[data-action="redeem-reward"]', function () {
            const $button = $(this);
            if (!window.confirm(__('Redeem points for this reward? This cannot be undone online.'))) return;
            $button.prop('disabled', true).text(__('Redeeming...'));
            ajax('yatoon_portal_redeem_reward', { reward_id: $button.data('reward-id') }, function (result) {
                $('.sbs-portal-reward-message').addClass('success').text((result.message || __('Reward redeemed.')) + ' Reference: ' + result.claim);
                setTimeout(loadDashboard, 1800);
            }, function (error) {
                $button.prop('disabled', false).text(__('Redeem'));
                $('.sbs-portal-reward-message').addClass('error').text(error && error.message ? error.message : __('Could not redeem reward.'));
            });
        });

        /* Logout */
        $('#sbs-portal-logout-btn').off('click.sbsPortal').on('click.sbsPortal', function () {
            ajax('yatoon_portal_logout', {}, function () {
                currentCustomer = null;
                allData = null;
                currentTab = 'upcoming';
                reloadAfterLogout();
            });
        });

        $('#sbs-portal-switch-account-btn').off('click.sbsPortal').on('click.sbsPortal', function () {
            const $button = $(this).prop('disabled', true).text(__('Switching...'));
            ajax('yatoon_portal_logout', {}, function () {
                currentCustomer = null;
                allData = null;
                currentTab = 'upcoming';
                reloadAfterLogout();
            }, function (error) {
                $button.prop('disabled', false).text('↻ ' + cfg.strings.switchAccount);
                defaultFail(error);
            });
        });

        /* Cancel */
        $(document).off('click.sbsPortalCancel', '[data-action="cancel"]').on('click.sbsPortalCancel', '[data-action="cancel"]', function () {
            const id   = $(this).data('id');
            const $btn = $(this);

            // Build confirm modal with notify checkbox
            const $modal = $(`
                <div id="yn-portal-cancel-modal" style="position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:99999;display:flex;align-items:center;justify-content:center;">
                  <div style="background:#fff;border-radius:10px;padding:28px 30px;max-width:340px;width:90%;box-shadow:0 8px 32px rgba(0,0,0,.2);">
                    <h3 style="margin:0 0 10px;font-size:16px;">${__('Cancel Appointment')}</h3>
                    <p style="margin:0 0 14px;color:#555;font-size:14px;">${escHtml(cfg.strings.confirmCancel)}</p>
                    <label style="display:flex;align-items:center;gap:8px;font-size:14px;margin-bottom:18px;cursor:pointer;">
                      <input type="checkbox" id="yn-portal-notify" checked style="width:16px;height:16px;">
                      ${__('Send me a confirmation email')}
                    </label>
                    <div style="display:flex;gap:10px;justify-content:flex-end;">
                      <button id="yn-pcm-no"  style="padding:8px 16px;border:1px solid #ccc;border-radius:6px;background:#f5f5f5;cursor:pointer;">${__('Keep')}</button>
                      <button id="yn-pcm-yes" style="padding:8px 16px;border:none;border-radius:6px;background:#e53e3e;color:#fff;cursor:pointer;">${__('Yes, Cancel')}</button>
                    </div>
                  </div>
                </div>`);

            $('body').append($modal);
            $modal.find('#yn-pcm-no').on('click', function () { $modal.remove(); });
            $modal.on('click', function (ev) { if ($(ev.target).is($modal)) $modal.remove(); });

            $modal.find('#yn-pcm-yes').on('click', function () {
                const notify = $modal.find('#yn-portal-notify').is(':checked') ? 1 : 0;
                $modal.remove();
                $btn.prop('disabled', true).text(__('Cancelling...'));
                ajax('yatoon_portal_cancel_booking', { booking_id: id, notify_customer: notify }, function () {
                    loadDashboard();
                }, function (d) {
                    $btn.prop('disabled', false).text(cfg.strings.cancel);
                    alert(d && d.message ? d.message : __('Failed to cancel'));
                });
            });
        });

        /* Reschedule */
        $(document).off('click.sbsPortalReschedule', '[data-action="reschedule"]').on('click.sbsPortalReschedule', '[data-action="reschedule"]', function () {
            rescheduleBookingId = $(this).data('id');
            openRescheduleModal(rescheduleBookingId);
        });

        $(document).off('click.sbsPortalChangeService', '[data-action="change-service"]').on('click.sbsPortalChangeService', '[data-action="change-service"]', function () {
            openChangeServiceModal($(this).data('id'));
        });

        $(document).off('click.sbsPortalAiAssistant', '[data-action="ai-assistant"]').on('click.sbsPortalAiAssistant', '[data-action="ai-assistant"]', function () {
            openAiAssistantModal($(this).data('id'));
        });
    }

    /* ══════════════════════════════════════════════════════
       RESCHEDULE MODAL
    ══════════════════════════════════════════════════════ */
    function openRescheduleModal(bookingId) {
        const today = new Date();
        const todayStr = today.toISOString().split('T')[0];

        $('body').append(`
            <div class="sbs-portal-modal-overlay" id="sbs-reschedule-overlay">
                <div class="sbs-portal-modal">
                    <button class="sbs-portal-modal-close" id="sbs-modal-close">✕</button>
                    <h3>${cfg.strings.rescheduleTitle}</h3>
                    <input type="date" id="sbs-reschedule-date" class="sbs-portal-input"
                           value="${todayStr}" min="${todayStr}" style="margin-bottom:0;">
                    <div id="sbs-reschedule-slots" style="margin-top:14px;min-height:60px;">
                        <p style="color:#9ca3af;font-size:13px;text-align:center;">${__('Select a date to see available times.')}</p>
                    </div>
                    <div class="sbs-portal-modal-footer"><button type="button" class="sbs-portal-btn-sm sbs-portal-btn-outline" id="sbs-reschedule-back">← ${__('Back to appointments')}</button></div>
                    <label style="display:flex;align-items:center;gap:8px;font-size:13px;margin-top:12px;cursor:pointer;">
                        <input type="checkbox" id="sbs-reschedule-notify" checked style="width:15px;height:15px;">
                        ${__('Send me a confirmation email')}
                    </label>
                    <p class="sbs-portal-msg" id="sbs-reschedule-msg" style="margin-top:10px;"></p>
                </div>
            </div>
        `);

        $('#sbs-modal-close, #sbs-reschedule-back, #sbs-reschedule-overlay').on('click', function (e) {
            if (e.target.id === 'sbs-reschedule-overlay' || e.target.id === 'sbs-modal-close' || e.target.id === 'sbs-reschedule-back') {
                $('#sbs-reschedule-overlay').remove();
            }
        });

        $('#sbs-reschedule-date').on('change', function () {
            loadRescheduleSlots(bookingId, $(this).val());
        });

        // Load today's slots immediately
        loadRescheduleSlots(bookingId, todayStr);
    }

    /* ══════════════════════════════════════════════════════
       AI ASSISTANT MODAL
       One chat box, entered from the "✨ Ask" button on any
       appointment card. AI only classifies intent (reschedule vs
       cancel) and, for reschedule, a date/time preference — the
       actual availability and the cancel target always come from
       real data, and nothing changes until the customer taps Confirm.
    ══════════════════════════════════════════════════════ */
    function openAiAssistantModal(bookingId) {
        const consentText = escHtml(cfg.aiConsentText || __('I agree that this request may be sent to the AI provider configured by this site. I have not included medical, payment, or account details.'));
        $('body').append(`
            <div class="sbs-portal-modal-overlay" id="sbs-ai-assistant-overlay">
                <div class="sbs-portal-modal">
                    <button class="sbs-portal-modal-close" id="sbs-ai-modal-close">✕</button>
                    <h3>${__('What would you like to do?')}</h3>
                    <p style="color:#6b7280;font-size:13px;margin-top:-6px;">${__('Tell us in your own words — for example "next Thursday afternoon" or "please cancel it".')}</p>
                    <div class="sbs-ai-reschedule-box" id="sbs-ai-reschedule-box" style="margin-top:0;padding-top:0;border-top:none;">
                        <div class="sbs-ai-chat-log" id="sbs-ai-chat-log"></div>
                        <div class="sbs-ai-row">
                            <input type="text" id="sbs-ai-reschedule-input" class="sbs-portal-input" placeholder="${__('e.g. "next Thursday afternoon"')}">
                            <button type="button" id="sbs-ai-reschedule-send" class="sbs-portal-btn-sm">${__('Ask')}</button>
                        </div>
                        <label style="display:flex;align-items:flex-start;gap:8px;font-size:12px;line-height:1.45;margin-top:10px;cursor:pointer;">
                            <input type="checkbox" id="sbs-ai-provider-consent" style="width:15px;height:15px;margin-top:2px;flex:0 0 auto;">
                            <span>${consentText}</span>
                        </label>
                    </div>
                    <div class="sbs-portal-modal-footer"><button type="button" class="sbs-portal-btn-sm sbs-portal-btn-outline" id="sbs-ai-modal-back">← ${__('Back to appointments')}</button></div>
                    <label style="display:flex;align-items:center;gap:8px;font-size:13px;margin-top:12px;cursor:pointer;">
                        <input type="checkbox" id="sbs-reschedule-notify" checked style="width:15px;height:15px;">
                        ${__('Send me a confirmation email')}
                    </label>
                </div>
            </div>
        `);

        $('#sbs-ai-modal-close, #sbs-ai-modal-back, #sbs-ai-assistant-overlay').on('click', function (e) {
            if (e.target.id === 'sbs-ai-assistant-overlay' || e.target.id === 'sbs-ai-modal-close' || e.target.id === 'sbs-ai-modal-back') {
                $('#sbs-ai-assistant-overlay').remove();
            }
        });

        $('#sbs-ai-reschedule-send').on('click', function () { sendAiAssistantMessage(bookingId); });
        $('#sbs-ai-reschedule-input').on('keydown', function (e) {
            if (e.key === 'Enter') { e.preventDefault(); sendAiAssistantMessage(bookingId); }
        });
        $('#sbs-ai-reschedule-input').trigger('focus');
    }

    function appendAiChatBubble(text, who) {
        const $log = $('#sbs-ai-chat-log');
        $log.append(`<div class="sbs-ai-bubble sbs-ai-bubble-${who === 'user' ? 'user' : 'ai'}">${escHtml(text)}</div>`);
        $log.scrollTop($log[0].scrollHeight);
    }

    function sendAiAssistantMessage(bookingId) {
        const $input = $('#sbs-ai-reschedule-input');
        const message = ($input.val() || '').trim();
        if (!message) return;
        if (!$('#sbs-ai-provider-consent').is(':checked')) {
            appendAiChatBubble(__('Please agree before sending your request to the configured third-party AI provider.'), 'ai');
            return;
        }

        $input.prop('disabled', true);
        $('#sbs-ai-reschedule-send').prop('disabled', true).text(__('...'));

        const finish = function () {
            $input.prop('disabled', false);
            $('#sbs-ai-reschedule-send').prop('disabled', false).text(__('Ask'));
            $input.trigger('focus');
        };

        ajax('yatoon_ai_consent_receipt', { agreed: 1 }, function (consent) {
            const receipt = consent && consent.receipt ? consent.receipt : '';
            if (!receipt) {
                finish();
                appendAiChatBubble(__('Unable to record AI consent. Please try again.'), 'ai');
                return;
            }
            appendAiChatBubble(message, 'user');
            $input.val('');
            ajax('yatoon_portal_ai_assistant_message', { booking_id: bookingId, message, ai_consent_receipt: receipt }, function (data) {
                finish();
                if (data.privacy_notice && data.privacy_notice !== data.reply) appendAiChatBubble(data.privacy_notice, 'ai');
                if (data.reply) appendAiChatBubble(data.reply, 'ai');
                if (data.proposal) renderAiProposal(bookingId, data.proposal);
            }, function (d) {
                finish();
                if (d && d.privacy_notice) appendAiChatBubble(d.privacy_notice, 'ai');
                appendAiChatBubble(d && d.message ? d.message : __('Sorry, something went wrong.'), 'ai');
            });
        }, function (d) {
            finish();
            appendAiChatBubble(d && d.message ? d.message : __('Unable to record AI consent. Please try again.'), 'ai');
        });
    }

    function renderAiProposal(bookingId, proposal) {
        const $log = $('#sbs-ai-chat-log');

        if (proposal.type === 'cancel') {
            $log.append(`
                <div class="sbs-ai-proposal">
                    <div>${escHtml(proposal.summary)}</div>
                    <button type="button" class="sbs-portal-btn-sm" id="sbs-ai-confirm-proposal" style="width:auto;padding:6px 14px;margin-top:8px;background:#fee2e2;color:#991b1b;">${__('Yes, cancel it')}</button>
                </div>
            `);
            $log.scrollTop($log[0].scrollHeight);

            $('#sbs-ai-confirm-proposal').off('click').on('click', function () {
                const $btn = $(this).prop('disabled', true).text(__('Cancelling...'));
                ajax('yatoon_portal_cancel_booking', { booking_id: bookingId }, function () {
                    $('#sbs-ai-assistant-overlay, #sbs-reschedule-overlay').remove();
                    loadDashboard();
                }, function (d) {
                    $btn.prop('disabled', false).text(__('Yes, cancel it'));
                    appendAiChatBubble(d && d.message ? d.message : __('Cancellation failed.'), 'ai');
                });
            });
            return;
        }

        // proposal.type === 'reschedule'
        $log.append(`
            <div class="sbs-ai-proposal">
                <div>${escHtml(proposal.date_label)} · <strong>${escHtml(proposal.display)}</strong></div>
                <button type="button" class="sbs-portal-btn-sm" id="sbs-ai-confirm-proposal" style="width:auto;padding:6px 14px;margin-top:8px;">${__('Confirm this time')}</button>
            </div>
        `);
        $log.scrollTop($log[0].scrollHeight);

        $('#sbs-ai-confirm-proposal').off('click').on('click', function () {
            const $btn = $(this).prop('disabled', true).text(__('Rescheduling...'));
            const notify = $('#sbs-reschedule-notify').is(':checked') ? 1 : 0;
            ajax('yatoon_portal_reschedule_booking', {
                booking_id:      bookingId,
                new_start_at:    proposal.start_at,
                staff_id:        (proposal.staff_ids || [])[0] || '',
                notify_customer: notify,
            }, function () {
                $('#sbs-ai-assistant-overlay, #sbs-reschedule-overlay').remove();
                loadDashboard();
            }, function (d) {
                $btn.prop('disabled', false).text(__('Confirm this time'));
                appendAiChatBubble(d && d.message ? d.message : __('Reschedule failed.'), 'ai');
            });
        });
    }

    function loadRescheduleSlots(bookingId, date) {
        $('#sbs-reschedule-slots').html('<div style="text-align:center;padding:16px;"><span class="sbs-portal-spinner" style="display:inline-block;"></span></div>');

        ajax('yatoon_portal_get_reschedule_slots', { booking_id: bookingId, date }, function (data) {
            if (!data.slots.length) {
                $('#sbs-reschedule-slots').html(`<p style="color:#9ca3af;font-size:13px;text-align:center;padding:12px 0;">${__('No available times on this date.')}</p>`);
                return;
            }
            let html = '<div class="sbs-portal-slot-grid">';
            data.slots.forEach(function (s) {
                html += `<div class="sbs-portal-slot" data-start="${s.start_at}" data-staff="${(s.staff_ids||[])[0]||''}">${s.display}</div>`;
            });
            html += '</div>';
            html += `<div style="margin-top:16px;text-align:right;">
                <button id="sbs-confirm-reschedule" class="sbs-portal-btn" style="width:auto;padding:10px 24px;" disabled>
                    Confirm Reschedule
                </button>
            </div>`;
            $('#sbs-reschedule-slots').html(html);

            // Slot selection
            let selectedStart = null, selectedStaff = null;
            $(document).on('click', '.sbs-portal-slot', function () {
                $('.sbs-portal-slot').removeClass('selected');
                $(this).addClass('selected');
                selectedStart = $(this).data('start');
                selectedStaff = $(this).data('staff');
                $('#sbs-confirm-reschedule').prop('disabled', false);
            });

            // Confirm
            $('#sbs-confirm-reschedule').off('click').on('click', function () {
                if (!selectedStart) return;
                const $btn = $(this).prop('disabled', true).text(__('Rescheduling...'));
                const notify = $('#sbs-reschedule-notify').is(':checked') ? 1 : 0;
                ajax('yatoon_portal_reschedule_booking', {
                    booking_id:      bookingId,
                    new_start_at:    selectedStart,
                    staff_id:        selectedStaff || '',
                    notify_customer: notify,
                }, function () {
                    $('#sbs-reschedule-overlay').remove();
                    loadDashboard();
                }, function (d) {
                    $btn.prop('disabled', false).text(__('Confirm Reschedule'));
                    showMsg($('#sbs-reschedule-msg'), d && d.message ? d.message : __('Reschedule failed.'), 'error');
                });
            });
        }, function (d) {
            $('#sbs-reschedule-slots').html('<p style="color:red;font-size:13px;text-align:center;">' + (d && d.message ? d.message : __('Error loading slots')) + '</p>');
        });
    }

    function openChangeServiceModal(bookingId) {
        const today = new Date().toISOString().split('T')[0];
        $('body').append(`
            <div class="sbs-portal-modal-overlay" id="sbs-change-service-overlay">
                <div class="sbs-portal-modal sbs-portal-change-service-modal">
                    <button class="sbs-portal-modal-close" id="sbs-change-service-close">✕</button>
                    <h3>${cfg.strings.changeService}</h3>
                    <p class="subtitle">${__('Choose the new service first. Your existing appointment remains unchanged until final confirmation.')}</p>
                    <div id="sbs-change-service-options"><span class="sbs-portal-spinner" style="display:inline-block;"></span></div>
                    <input type="date" id="sbs-change-service-date" class="sbs-portal-input" value="${today}" min="${today}" disabled>
                    <div id="sbs-change-service-slots"></div>
                    <p class="sbs-portal-msg" id="sbs-change-service-msg"></p>
                    <div class="sbs-portal-modal-footer"><button type="button" class="sbs-portal-btn-sm sbs-portal-btn-outline" id="sbs-change-service-back">← ${__('Back to appointments')}</button></div>
                </div>
            </div>`);
        $('#sbs-change-service-close, #sbs-change-service-back, #sbs-change-service-overlay').on('click', function (e) { if (e.target.id === 'sbs-change-service-overlay' || e.target.id === 'sbs-change-service-close' || e.target.id === 'sbs-change-service-back') $('#sbs-change-service-overlay').remove(); });
        ajax('yatoon_portal_get_change_service_options', { booking_id: bookingId }, function (data) {
            const choices = data.choices || [];
            if (!choices.length) { $('#sbs-change-service-options').html(`<p>${__('No services are currently available for online changes.')}</p>`); return; }
            $('#sbs-change-service-options').html(`<div class="sbs-change-service-list">${choices.map(c => `<button type="button" class="sbs-change-service-choice" data-service="${c.service_id}" data-option="${c.option_id}" data-name="${escHtml(c.name)}"><strong>${escHtml(c.name)}</strong><span>${c.duration} min · ${escHtml(c.price_label)}</span></button>`).join('')}</div>`);
            $('.sbs-change-service-choice').on('click', function () {
                $('.sbs-change-service-choice').removeClass('selected'); $(this).addClass('selected');
                $('#sbs-change-service-date').prop('disabled', false).trigger('change');
            });
            $('#sbs-change-service-date').on('change', function () { loadChangeServiceSlots(bookingId, $(this).val()); });
        }, function (d) { $('#sbs-change-service-options').html(`<p style="color:#b91c1c;">${escHtml(d && d.message ? d.message : __('Could not load services.'))}</p>`); });
    }

    function loadChangeServiceSlots(bookingId, date) {
        const $choice = $('.sbs-change-service-choice.selected');
        if (!$choice.length) return;
        const payload = { booking_id: bookingId, service_id: $choice.data('service'), option_id: $choice.data('option'), date };
        $('#sbs-change-service-slots').html('<div style="text-align:center;padding:16px;"><span class="sbs-portal-spinner" style="display:inline-block;"></span></div>');
        ajax('yatoon_portal_get_change_service_slots', payload, function (data) {
            if (!data.slots || !data.slots.length) { $('#sbs-change-service-slots').html(`<p>${__('No available times on this date.')}</p>`); return; }
            $('#sbs-change-service-slots').html(`<div class="sbs-portal-slot-grid">${data.slots.map(s => `<button type="button" class="sbs-portal-slot sbs-change-service-slot" data-start="${escHtml(s.start_at)}" data-staff="${escHtml((s.staff_ids || [])[0] || '')}">${escHtml(s.display)}</button>`).join('')}</div><div class="sbs-change-service-confirm"><p>${__('Changing service recalculates the duration and price and removes existing add-ons. Paid or multi-service appointments must be changed by the salon.')}</p><button type="button" id="sbs-change-service-confirm" class="sbs-portal-btn" disabled>${__('Confirm Service Change')}</button></div>`);
            $('.sbs-change-service-slot').on('click', function () { $('.sbs-change-service-slot').removeClass('selected'); $(this).addClass('selected'); $('#sbs-change-service-confirm').prop('disabled', false); });
            $('#sbs-change-service-confirm').on('click', function () {
                const $slot = $('.sbs-change-service-slot.selected'); if (!$slot.length) return;
                const $btn = $(this).prop('disabled', true).text(__('Updating...'));
                ajax('yatoon_portal_change_booking_service', Object.assign({}, payload, { new_start_at: $slot.data('start'), staff_id: $slot.data('staff') }), function () { $('#sbs-change-service-overlay').remove(); loadDashboard(); }, function (d) { $btn.prop('disabled', false).text(__('Confirm Service Change')); showMsg($('#sbs-change-service-msg'), d && d.message ? d.message : __('Service change failed.'), 'error'); });
            });
        }, function (d) { $('#sbs-change-service-slots').html(`<p style="color:#b91c1c;">${escHtml(d && d.message ? d.message : __('Could not load times.'))}</p>`); });
    }

    /* ══════════════════════════════════════════════════════
       SECURITY HELPER
    ══════════════════════════════════════════════════════ */
    function escHtml(str) {
        return String(str || '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
    }

    /* ══════════════════════════════════════════════════════
       INIT: check if already logged in
    ══════════════════════════════════════════════════════ */
    function init() {
        renderLoading();
        // Try to get bookings - if session exists, it'll succeed
        ajax('yatoon_portal_get_bookings', {}, function (data) {
            allData         = data;
            currentCustomer = data.customer;
            currentTab      = data.upcoming && data.upcoming.length ? 'upcoming' : 'past';
            renderDashboard();
        }, function (d) {
            // Not logged in or error - show phone step
            currentTab = 'upcoming';
            renderPhoneStep();
        });
    }

    init();

})(jQuery);

})();
