// BookUno Web — Storefront page (public business) with side booking widget

const DAY_NAMES = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
const { useState, useEffect, useRef } = React;

// Lazy-load Stripe.js only when a customer actually reaches the payment step.
let _stripeJsPromise = null;
function loadStripeJs() {
  if (typeof window !== 'undefined' && window.Stripe) return Promise.resolve(window.Stripe);
  if (_stripeJsPromise) return _stripeJsPromise;
  _stripeJsPromise = new Promise((resolve, reject) => {
    const s = document.createElement('script');
    s.src = 'https://js.stripe.com/v3/';
    s.async = true;
    s.onload = () => window.Stripe ? resolve(window.Stripe) : reject(new Error('Stripe.js failed to initialise.'));
    s.onerror = () => { _stripeJsPromise = null; reject(new Error('Could not load the secure payment form.')); };
    document.head.appendChild(s);
  });
  return _stripeJsPromise;
}

// Lazy-load Apple MapKit JS, gated on a successful token pre-flight so that if
// the mapkit-token edge function isn't deployed/configured yet the caller can
// fall back to a plain map embed. The private MapKit key never reaches the
// browser — the edge function mints a short-lived token server-side.
let _mapkitPromise = null;
function loadMapKitScript() {
  return new Promise((resolve, reject) => {
    if (window.mapkit && window.mapkit.maps) { resolve(); return; }
    const existing = document.getElementById('mapkit-js');
    if (existing) {
      existing.addEventListener('__mk_loaded', () => resolve(), { once: true });
      existing.addEventListener('error', () => reject(new Error('Apple Maps failed to load')), { once: true });
      return;
    }
    const s = document.createElement('script');
    s.id = 'mapkit-js';
    s.src = 'https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.core.js';
    s.crossOrigin = 'anonymous';
    s.async = true;
    s.setAttribute('data-callback', '__bookunoMapKitOnLoad');
    s.setAttribute('data-libraries', 'map,annotations,services');
    window.__bookunoMapKitOnLoad = () => { try { s.dispatchEvent(new Event('__mk_loaded')); } catch (_) {} resolve(); };
    s.onerror = () => reject(new Error('Could not load Apple Maps.'));
    document.head.appendChild(s);
  });
}
function loadMapKit() {
  if (typeof window === 'undefined') return Promise.reject(new Error('no window'));
  if (window.__bookunoMapKitReady && window.mapkit) return Promise.resolve(window.mapkit);
  if (_mapkitPromise) return _mapkitPromise;
  _mapkitPromise = (async () => {
    const cfg = window.BOOKUNO_CONFIG || {};
    const base = (cfg.supabaseUrl || '').replace(/\/$/, '');
    if (!base) throw new Error('Maps are not configured.');
    const tokenUrl = `${base}/functions/v1/mapkit-token`;
    const fetchToken = () => fetch(tokenUrl, { headers: cfg.supabaseAnonKey ? { apikey: cfg.supabaseAnonKey } : {} })
      .then(r => (r.ok ? r.json() : Promise.reject(new Error('token http ' + r.status))))
      .then(d => { if (d && d.token) return d.token; throw new Error('no token'); });
    // Pre-flight: surfaces 503 (unconfigured) / network errors before we bother
    // loading the SDK, so BusinessMap can fall back cleanly.
    const firstToken = await fetchToken();
    await loadMapKitScript();
    let primed = false;
    window.mapkit.init({
      authorizationCallback: (done) => {
        if (!primed) { primed = true; done(firstToken); return; }
        fetchToken().then(done).catch(() => { try { done(''); } catch (_) {} });
      },
    });
    window.__bookunoMapKitReady = true;
    return window.mapkit;
  })().catch((e) => { _mapkitPromise = null; throw e; });
  return _mapkitPromise;
}

function fmtMoney(cents) {
  const n = (Number(cents) || 0) / 100;
  return `£${n % 1 === 0 ? n : n.toFixed(2)}`;
}

// Stripe appends ?payment_intent_client_secret=... to return_url on redirect-based
// methods (3DS bank pages, wallets). With a hash router those params can land inside
// the fragment, so check both the query string and the hash.
function readReturnParams() {
  const out = {};
  const grab = (qs) => { try { new URLSearchParams(qs).forEach((v, k) => { out[k] = v; }); } catch {} };
  grab(window.location.search.replace(/^\?/, ''));
  const hash = window.location.hash || '';
  const qIdx = hash.indexOf('?');
  if (qIdx >= 0) grab(hash.slice(qIdx + 1));
  return out;
}

function fmtAddr(business) {
  const a = business?.address;
  const street = typeof a === 'object' && a
    ? (a.formatted || [a.line1, a.city, a.postcode].filter(Boolean).join(', '))
    : (typeof a === 'string' ? a : '');
  const city = typeof a === 'object' ? '' : (business?.city || '');
  return [street, city].filter(Boolean).join(', ');
}

