<?php
declare(strict_types=1);

if (!defined('ABSPATH')) { exit; }

/**
 * Booking Block element renderer (shared).
 *
 * Phase 1, step 30. The editor's version was the modern, polished
 * implementation — three-step booking flow with form validation, an
 * empty-state UI directing builders to add services, configurable
 * styling vars passed to the inline engine, and postMessage hooks so
 * clicking inside the booking widget in the builder keeps the parent
 * element selected.
 *
 * The frontend's version (94 lines) was a stripped-down sibling that
 * lacked the empty-state UI, the validation, the postMessage glue, and
 * shipped with a simpler calendar that didn't visualise the selected
 * day. This refactor adopts the editor's renderer as canonical; the
 * live frontend gets the upgrade.
 *
 * Pre-extraction differences resolved:
 *
 *   - DB driver: frontend uses `\Instinctor\Core\DB`, editor uses
 *     `$wpdb`. Unified — DB class when available, else $wpdb.
 *
 *   - Empty state: frontend silently `break`s when no services exist
 *     (block disappears). Editor renders a "No services found" card
 *     with an "Add Service ›" CTA pointing into the admin. Editor-only
 *     UI is preserved here; frontend returns '' so the block hides.
 *
 *   - AJAX URL: editor handled standalone + WP; frontend never did.
 *     Editor's branching adopted.
 *
 *   - Visibility badge: editor-only helper; wrapped in function_exists.
 *
 *   - Element id: previously `$id` (editor) vs `$el['id']` (frontend).
 *     Standardised to `$el['id']` since both contexts now pass the
 *     element array.
 *
 *   - Inline JS `_keepSelected` postMessage: editor-specific but inert
 *     on frontend (the `window.impulseIsPreview` flag is only set in
 *     the builder iframe), so it ships in both contexts unchanged.
 *
 * @sideeffect wp_add_inline_style('instinctor-page-inline', ...) for
 *             per-instance hover overrides on the calendar / slot grid.
 */
