// VeloraTech — /book — Technician visit, 4-step flow
// Payment-first: Stripe $49 deposit MUST succeed before refId or DB write
const { useState: useBook, useMemo: useMemoBook, useRef: useRefBook, useEffect: useEffectBook } = React;

// ── Module-level singletons — each initialises at most ONCE per page load ─────
let _stripeConfigCache = null;  // caches get-config response — fetched at most once per page load

const BOOK_SERVICES = [
  { id: 'web',     icon: 'Globe',    name: 'Web Design & Ongoing Care',      price: 'from $375',      urlParam: 'web' },
  { id: 'wifi',    icon: 'Wifi',     name: 'Home Internet & Wi-Fi Setup',    price: 'from $140',      urlParam: 'wifi' },
  { id: 'cctv',   icon: 'Camera',   name: 'CCTV & Smart Home Installation', price: 'from $220',      urlParam: 'cctv' },
  { id: 'biz',    icon: 'Building', name: 'Business IT Support',            price: 'from $100',      urlParam: 'business-it' },
  { id: 'compute',icon: 'Tv',       name: 'Computer & Printer Help',        price: 'from $110',      urlParam: 'computer' },
  { id: 'other',  icon: 'Hand',     name: 'Something Else',                 price: 'quoted on call', urlParam: 'something-else' },
];

const URL_PARAM_TO_SVC = {
  'web': 'web', 'website': 'web',
  'wifi': 'wifi', 'cctv': 'cctv',
  'business-it': 'biz', 'computer': 'compute', 'something-else': 'other',
};

const STEP_NAMES = ['Services', 'Your details', 'Pick a time', 'Confirm & Pay'];

const CAL_MONTHS = ['January','February','March','April','May','June',
                    'July','August','September','October','November','December'];
const CAL_DAYS   = ['MON','TUE','WED','THU','FRI','SAT','SUN'];

const BOOK_SLOTS = [
  { id: 'morning',   name: 'Morning',   time: '8:00 AM – 12:00 PM' },
  { id: 'afternoon', name: 'Afternoon', time: '12:00 PM – 5:00 PM' },
  { id: 'flexible',  name: 'Flexible',  time: null },
];

const SLOT_DISPLAY = {
  morning:   'Morning (8:00am – 12:00pm)',
  afternoon: 'Afternoon (12:00pm – 5:00pm)',
  flexible:  'Flexible',
};

const SLOT_TIME_MAP = { morning: '09:00', afternoon: '13:00', flexible: '09:00' };
const SVC_AMT_MAP   = { web: 375, wifi: 140, cctv: 220, biz: 100, compute: 110, other: 0 };

const _INIT_PARAMS = (() => {
  try {
    const sp  = new URLSearchParams(window.location.search);
    const raw = sp.get('service') || '';
    if (raw === 'web') { window.location.href = 'consult.html'; return { step: 1, services: [], fromSomethingElse: false }; }
    const id = URL_PARAM_TO_SVC[raw] || null;
    return { step: id ? 2 : 1, services: id ? [id] : [], fromSomethingElse: raw === 'something-else' };
  } catch (_) {
    return { step: 1, services: [], fromSomethingElse: false };
  }
})();

// ── Shared: Inline Calendar ───────────────────────────────────────────────────
function InlineCalendar({ value, onChange }) {
  const todayObj = new Date(); todayObj.setHours(0, 0, 0, 0);
  const [vy, setVy] = useBook(todayObj.getFullYear());
  const [vm, setVm] = useBook(todayObj.getMonth());

  const firstOfMo = new Date(vy, vm, 1);
  const daysInMo  = new Date(vy, vm + 1, 0).getDate();
  const startDow  = (firstOfMo.getDay() + 6) % 7;
  const selDate   = value ? new Date(value + 'T00:00:00') : null;
  const pad = n => String(n).padStart(2, '0');

  const cells = [];
  for (let i = 0; i < startDow; i++) cells.push(null);
  for (let d = 1; d <= daysInMo; d++) cells.push(d);

  const canPrev = vy > todayObj.getFullYear() ||
    (vy === todayObj.getFullYear() && vm > todayObj.getMonth());

  const prevMo = () => { if (vm === 0) { setVy(y => y - 1); setVm(11); } else setVm(m => m - 1); };
  const nextMo = () => { if (vm === 11) { setVy(y => y + 1); setVm(0); } else setVm(m => m + 1); };

  return (
    <div className="cal">
      <div className="cal__head">
        <button type="button" className="cal__nav" onClick={prevMo} disabled={!canPrev} aria-label="Previous month">
          <Icon.ChevronL/>
        </button>
        <span className="cal__month">{CAL_MONTHS[vm]} {vy}</span>
        <button type="button" className="cal__nav" onClick={nextMo} aria-label="Next month">
          <Icon.ChevronR/>
        </button>
      </div>
      <div className="cal__grid">
        {CAL_DAYS.map(d => <div key={d} className="cal__dow">{d}</div>)}
        {cells.map((day, i) => {
          if (!day) return <div key={'e' + i} className="cal__cell cal__cell--empty"/>;
          const cellMs     = new Date(vy, vm, day).getTime();
          const isPast     = cellMs < todayObj.getTime();
          const isToday    = cellMs === todayObj.getTime();
          const isSelected = selDate && cellMs === selDate.getTime();
          const ds = `${vy}-${pad(vm + 1)}-${pad(day)}`;
          return (
            <button type="button" key={day}
              className={['cal__cell', isPast && 'past', isToday && 'today', isSelected && 'selected'].filter(Boolean).join(' ')}
              onClick={() => !isPast && onChange(ds)}
              disabled={isPast}
              aria-pressed={!!isSelected}
            >{day}</button>
          );
        })}
      </div>
    </div>
  );
}