function PageStorefront({ logoVariant = 'default' }) {
  const signedIn = (() => {
    try {
      return Boolean(JSON.parse(window.localStorage.getItem('bookuno.local_test_session') || 'null')?.user?.email);
    } catch { return false; }
  })();

  const getSlug = () => {
    try {
      const hash = window.location.hash || '';
      // Support #/b/slug, #/booking/slug, #/book/slug, #/storefront/slug
      const m = hash.match(/^#\/?(?:b|booking|book|storefront)\/([^/?#]+)/);
      if (m) return m[1];
      // Path-based storefront URLs (/b/<slug> rewritten to index.html) carry
      // the slug in the pathname instead of the hash.
      const p = window.location.pathname.match(/^\/(?:b|booking|book|storefront)\/([^/?#]+)/);
      return p ? p[1] : '';
    } catch { return ''; }
  };

  const [business, setBusiness] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  // Booking widget state
  const [selectedService, setSelectedService] = useState(null);
  const [bookingOpen, setBookingOpen] = useState(false);
  const getTomorrow = () => {
    const d = new Date(); d.setDate(d.getDate() + 1);
    return d.toISOString().slice(0, 10);
  };
  const getSessionPrefill = () => {
    try {
      const s = JSON.parse(window.localStorage.getItem('bookuno.local_test_session') || 'null');
      return { name: s?.user?.user_metadata?.full_name || '', email: s?.user?.email || '' };
    } catch { return {}; }
  };
  const [form, setForm] = useState(() => { const p = getSessionPrefill(); return { name: p.name || '', email: p.email || '', phone: '', date: getTomorrow(), time: '09:00', notes: '' }; });
  const [booking, setBooking] = useState({ submitting: false, phase: 'form', err: null });
  const [fieldErrors, setFieldErrors] = useState({});
  const [slots, setSlots] = useState(null); // null = not loaded / unavailable → fall back to free time input
  const [slotsLoading, setSlotsLoading] = useState(false);

  useEffect(() => {
    const slug = getSlug();
    if (!slug) { setError('No business slug in URL'); setLoading(false); return; }
    const api = window.BOOKUNO_API;
    if (!api || typeof api.getPublicBusiness !== 'function') {
      setError('API not available'); setLoading(false); return;
    }
    api.getPublicBusiness(slug)
      .then(data => { setBusiness(data); setLoading(false); })
      .catch(err => { setError(err?.message || 'Business not found'); setLoading(false); });
  }, []);

  // Restore the confirmation if the customer returns from a redirect-based payment (3DS/wallet).
  useEffect(() => {
    const cs = readReturnParams().payment_intent_client_secret;
    if (!cs) return;
    let stash = {};
    try { stash = JSON.parse(sessionStorage.getItem('bookuno.pending_payment') || '{}'); } catch {}
    let cancelled = false;
    (async () => {
      try {
        const pk = window.BOOKUNO_CONFIG?.stripePublishableKey;
        if (!pk) return;
        const StripeCtor = await loadStripeJs();
        if (cancelled) return;
        const stripe = StripeCtor(pk, stash.stripeAccount ? { stripeAccount: stash.stripeAccount } : undefined);
        const { paymentIntent } = await stripe.retrievePaymentIntent(cs);
        if (cancelled || !paymentIntent) return;
        if (paymentIntent.status === 'succeeded' || paymentIntent.status === 'processing') {
          if (stash.service) setSelectedService(stash.service);
          if (stash.form) setForm(f => ({ ...f, ...stash.form }));
          setBookingOpen(true);
          setBooking({ submitting: false, phase: 'done', err: null, paid: true, bookingId: stash.bookingId || null });
          const api = window.BOOKUNO_API;
          if (stash.bookingId && api && typeof api.sendBookingConfirmation === 'function') api.sendBookingConfirmation(stash.bookingId).catch(() => {});
        }
        try { sessionStorage.removeItem('bookuno.pending_payment'); } catch {}
      } catch {}
    })();
    return () => { cancelled = true; };
  }, []);

  // Default the booking date to the next OPEN day (per opening_hours) instead of
  // blindly tomorrow — otherwise a shop closed tomorrow greets the customer with
  // "no free slots". Skips the 3DS-return path, which restores its own date.
  useEffect(() => {
    if (!business) return;
    if (readReturnParams().payment_intent_client_secret) return;
    const hrs = business.opening_hours || [];
    const openDow = new Set(hrs.filter(h => !h.is_closed).map(h => h.day_of_week));
    if (!openDow.size) return; // no hours info → keep the tomorrow default
    const base = new Date();
    for (let i = 1; i <= 14; i++) {
      const cand = new Date(base);
      cand.setDate(base.getDate() + i);
      const dow = (cand.getDay() + 6) % 7; // JS 0=Sun..6=Sat → bookuno 0=Mon..6=Sun
      if (openDow.has(dow)) { setForm(f => ({ ...f, date: cand.toISOString().slice(0, 10) })); break; }
    }
  }, [business && business.id]);

  // F9: fetch real free slots for the chosen service + date so the customer can
  // only pick times the business is open and free. The server re-validates, so a
  // failed fetch safely falls back to a free time input.
  useEffect(() => {
    const svcId = selectedService?.id;
    if (!bookingOpen || !svcId || !form.date || !business?.id) { setSlots(null); return; }
    const api = window.BOOKUNO_API;
    if (!api || typeof api.getAvailability !== 'function') { setSlots(null); return; }
    let cancelled = false;
    setSlotsLoading(true);
    api.getAvailability(svcId, form.date, business.id)
      .then(list => {
        if (cancelled) return;
        const arr = Array.isArray(list) ? list : [];
        setSlots(arr);
        setForm(f => (arr.length && !arr.includes(f.time)) ? { ...f, time: arr[0] } : f);
      })
      .catch(() => { if (!cancelled) setSlots(null); })
      .finally(() => { if (!cancelled) setSlotsLoading(false); });
    return () => { cancelled = true; };
  }, [selectedService?.id, form.date, bookingOpen, business?.id]);

  const handleBook = (svc) => {
    setSelectedService(svc);
    setBookingOpen(true);
    setBooking({ submitting: false, phase: 'form', err: null });
    setTimeout(() => {
      const el = document.getElementById('sf-booking-form');
      if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
    }, 50);
  };

  const submitBooking = async (e) => {
    e.preventDefault();
    const errs = {};
    if (!form.name.trim()) errs.name = 'Name is required';
    if (!form.email.trim()) errs.email = 'Email is required';
    else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)) errs.email = 'Please enter a valid email address';
    if (!form.date) errs.date = 'Please select a date';
    else {
      const today = new Date(); today.setHours(0,0,0,0);
      if (new Date(form.date) < today) errs.date = 'Please select today or a future date';
    }
    if (!form.time) errs.time = 'Please select a time';
    if (Object.keys(errs).length > 0) {
      setFieldErrors(errs);
      // Scroll to first error
      setTimeout(() => {
        const el = document.getElementById('sf-booking-form');
        if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
      }, 50);
      return;
    }
    setFieldErrors({});
    setBooking({ submitting: true, phase: 'form', err: null });
    try {
      const api = window.BOOKUNO_API;
      if (!api || typeof api.createPublicBookingPayment !== 'function') {
        throw new Error('Booking API not available. Please try again later.');
      }
      const selectedServiceObj = (business.services || []).find(s => s.id === selectedService?.id) || selectedService;
      // The amount charged is derived server-side from the service price + the
      // business's payment policy — never sent from here (would be untrusted).
      const result = await api.createPublicBookingPayment({
        business_id: business.id,
        service_id: selectedService?.id,
        date: form.date,
        time: form.time,
        duration_minutes: selectedServiceObj?.duration_minutes || 60,
        notes: form.notes,
        customer: { name: form.name, email: form.email, phone: form.phone },
        status: 'pending',
        currency: 'gbp',
      });
      const bookingId = result?.booking?.id || null;
      if (!bookingId) throw new Error('Booking could not be completed. Please try again.');
      if (result.requiresOnlinePayment && result.clientSecret) {
        // Stash what's needed to restore the confirmation after a redirect-based 3DS return.
        try {
          sessionStorage.setItem('bookuno.pending_payment', JSON.stringify({
            bookingId,
            stripeAccount: result.stripeAccount || null,
            form,
            service: selectedServiceObj ? { name: selectedServiceObj.name, duration_minutes: selectedServiceObj.duration_minutes, price_cents: selectedServiceObj.price_cents } : null,
          }));
        } catch {}
        setBooking({
          submitting: false, phase: 'pay', err: null, bookingId,
          payment: {
            clientSecret: result.clientSecret,
            stripeAccount: result.stripeAccount || null,
            chargeCents: result.chargeCents || 0,
            currency: result.currency || 'gbp',
          },
        });
      } else {
        if (typeof api.sendBookingConfirmation === 'function') api.sendBookingConfirmation(bookingId).catch(() => {});
        setBooking({ submitting: false, phase: 'done', err: null, paid: false, bookingId });
      }
    } catch (err) {
      setBooking({ submitting: false, phase: 'form', err: err?.message || 'Booking failed. Please try again.' });
    }
  };

  const handlePaymentSuccess = () => {
    const api = window.BOOKUNO_API;
    const id = booking.bookingId;
    if (id && api && typeof api.sendBookingConfirmation === 'function') api.sendBookingConfirmation(id).catch(() => {});
    try { sessionStorage.removeItem('bookuno.pending_payment'); } catch {}
    setBooking(b => ({ ...b, phase: 'done', err: null, paid: true }));
  };

  if (loading) {
    return (
      <div className="web">
        <TopNav active="discover" logoVariant={logoVariant} loggedIn={signedIn}/>
        <div style={{ padding: '120px 0', textAlign: 'center', color: 'var(--muted)' }}>
          <div style={{ fontSize: 36, marginBottom: 12 }}>⏳</div>
          <div style={{ fontWeight: 700 }}>Loading…</div>
        </div>
        <Footer logoVariant={logoVariant}/>
      </div>
    );
  }

  if (error || !business) {
    return (
      <div className="web">
        <TopNav active="discover" logoVariant={logoVariant} loggedIn={signedIn}/>
        <div style={{ padding: '120px 0', textAlign: 'center', color: 'var(--muted)' }}>
          <div style={{ fontSize: 36, marginBottom: 12 }}>😕</div>
          <div style={{ fontWeight: 800, fontSize: 20, color: 'var(--ink)', marginBottom: 8 }}>Business not found</div>
          <p style={{ marginBottom: 20 }}>{error || 'This page may have moved.'}</p>
          <a href="/app.html#/search" className="btn primary sm">← Back to search</a>
        </div>
        <Footer logoVariant={logoVariant}/>
      </div>
    );
  }

  const services = business.services || [];
  const canPayOnline = Boolean(business.stripe_account_id && business.stripe_charges_enabled);
  const hours = business.opening_hours || [];
  const address = fmtAddr(business);
  const city = '';
  const mapQuery = encodeURIComponent(address || business.name);
  const minPrice = services.length
    ? Math.min(...services.map(s => s.price_cents || 0)) / 100
    : null;

  // Open/closed status from opening_hours
  const openStatus = (() => {
    if (!hours || hours.length === 0) return null;
    const now = new Date();
    // day_of_week: 0=Mon..6=Sun. JS getDay(): 0=Sun..6=Sat
    const jsDay = now.getDay();
    const dow = jsDay === 0 ? 6 : jsDay - 1;
    const todayRow = hours.find(h => h.day_of_week === dow);
    if (!todayRow || todayRow.is_closed) return { label: 'Closed', ok: false };
    const nowMins = now.getHours() * 60 + now.getMinutes();
    const parseTime = (t) => { const [h, m] = (t || '').split(':').map(Number); return (h || 0) * 60 + (m || 0); };
    const openMins  = parseTime(todayRow.opens_at);
    const closeMins = parseTime(todayRow.closes_at);
    if (nowMins < openMins) return { label: `Opens at ${(todayRow.opens_at || '').slice(0, 5)}`, ok: false };
    if (nowMins >= closeMins) return { label: 'Closed', ok: false };
    return { label: `Open until ${(todayRow.closes_at || '').slice(0, 5)}`, ok: true };
  })();

  return (
    <div className="web">
      <TopNav active="discover" logoVariant={logoVariant} loggedIn={signedIn}/>

      {/* Breadcrumb */}
      <div className="container" style={{ padding: '14px 28px', fontSize: 12.5, color: 'var(--muted)', display: 'flex', alignItems: 'center', gap: 6 }}>
        <a href="/app.html#/search">Discover</a><Ic.chev style={{ width: 11, height: 11 }}/>
        <span style={{ color: 'var(--ink)', fontWeight: 700 }}>{business.name}</span>
      </div>

      {/* Hero banner — category photo (or brand gradient fallback) with the
          business monogram. Replaces the old faux photo-gallery so storefronts
          without real photos never show placeholder tiles or a fake counter. */}
      <section className="container" style={{ paddingBottom: 0 }}>
        <Cover seed={0} h={280} r={22} photo={(categoryPhoto(business.type) || '').replace('w=800&h=1066', 'w=1600&h=600') || null} style={{ position: 'relative', alignItems: 'flex-end' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
            <div style={{ width: 56, height: 56, borderRadius: 16, background: business.brand_color || 'var(--pri)', color: '#fff', display: 'grid', placeItems: 'center', fontWeight: 800, fontSize: 24, boxShadow: '0 4px 16px rgba(0,0,0,0.25)', overflow: 'hidden', flexShrink: 0 }}>
              {business.logo_url
                ? <img src={business.logo_url} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }}/>
                : (business.name || '?').slice(0, 1).toUpperCase()}
            </div>
            <div style={{ color: '#fff', fontWeight: 800, fontSize: 18, letterSpacing: -0.3, textShadow: '0 1px 8px rgba(0,0,0,0.3)' }}>{business.name}</div>
          </div>
        </Cover>
      </section>

      {/* Title strip */}
      <section className="container" style={{ paddingTop: 28, paddingBottom: 8 }}>
        <div style={{ display: 'flex', alignItems: 'flex-start', gap: 28, flexWrap: 'wrap' }}>
          <div style={{ flex: 1, minWidth: 280 }}>
            <h1 style={{ fontSize: 'clamp(28px, 4cqi, 40px)', fontWeight: 800, letterSpacing: -0.03, lineHeight: 1.05 }}>{business.name}</h1>
            <div style={{ display: 'flex', alignItems: 'center', gap: 18, marginTop: 10, fontSize: 14, flexWrap: 'wrap' }}>
              {business.type && (
                <span className="badge" style={{ background: 'var(--chip)', color: 'var(--ink)', fontSize: 12 }}>{business.type.replace(/_/g, ' ')}</span>
              )}
              {(address || city) && (
                <>
                  <span style={{ color: 'var(--muted)' }}>·</span>
                  <div style={{ display: 'inline-flex', alignItems: 'center', gap: 6, color: 'var(--muted)' }}>
                    <Ic.pin style={{ width: 14, height: 14 }}/>{[address, city].filter(Boolean).join(', ')}
                  </div>
                </>
              )}
              {business.phone && (
                <>
                  <span style={{ color: 'var(--muted)' }}>·</span>
                  <a href={`tel:${business.phone}`} style={{ display: 'inline-flex', alignItems: 'center', gap: 6, color: 'var(--muted)' }}>
                    <Ic.phone style={{ width: 13, height: 13 }}/>{business.phone}
                  </a>
                </>
              )}
              {openStatus && (
                <>
                  <span style={{ color: 'var(--muted)' }}>·</span>
                  <span style={{ fontSize: 13, fontWeight: 700, color: openStatus.ok ? 'var(--green, #16a34a)' : 'var(--muted)' }}>{openStatus.label}</span>
                </>
              )}
            </div>
          </div>
          <div style={{ display: 'flex', gap: 8 }}>
            <button className="icon-btn" aria-label="Share storefront"><Ic.share style={{ width: 18, height: 18 }}/></button>
            <button className="icon-btn" aria-label="Save storefront"><Ic.heart style={{ width: 18, height: 18 }}/></button>
          </div>
        </div>
      </section>

      {/* Two-col body */}
      <section className="container col2" style={{ paddingTop: 28, paddingBottom: 64 }}>
        <div>
          {/* Tabs */}
          <div style={{ display: 'flex', gap: 0, borderBottom: '1px solid var(--line)', marginBottom: 24, overflowX: 'auto' }}>
            {['Overview', 'Services', 'Hours', 'Location'].map((t, i) => (
              <button key={t} style={{ flex: 1, minWidth: 0, padding: '14px 10px', fontSize: 14, fontWeight: 700, color: i === 0 ? 'var(--pri)' : 'var(--muted)', borderBottom: i === 0 ? '2px solid var(--pri)' : '2px solid transparent', marginBottom: -1, whiteSpace: 'nowrap', textAlign: 'center' }}>{t}</button>
            ))}
          </div>

          {/* About */}
          <Rv>
            <div style={{ marginBottom: 36 }}>
              <h3 style={{ fontSize: 22, fontWeight: 800, letterSpacing: -0.02, marginBottom: 12 }}>About</h3>
              <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: 10, marginTop: 8 }}>
                {business.email && (
                  <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: 12, borderRadius: 12, background: 'var(--bg)' }}>
                    <Ic.msg style={{ width: 18, height: 18, color: 'var(--pri)' }}/>
                    <a href={`mailto:${business.email}`} style={{ fontSize: 13, fontWeight: 600, color: 'var(--ink)' }}>{business.email}</a>
                  </div>
                )}
                {business.phone && (
                  <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: 12, borderRadius: 12, background: 'var(--bg)' }}>
                    <Ic.phone style={{ width: 18, height: 18, color: 'var(--pri)' }}/>
                    <a href={`tel:${business.phone}`} style={{ fontSize: 13, fontWeight: 600, color: 'var(--ink)' }}>{business.phone}</a>
                  </div>
                )}
                <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: 12, borderRadius: 12, background: 'var(--bg)' }}>
                  <Ic.zap style={{ width: 18, height: 18, color: 'var(--pri)' }}/>
                  <span style={{ fontSize: 13, fontWeight: 600 }}>Online booking</span>
                </div>
              </div>
            </div>
          </Rv>

          {/* Services */}
          {services.length > 0 && (
            <Rv>
              <div id="sf-services" style={{ marginBottom: 36, scrollMarginTop: '84px' }}>
                <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 14 }}>
                  <h3 style={{ fontSize: 22, fontWeight: 800, letterSpacing: -0.02 }}>Services</h3>
                  <div style={{ fontSize: 13, color: 'var(--muted)' }}>{services.length} available</div>
                </div>
                <div style={{ display: 'grid', gap: 10 }}>
                  {services.map((s, i) => (
                    <div key={s.id || i} className="card storefront-service-card" style={{ display: 'flex', alignItems: 'center', gap: 16, padding: 16 }}>
                      <div style={{ flex: 1, minWidth: 0 }}>
                        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                          <div style={{ fontWeight: 800, fontSize: 15 }}>{s.name}</div>
                        </div>
                        {s.description && <div style={{ fontSize: 13, color: 'var(--muted)', marginTop: 3 }}>{s.description}</div>}
                        {s.duration_minutes && (
                          <div style={{ fontSize: 12, color: 'var(--muted-2)', marginTop: 4, display: 'flex', alignItems: 'center', gap: 4 }}>
                            <Ic.clock style={{ width: 11, height: 11 }}/>{s.duration_minutes} min
                          </div>
                        )}
                      </div>
                      {s.price_cents != null && (
                        <div className="storefront-service-price" style={{ fontWeight: 800, fontSize: 17 }}>
                          £{(s.price_cents / 100).toFixed(s.price_cents % 100 === 0 ? 0 : 2)}
                        </div>
                      )}
                      <button className="btn primary sm storefront-service-book" onClick={() => handleBook(s)}>Book</button>
                    </div>
                  ))}
                </div>
              </div>
            </Rv>
          )}

          {/* Inline booking form */}
          {bookingOpen && (
            <Rv>
              <div id="sf-booking-form" className="card" style={{ padding: 24, marginBottom: 36, borderRadius: 20 }}>
                {booking.phase === 'done' ? (
                  <BookingSuccess
                    business={business}
                    selectedService={selectedService}
                    form={form}
                    bookingId={booking.bookingId}
                    paid={booking.paid}
                  />
                ) : booking.phase === 'pay' ? (
                  <PaymentStep
                    payment={booking.payment}
                    bookingId={booking.bookingId}
                    chargeError={booking.err}
                    onSuccess={handlePaymentSuccess}
                    businessName={business.name}
                    serviceName={selectedService?.name}
                  />
                ) : (
                  <form onSubmit={submitBooking}>
                    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
                      <h3 style={{ fontSize: 18, fontWeight: 800, margin: 0 }}>
                        Book: {selectedService?.name}
                        {selectedService?.price_cents != null && <span style={{ fontWeight: 500, fontSize: 15, marginLeft: 8, color: 'var(--muted)' }}>£{(selectedService.price_cents / 100).toFixed(selectedService.price_cents % 100 === 0 ? 0 : 2)}</span>}
                      </h3>
                      <button type="button" className="icon-btn" aria-label="Close booking form" onClick={() => setBookingOpen(false)}><Ic.close style={{ width: 16, height: 16 }}/></button>
                    </div>
                    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: 12, marginBottom: 12 }}>
                      <div>
                        <label style={{ fontSize: 11.5, fontWeight: 800, letterSpacing: 0.08, color: 'var(--muted)', textTransform: 'uppercase', display: 'block', marginBottom: 4 }}>Name</label>
                        <input required className="input" style={{ width: '100%', borderColor: fieldErrors.name ? 'var(--red, #E11D48)' : undefined }} value={form.name} onChange={e => { setForm(f => ({ ...f, name: e.target.value })); setFieldErrors(fe => ({ ...fe, name: undefined })); }} placeholder="Your name"/>
                        {fieldErrors.name && <div style={{ fontSize: 12, color: 'var(--red, #E11D48)', marginTop: 4, fontWeight: 600 }}>{fieldErrors.name}</div>}
                      </div>
                      <div>
                        <label style={{ fontSize: 11.5, fontWeight: 800, letterSpacing: 0.08, color: 'var(--muted)', textTransform: 'uppercase', display: 'block', marginBottom: 4 }}>Email</label>
                        <input required type="email" className="input" style={{ width: '100%', borderColor: fieldErrors.email ? 'var(--red, #E11D48)' : undefined }} value={form.email} onChange={e => { setForm(f => ({ ...f, email: e.target.value })); setFieldErrors(fe => ({ ...fe, email: undefined })); }} placeholder="your@email.com"/>
                        {fieldErrors.email && <div style={{ fontSize: 12, color: 'var(--red, #E11D48)', marginTop: 4, fontWeight: 600 }}>{fieldErrors.email}</div>}
                      </div>
                      <div>
                        <label style={{ fontSize: 11.5, fontWeight: 800, letterSpacing: 0.08, color: 'var(--muted)', textTransform: 'uppercase', display: 'block', marginBottom: 4 }}>Phone</label>
                        <input className="input" style={{ width: '100%' }} value={form.phone} onChange={e => setForm(f => ({ ...f, phone: e.target.value }))} placeholder="Optional"/>
                      </div>
                      <div>
                        <label style={{ fontSize: 11.5, fontWeight: 800, letterSpacing: 0.08, color: 'var(--muted)', textTransform: 'uppercase', display: 'block', marginBottom: 4 }}>Date</label>
                        <input required type="date" className="input" style={{ width: '100%', borderColor: fieldErrors.date ? 'var(--red, #E11D48)' : undefined }} value={form.date} onChange={e => { setForm(f => ({ ...f, date: e.target.value })); setFieldErrors(fe => ({ ...fe, date: undefined })); }}/>
                        {fieldErrors.date && <div style={{ fontSize: 12, color: 'var(--red, #E11D48)', marginTop: 4, fontWeight: 600 }}>{fieldErrors.date}</div>}
                      </div>
                      <div>
                        <label style={{ fontSize: 11.5, fontWeight: 800, letterSpacing: 0.08, color: 'var(--muted)', textTransform: 'uppercase', display: 'block', marginBottom: 4 }}>Time</label>
                        {slotsLoading ? (
                          <div className="input" style={{ width: '100%', color: 'var(--muted)', display: 'flex', alignItems: 'center' }}>Checking availability…</div>
                        ) : Array.isArray(slots) ? (
                          slots.length ? (
                            <select required className="input" style={{ width: '100%', borderColor: fieldErrors.time ? 'var(--red, #E11D48)' : undefined }} value={form.time} onChange={e => { setForm(f => ({ ...f, time: e.target.value })); setFieldErrors(fe => ({ ...fe, time: undefined })); }}>
                              {slots.map(s => <option key={s} value={s}>{s}</option>)}
                            </select>
                          ) : (
                            <div className="input" style={{ width: '100%', color: 'var(--red, #E11D48)', display: 'flex', alignItems: 'center', fontWeight: 600 }}>No free slots — try another date</div>
                          )
                        ) : (
                          <input required type="time" className="input" style={{ width: '100%', borderColor: fieldErrors.time ? 'var(--red, #E11D48)' : undefined }} value={form.time} onChange={e => { setForm(f => ({ ...f, time: e.target.value })); setFieldErrors(fe => ({ ...fe, time: undefined })); }}/>
                        )}
                        {fieldErrors.time && <div style={{ fontSize: 12, color: 'var(--red, #E11D48)', marginTop: 4, fontWeight: 600 }}>{fieldErrors.time}</div>}
                      </div>
                    </div>
                    <div style={{ marginBottom: 16 }}>
                      <label style={{ fontSize: 11.5, fontWeight: 800, letterSpacing: 0.08, color: 'var(--muted)', textTransform: 'uppercase', display: 'block', marginBottom: 4 }}>Notes</label>
                      <textarea className="input" style={{ width: '100%', height: 64, resize: 'vertical' }} value={form.notes} onChange={e => setForm(f => ({ ...f, notes: e.target.value }))} placeholder="Anything the business should know?"/>
                    </div>
                    {booking.err && <div style={{ color: '#DC2626', fontSize: 13, marginBottom: 12 }}>{booking.err}</div>}
                    <button type="submit" className="btn primary lg block" disabled={booking.submitting || (Array.isArray(slots) && slots.length === 0)}>
                      {booking.submitting ? 'Processing…' : (canPayOnline ? 'Continue to payment' : 'Send booking request')}
                    </button>
                  </form>
                )}
              </div>
            </Rv>
          )}

          {/* Hours + Location */}
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))', gap: 18 }}>
            {hours.length > 0 && (
              <Rv>
                <div className="card" style={{ padding: 18 }}>
                  <h3 style={{ fontSize: 16, fontWeight: 800, marginBottom: 12, display: 'flex', alignItems: 'center', gap: 8 }}>
                    <Ic.clock style={{ width: 18, height: 18, color: 'var(--pri)' }}/>Opening hours
                  </h3>
                  {hours.map((h, i) => {
                    const dayName = DAY_NAMES[h.day_of_week] || `Day ${h.day_of_week}`;
                    const isClosed = h.is_closed;
                    const timeStr = isClosed ? 'Closed' : `${(h.opens_at || '').slice(0,5)} – ${(h.closes_at || '').slice(0,5)}`;
                    return (
                      <div key={i} style={{ display: 'flex', justifyContent: 'space-between', padding: '8px 0', borderBottom: i < hours.length - 1 ? '1px solid var(--line-2)' : 0 }}>
                        <span style={{ fontSize: 13, fontWeight: 600, color: 'var(--muted)' }}>{dayName}</span>
                        <span style={{ fontSize: 13, color: isClosed ? 'var(--muted-2)' : 'var(--ink)' }}>{timeStr}</span>
                      </div>
                    );
                  })}
                </div>
              </Rv>
            )}
            {(address || city) && (
              <Rv delay={80}>
                <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
                  <BusinessMap
                    business={business}
                    fallbackQuery={mapQuery}
                    label={[address, city].filter(Boolean).join(', ')}
                  />
                  <div style={{ padding: 14 }}>
                    <div style={{ fontWeight: 700, fontSize: 14 }}>{[address, city].filter(Boolean).join(', ')}</div>
                    <div style={{ display: 'flex', gap: 8, marginTop: 12 }}>
                      <a href={`https://maps.apple.com/?q=${mapQuery}`} target="_blank" rel="noopener noreferrer" className="btn outline sm" style={{ flex: 1 }}>Directions</a>
                    </div>
                  </div>
                </div>
              </Rv>
            )}
          </div>
        </div>

        {/* Sticky booking widget */}
        <aside>
          <div className="aside-sticky">
            <div className="card" style={{ padding: 20, boxShadow: 'var(--sh-3)', borderRadius: 22 }}>
              <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between' }}>
                <div>
                  <div style={{ fontSize: 12, color: 'var(--muted)', fontWeight: 700 }}>FROM</div>
                  <div style={{ fontSize: 30, fontWeight: 800, letterSpacing: -0.025, lineHeight: 1 }}>
                    {minPrice != null ? `£${minPrice % 1 === 0 ? minPrice : minPrice.toFixed(2)}` : 'Contact'}
                  </div>
                </div>
                <span className="badge ok dot">Booking now</span>
              </div>

              {services.length > 0 ? (
                <div style={{ marginTop: 18 }}>
                  <label style={{ fontSize: 11.5, fontWeight: 800, letterSpacing: 0.08, color: 'var(--muted)', textTransform: 'uppercase', display: 'block', marginBottom: 8 }}>Choose a service</label>
                  <div style={{ display: 'grid', gap: 8 }}>
                    {services.map((s, i) => (
                      <button key={s.id || i} onClick={() => handleBook(s)} className="btn outline sm" style={{ justifyContent: 'space-between', textAlign: 'left', display: 'flex', padding: '10px 14px' }}>
                        <span style={{ fontWeight: 700 }}>{s.name}</span>
                        {s.price_cents != null && <span style={{ color: 'var(--muted)', fontWeight: 600 }}>£{(s.price_cents / 100).toFixed(s.price_cents % 100 === 0 ? 0 : 2)}</span>}
                      </button>
                    ))}
                  </div>
                </div>
              ) : (
                <div style={{ marginTop: 18 }}>
                  {business.phone && (
                    <a href={`tel:${business.phone}`} className="btn primary lg block" style={{ marginBottom: 10 }}>
                      <Ic.phone style={{ width: 16, height: 16 }}/> Call to book
                    </a>
                  )}
                  {business.email && (
                    <a href={`mailto:${business.email}`} className="btn outline sm block">
                      <Ic.msg style={{ width: 14, height: 14 }}/> Email us
                    </a>
                  )}
                </div>
              )}

              {business.phone && (
                <div style={{ display: 'flex', gap: 6, marginTop: 12 }}>
                  {business.email && <a href={`mailto:${business.email}`} className="btn outline sm" style={{ flex: 1 }}><Ic.msg style={{ width: 14, height: 14 }}/> Email</a>}
                  <a href={`tel:${business.phone}`} className="btn outline sm" style={{ flex: 1 }}><Ic.phone style={{ width: 14, height: 14 }}/> Call</a>
                </div>
              )}

              <div style={{ marginTop: 16, padding: 12, borderRadius: 10, background: 'var(--pri-50)', display: 'flex', alignItems: 'center', gap: 10 }}>
                <Ic.shield style={{ width: 16, height: 16, color: 'var(--pri)', flexShrink: 0 }}/>
                <div style={{ fontSize: 11.5, color: 'var(--pri-700)' }}>{canPayOnline ? 'Secure card payment by Stripe. Free to cancel up to 24 h before.' : 'No card required — pay in person. Free to cancel up to 24 h before.'}</div>
              </div>
            </div>
          </div>
        </aside>

        {/* Mobile-only sticky book bar — a persistent CTA so customers on a phone
            never have to hunt for how to book. Hidden on desktop (sticky aside
            covers it) and on the confirmation screen. */}
        {booking.phase !== 'done' && (
          <div className="sf-book-bar sf-widget">
            <div>
              <div style={{ fontSize: 10.5, color: 'var(--muted)', fontWeight: 800, letterSpacing: 0.06 }}>{minPrice != null ? 'FROM' : ''}</div>
              <div style={{ fontSize: 20, fontWeight: 800, lineHeight: 1 }}>
                {minPrice != null ? `£${minPrice % 1 === 0 ? minPrice : minPrice.toFixed(2)}` : (canPayOnline ? 'Book now' : 'Enquire')}
              </div>
            </div>
            {services.length > 0 ? (
              <button className="btn primary lg" style={{ flex: 1, maxWidth: 220, justifyContent: 'center' }}
                onClick={() => {
                  if (services.length === 1) { handleBook(services[0]); return; }
                  const el = document.getElementById(bookingOpen ? 'sf-booking-form' : 'sf-services');
                  if (el) el.scrollIntoView({ block: 'start' });
                }}>
                {bookingOpen ? 'Finish booking' : 'Book now'}
              </button>
            ) : business.phone ? (
              <a href={`tel:${business.phone}`} className="btn primary lg" style={{ flex: 1, maxWidth: 220, justifyContent: 'center' }}>Call to book</a>
            ) : business.email ? (
              <a href={`mailto:${business.email}`} className="btn primary lg" style={{ flex: 1, maxWidth: 220, justifyContent: 'center' }}>Enquire</a>
            ) : null}
          </div>
        )}
        <style>{`
          .sf-book-bar{ display:none }
          @media (max-width: 900px){
            .sf-book-bar{
              position:fixed; left:0; right:0; bottom:0; z-index:60;
              display:flex; align-items:center; justify-content:space-between; gap:14px;
              padding:11px 16px calc(11px + env(safe-area-inset-bottom, 0px));
              background:rgba(255,255,255,0.94); backdrop-filter:blur(14px); -webkit-backdrop-filter:blur(14px);
              border-top:1px solid var(--line); box-shadow:0 -6px 24px rgba(20,18,40,0.10);
            }
            .web .footer, .web footer{ padding-bottom:84px }
          }
        `}</style>
      </section>

      <Footer logoVariant={logoVariant}/>
    </div>
  );
}