function impulse_element_render_booking($el, array $settings, string $context): string {
    if (is_object($el)) $el = (array) $el;
    $isEditor = ($context === 'editor');

    // Personalized-demo signals (set by DemoService on the per-token page). When
    // demo_services are present the widget seeds from settings and runs fully
    // client-side — slots/staff/submit never hit the host store's REST/AJAX.
    $bkDemoVertical = (string) ($settings['demo_vertical'] ?? '');
    $bkDemo         = (!empty($settings['demo_services']) && is_array($settings['demo_services']));
    $bkDemoStaff    = (!empty($settings['demo_staff']) && is_array($settings['demo_staff'])) ? $settings['demo_staff'] : [];

    $rawId = (string) ($el['id'] ?? uniqid());
    $rawId = preg_replace('/[^a-z0-9_-]/i', '', $rawId);
    $bkId  = 'ibk-' . $rawId;

    // Store-type-aware defaults: salons read "Appointment", others "Service".
    $bkDefaultTitle = 'Book a Service';
    
    $bkDefaultBtn   = \Instinctor\Core\StoreFeatures::bookingCtaLabel();
    $bkTitle      = (string) ($settings['title']        ?? $bkDefaultTitle);
    $bkBtnText    = (string) ($settings['button_text']  ?? $bkDefaultBtn);
    $bkBtnBg      = esc_attr((string) ($settings['button_bg']    ?? '#1d2327'));
    $bkBtnColor   = esc_attr((string) ($settings['button_color'] ?? '#ffffff'));
    $bkFieldBg    = esc_attr((string) ($settings['field_bg']     ?? '#ffffff'));
    $bkFieldBd    = esc_attr((string) ($settings['field_border'] ?? '#e0e0e0'));
    $bkFieldR     = intval($settings['field_radius'] ?? 8);
    $bkBtnR       = intval($settings['button_radius'] ?? 8);
    $bkMaxW       = intval($settings['max_width']    ?? 560);
    $bkTitleSize  = intval($settings['title_size']   ?? 22);
    // Optional card surface — lets the widget read as a floating panel over a
    // dark/animated screen background (the block's text is dark by design, so
    // it needs a light surface to stay readable). Empty = transparent (legacy).
    $bkCardBg     = trim((string) ($settings['card_bg'] ?? ''));

    // ── Fetch services ──────────────────────────────────────────────────
    // Builder-demo hook: when $GLOBALS['impulse_bd_services_override'] is
    // set (marketing-page Booking demo on instinctor.com), use that array
    // verbatim and skip the DB query. The global is unset by the caller
    // after render so this branch never runs in production.
    if (!empty($GLOBALS['impulse_bd_services_override'])
        && is_array($GLOBALS['impulse_bd_services_override'])) {
        $bkServices = $GLOBALS['impulse_bd_services_override'];
    } elseif ($bkDemo) {
        // Personalized demo (/demo/:token): the per-token page carries the
        // generated salon services in-settings; no host-store query.
        $bkServices = $settings['demo_services'];
    } else {
        $bkServices = [];
        if (class_exists('\\Instinctor\\Core\\DB')) {
            $tbl = \Instinctor\Core\DB::prefix() . 'instinctor_services';
            $bkServices = \Instinctor\Core\DB::get_results(
                "SELECT id,name,duration,price,color,capacity,deposit FROM {$tbl} WHERE status='active' ORDER BY sort_order ASC, name ASC",
                ARRAY_A
            ) ?: [];
        } else {
            global $wpdb;
            if (isset($wpdb)) {
                $bkServices = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- plugin's own custom table; query is prepared; per-request render value, not object-cacheable
                    "SELECT id,name,duration,price,color,capacity,deposit FROM {$wpdb->prefix}instinctor_services WHERE status='active' ORDER BY sort_order ASC, name ASC",
                    ARRAY_A
                ) ?: [];
            }
        }
    }

    // Salon staff picker flag. Generic default is off; the real check is stripped
    // from the free build, where $bkIsSalon (and the JS _isSalon) is always false.
    $bkIsSalon = false;
    

    // ── Empty state ─────────────────────────────────────────────────────
    if (empty($bkServices)) {
        if (!$isEditor) return '';
        $servicesUrl = (defined('INSTINCTOR_ENV') && INSTINCTOR_ENV === 'standalone')
            ? '/instinctor/store?section=bookings'
            : (function_exists('admin_url') ? esc_url(admin_url('admin.php?page=instinctor#store/bookings')) : '#');
        ob_start();
        $visBadge = function_exists('impulse_visibility_badge') ? impulse_visibility_badge($settings) : '';
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- impulse_visibility_badge() returns the plugin's own curated badge markup (no user input).
        if ($visBadge) echo '<div style="position:relative">' . wp_kses($visBadge, impulse_svg_allowed());
        echo '<div style="max-width:' . esc_attr((string) $bkMaxW) . 'px;margin:0 auto;padding:32px 24px;box-sizing:border-box;border:1px dashed #3a3f44;border-radius:8px;background:#1d2327;text-align:center">';
        echo '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#555" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="margin-bottom:12px"><path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/><path d="M8 14h.01"/><path d="M12 14h.01"/><path d="M16 14h.01"/><path d="M8 18h.01"/><path d="M12 18h.01"/><path d="M16 18h.01"/></svg>';
        echo '<div style="font-size:13px;font-weight:600;color:#e0e0e0;margin-bottom:6px">No services found</div>';
        echo '<div style="font-size:12px;color:#888;margin-bottom:16px">Add at least one active service in Store &rsaquo; Bookings &rsaquo; Services before this block will work.</div>';
        echo '<button type="button" class="impulse-goto-add-service" data-url="' . esc_attr($servicesUrl) . '" style="display:inline-block;padding:7px 16px;background:#2a2e33;border:1px solid #3a3f44;border-radius:4px;font-size:12px;color:#e0e0e0;cursor:pointer;pointer-events:auto;position:relative;z-index:10">Add Service &rsaquo;</button>';
        echo '</div>';
        if ($visBadge) echo '</div>';
        return (string) ob_get_clean();
    }

    // ── Resolve URLs + endpoints ────────────────────────────────────────
    $bkAjax = (defined('INSTINCTOR_ENV') && INSTINCTOR_ENV === 'standalone')
        ? esc_url(rtrim(defined('INSTINCTOR_STANDALONE_URL') ? INSTINCTOR_STANDALONE_URL : '', '/') . '/admin/admin-ajax.php')
        : (function_exists('admin_url') ? esc_url(admin_url('admin-ajax.php')) : '/admin/admin-ajax.php');
    $bkRestBase = class_exists('\\Instinctor\\Core\\Router')
        ? \Instinctor\Core\Router::restBase()
        : '/wp-json/instinctor/v1';
    $bkNonce = class_exists('\\Instinctor\\Core\\Session')
        ? \Instinctor\Core\Session::token('instinctor_frontend')
        : (function_exists('wp_create_nonce') ? wp_create_nonce('instinctor_frontend') : '');

    $bkSvcsJs = function_exists('wp_json_encode') ? wp_json_encode($bkServices) : wp_json_encode($bkServices);

    $bkFStyle   = 'width:100%;padding:10px 14px;background:' . $bkFieldBg . ';border:1px solid ' . $bkFieldBd
                . ';border-radius:' . $bkFieldR . 'px;font-size:14px;color:#1d2327;outline:none;box-sizing:border-box;font-family:inherit';
    $bkBtnStyle = 'width:100%;padding:12px;background:' . $bkBtnBg . ';color:' . $bkBtnColor
                . ';border:none;border-radius:' . $bkBtnR . 'px;font-size:15px;font-weight:600;cursor:pointer;font-family:inherit';

    $currencySymbol = function_exists('get_option') ? (string) get_option('instinctor_currency_symbol', '$') : '$';

    // ── Booking deposits (upfront, optional) ────────────────────────────
    // Deposits engage only when Stripe is connected, payment is required, and at
    // least one active service carries a deposit. Stripe.js is loaded only then.
    $bkStripePk   = class_exists('\\Instinctor\\Core\\Config') ? (string) \Instinctor\Core\Config::get('instinctor_stripe_publishable_key', '') : '';
    $bkRequirePay = class_exists('\\Instinctor\\Services\\BookingService')
        ? !empty(\Instinctor\Services\BookingService::instance()->getSettings()['require_payment'])
        : true;
    $bkAnyDeposit = false;
    foreach ($bkServices as $sv) { if ((float) ($sv['deposit'] ?? 0) > 0) { $bkAnyDeposit = true; break; } }
    $bkUseDeposits = (!$isEditor && $bkStripePk !== '' && $bkRequirePay && $bkAnyDeposit);
    if ($bkUseDeposits && function_exists('wp_enqueue_script')) {
        // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Stripe's versioned CDN URL; passing a version string would duplicate the version in the query param
        wp_enqueue_script('stripe-js', 'https://js.stripe.com/v3/', [], null, true);
    }

    ob_start();

    $visBadge = $isEditor && function_exists('impulse_visibility_badge') ? impulse_visibility_badge($settings) : '';
    if ($visBadge) echo '<div style="position:relative">' . wp_kses($visBadge, impulse_svg_allowed());

    // Calendar / slot hover styling — same rule for every booking block on
    // every page (CSS consolidation pass, post-step-33). Pre-consolidation
    // this emitted two `#bkId .ibk-cal-grid …` and `#bkId .ibk-slots-grid …`
    // rules per booking block; the body of each rule was identical, so a
    // single class-based pair covers all instances. Singleton-gated by a
    // GLOBALS flag so the rule emits at most once per page render.
    if (empty($GLOBALS['impulse_booking_block_css_emitted']) && function_exists('wp_add_inline_style')) {
        $GLOBALS['impulse_booking_block_css_emitted'] = true;
        wp_add_inline_style('instinctor-page-inline',
            '.impulse-booking-block .ibk-cal-grid button:not([disabled]):not(.ibk-selected-day):hover{background:#f0f0f0!important;border-color:#ddd!important}'
          . '.impulse-booking-block .ibk-slots-grid button:not([disabled]):not(.ibk-selected-slot):hover{background:#f0f0f0!important;border-color:#ddd!important}'
          . '.impulse-booking-block .ibk-staff-card:hover{opacity:0.8}'
        );
    }

    $bkCardStyle = 'max-width:' . $bkMaxW . 'px;margin:0 auto;padding:24px;box-sizing:border-box';
    if ($bkCardBg !== '') {
        $bkCardStyle .= ';background:' . esc_attr($bkCardBg) . ';color:#1d2327;border-radius:14px;box-shadow:0 10px 40px rgba(0,0,0,0.45)';
    }
    echo '<div id="' . esc_attr($bkId) . '" class="impulse-booking-block" style="' . esc_attr($bkCardStyle) . '">';
    echo '<h2 style="margin:0 0 20px;font-size:' . esc_attr((string) $bkTitleSize) . 'px;color:#1d2327">' . esc_html($bkTitle) . '</h2>';
    echo '<div class="ibk-toast" style="display:none;background:#1d2327;color:#fff;font-size:13px;padding:10px 14px;border-radius:6px;margin-bottom:12px"></div>';

    // ── Step 1: service + date/time ─────────────────────────────────────
    echo '<div class="ibk-step ibk-step1" data-step="1">';
    echo '<div style="display:flex;flex-direction:column;gap:12px">';

    
    echo '<select class="ibk-service" style="' . esc_attr($bkFStyle) . '"><option value="">Select service…</option>';
    foreach ($bkServices as $sv) {
        $svId       = intval($sv['id']);
        $svName     = (string) ($sv['name'] ?? '');
        $svDuration = intval($sv['duration'] ?? 0);
        $svPrice    = (float) ($sv['price'] ?? 0);
        $priceLabel = $svPrice > 0 ? ' — ' . $currencySymbol . number_format($svPrice, 2) : '';
        echo '<option value="' . esc_attr((string) $svId) . '" data-duration="' . esc_attr((string) $svDuration) . '" data-price="' . esc_attr((string) $svPrice) . '">' . esc_html($svName . $priceLabel) . '</option>';
    }
    echo '</select>';

    

    echo '<div class="ibk-calendar-wrap">';
    echo '<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">';
    echo '<button type="button" class="ibk-prev-month" style="background:none;border:none;cursor:pointer;padding:4px 8px;font-size:18px;color:#888">&#8249;</button>';
    echo '<span class="ibk-month-label" style="font-size:14px;font-weight:600;color:#1d2327"></span>';
    echo '<button type="button" class="ibk-next-month" style="background:none;border:none;cursor:pointer;padding:4px 8px;font-size:18px;color:#888">&#8250;</button>';
    echo '</div>';
    echo '<div class="ibk-cal-grid" style="display:grid;grid-template-columns:repeat(7,1fr);gap:4px;text-align:center"></div>';
    echo '</div>';

    echo '<div class="ibk-slots-wrap" style="display:none">';
    echo '<div style="font-size:13px;color:#888;margin-bottom:6px">Available times</div>';
    echo '<div class="ibk-slots-grid" style="display:grid;grid-template-columns:repeat(4,1fr);gap:6px"></div>';
    echo '</div>';

    echo '<button type="button" class="ibk-next-btn" style="' . esc_attr($bkBtnStyle) . ';opacity:0.45">Next &rsaquo;</button>';
    echo '</div></div>'; // gap + step1

    // ── Step 2: customer details ────────────────────────────────────────
    echo '<div class="ibk-step ibk-step2" data-step="2" style="display:none">';
    echo '<div style="display:flex;flex-direction:column;gap:12px">';
    echo '<div style="font-size:13px;color:#888;margin-bottom:-4px" class="ibk-booking-summary"></div>';
    echo '<input type="text" class="ibk-name" placeholder="Your name *" style="' . esc_attr($bkFStyle) . '">';
    echo '<input type="email" class="ibk-email" placeholder="Email *" style="' . esc_attr($bkFStyle) . '">';
    echo '<input type="tel" class="ibk-phone" placeholder="Phone" style="' . esc_attr($bkFStyle) . '">';
    echo '<textarea class="ibk-note" placeholder="Note (optional)" rows="3" style="' . esc_attr($bkFStyle) . ';resize:vertical"></textarea>';
    // Deposit payment mount point — revealed by the widget JS only when the
    // selected service requires a deposit. Stripe Payment Element mounts here.
    echo '<div class="ibk-pay-wrap" style="display:none"><div class="ibk-pay-label" style="font-size:13px;color:#1d2327;margin-bottom:8px;font-weight:600"></div><div class="ibk-pay-element"></div></div>';
    echo '<div style="display:flex;gap:10px">';
    echo '<button type="button" class="ibk-back-btn" style="flex:1;padding:12px;background:transparent;color:#888;border:1px solid #e0e0e0;border-radius:' . esc_attr((string) $bkBtnR) . 'px;font-size:14px;cursor:pointer;font-family:inherit">Back</button>';
    echo '<button type="button" class="ibk-submit-btn" style="flex:3;' . esc_attr($bkBtnStyle) . '">' . esc_html($bkBtnText) . '</button>';
    echo '</div>';
    echo '</div></div>'; // gap + step2

    // ── Step 3: confirmation ────────────────────────────────────────────
    echo '<div class="ibk-step ibk-step3" data-step="3" style="display:none">';
    echo '<div style="text-align:center;padding:24px 0">';
    echo '<div style="font-size:40px;margin-bottom:12px">&#x2713;</div>';
    echo '<div class="ibk-confirm-msg" style="font-size:16px;color:#1d2327;font-weight:600"></div>';
    echo '<div class="ibk-confirm-detail" style="font-size:13px;color:#888;margin-top:6px"></div>';
    echo '</div></div>';

    echo '</div>'; // .impulse-booking-block

    if ($visBadge) echo '</div>';

    // ── Inline engine ───────────────────────────────────────────────────
    $jsonEnc = function_exists('wp_json_encode') ? 'wp_json_encode' : 'json_encode';
    ob_start();
    echo '(function(){';
    echo 'var _wrap=document.getElementById(' . wp_json_encode($bkId) . ');if(!_wrap)return;';
    echo 'var _svcs=' . wp_json_encode($bkServices) . ';';
    echo 'var _ajax=' . wp_json_encode($bkAjax) . ';';
    echo 'var _nonce=' . wp_json_encode((string) $bkNonce) . ';';
    echo 'var _ajaxNonce=' . wp_json_encode((string) $bkNonce) . ';';
    echo 'var _restBase=' . wp_json_encode((string) $bkRestBase) . ';';
    echo 'var _fldBg=' . wp_json_encode($bkFieldBg) . ';';
    echo 'var _fldBd=' . wp_json_encode($bkFieldBd) . ';';
    echo 'var _fldR=' . wp_json_encode($bkFieldR) . ';';
    echo 'var _btnBg=' . wp_json_encode($bkBtnBg) . ';';
    echo 'var _btnR=' . wp_json_encode($bkBtnR) . ';';
    echo 'var _btnColor=' . wp_json_encode($bkBtnColor) . ';';
    echo 'var _useDeposits=' . ($bkUseDeposits ? 'true' : 'false') . ';';
    echo 'var _cur=' . wp_json_encode($currencySymbol) . ';';
    echo 'var _isSalon=' . ($bkIsSalon ? 'true' : 'false') . ';';
    echo 'var _bkDemo=' . ($bkDemo ? 'true' : 'false') . ';';
    echo 'var _bkDemoStaff=' . (function_exists('wp_json_encode') ? wp_json_encode($bkDemoStaff) : json_encode($bkDemoStaff)) . ';';
    echo "