// ── Shared: Time Slot Cards ───────────────────────────────────────────────────
function TimeSlotCards({ slots, value, onChange, selectedDate }) {
  const now     = new Date();
  const todayMs = new Date(); todayMs.setHours(0, 0, 0, 0);
  const selDay  = selectedDate ? new Date(selectedDate + 'T00:00:00') : null;
  const isToday = selDay && selDay.getTime() === todayMs.getTime();
  const curMins = now.getHours() * 60 + now.getMinutes();

  const isDisabled = id => {
    if (!isToday) return false;
    if (id === 'morning')   return curMins >= 600;
    if (id === 'afternoon') return curMins >= 870;
    if (id === 'evening')   return curMins >= 1080;
    return false;
  };

  return (
    <div className="slot-grid">
      {slots.map(s => {
        const dis = isDisabled(s.id);
        return (
          <button type="button" key={s.id}
            className={['slot-card', value === s.id && 'active', dis && 'disabled'].filter(Boolean).join(' ')}
            onClick={() => !dis && onChange(s.id)}
            disabled={dis}
            aria-pressed={value === s.id}
          >
            <span className="slot-card__name">{s.name}</span>
            {s.time && <span className="slot-card__time">{s.time}</span>}
            {dis && <span className="slot-card__unavail">Unavailable</span>}
          </button>
        );
      })}
    </div>
  );
}

// ── Address Autocomplete ──────────────────────────────────────────────────────
// Maps loads async (async defer script) — attach Autocomplete once ready.
// acRef guard (Cause 3/4): prevents double-init if effect re-runs.
// pollTimer (Cause 3): retries every 100ms if Maps not yet ready on Step 2 mount.
function AddressAutocomplete({ address, setAddress, setSuburb, setPostcode, setAddrState }) {
  const inputRef = useRefBook(null);
  const acRef    = useRefBook(null);   // Autocomplete instance — set ONCE, never reset mid-session
  const [manual, setManual] = useBook(false);

  useEffectBook(() => {
    if (manual || !inputRef.current) return;
    if (acRef.current) return; // already initialised — guard against double-init

    let pollTimer = null;

    const tryAttach = () => {
      if (!inputRef.current || acRef.current) return;
      if (!window.google?.maps?.places?.Autocomplete) return; // Maps not ready yet

      const ac = new window.google.maps.places.Autocomplete(inputRef.current, {
        componentRestrictions: { country: 'au' },
        fields: ['address_components', 'formatted_address'],
        types: ['address'],
        bounds: new window.google.maps.LatLngBounds(
          { lat: -28.5, lng: 152.6 }, { lat: -26.5, lng: 153.5 }
        ),
        strictBounds: false,
      });
      acRef.current = ac;
      if (pollTimer) { clearInterval(pollTimer); pollTimer = null; }

      ac.addListener('place_changed', () => {
        const place = ac.getPlace();
        if (!place?.address_components) return;
        let sn = '', rt = '', sub = '', pc = '', st = '';
        place.address_components.forEach(c => {
          if (c.types.includes('street_number'))               sn  = c.long_name;
          if (c.types.includes('route'))                       rt  = c.long_name;
          if (c.types.includes('locality'))                    sub = c.long_name;
          if (c.types.includes('postal_code'))                 pc  = c.short_name;
          if (c.types.includes('administrative_area_level_1')) st  = c.short_name;
        });
        setAddress(place.formatted_address || [sn, rt].filter(Boolean).join(' '));
        setSuburb(sub); setPostcode(pc); setAddrState(st);
      });
    };

    // Try immediately — works if Maps already finished loading
    tryAttach();

    // If Maps is still loading (async script), poll until available (max 5 s)
    if (!acRef.current) {
      let polls = 0;
      pollTimer = setInterval(() => {
        polls++;
        tryAttach();
        if (acRef.current || polls >= 50) { clearInterval(pollTimer); pollTimer = null; }
      }, 100);
    }

    return () => {
      if (pollTimer) { clearInterval(pollTimer); pollTimer = null; }
      if (acRef.current) {
        try { window.google?.maps?.event?.clearInstanceListeners(acRef.current); } catch (_) {}
        acRef.current = null;
      }
    };
  }, [manual]);

  if (manual) {
    return (
      <input className="big-field__input" type="text" value={address}
        onChange={e => setAddress(e.target.value)}
        placeholder="e.g. 28 Wordsworth St, Norman Park QLD 4170"
        autoComplete="street-address"/>
    );
  }
  return (
    <>
      <input
        ref={inputRef}
        id="bk-address"
        className="big-field__input"
        type="text"
        defaultValue={address}
        placeholder="Start typing your address…"
        autoComplete="off"
        onBlur={e => { if (e.target.value && !address) setAddress(e.target.value); }}
      />
      <button type="button" className="addr-manual-link" onClick={() => setManual(true)}>
        Can't find your address? Enter it manually →
      </button>
    </>
  );
}