// Apple Maps location card. Renders an interactive MapKit map with the
// business pinned by a custom uploaded icon (map_settings.markerIconUrl) or a
// brand-coloured marker. If MapKit can't load (token endpoint not deployed yet,
// offline, etc.) it falls back to a tappable location card that opens the
// address in Apple Maps, so the storefront never loses its map.
function BusinessMap({ business, fallbackQuery, label }) {
  const elRef = useRef(null);
  const [mode, setMode] = useState('loading'); // loading | apple | fallback

  useEffect(() => {
    let cancelled = false;
    let map = null;
    (async () => {
      try {
        const mapkit = await loadMapKit();
        if (cancelled || !elRef.current) return;
        const ms = (business && business.map_settings) || {};
        const colorScheme = ms.colorScheme === 'dark'
          ? mapkit.Map.ColorSchemes.Dark
          : mapkit.Map.ColorSchemes.Light;
        const mapType = ({
          standard: mapkit.Map.MapTypes.Standard,
          muted: mapkit.Map.MapTypes.MutedStandard,
          hybrid: mapkit.Map.MapTypes.Hybrid,
          satellite: mapkit.Map.MapTypes.Satellite,
        })[ms.mapType] || mapkit.Map.MapTypes.Standard;

        map = new mapkit.Map(elRef.current, {
          colorScheme,
          mapType,
          showsMapTypeControl: false,
          showsCompass: mapkit.FeatureVisibility.Hidden,
          isRotationEnabled: false,
        });
        if (cancelled) { try { map.destroy(); } catch (_) {} return; }
        setMode('apple');

        const query = label || (business && business.name) || '';
        if (!query) return;
        const geocoder = new mapkit.Geocoder({ language: 'en-GB', getsUserLocation: false });
        geocoder.lookup(query, (err, data) => {
          if (cancelled || !map) return;
          const first = data && data.results && data.results[0];
          if (!first || !first.coordinate) return;
          const coord = first.coordinate;
          map.region = new mapkit.CoordinateRegion(coord, new mapkit.CoordinateSpan(0.012, 0.012));
          const name = (business && business.name) || '';
          const annotation = ms.markerIconUrl
            ? new mapkit.ImageAnnotation(coord, {
                title: name,
                url: { 1: ms.markerIconUrl, 2: ms.markerIconUrl },
              })
            : new mapkit.MarkerAnnotation(coord, {
                title: name,
                color: (business && business.brand_color) || '#6B57FF',
                glyphText: (name || '?').slice(0, 1).toUpperCase(),
              });
          map.addAnnotation(annotation);
        });
      } catch (e) {
        if (!cancelled) setMode('fallback');
      }
    })();
    return () => { cancelled = true; try { if (map) map.destroy(); } catch (_) {} };
  }, [business && business.id]);

  if (mode === 'fallback') {
    // No embedded map without MapKit credentials — show a tappable location
    // card that opens the address in Apple Maps instead.
    return (
      <a
        href={`https://maps.apple.com/?q=${fallbackQuery}`}
        target="_blank"
        rel="noopener noreferrer"
        aria-label={`Open ${label || 'business location'} in Apple Maps`}
        style={{ position: 'relative', height: 280, background: 'var(--pri-50)', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 10, padding: 20, textAlign: 'center', textDecoration: 'none' }}
      >
        <div style={{ width: 48, height: 48, borderRadius: 16, background: '#fff', color: 'var(--pri)', display: 'grid', placeItems: 'center', boxShadow: 'var(--sh-1, 0 2px 8px rgba(0,0,0,0.08))' }}>
          <Ic.pin style={{ width: 22, height: 22 }}/>
        </div>
        <div style={{ fontWeight: 700, fontSize: 13.5, color: 'var(--pri-700)', maxWidth: 260 }}>{label || 'Business location'}</div>
        <span className="btn outline sm" style={{ pointerEvents: 'none' }}>Open in Apple Maps</span>
      </a>
    );
  }
  return (
    <div style={{ position: 'relative', height: 280 }}>
      <div ref={elRef} style={{ width: '100%', height: '100%' }} />
      {mode === 'loading' && (
        <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--muted)', fontSize: 13, background: 'var(--bg)' }}>
          Loading map…
        </div>
      )}
    </div>
  );
}