var _state={svcId:0,svcName:'',staffId:0,staffName:'Any available',date:'',time:'',year:new Date().getFullYear(),month:new Date().getMonth()};
var _payReady=false,_stripe=null,_elements=null,_piId='',_payLabel='';
var \$=function(sel){return _wrap.querySelector(sel);};
function fmtDate(y,m,d){return y+'-'+(m+1<10?'0':'')+(m+1)+'-'+(d<10?'0':'')+d;}
function fmtDisplay(y,m,d){var ms=['January','February','March','April','May','June','July','August','September','October','November','December'];return ms[m]+' '+d+', '+y;}
function fmtTime(t){var p=t.split(':');var h=parseInt(p[0]);var suf=h>=12?'PM':'AM';h=h%12||12;return h+':'+p[1]+' '+suf;}
function renderCalendar(){
  var g=\$('.ibk-cal-grid'),lbl=\$('.ibk-month-label');
  var ms=['January','February','March','April','May','June','July','August','September','October','November','December'];
  var days=['Su','Mo','Tu','We','Th','Fr','Sa'];
  lbl.textContent=ms[_state.month]+' '+_state.year;
  g.innerHTML='';
  days.forEach(function(d){var h=document.createElement('div');h.textContent=d;h.style.cssText='font-size:11px;color:#888;padding:4px 0';g.appendChild(h);});
  var first=new Date(_state.year,_state.month,1).getDay();
  for(var i=0;i<first;i++){g.appendChild(document.createElement('div'));}
  var dim=new Date(_state.year,_state.month+1,0).getDate();
  var today=new Date();today.setHours(0,0,0,0);
  for(var d=1;d<=dim;d++){
    var dt=new Date(_state.year,_state.month,d);
    var btn=document.createElement('button');
    btn.type='button';btn.textContent=d;
    var past=dt<today;
    var dateStr=fmtDate(_state.year,_state.month,d);
    var isSelected=(!past&&dateStr===_state.date);
    if(isSelected)btn.classList.add('ibk-selected-day');
    btn.style.border='1px solid '+(past?'transparent':isSelected?_btnBg:_fldBd);
    btn.style.background=past?'transparent':isSelected?_btnBg:_fldBg;
    btn.style.borderRadius=_fldR+'px';
    btn.style.padding='6px 2px';
    btn.style.fontSize='13px';
    btn.style.lineHeight='1';
    btn.style.cursor=past?'default':'pointer';
    btn.style.color=past?'#ccc':isSelected?_btnColor:'#1d2327';
    btn.style.fontFamily='inherit';
    btn.style.boxSizing='border-box';
    btn.style.width='100%';
    if(!past){
      btn.addEventListener('click',function(ds){return function(e){_keepSelected(e);_state.date=ds;_state.time='';\$('.ibk-next-btn').style.opacity='0.45';renderCalendar();loadSlots();};}(dateStr));
    }
    g.appendChild(btn);
  }
}
function renderDemoSlots(){
  var sw=\$('.ibk-slots-wrap'),sg=\$('.ibk-slots-grid');
  sw.style.display='block';sg.innerHTML='';
  var pr=_state.date.split('-'),dd=parseInt(pr[2]),mm=parseInt(pr[1]),yy=parseInt(pr[0]);
  var dow=new Date(yy,mm-1,dd).getDay();
  var startH=(dow===0)?11:9,endH=(dow===0)?16:19;
  var seed=dd+mm*31+(_state.svcId||0)+(_state.staffId||0)*7;
  var made=0;
  for(var h=startH;h<endH;h++){
    [0,30].forEach(function(min){
      if(h===endH-1&&min===30)return;
      var t=(h<10?'0':'')+h+':'+(min<10?'0':'')+min;
      seed=(seed*9301+49297)%233280;
      var avail=(seed/233280)>0.38;
      var b=document.createElement('button');
      b.type='button';b.textContent=fmtTime(t);
      b.style.cssText='padding:8px 4px;font-size:13px;border-radius:'+_fldR+'px;border:1px solid '+_fldBd+';background:'+(avail?_fldBg:'#f3f3f3')+';color:'+(avail?'#1d2327':'#ccc')+';cursor:'+(avail?'pointer':'default')+';font-family:inherit';
      if(avail){made++;b.addEventListener('click',(function(tt,el){return function(e){_keepSelected(e);_state.time=tt;sg.querySelectorAll('button').forEach(function(x){x.style.background=_fldBg;x.style.color='#1d2327';x.style.borderColor=_fldBd;});el.style.background=_btnBg;el.style.color='#fff';el.style.borderColor=_btnBg;\$('.ibk-next-btn').style.opacity='1';};})(t,b));}else{b.disabled=true;}
      sg.appendChild(b);
    });
  }
  if(!made){sg.innerHTML='<div style=\"color:#888;font-size:13px;grid-column:1/-1\">Fully booked. Try another day.</div>';}
}
function loadSlots(){
  var _pe=\$('.ibk-party');
  if(!_state.svcId||!_state.date||(_pe&&!_state.party)){return;}
  if(_bkDemo){renderDemoSlots();return;}
  var sw=\$('.ibk-slots-wrap'),sg=\$('.ibk-slots-grid');
  sw.style.display='block';sg.innerHTML='<div style=\"color:#888;font-size:13px;grid-column:1/-1\">Loading…</div>';
  var xhr=new XMLHttpRequest();
  xhr.open('GET',_restBase+'/bookings/slots?product_id='+_state.svcId+'&date='+_state.date+((_pe&&_state.party)?'&party_size='+_state.party:'')+(_state.staffId?'&staff_id='+_state.staffId:''));
  xhr.setRequestHeader('X-Instinctor-Nonce',_nonce);
  xhr.onload=function(){
    if(xhr.status!==200){sg.innerHTML='<div style=\"color:#888;font-size:13px;grid-column:1/-1\">Could not load slots (HTTP '+xhr.status+').</div>';return;}
    var data;try{data=JSON.parse(xhr.responseText);}catch(e){sg.innerHTML='<div style=\"color:#888;font-size:13px;grid-column:1/-1\">Invalid slot response.</div>';return;}
    var slots=data.slots||[];
    sg.innerHTML='';
    if(!slots.length){sg.innerHTML='<div style=\"color:#888;font-size:13px;grid-column:1/-1\">No slots available.</div>';return;}
    slots.forEach(function(s){
      var b=document.createElement('button');
      b.type='button';b.textContent=s.display;
      var active=s.time===_state.time;
      b.style.cssText='padding:8px 4px;font-size:13px;border-radius:'+_fldR+'px;border:1px solid '+(active?_btnBg:_fldBd)+';background:'+(active?_btnBg:_fldBg)+';color:'+(active?'#fff':(s.available?'#1d2327':'#ccc'))+';cursor:'+(s.available?'pointer':'default');
      if(s.available){
        b.addEventListener('click',function(t,el){return function(e){_keepSelected(e);_state.time=t;sg.querySelectorAll('button').forEach(function(x){x.style.background=_fldBg;x.style.color='#1d2327';x.style.borderColor=_fldBd;});el.style.background=_btnBg;el.style.color='#fff';el.style.borderColor=_btnBg;\$('.ibk-next-btn').style.opacity='1';};}(s.time,b));
      }else{b.disabled=true;}
      sg.appendChild(b);
    });
  };
  xhr.send();
}