// ── Main Component ────────────────────────────────────────────────────────────
function TechnicianBookingPage() {
  const [step,      setStep]      = useBook(_INIT_PARAMS.step);
  const [services,  setServices]  = useBook(_INIT_PARAMS.services);
  const [name,      setName]      = useBook('');
  const [phone,     setPhone]     = useBook('');
  const [email,     setEmail]     = useBook('');
  const [address,   setAddress]   = useBook('');
  const [suburb,    setSuburb]    = useBook('');
  const [postcode,  setPostcode]  = useBook('');
  const [addrState, setAddrState] = useBook('');
  const [notes,     setNotes]     = useBook('');
  const [date,      setDate]      = useBook('');
  const [slot,      setSlot]      = useBook('');
  const [customTime,setCustomTime]= useBook('');

  const [submitting,   setSubmitting]   = useBook(false);
  const [submitErr,    setSubmitErr]    = useBook('');
  const [submitted,    setSubmitted]    = useBook(false);
  const [refId,        setRefId]        = useBook('');
  const [bookingPhone, setBookingPhone] = useBook('');
  const [bookingEmail, setBookingEmail] = useBook('');

  // Stripe state
  const [payState,   setPayState]  = useBook('idle'); // idle | loading | ready | error
  const [elemReady,  setElemReady] = useBook(false);
  const [retryCount, setRetryCount]= useBook(0);      // incremented by onRetry to re-trigger Effect 2
  const elemReadyRef  = useRefBook(false);
  const elemTimerRef  = useRefBook(null);
  const stripeInst    = useRefBook(null);
  const stripeElems   = useRefBook(null);
  const payDomRef     = useRefBook(null);
  const stripeInitRef = useRefBook(false); // true while Effect 2 is running — guards re-entry

  // ── Cache reset: clear stale config on every mount ───────────────────────
  // Prevents a prior failed/empty cache from blocking Effect 2 silently.
  useEffectBook(() => { _stripeConfigCache = null; }, []);

  // ── Step-change guard: reset payment state whenever user leaves Step 4 ───
  // Ensures payState is 'idle' if the user navigates away and returns.
  // The back() function also does this explicitly — this is defence-in-depth.
  useEffectBook(() => {
    if (step !== 4) {
      setPayState('idle');
      stripeInitRef.current = false;  // allow Effect 2 to re-run next time step reaches 4
      stripeInst.current    = null;
      stripeElems.current   = null;
      console.log('[VT] Payment state reset — left Step 4');
    }
  }, [step]);

  // ── Effect 1: handle 3DS redirect back ───────────────────────────────────
  useEffectBook(() => {
    const sp     = new URLSearchParams(window.location.search);
    const piId   = sp.get('payment_intent');
    const secret = sp.get('payment_intent_client_secret');
    if (!piId || !secret || !window.Stripe) return;

    (async () => {
      try {
        if (!_stripeConfigCache) {
          const cfgRes = await fetch('/.netlify/functions/get-config');
          if (!cfgRes.ok) return;
          _stripeConfigCache = await cfgRes.json();
        }
        const { stripePublicKey } = _stripeConfigCache;
        if (!stripePublicKey) return;
        const stripe = window.Stripe(stripePublicKey);
        const { paymentIntent, error } = await stripe.retrievePaymentIntent(secret);
        if (error || !paymentIntent) return;
        if (paymentIntent.status === 'succeeded') {
          setBookingPhone(sp.get('phone') || '');
          setBookingEmail(sp.get('email') || '');
          setSubmitted(true);
        }
      } catch (e) { console.warn('[VT] 3DS redirect check:', e.message); }
    })();
  }, []);

  // ── Effect 2: init Stripe when step 4 is reached ─────────────────────────
  // deps: [step, retryCount] — payState intentionally excluded.
  // Reason: calling setPayState('loading') inside this effect would cause React
  // to re-run the effect (because payState was a dep), which triggered cleanup
  // (isActive=false) mid-fetch, silently killing the async chain.
  // stripeInitRef guards re-entry; retryCount lets onRetry re-trigger explicitly.
  useEffectBook(() => {
    if (step !== 4) return;
    if (stripeInitRef.current) return;  // already running or completed — no re-entry
    if (!window.Stripe) {
      setPayState('error');
      setSubmitErr('Payment system could not be loaded. Please refresh or call 0449 991 572.');
      return;
    }

    stripeInitRef.current = true;  // claim synchronously before any async work
    const effectId = Date.now();
    let isActive = true;

    console.log('[VT] Effect 2 fired — initialising Stripe', { effectId });
    console.log('[VT] Booking data at Step 4:', {
      name: name || '(empty)', email: email || '(empty)',
      phone: phone || '(empty)', services,
    });

    setPayState('loading');  // safe: payState is NOT in deps — this won't re-trigger the effect

    async function runEffect() {
      try {
        // Step 1: fetch config — always fresh (no cache)
        console.log('[VT] Effect 2 — step 1: fetch config');
        const cfgRes = await fetch('/.netlify/functions/get-config');
        if (!isActive) { console.log('[VT] Effect 2 cancelled after step 1, effectId:', effectId); return; }
        if (!cfgRes.ok) throw new Error('get-config failed: ' + cfgRes.status);
        const config = await cfgRes.json();
        if (!isActive) return;

        console.log('[VT] Effect 2 — step 2:', {
          hasKey: !!config.stripePublicKey,
          prefix: config.stripePublicKey?.slice(0, 12) || '(empty)',
          effectId,
        });

        if (!config.stripePublicKey || config.stripePublicKey.trim() === '') {
          throw new Error('stripePublicKey is empty — check STRIPE_PUBLIC_KEY in Netlify env variables.');
        }

        // Step 3: init Stripe
        console.log('[VT] Effect 2 — step 3: init Stripe');
        const stripe = window.Stripe(config.stripePublicKey);
        if (!isActive) return;
        stripeInst.current = stripe;

        // Step 4: create PaymentIntent
        console.log('[VT] Effect 2 — step 4: create PaymentIntent');
        const serviceNames = services
          .map(id => (BOOK_SERVICES.find(s => s.id === id) || {}).name)
          .filter(Boolean).join(', ');
        const slotId = slot || 'flexible';

        const piRes = await fetch('/.netlify/functions/create-payment-intent', {
          method:  'POST',
          headers: { 'Content-Type': 'application/json' },
          body:    JSON.stringify({
            amount: 4900, currency: 'aud',
            metadata: {
              source:              'book-page',
              customer_name:       name.trim(),
              customer_email:      email.trim(),
              customer_phone:      phone.trim(),
              service:             serviceNames,
              address:             address.trim(),
              suburb:              suburb.trim(),
              postcode:            postcode.trim(),
              date,
              preferred_time:      SLOT_TIME_MAP[slotId] || '09:00',
              preferred_time_slot: slotId,
              custom_time:         customTime.trim(),
              notes:               notes.trim(),
            },
          }),
        });

        console.log('[VT] Effect 2 — step 5: pi status', piRes.status);
        if (!isActive) return;

        const piData = await piRes.json();
        if (!isActive) return;

        console.log('[VT] Effect 2 — step 6:', {
          hasSecret: !!piData.clientSecret,
          error:     piData.error || null,
          effectId,
        });

        if (piData.error) throw new Error(piData.error);
        if (!piData.clientSecret) throw new Error('No clientSecret — response: ' + JSON.stringify(piData));

        // Step 7: create Elements instance
        console.log('[VT] Effect 2 — step 7: create elements');
        const elements = stripe.elements({
          clientSecret: piData.clientSecret,
          appearance: {
            theme: 'stripe',
            variables: {
              colorPrimary:    '#E8621A',
              colorBackground: '#ffffff',
              colorText:       '#1A1A1A',
              borderRadius:    '10px',
              fontFamily:      '-apple-system, BlinkMacSystemFont, "Inter", sans-serif',
            },
            rules: {
              '.Input': { border: '1.5px solid #E5E1DC', boxShadow: 'none', padding: '12px 14px' },
              '.Input:focus': { border: '1.5px solid #E8621A', boxShadow: '0 0 0 3px rgba(232,98,26,0.12)' },
            },
          },
        });
        if (!isActive) return;

        stripeElems.current = elements;
        console.log('[VT] Effect 2 — step 8: setting payState to ready');
        setPayState('ready');

      } catch (e) {
        if (!isActive) return;
        console.error('[VT] Effect 2 FAILED:', e.message, e.stack);
        stripeInitRef.current = false;  // allow retry to re-run the effect
        setPayState('error');
        setSubmitErr('Could not load payment system. Please refresh or call 0449 991 572.');
      }
    }

    runEffect();

    return () => {
      console.log('[VT] Effect 2 cleanup — isActive set false, effectId:', effectId);
      isActive = false;
      stripeInitRef.current = false;  // reset so next mount or retry can re-enter
    };
  }, [step, retryCount]);  // payState intentionally excluded — see comment above

  // ── Effect 3: mount PaymentElement + watch for ready event ───────────────
  useEffectBook(() => {
    if (payState !== 'ready') return;

    let mountTimer = null;
    let stripeEl   = null;

    // 100ms delay — ensures React has committed the DOM update before we touch the div
    mountTimer = setTimeout(() => {
      console.log('[VT] Effect 3 fired', {
        payDomRef:         !!payDomRef.current,
        paymentElementDiv: !!document.getElementById('payment-element'),
        stripeElems:       !!stripeElems.current,
      });

      if (!payDomRef.current) {
        console.error('[VT] payDomRef.current is null — cannot mount');
        return;
      }
      if (!stripeElems.current) {
        console.error('[VT] stripeElems.current is null — cannot mount');
        return;
      }
      if (payDomRef.current._mounted) {
        console.log('[VT] already mounted — skipping');
        return;
      }

      try {
        stripeEl = stripeElems.current.create('payment', { layout: 'tabs' });

        stripeEl.on('ready', () => {
          console.log('[VT] PaymentElement ready ✓');
          elemReadyRef.current = true;
          setElemReady(true);
          if (elemTimerRef.current) { clearTimeout(elemTimerRef.current); elemTimerRef.current = null; }
        });

        stripeEl.on('loaderror', (evt) => {
          console.error('[VT] PaymentElement load error:', evt.error?.message || evt);
          setSubmitErr('Payment form failed to load. Please try again or call +61 449 991 572.');
          setPayState('error');
          if (elemTimerRef.current) { clearTimeout(elemTimerRef.current); elemTimerRef.current = null; }
        });

        console.log('[VT] mount() called ✓');
        stripeEl.mount(payDomRef.current);
        payDomRef.current._mounted = true;

        // 10-second safety timeout
        elemTimerRef.current = setTimeout(() => {
          if (!elemReadyRef.current) {
            console.warn('[VT] Stripe PaymentElement timed out after 10s');
            setSubmitErr('Payment form timed out. Please refresh or call +61 449 991 572.');
          }
        }, 10000);

      } catch (err) {
        console.error('[VT] Effect 3 mount error:', err.message);
        setPayState('error');
      }
    }, 100);

    return () => {
      clearTimeout(mountTimer);
      if (stripeEl) {
        try { stripeEl.unmount(); } catch (_) {}
      }
      if (payDomRef.current) payDomRef.current._mounted = false;
      if (elemTimerRef.current) { clearTimeout(elemTimerRef.current); elemTimerRef.current = null; }
    };
  }, [payState]);

  const errs = useMemoBook(() => {
    const e = {};
    if (step === 1) {
      if (!services.length) e.services = 'Pick at least one thing we can help with.';
    } else if (step === 2) {
      if (!name.trim()) e.name = 'Please tell us your name.';
      const cp = phone.replace(/[\s\-\(\)\.]/g, '');
      if (!cp || !/^\d{10}$/.test(cp) || !cp.startsWith('04'))
        e.phone = 'Please enter a valid Australian mobile number starting with 04 — e.g. 0412 345 678';
      const em = email.trim();
      if (!em || /\s/.test(em) || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(em))
        e.email = 'Please enter a valid email address — e.g. yourname@gmail.com';
      if (!address.trim()) e.address = 'Please enter your address so we know where to come.';
    } else if (step === 3) {
      if (!date && !customTime.trim())         e.date = 'Pick a date or type a preferred time below.';
      if (date && !slot && !customTime.trim()) e.slot = 'Pick a time slot, or type your preferred time below.';
    }
    return e;
  }, [step, services, name, phone, email, address, date, slot, customTime]);

  const toggleService = id =>
    setServices(ss => ss.includes(id) ? ss.filter(x => x !== id) : [...ss, id]);

  const next = () => {
    if (Object.keys(errs).length) return;
    setStep(s => Math.min(4, s + 1));
    setTimeout(() => window.scrollTo({ top: 0, behavior: 'smooth' }), 30);
  };

  const back = () => {
    if (step === 4) {
      setPayState('idle');
      setElemReady(false);
      elemReadyRef.current = false;
      if (elemTimerRef.current) { clearTimeout(elemTimerRef.current); elemTimerRef.current = null; }
      stripeInst.current  = null;
      stripeElems.current = null;
      setSubmitErr('');
    }
    setStep(s => Math.max(1, s - 1));
    setSubmitErr('');
  };

  // ── finishBooking: DB + emails after confirmed payment ────────────────────
  const finishBooking = async (paymentIntentId) => {
    const serviceNames = services
      .map(id => (BOOK_SERVICES.find(s => s.id === id) || {}).name)
      .filter(Boolean).join(', ');
    const totalAmt = services.reduce((s, id) => s + (SVC_AMT_MAP[id] || 0), 0);
    const today    = new Date().toISOString().split('T')[0];
    const slotId   = slot || 'flexible';
    const notesStr = [notes.trim(), customTime ? 'Preferred time: ' + customTime : ''].filter(Boolean).join('\n');
    const bookingRefId = window.vtGenerateRefId('BOOK');

    try {
      const jobId = await window.vtNextJobId();
      await window.vtInsertJob({
        id: jobId, reference_id: bookingRefId, source: 'booking',
        customer: name.trim(), phone: phone.trim(), email: email.trim(),
        service: serviceNames, address: address.trim(), suburb: suburb.trim(),
        postcode: postcode.trim(), lat: 0, lng: 0,
        date: date || today, time: SLOT_TIME_MAP[slotId] || '09:00',
        status: 'Pending', assigned_to: '',
        deposit_paid: true, deposit_amount: 49,
        stripe_payment_id: paymentIntentId, stripe_status: 'paid',
        service_amount: totalAmt, notes: notesStr, amount: totalAmt, created: today,
      });
    } catch (dbErr) {
      console.error('[VT] DB insert failed after payment:', dbErr.message);
      // Webhook handles insert — show success anyway
      setRefId(paymentIntentId);
      setBookingPhone(phone.trim());
      setBookingEmail(email.trim());
      setSubmitted(true);
      setSubmitting(false);
      return;
    }

    const dateLabel = date
      ? new Date(date + 'T00:00:00').toLocaleDateString('en-AU',
          { day: 'numeric', month: 'long', year: 'numeric' })
      : 'Flexible';

    window.vtSendEmail({
      type: 'booking', refId: bookingRefId,
      to: email.trim(), name: name.trim(), phone: phone.trim(),
      service: serviceNames, date: dateLabel,
      slot: BOOK_SLOTS.find(s => s.id === slot)?.name || (customTime || 'Flexible'),
      address: address.trim(), suburb: suburb.trim(), postcode: postcode.trim(),
      notes: notesStr, customTime: customTime.trim(),
      submittedAt: new Date().toLocaleString('en-AU', { timeZone: 'Australia/Brisbane' }),
    }).catch(e => console.warn('[VT] Email error:', e.message));

    setRefId(bookingRefId);
    setBookingPhone(phone.trim());
    setBookingEmail(email.trim());
    setSubmitted(true);
    setSubmitting(false);
    setTimeout(() => window.scrollTo({ top: 0, behavior: 'smooth' }), 30);
  };

  const submit = async () => {
    if (!stripeInst.current || !stripeElems.current) {
      setSubmitErr('Payment system not ready. Please wait a moment then try again.');
      return;
    }
    setSubmitting(true);
    setSubmitErr('');

    try {
      const { error, paymentIntent } = await stripeInst.current.confirmPayment({
        elements: stripeElems.current,
        confirmParams: {
          return_url: window.location.href,
          payment_method_data: { billing_details: { name: name.trim(), email: email.trim(), phone: phone.trim() } },
        },
        redirect: 'if_required',
      });

      if (error) {
        console.warn('[VT] Stripe confirmPayment error:', error.message);
        setSubmitErr(error.message || 'Payment failed — please check your card details and try again.');
        setSubmitting(false);
        return;
      }

      if (paymentIntent && paymentIntent.status === 'succeeded') {
        console.log('[VT] Payment succeeded ✓', paymentIntent.id);
        await finishBooking(paymentIntent.id);
      } else {
        setSubmitErr('Payment could not be completed. Please try again or call 0449 991 572.');
        setSubmitting(false);
      }
    } catch (e) {
      console.error('[VT] confirmPayment error:', e.message);
      setSubmitErr('Something went wrong. Please try again or call 0449 991 572.');
      setSubmitting(false);
    }
  };

  // ── Success screen ─────────────────────────────────────────────────────────
  if (submitted) {
    return (
      <div className="success-card">
        <div className="success-card__icon"><Icon.Check/></div>
        <h2 className="success-card__title">Booking <em>Confirmed!</em></h2>

        {refId && refId.startsWith('VT-') && (
          <div style={{ margin: '18px 0', padding: '18px 24px', background: 'rgba(232,98,26,0.07)',
            borderRadius: '12px', border: '1.5px solid rgba(232,98,26,0.22)', textAlign: 'center' }}>
            <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: '#E8621A', marginBottom: 8 }}>
              Your Reference Number
            </div>
            <div style={{ fontSize: 30, fontWeight: 800, color: '#E8621A', letterSpacing: '0.05em', fontFamily: 'monospace' }}>
              {refId}
            </div>
            <div style={{ fontSize: 12, color: 'rgba(10,10,10,0.4)', marginTop: 6 }}>
              Save this — we'll reference it when we call you
            </div>
          </div>
        )}

        <p className="success-card__sub">
          Your $49 deposit has been received.
          {bookingPhone && <> We will call you on <strong>{bookingPhone}</strong> within 2 hours to confirm your appointment (during business hours).</>}
        </p>
        {bookingEmail && (
          <p className="success-card__sub" style={{ fontSize: 16, marginTop: -10 }}>
            A confirmation has been sent to <strong>{bookingEmail}</strong>.
          </p>
        )}

        <a className="btn btn--primary btn--lg success-card__cta" href="index.html">
          Back to Home <Icon.Arrow/>
        </a>
        <p className="success-help">
          Need help now? 📞 <a href="tel:0449991572">+61 449 991 572</a>
        </p>
      </div>
    );
  }

  return (
    <>
      <div className="pg-stepper">
        <div className="pg-stepper__label">
          <span>Step <strong style={{color:'var(--ink)'}}>{step}</strong> <span className="of">of 4</span></span>
          <span className="name">— {STEP_NAMES[step - 1]}</span>
        </div>
        <div className="pg-stepper__bar">
          {[1,2,3,4].map(n => <span key={n} className={step === n ? 'active' : step > n ? 'done' : ''}/>)}
        </div>
      </div>

      <div className="flow">
        {step === 1 && <Step1 services={services} toggleService={toggleService} errs={errs}/>}
        {step === 2 && (
          <Step2
            name={name} setName={setName} phone={phone} setPhone={setPhone}
            email={email} setEmail={setEmail} address={address} setAddress={setAddress}
            setSuburb={setSuburb} setPostcode={setPostcode} setAddrState={setAddrState}
            notes={notes} setNotes={setNotes} errs={errs}
            fromSomethingElse={_INIT_PARAMS.fromSomethingElse}
          />
        )}
        {step === 3 && (
          <Step3
            date={date} setDate={ds => { setDate(ds); setSlot(''); }}
            slot={slot} setSlot={setSlot}
            customTime={customTime} setCustomTime={setCustomTime}
            errs={errs}
          />
        )}
        {step === 4 && (
          <Step4
            services={services} name={name} phone={phone} email={email}
            address={address} suburb={suburb} postcode={postcode}
            notes={notes} date={date} slot={slot} customTime={customTime}
            onSubmit={submit} submitting={submitting} submitErr={submitErr}
            payState={payState} elemReady={elemReady} payDomRef={payDomRef}
            onEdit={back}
            onRetry={() => { stripeInitRef.current = false; setPayState('idle'); setSubmitErr(''); setRetryCount(c => c + 1); }}
          />
        )}

        {step < 4 && (
          <div className="flow__actions">
            {step > 1
              ? <button className="flow__back" onClick={back}><Icon.ChevronL/> Back</button>
              : <span/>}
            <div className="spacer"/>
            <button className="btn btn--ink btn--lg" onClick={next}
              disabled={Object.keys(errs).length > 0}
              style={Object.keys(errs).length ? { opacity: 0.55, cursor: 'not-allowed' } : {}}>
              Continue <Icon.Arrow/>
            </button>
          </div>
        )}
      </div>
    </>
  );
}