function PaymentStep({ payment, chargeError, onSuccess, serviceName }) {
  const elRef = useRef(null);
  const stripeRef = useRef(null);
  const elementsRef = useRef(null);
  const [status, setStatus] = useState('loading'); // loading | ready | paying | error
  const [errMsg, setErrMsg] = useState(chargeError || null);

  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const pk = window.BOOKUNO_CONFIG?.stripePublishableKey;
        if (!pk) throw new Error('Online payment is not configured for this business.');
        if (!payment?.clientSecret) throw new Error('Payment could not be initialised. Please try again.');
        const StripeCtor = await loadStripeJs();
        if (cancelled) return;
        const stripe = StripeCtor(pk, payment.stripeAccount ? { stripeAccount: payment.stripeAccount } : undefined);
        stripeRef.current = stripe;
        const elements = stripe.elements({ clientSecret: payment.clientSecret });
        elementsRef.current = elements;
        const paymentElement = elements.create('payment');
        paymentElement.mount(elRef.current);
        paymentElement.on('ready', () => { if (!cancelled) setStatus('ready'); });
      } catch (e) {
        if (!cancelled) { setStatus('error'); setErrMsg(e?.message || 'Could not load the payment form.'); }
      }
    })();
    return () => { cancelled = true; };
  }, []);

  const pay = async (e) => {
    e.preventDefault();
    const stripe = stripeRef.current, elements = elementsRef.current;
    if (!stripe || !elements) return;
    setStatus('paying'); setErrMsg(null);
    const returnUrl = `${window.location.origin}${window.location.pathname}${window.location.hash}`;
    let result;
    try {
      result = await stripe.confirmPayment({ elements, confirmParams: { return_url: returnUrl }, redirect: 'if_required' });
    } catch {
      setStatus('ready'); setErrMsg('Payment failed. Please try again.'); return;
    }
    const { error, paymentIntent } = result || {};
    if (error) { setStatus('ready'); setErrMsg(error.message || 'Payment failed. Please check your card details and try again.'); return; }
    if (paymentIntent && (paymentIntent.status === 'succeeded' || paymentIntent.status === 'processing')) { onSuccess(); return; }
    setStatus('ready'); setErrMsg('Payment could not be completed. Please try again.');
  };

  const amountStr = fmtMoney(payment?.chargeCents);
  return (
    <form onSubmit={pay}>
      <div style={{ marginBottom: 16 }}>
        <h3 style={{ fontSize: 18, fontWeight: 800, margin: 0 }}>Pay {amountStr}</h3>
        {serviceName && <div style={{ fontSize: 13, color: 'var(--muted)', marginTop: 2 }}>{serviceName}</div>}
      </div>
      <div ref={elRef} style={{ minHeight: 40, marginBottom: 14 }} />
      {status === 'loading' && <div style={{ color: 'var(--muted)', fontSize: 13, marginBottom: 12 }}>Loading secure payment form…</div>}
      {errMsg && <div style={{ color: '#DC2626', fontSize: 13, marginBottom: 12 }}>{errMsg}</div>}
      <button type="submit" className="btn primary lg block" disabled={status !== 'ready'}>
        {status === 'paying' ? 'Processing payment…' : `Pay ${amountStr}`}
      </button>
      <div style={{ marginTop: 12, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8, color: 'var(--muted)', fontSize: 11.5 }}>
        <Ic.shield style={{ width: 14, height: 14 }} /> Secure payment powered by Stripe
      </div>
    </form>
  );
}