function ibkToast(msg){var t=\$('.ibk-toast');t.textContent=msg;t.style.display='block';clearTimeout(t._timer);t._timer=setTimeout(function(){t.style.display='none';},3000);}
function showStep(n){\$('.ibk-toast').style.display='none';_wrap.querySelectorAll('.ibk-step').forEach(function(s){s.style.display='none';});_wrap.querySelector('.ibk-step'+n).style.display='block';}
var _elemId=_wrap.id.replace('ibk-','');
function _keepSelected(e){if(e)e.stopPropagation();if(window.impulseIsPreview&&window.parent&&_elemId){window.parent.postMessage({type:'impulse-select',id:_elemId,elementType:'booking'},'*');}}
var svcEl=\$('.ibk-service');
function onSvcChange(){var opt=svcEl.options?svcEl.options[svcEl.selectedIndex]:null;_state.svcId=parseInt(svcEl.value)||0;_state.svcName=opt?opt.text:'';_state.staffId=0;_state.staffName='Any available';_state.date='';_state.time='';\$('.ibk-slots-wrap').style.display='none';\$('.ibk-next-btn').style.opacity='0.45';if(_isSalon&&_state.svcId)loadStaff();renderCalendar();}
if(svcEl.tagName==='SELECT')svcEl.addEventListener('change',onSvcChange);
else{_state.svcId=parseInt(svcEl.value)||0;_state.svcName=(_svcs[0]||{}).name||'';}
var _partyEl=\$('.ibk-party');
if(_partyEl){_partyEl.addEventListener('change',function(){_state.party=parseInt(_partyEl.value)||0;_state.time='';\$('.ibk-next-btn').style.opacity='0.45';if(_state.date)loadSlots();});}
\$('.ibk-prev-month').addEventListener('click',function(e){_keepSelected(e);_state.month--;if(_state.month<0){_state.month=11;_state.year--;}_state.date='';_state.time='';\$('.ibk-next-btn').style.opacity='0.45';renderCalendar();});
\$('.ibk-next-month').addEventListener('click',function(e){_keepSelected(e);_state.month++;if(_state.month>11){_state.month=0;_state.year++;}_state.date='';_state.time='';\$('.ibk-next-btn').style.opacity='0.45';renderCalendar();});
\$('.ibk-next-btn').addEventListener('click',function(){if(\$('.ibk-party')&&!_state.party){ibkToast('Please choose a party size.');return;}if(!_state.svcId){ibkToast('Please select a service.');return;}if(!_state.date){ibkToast('Please select a date.');return;}if(!_state.time){ibkToast('Please select a time slot.');return;}\$('.ibk-booking-summary').textContent=_state.svcName+(_isSalon&&_state.staffId?' with '+_state.staffName:'')+' · '+fmtDisplay(_state.year,_state.month,parseInt(_state.date.split('-')[2]))+' at '+fmtTime(_state.time);_payReady=false;var _pw=\$('.ibk-pay-wrap');if(_pw)_pw.style.display='none';var _pe2=\$('.ibk-pay-element');if(_pe2)_pe2.innerHTML='';showStep(2);});
\$('.ibk-back-btn').addEventListener('click',function(){showStep(1);});
function _doBook(piId){
  if(_bkDemo){
    _wrap.querySelectorAll('.ibk-step').forEach(function(s){s.style.display='none';});
    var c=\$('.ibk-demo-confirm');
    if(!c){c=document.createElement('div');c.className='ibk-demo-confirm';c.style.cssText='text-align:center;padding:14px 4px';_wrap.appendChild(c);}
    var nm=(\$('.ibk-name')&&\$('.ibk-name').value.trim())||'';nm=nm.replace(/[<>]/g,'');
    c.innerHTML='<div style=\"width:46px;height:46px;border-radius:50%;background:'+_btnBg+';display:flex;align-items:center;justify-content:center;margin:0 auto 14px\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"22\" height=\"22\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M20 6 9 17l-5-5\"/></svg></div><div style=\"font-size:17px;font-weight:700;color:#1d2327;margin-bottom:6px\">Appointment reserved'+(nm?', '+nm:'')+'.</div><div style=\"font-size:13px;color:#666;line-height:1.5;max-width:330px;margin:0 auto\">'+_state.svcName+' on '+fmtDisplay(_state.year,_state.month,parseInt(_state.date.split('-')[2]))+' at '+fmtTime(_state.time)+'. On your live site this books instantly and texts your client a confirmation.</div>';
    c.style.display='block';return;
  }
  var btn=\$('.ibk-submit-btn');btn.disabled=true;btn.textContent='Booking…';
  var fd=new FormData();
  fd.append('action','instinctor_public_book');
  fd.append('nonce',_ajaxNonce);
  fd.append('service_id',_state.svcId);
  fd.append('guest_count',_state.party||1);
  fd.append('booking_date',_state.date);
  fd.append('booking_time',_state.time);
  fd.append('customer_name',\$('.ibk-name').value.trim());
  fd.append('customer_email',\$('.ibk-email').value.trim());
  fd.append('customer_phone',\$('.ibk-phone').value);
  fd.append('note',\$('.ibk-note').value);
  if(piId)fd.append('payment_intent_id',piId);
  if(_state.staffId)fd.append('staff_id',_state.staffId);
  var xhr=new XMLHttpRequest();xhr.open('POST',_ajax);
  xhr.onload=function(){
    var res;try{res=JSON.parse(xhr.responseText);}catch(e){btn.disabled=false;btn.textContent=" . wp_json_encode(esc_html($bkBtnText)) . ";ibkToast('Connection error');return;}
    if(res.success){
      var paid=res.data&&res.data.deposit_paid;
      \$('.ibk-confirm-msg').textContent=paid?'Appointment confirmed':'Request received';
      \$('.ibk-confirm-detail').textContent=_state.svcName+(_isSalon&&_state.staffId?' with '+_state.staffName:'')+' · '+fmtDisplay(_state.year,_state.month,parseInt(_state.date.split('-')[2]))+' at '+fmtTime(_state.time)+(paid?', deposit paid.':', we will confirm shortly.');
      showStep(3);
    }else{
      btn.disabled=false;btn.textContent=" . wp_json_encode(esc_html($bkBtnText)) . ";
      ibkToast(res.data&&res.data.message?res.data.message:'Booking failed. Please try again.');
    }
  };
  xhr.send(fd);
}
function _confirmAndBook(){
  var btn=\$('.ibk-submit-btn');btn.disabled=true;btn.textContent='Processing…';
  _stripe.confirmPayment({elements:_elements,redirect:'if_required'}).then(function(result){
    if(result.error){btn.disabled=false;btn.textContent='Pay '+_payLabel+' & Book';ibkToast(result.error.message||'Payment failed.');return;}
    _doBook(_piId);
  });
}
\$('.ibk-submit-btn').addEventListener('click',function(){
  var name=\$('.ibk-name').value.trim(),email=\$('.ibk-email').value.trim();
  if(!name||!email){ibkToast('Please enter your name and email.');return;}
  if(_payReady){_confirmAndBook();return;}
  var svc=_svcs.filter(function(s){return parseInt(s.id)===_state.svcId;})[0];
  var dep=svc?parseFloat(svc.deposit||0):0;
  if(!_useDeposits||!dep||dep<=0||typeof Stripe==='undefined'){_doBook('');return;}
  var btn=this;btn.disabled=true;btn.textContent='…';
  var fd=new FormData();fd.append('action','instinctor_booking_deposit_intent');fd.append('nonce',_ajaxNonce);fd.append('service_id',_state.svcId);
  var xhr=new XMLHttpRequest();xhr.open('POST',_ajax);
  xhr.onload=function(){
    var res;try{res=JSON.parse(xhr.responseText);}catch(e){btn.disabled=false;btn.textContent=" . wp_json_encode(esc_html($bkBtnText)) . ";ibkToast('Connection error');return;}
    if(!res.success){btn.disabled=false;btn.textContent=" . wp_json_encode(esc_html($bkBtnText)) . ";ibkToast(res.data&&res.data.message?res.data.message:'Payment error.');return;}
    if(!res.data.deposit||res.data.deposit<=0){_doBook('');return;}
    _piId=res.data.payment_intent_id;_payLabel=_cur+Number(res.data.deposit).toFixed(2);
    try{_stripe=Stripe(res.data.pk);_elements=_stripe.elements({clientSecret:res.data.client_secret});_elements.create('payment').mount(\$('.ibk-pay-element'));}catch(e){_doBook('');return;}
    \$('.ibk-pay-label').textContent='A '+_payLabel+' deposit holds your appointment.';
    \$('.ibk-pay-wrap').style.display='block';
    _payReady=true;btn.disabled=false;btn.textContent='Pay '+_payLabel+' & Book';
  };
  xhr.send(fd);
});
if(svcEl.tagName!=='SELECT'&&_state.svcId){if(_isSalon)loadStaff();renderCalendar();}
else if(svcEl.tagName==='SELECT'&&svcEl.value)onSvcChange();
else renderCalendar();
})();";
    $_inlineJs = ob_get_clean();
    wp_add_inline_script('instinctor-frontend-js', $_inlineJs, 'after');

    return (string) ob_get_clean();
}