// ----- Step 1 -----
function Step1({ services, toggleService, errs }) {
  return (
    <>
      <h2 className="flow__h">What do you <em>need help with?</em></h2>
      <p className="flow__intro">Pick your service — we come to homes and businesses across Brisbane, the Gold Coast and Tweed Heads.</p>
      <div className="flow__group">
        <span className="flow__label">Select a service <span className="opt">(pick any)</span></span>
        <div className="svc-grid">
          {BOOK_SERVICES.map(s => {
            const Ico    = Icon[s.icon];
            const active = services.includes(s.id);
            if (s.consultOnly) {
              return (
                <button type="button" key={s.id} className="svc-pick svc-pick--consult"
                  onClick={() => { window.location.href = 'consult.html'; }}>
                  <div className="svc-pick__badge"><Ico/></div>
                  <div className="svc-pick__body">
                    <p className="svc-pick__name">{s.name}</p>
                    <p className="svc-pick__sub">{s.price}</p>
                  </div>
                  <div className="svc-pick__check"><Icon.Arrow/></div>
                </button>
              );
            }
            return (
              <button type="button" key={s.id} className={'svc-pick' + (active ? ' active' : '')}
                onClick={() => toggleService(s.id)}>
                <div className="svc-pick__badge"><Ico/></div>
                <div className="svc-pick__body">
                  <p className="svc-pick__name">{s.name}</p>
                  <p className="svc-pick__sub">{s.price}</p>
                </div>
                <div className="svc-pick__check"><Icon.Check/></div>
              </button>
            );
          })}
        </div>
        {errs.services && <span className="flow__err">{errs.services}</span>}
      </div>
    </>
  );
}