function BookingSuccess({ business, selectedService, form, bookingId, paid }) {
  const ref = bookingId ? bookingId.slice(0, 8).toUpperCase() : null;
  const serviceName = selectedService?.name || 'Booking';
  const businessName = business?.name || '';

  function fmtLocalDt(d) {
    const p = n => String(n).padStart(2, '0');
    return `${d.getFullYear()}${p(d.getMonth()+1)}${p(d.getDate())}T${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
  }

  function makeICS() {
    const durationMins = selectedService?.duration_minutes || 60;
    const startDate = new Date(`${form.date}T${form.time}:00`);
    const endDate = new Date(startDate.getTime() + durationMins * 60_000);
    const start = fmtLocalDt(startDate);
    const end = fmtLocalDt(endDate);
    const loc = fmtAddr(business);
    const ics = [
      'BEGIN:VCALENDAR',
      'VERSION:2.0',
      'PRODID:-//BookUno//EN',
      'BEGIN:VEVENT',
      `UID:${bookingId || Date.now()}@bookuno.co`,
      `DTSTART:${start}`,
      `DTEND:${end}`,
      `SUMMARY:${serviceName} at ${businessName}`,
      loc ? `LOCATION:${loc}` : '',
      `DESCRIPTION:Booking ref: ${ref || '—'}`,
      'END:VEVENT',
      'END:VCALENDAR',
    ].filter(Boolean).join('\r\n');
    return ics;
  }

  function downloadICS() {
    const ics = makeICS();
    const blob = new Blob([ics], { type: 'text/calendar' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'booking.ics';
    a.click();
    URL.revokeObjectURL(url);
  }

  function googleCalLink() {
    const durationMins = selectedService?.duration_minutes || 60;
    const startDate = new Date(`${form.date}T${form.time}:00`);
    const endDate = new Date(startDate.getTime() + durationMins * 60_000);
    const start = fmtLocalDt(startDate);
    const end = fmtLocalDt(endDate);
    const title = encodeURIComponent(`${serviceName} at ${businessName}`);
    const details = encodeURIComponent(`Booking ref: ${ref || '—'}`);
    const loc = encodeURIComponent(fmtAddr(business));
    return `https://calendar.google.com/calendar/render?action=TEMPLATE&text=${title}&dates=${start}/${end}&details=${details}&location=${loc}`;
  }

  return (
    <div style={{ textAlign: 'center', padding: '20px 0' }}>
      <div style={{ fontSize: 42, marginBottom: 12 }}>{paid ? '✅' : '📩'}</div>
      <div style={{ fontWeight: 800, fontSize: 22, marginBottom: 8 }}>{paid ? 'Booking confirmed!' : 'Booking request sent'}</div>
      {!paid && (
        <div style={{ fontSize: 13.5, color: 'var(--muted)', marginBottom: 12, maxWidth: 300, marginLeft: 'auto', marginRight: 'auto' }}>
          {businessName || 'The business'} will confirm your appointment shortly — you'll get an email when they do.
        </div>
      )}
      {ref && (
        <div style={{ display: 'inline-block', padding: '6px 16px', borderRadius: 999, background: 'var(--pri-50)', color: 'var(--pri-700)', fontWeight: 800, fontSize: 13, letterSpacing: 0.08, marginBottom: 14 }}>
          REF: {ref}
        </div>
      )}
      <div style={{ fontSize: 14, color: 'var(--muted)', lineHeight: 1.7 }}>
        <div><b style={{ color: 'var(--ink)' }}>{businessName}</b></div>
        {selectedService && <div>{serviceName}</div>}
        <div>{form.date} at {form.time}</div>
        {business.phone && <div style={{ marginTop: 4 }}><a href={`tel:${business.phone}`} style={{ color: 'var(--pri)', fontWeight: 600 }}>{business.phone}</a></div>}
        {fmtAddr(business) && (
          <div style={{ marginTop: 2, fontSize: 13 }}>{fmtAddr(business)}</div>
        )}
      </div>
      {form.email && (
        <p style={{ color: 'var(--muted)', fontSize: 13, marginTop: 12 }}>
          Confirmation sent to <b style={{ color: 'var(--ink)' }}>{form.email}</b>
        </p>
      )}
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8, alignItems: 'center', marginTop: 18 }}>
        <button onClick={downloadICS} className="btn outline sm" style={{ minWidth: 220 }}>
          📅 Add to calendar (.ics)
        </button>
        <a href={googleCalLink()} target="_blank" rel="noopener noreferrer" className="btn outline sm" style={{ minWidth: 220 }}>
          Add to Google Calendar
        </a>
        <a href="/app.html#/search" className="btn outline sm" style={{ marginTop: 4 }}>← Back to search</a>
      </div>
    </div>
  );
}

Object.assign(window, { PageStorefront, BookingSuccess, PaymentStep, BusinessMap });