// ----- Step 2 -----
function Step2({ name, setName, phone, setPhone, email, setEmail, address, setAddress,
                 setSuburb, setPostcode, setAddrState, notes, setNotes, errs, fromSomethingElse }) {
  const notesRef = useRefBook(null);
  useEffectBook(() => {
    if (fromSomethingElse && notesRef.current) {
      notesRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
      setTimeout(() => notesRef.current?.focus(), 300);
    }
  }, []);

  return (
    <>
      <h2 className="flow__h">Your <em>details</em>.</h2>
      <p className="flow__intro">We'll only use these to confirm your visit. No mailing lists — promise.</p>

      <div className="big-field">
        <label className="big-field__label" htmlFor="bk-name">Full name</label>
        <input id="bk-name" className="big-field__input" type="text" value={name}
          onChange={e => setName(e.target.value)} placeholder="e.g. Margaret Wilson" autoComplete="name"/>
        {errs.name && <span className="flow__err">{errs.name}</span>}
      </div>

      <div className="big-field__row">
        <div className="big-field">
          <label className="big-field__label" htmlFor="bk-phone">Phone number</label>
          <input id="bk-phone" className="big-field__input" type="tel" value={phone}
            onChange={e => setPhone(e.target.value)} placeholder="04xxxxxxxx" autoComplete="tel"/>
          {errs.phone && <span className="flow__err">{errs.phone}</span>}
        </div>
        <div className="big-field">
          <label className="big-field__label" htmlFor="bk-email">Email address</label>
          <input id="bk-email" className="big-field__input" type="email" value={email}
            onChange={e => setEmail(e.target.value)} placeholder="e.g. yourname@email.com" autoComplete="email"/>
          {errs.email && <span className="flow__err">{errs.email}</span>}
        </div>
      </div>

      <div className="big-field">
        <label className="big-field__label" htmlFor="bk-address">Your address</label>
        <AddressAutocomplete address={address} setAddress={setAddress}
          setSuburb={setSuburb} setPostcode={setPostcode} setAddrState={setAddrState}/>
        {errs.address && <span className="flow__err">{errs.address}</span>}
      </div>

      <div className="big-field">
        <label className="big-field__label" htmlFor="bk-notes">Any extra notes <span className="opt">(optional)</span></label>
        <textarea ref={notesRef} id="bk-notes" className="big-field__textarea" value={notes}
          onChange={e => setNotes(e.target.value)}
          placeholder="e.g. CCTV for the back gate, 4 cameras, NBN modem is in the laundry."></textarea>
      </div>
    </>
  );
}

// ----- Step 3 -----
function Step3({ date, setDate, slot, setSlot, customTime, setCustomTime, errs }) {
  return (
    <>
      <h2 className="flow__h">Pick a <em>time</em>.</h2>
      <p className="flow__intro">A real human will call to confirm — we never just turn up.</p>

      <div className="big-field">
        <label className="big-field__label">Preferred date</label>
        <InlineCalendar value={date} onChange={setDate}/>
        {errs.date && <span className="flow__err">{errs.date}</span>}
      </div>

      {date && (
        <div className="big-field">
          <label className="big-field__label">Preferred time slot</label>
          <TimeSlotCards slots={BOOK_SLOTS} value={slot} onChange={setSlot} selectedDate={date}/>
          {errs.slot && <span className="flow__err">{errs.slot}</span>}
        </div>
      )}

      <div className="big-field">
        <label className="big-field__label" htmlFor="bk-custom">
          None of these work? Type your preferred time <span className="opt">(optional)</span>
        </label>
        <input id="bk-custom" className="big-field__input" type="text" value={customTime}
          onChange={e => setCustomTime(e.target.value)}
          placeholder="e.g. Tuesday after 3pm, or weekend mornings"/>
        <span className="big-field__help">We'll call you to confirm a time that suits.</span>
      </div>
    </>
  );
}

// ----- Step 4 -----
function Step4({ services, name, phone, email, address, suburb, postcode,
                 notes, date, slot, customTime, onSubmit, submitting, submitErr,
                 payState, elemReady, payDomRef, onEdit, onRetry }) {

  const serviceNames = services
    .map(id => (BOOK_SERVICES.find(s => s.id === id) || {}).name)
    .filter(Boolean).join(', ');

  const dateLabel = date
    ? new Date(date + 'T00:00:00').toLocaleDateString('en-AU',
        { day: 'numeric', month: 'long', year: 'numeric' })
    : null;

  const slotLabel = customTime
    ? (slot ? SLOT_DISPLAY[slot] + ' · ' + customTime : customTime)
    : (slot ? SLOT_DISPLAY[slot] : null);

  const addrParts = [address, suburb, postcode].filter(Boolean);
  const addrDisplay = addrParts.join(', ') || null;

  // Button is disabled until: Stripe element fires 'ready'
  const btnDisabled  = submitting || !elemReady;
  const isLoadingPay = payState === 'loading' || (payState === 'ready' && !elemReady);

  return (
    <>
      <h2 className="flow__h">Confirm <em>& Pay</em>.</h2>
      <p className="flow__intro">Review your booking below then pay the $49 deposit to lock in your appointment.</p>

      {/* A — Booking summary */}
      <div className="pay4-summary">
        <div className="pay4-summary__head">
          <h3>Your booking</h3>
          <button type="button" className="pay4-summary__edit" onClick={onEdit}>← Edit</button>
        </div>

        {serviceNames && (
          <div className="summary-row">
            <span className="summary-label">Services</span>
            <span className="summary-value">{serviceNames}</span>
          </div>
        )}
        {addrDisplay && (
          <div className="summary-row">
            <span className="summary-label">Address</span>
            <span className="summary-value">{addrDisplay}</span>
          </div>
        )}
        {dateLabel && (
          <div className="summary-row">
            <span className="summary-label">Date</span>
            <span className="summary-value">{dateLabel}</span>
          </div>
        )}
        {slotLabel && (
          <div className="summary-row">
            <span className="summary-label">Time</span>
            <span className="summary-value">{slotLabel}</span>
          </div>
        )}
        {name && (
          <div className="summary-row">
            <span className="summary-label">Name</span>
            <span className="summary-value">{name}</span>
          </div>
        )}
        {phone && (
          <div className="summary-row">
            <span className="summary-label">Phone</span>
            <span className="summary-value">{phone}</span>
          </div>
        )}
        {email && (
          <div className="summary-row">
            <span className="summary-label">Email</span>
            <span className="summary-value">{email}</span>
          </div>
        )}
        {notes && (
          <div className="summary-row">
            <span className="summary-label">Notes</span>
            <span className="summary-value">{notes}</span>
          </div>
        )}
      </div>

      {/* B — Deposit info */}
      <div className="pay4-deposit">
        <p className="pay4-deposit__head">💳 $49 Deposit Required</p>
        <p className="pay4-deposit__body">This deposit confirms your appointment. It is fully refundable if we are unable to fulfil your request after our initial call — no questions asked.</p>
        <p className="pay4-deposit__body">You will not be charged anything further until after your visit is complete and you are happy with the result.</p>
      </div>

      {/* C — Stripe Payment Element */}
      {payState === 'error' ? (
        <div style={{
          padding: '20px 24px', background: '#fff3f3',
          border: '1.5px solid #ffcccc', borderRadius: '12px',
          marginBottom: '16px', textAlign: 'center',
        }}>
          <p style={{ color: '#cc0000', marginBottom: '12px', fontWeight: 600 }}>
            Payment form could not load.
          </p>
          {submitErr && (
            <p style={{ fontSize: 14, color: '#cc0000', marginBottom: '12px' }}>
              {submitErr}
            </p>
          )}
          <button className="btn btn--primary" onClick={onRetry}>
            Try Again
          </button>
          <p style={{ fontSize: 13, color: 'rgba(10,10,10,0.45)', marginTop: 10 }}>
            Or call us: <a href="tel:0449991572">0449 991 572</a>
          </p>
        </div>
      ) : (
        <div className="stripe-pay-wrap">
          <p className="stripe-pay-wrap__title">🔒 Secure payment</p>
          {isLoadingPay && (
            <div className="pay4-loading">
              <div className="vt-spinner"/>
              Loading payment form…
            </div>
          )}
          <div ref={payDomRef} id="payment-element"
            style={{
              visibility: payState === 'ready' ? 'visible' : 'hidden',
              minHeight:  payState === 'ready' ? 'auto' : '0px',
              overflow:   'hidden',
            }}/>
        </div>
      )}

      {/* D — Submit */}
      {payState !== 'error' && (
        <div className="flow__submit-row">
          <button className="btn btn--ink btn--lg" onClick={onSubmit}
            disabled={btnDisabled}
            style={{ opacity: btnDisabled ? 0.5 : 1, cursor: btnDisabled ? (submitting ? 'wait' : 'not-allowed') : 'pointer' }}>
            <Icon.Calendar/>
            {submitting ? 'Processing payment…' : (elemReady ? 'Confirm & Pay $49' : 'Loading payment form…')}
            {!submitting && elemReady && <Icon.Arrow/>}
          </button>
          <div className="flow__submit-foot">
            Deposit is 100% refundable if we can't help. Prefer to call first? <a href="tel:0449991572">0449 991 572</a>
          </div>
          {submitErr && (
            <div style={{ marginTop: 12, textAlign: 'center' }}>
              <span className="flow__err">{submitErr}</span>
            </div>
          )}
        </div>
      )}
    </>
  );
}

// ----- SuccessCard (kept for consult-page import) -----
function SuccessCard({ title, sub, refId }) {
  return (
    <div className="success-card">
      <div className="success-card__icon"><Icon.Check/></div>
      <h2 className="success-card__title">{title}</h2>
      {refId && (
        <div style={{ margin: '18px 0', padding: '18px 24px', background: 'rgba(232,98,26,0.07)',
          borderRadius: '12px', border: '1.5px solid rgba(232,98,26,0.22)', textAlign: 'center' }}>
          <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: '#E8621A', marginBottom: 8 }}>
            Your Reference Number
          </div>
          <div style={{ fontSize: 30, fontWeight: 800, color: '#E8621A', letterSpacing: '0.05em', fontFamily: 'monospace' }}>
            {refId}
          </div>
          <div style={{ fontSize: 12, color: 'rgba(10,10,10,0.4)', marginTop: 6 }}>
            Save this — we'll reference it when we call you
          </div>
        </div>
      )}
      <p className="success-card__sub">{sub}</p>
      <a className="btn btn--primary btn--lg success-card__cta" href="index.html">
        Back to home <Icon.Arrow/>
      </a>
    </div>
  );
}

Object.assign(window, { TechnicianBookingPage, SuccessCard, InlineCalendar, TimeSlotCards });
