// Multi-step booking modal — live windows from the dispatch board (CB_DISPATCH)
const { useState: useStateB, useEffect: useEffectB } = React;

// TCPA consent block. Two explicit opt-ins, unchecked by default.
function ConsentBox({ checked, onChange, smsChecked, emailChecked, onSmsChange, onEmailChange }) {
  const smsOn = typeof smsChecked === 'boolean' ? smsChecked : !!checked;
  const emailOn = typeof emailChecked === 'boolean' ? emailChecked : !!checked;
  const setSms = onSmsChange || onChange || (() => {});
  const setEmail = onEmailChange || onChange || (() => {});
  const smsText = window.CB_DISPATCH ? window.CB_DISPATCH.SMS_CONSENT_TEXT : '';
  const smsTermsLabel = 'SMS Terms & Privacy Policy';
  const smsTextParts = smsText.split(smsTermsLabel);
  return (
    <div className="consent-stack">
      <label className="consent-box">
        <input type="checkbox" checked={smsOn} onChange={e => setSms(e.target.checked)} />
        <span className="consent-text">
          {smsTextParts.length > 1 ? (
            <>{smsTextParts[0]}<a href="sms-terms.html" target="_blank" rel="noopener" onClick={e => e.stopPropagation()}>{smsTermsLabel}</a>{smsTextParts.slice(1).join(smsTermsLabel)}</>
          ) : smsText}
        </span>
      </label>
      <label className="consent-box">
        <input type="checkbox" checked={emailOn} onChange={e => setEmail(e.target.checked)} />
        <span className="consent-text">{window.CB_DISPATCH ? window.CB_DISPATCH.EMAIL_CONSENT_TEXT : ''}</span>
      </label>
    </div>
  );
}
window.ConsentBox = ConsentBox;

function BookingModal({ open, onClose, prefill }) {
  const [step, setStep] = useStateB(0);
  const [memberAdd, setMemberAdd] = useStateB(false);
  const [slots, setSlots] = useStateB(undefined); // undefined=loading, null=feed down, []=none
  const [resultMsg, setResultMsg] = useStateB("");
  // Address verification verdict from Sheetz: {status:'checking'|'ok'|'review'|'bad', message, verified}
  const [addrCheck, setAddrCheck] = useStateB(null);
  const [submitting, setSubmitting] = useStateB(false);  // prevents double-submit / duplicate bookings
  const [bookError, setBookError] = useStateB("");        // shown only if BOTH primary + backup sends fail
  const [hp, setHp] = useStateB("");                      // honeypot — real users never fill this
  const panelRef = React.useRef(null);                    // dialog focus management
  const idemKeyRef = React.useRef("");                    // idempotency key — one per booking session
  const submittingRef = React.useRef(false);              // SYNCHRONOUS guard — blocks same-tick double-submit
  const [data, setData] = useStateB({
    service: "",
    when: "",
    whenISO: "",
    date: "",        // YYYY-MM-DD of the picked live slot (blank = office confirms)
    window: "",      // exact Sheetz arrival-window label of the picked slot
    name: "",
    phone: "",
    email: "",
    address: "",
    city: "Richmond",
    homeAge: "",
    platePhoto: "",  // optional data-plate photo (data URL) for water heaters etc.
    notes: "",
    smsConsent: false,
    emailConsent: false,
  });

  useEffectB(() => {
    if (open && prefill) {
      const map = {
        clog: "Drain Cleaning",
        leak: "Leak Detection",
        noHotWater: "Water Heater Repair",
        sewer: "Sewer Services",
        noWater: "Water Line",
        lowPressure: "Plumbing Repairs",
        'wh-install': "Water Heater Install",
        'wh-tankless-sizing': "Tankless Water Heater Sizing",
      };
      setData(d => ({
        ...d,
        service: prefill.presetService || map[prefill.problem] || d.service,
        notes: prefill.notes || d.notes,
      }));
    }
  }, [open, prefill]);

  // Pull live open windows from the dispatch board when the modal opens
  useEffectB(() => {
    if (!open) return;
    setSlots(undefined);
    // one idempotency key per booking session — lets the backend + backup dedupe retries/double-taps
    idemKeyRef.current = (window.crypto && crypto.randomUUID) ? crypto.randomUUID() : (String(Date.now()) + Math.random().toString(36).slice(2));
    if (window.CB_DISPATCH) {
      window.CB_DISPATCH.loadSlots(res => setSlots(res));
    } else {
      setSlots(null);
    }
  }, [open]);

  // Accessible dialog: focus the panel on open, trap Tab inside, Esc closes, restore focus on close.
  useEffectB(() => {
    if (!open) return;
    const panel = panelRef.current;
    if (!panel) return;
    const prevFocus = document.activeElement;
    const getF = () => Array.from(panel.querySelectorAll(
      'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])'
    )).filter(el => el.offsetParent !== null);
    const f0 = getF()[0];
    if (f0) { try { f0.focus(); } catch (e) {} } else { try { panel.focus(); } catch (e) {} }
    const onKey = (e) => {
      if (e.key === 'Escape') { e.preventDefault(); onClose(); return; }
      if (e.key === 'Tab') {
        const f = getF();
        if (!f.length) return;
        const first = f[0], last = f[f.length - 1];
        if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
        else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
      }
    };
    panel.addEventListener('keydown', onKey);
    return () => { panel.removeEventListener('keydown', onKey); try { if (prevFocus && prevFocus.focus) prevFocus.focus(); } catch (e) {} };
  }, [open]);

  if (!open) return null;

  const upd = (k, v) => setData(d => ({ ...d, [k]: v }));
  const total = 4;
  const next = () => setStep(s => Math.min(s + 1, total));
  const back = () => setStep(s => Math.max(s - 1, 0));

  // Verify the service address with Sheetz: real + mappable + in our area. Fail-soft — if the check
  // can't run, we don't block (the office still reviews every web booking).
  const verifyAddr = async () => {
    const street = data.address.trim(), city = data.city.trim();
    if (!street || !city) { setAddrCheck(null); return null; }
    setAddrCheck({ status: 'checking', message: 'Checking your address…' });
    try {
      const r = await fetch('https://tech.sheetzz.com/api/validate-address', {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ address: street, city, state: 'KY' }),
      });
      const j = await r.json();
      let v;
      if (!j || j.ok === false || j.configured === false) v = { status: 'ok', message: '' };
      else if (j.real === false) v = { status: 'bad', message: j.message || "We couldn't find that address — please check it." };
      else if (j.hasStreetNumber === false) v = { status: 'bad', message: j.message || 'Add the house/street number so the crew can find you.' };
      else if (j.needsReview) v = { status: 'review', message: j.message, verified: j.verified };
      else v = { status: 'ok', message: '✓ Address verified', verified: j.verified };
      setAddrCheck(v);
      return v;
    } catch (e) { const v = { status: 'ok', message: '' }; setAddrCheck(v); return v; }
  };

  // Advance handler — on the DETAILS step, run/await the address check before moving to CONFIRM.
  const onStepNext = async () => {
    if (step === 2) {
      let v = addrCheck;
      if (!v || v.status === 'checking' || v.status === 'bad') v = await verifyAddr();
      if (!v || v.status === 'bad') return; // block on a bad/unfound address; user fixes it
    }
    next();
  };

  const services = window.CB_DATA.services.slice(0, 12);
  const fallbackSlots = ["TODAY · ASAP","TOMORROW · MORNING","TOMORROW · AFTERNOON","THIS WEEK · FLEXIBLE"];
  const validPhone = data.phone.replace(/\D/g, '').length >= 10;
  const validEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email.trim());
  const requestedISO = data.whenISO ? data.whenISO.slice(0, 16) : '';
  const fullAddress = [data.address.trim(), data.city.trim() ? `${data.city.trim()} KY` : ''].filter(Boolean).join(', ');
  const urgencyForBoard = (() => {
    const w = (data.when || '').toLowerCase();
    if (w.includes('asap') || w.includes('emergency')) return 'emergency';
    if (w.includes('today')) return 'today';
    if (w.includes('tomorrow') || w.includes('week')) return 'this week';
    return 'flexible';
  })();
  const notesForBoard = [
    memberAdd ? 'JOIN CB GOLD CARD EARLY LIST (membership not launched yet)' : '',
    data.notes || '',
    requestedISO ? `Requested window ${requestedISO}` : (data.when ? `Requested preference ${data.when}` : ''),
  ].filter(Boolean).join('\n');

  // Water-damage cross-sell gate — only offer Floodbusterz when this booking is an actual
  // water-escape job (leak/flood/burst/water line/sump/flood-tagged diagnosis). Shown at the END
  // (success screen). Replaces the old site-wide exit-intent/scroll popup that fired for everyone.
  const isWaterDamage = (() => {
    const hay = [data.service, data.notes, prefill && prefill.presetService, prefill && prefill.problem]
      .filter(Boolean).join(' ').toLowerCase();
    return !!(prefill && prefill.flood) || /leak|flood|burst|water line|water damage|slab|sump|sewer back|standing water|ceiling|soaked/.test(hay);
  })();

  const canNext = (
    (step === 0 && data.service) ||
    (step === 1 && data.when) ||
    (step === 2 && data.name.trim() && validPhone && validEmail && data.address.trim())
  );

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" role="dialog" aria-modal="true" aria-labelledby="booking-modal-title" ref={panelRef} tabIndex={-1} onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div>
            <div className="modal-tag">SERVICE REQUEST</div>
            <div className="modal-title" id="booking-modal-title">REQUEST A VISIT</div>
          </div>
          <button className="modal-x" onClick={onClose} aria-label="Close booking dialog">×</button>
        </div>

        <div className="modal-progress">
          {["SERVICE","WHEN","DETAILS","CONFIRM"].map((l, i) => (
            <div key={i} className={`mp-step ${i <= step ? 'on' : ''} ${i === step ? 'cur' : ''}`}>
              <div className="mp-num">{String(i+1).padStart(2,'0')}</div>
              <div className="mp-lbl">{l}</div>
            </div>
          ))}
        </div>

        <div className="modal-body">
          {step === 0 && (
            <div>
              <div className="step-label">STEP 01 — WHAT DO YOU NEED?</div>
              <div className="svc-pick-grid">
                {data.service && !services.some(s => s.name === data.service) && (
                  <button className="svc-pick sel" onClick={() => upd('service', data.service)}>
                    <div className="svc-pick-code">{prefill?.serviceCode || 'REQ'}</div>
                    <div className="svc-pick-name">{data.service}</div>
                  </button>
                )}
                {services.map(s => (
                  <button key={s.id} className={`svc-pick ${data.service === s.name ? 'sel' : ''}`} onClick={() => upd('service', s.name)}>
                    <div className="svc-pick-code">{s.code}</div>
                    <div className="svc-pick-name">{s.name}</div>
                  </button>
                ))}
              </div>
            </div>
          )}

          {step === 1 && (
            <div>
              <div className="step-label">STEP 02 — WHEN WORKS FOR YOU?</div>
              {slots === undefined && (
                <div className="slots-loading">CHECKING THE LIVE DISPATCH BOARD…</div>
              )}
              {slots && slots.length > 0 && (
                <>
                  <div className="slots-live-tag">● LIVE · OPEN WINDOWS ON OUR BOARD RIGHT NOW</div>
                  {(() => {
                    const shown = slots.slice(0, 16);
                    const soonestISO = shown[0] && shown[0].startISO;
                    const groups = [];
                    shown.forEach(w => {
                      const day = (w.label || '').split(' · ')[0] || w.date;
                      let g = groups.find(x => x.date === w.date);
                      if (!g) { g = { date: w.date, day, windows: [] }; groups.push(g); }
                      g.windows.push(w);
                    });
                    return groups.map(g => (
                      <div key={g.date} className="slot-day" style={{ marginBottom: '14px' }}>
                        <div className="slot-day-h" style={{ fontWeight: 800, fontSize: '13px', letterSpacing: '.04em', textTransform: 'uppercase', opacity: .72, margin: '2px 0 8px' }}>{g.day}</div>
                        <div className="slot-grid">
                          {g.windows.map(w => (
                            <button key={w.startISO} className={`slot ${data.whenISO === w.startISO ? 'sel' : ''} ${w.startISO === soonestISO ? 'soonest' : ''}`}
                              onClick={() => setData(d => ({ ...d, when: w.label, whenISO: w.startISO, date: w.date || '', window: w.window || '' }))}>
                              <span className="slot-time">{w.window || w.label}</span>
                              <span className="slot-meta">
                                {w.startISO === soonestISO && <span className="slot-badge slot-soonest">⚡ Soonest</span>}
                                {typeof w.remaining === 'number' && w.remaining > 0 && w.remaining <= 2 && w.startISO !== soonestISO && <span className="slot-badge slot-left">{w.remaining} left</span>}
                                <span className="slot-check">{data.whenISO === w.startISO ? '✓' : '→'}</span>
                              </span>
                            </button>
                          ))}
                        </div>
                      </div>
                    ));
                  })()}
                  <div className="slots-confirm-note">Pick a window and we'll text to confirm it. These are open right now, but your time isn't locked until we confirm — a true emergency can always shift the board.</div>
                </>
              )}
              {slots === null && (
                <>
                  <div className="slots-fallback-note">Pick the timing that works best — a dispatcher will call to confirm your exact arrival window.</div>
                  <div className="slot-grid">
                    {fallbackSlots.map(s => (
                      <button key={s} className={`slot ${data.when === s ? 'sel' : ''}`} onClick={() => setData(d => ({ ...d, when: s, whenISO: '', date: '', window: '' }))}>
                        <span className="slot-time">{s}</span>
                        <span className="slot-meta"><span className="slot-check">{data.when === s ? '✓' : '→'}</span></span>
                      </button>
                    ))}
                  </div>
                </>
              )}
              <div className="emergency-note">
                <strong>ACTIVELY FLOODING?</strong> Don't book online — call <a href={`tel:${window.CB_DATA.brand.phoneRaw}`}>{window.CB_DATA.brand.phone}</a> right now.
              </div>
            </div>
          )}

          {step === 2 && (
            <div>
              <div className="step-label">STEP 03 — WHERE'S THE CREW HEADED?</div>
              {/* Honeypot — hidden from real users; a filled value means a bot, and we silently drop it. */}
              <input type="text" name="company" tabIndex={-1} autoComplete="off" aria-hidden="true"
                value={hp} onChange={e => setHp(e.target.value)}
                style={{ position: 'absolute', width: '1px', height: '1px', padding: 0, margin: '-1px', overflow: 'hidden', clip: 'rect(0,0,0,0)', whiteSpace: 'nowrap', border: 0 }} />
              <div className="form-grid">
                <Field label="Full Name" name="name" type="text" autoComplete="name" required value={data.name} onChange={v => upd('name', v)} placeholder="Jane Smith" />
                <Field label="Phone" name="phone" type="tel" inputMode="tel" autoComplete="tel" required value={data.phone} onChange={v => upd('phone', v)} placeholder="(859) 555-0123" />
                <Field label="Email" name="email" type="email" inputMode="email" autoComplete="email" required value={data.email} onChange={v => upd('email', v)} placeholder="jane@email.com" />
                <Field label="Address" name="address" type="text" autoComplete="street-address" required value={data.address} onChange={v => upd('address', v)} placeholder="123 Main St" onBlur={() => { if (data.address.trim() && data.city.trim()) verifyAddr(); }} full />
                <Field label="City" name="city" type="text" autoComplete="address-level2" value={data.city} onChange={v => upd('city', v)} placeholder="Richmond" onBlur={() => { if (data.address.trim() && data.city.trim()) verifyAddr(); }} />
                <Field label="Notes (optional)" name="notes" autoComplete="off" value={data.notes} onChange={v => upd('notes', v)} placeholder="Anything we should know?" textarea full />
              </div>

              {/* Address verification — confirm it's real + in our service area before booking a slot. */}
              <div className="addr-verify-row" style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: '10px', margin: '4px 0 6px' }}>
                <button type="button" className="btn btn-ghost" style={{ flex: '0 0 auto' }}
                  disabled={!data.address.trim() || !data.city.trim() || (addrCheck && addrCheck.status === 'checking')} onClick={verifyAddr}>
                  {addrCheck && addrCheck.status === 'checking' ? 'Checking…' : 'Check my address'}
                </button>
                {addrCheck && addrCheck.message && (
                  <div style={{
                    flex: '1 1 220px', fontSize: '13px', lineHeight: 1.35, padding: '8px 10px', borderRadius: '6px',
                    color: addrCheck.status === 'bad' ? '#7a1c1c' : addrCheck.status === 'review' ? '#7a4a00' : addrCheck.status === 'ok' ? '#0a5a2a' : '#555',
                    background: addrCheck.status === 'bad' ? '#fdeaea' : addrCheck.status === 'review' ? '#fff4e0' : addrCheck.status === 'ok' ? '#e9f7ef' : '#f1f1f1',
                    border: '1px solid ' + (addrCheck.status === 'bad' ? '#f0bcbc' : addrCheck.status === 'review' ? '#f0d49a' : addrCheck.status === 'ok' ? '#bfe6cd' : '#ddd'),
                  }}>
                    {addrCheck.status === 'ok' ? '✅ ' : addrCheck.status === 'review' ? '🗺 ' : addrCheck.status === 'bad' ? '⚠️ ' : '⏳ '}
                    {addrCheck.message}
                  </div>
                )}
              </div>

              {/* Qualifying questions — give dispatch what they need to quote/dispatch right. */}
              <div className="form-grid">
                <label className="field">
                  <span>How old is the home? (optional)</span>
                  <select value={data.homeAge} onChange={e => upd('homeAge', e.target.value)}>
                    <option value="">Not sure</option>
                    <option value="0-5 yrs">Newer (0–5 yrs)</option>
                    <option value="6-15 yrs">6–15 yrs</option>
                    <option value="16-30 yrs">16–30 yrs</option>
                    <option value="30+ yrs">Older (30+ yrs)</option>
                  </select>
                </label>
                <label className="field photo-field">
                  <span>📷 Photo of the unit's data plate (optional)</span>
                  <span className="photo-help">Water heater? Snap the silver label so we bring the right parts.</span>
                  <input type="file" accept="image/*" capture="environment" onChange={e => {
                    const f = e.target.files && e.target.files[0];
                    if (!f) return;
                    const r = new FileReader();
                    r.onload = () => upd('platePhoto', String(r.result || ''));
                    r.readAsDataURL(f);
                  }} />
                  {data.platePhoto && <span className="photo-ok">✓ Photo attached <button type="button" className="member-undo" onClick={() => upd('platePhoto', '')}>remove</button></span>}
                </label>
              </div>

              {data.phone && !validPhone && <div className="consent-hint">Use a 10-digit phone number so dispatch can reach you.</div>}
              {data.email && !validEmail && <div className="consent-hint">Use a valid email so dispatch can prefill the job correctly.</div>}
              <ConsentBox
                smsChecked={data.smsConsent}
                emailChecked={data.emailConsent}
                onSmsChange={v => upd('smsConsent', v)}
                onEmailChange={v => upd('emailConsent', v)}
              />
            </div>
          )}

          {step === 3 && (
            <div className="confirm">
              <div className="confirm-receipt">
                <div className="cr-head">
                  <div>
                    <div className="cr-brand">CLOG BUSTERZ · DISPATCH</div>
                    <div className="cr-sub">License #M7707 · Richmond, KY</div>
                  </div>
                  <div className="cr-num">NEW</div>
                </div>
                <ConfirmRow k="SERVICE" v={data.service} />
                <ConfirmRow k="WHEN" v={data.when} />
                <ConfirmRow k="NAME" v={data.name} />
                <ConfirmRow k="PHONE" v={data.phone} />
                <ConfirmRow k="EMAIL" v={data.email} />
                <ConfirmRow k="ADDRESS" v={fullAddress} />
                <ConfirmRow k="PROMO" v="ONLINE25 · $25 OFF — booked online" />
                {notesForBoard && <ConfirmRow k="NOTES" v={notesForBoard} />}
                <div className="cr-bar" />
                <div className="cr-foot">
                  <div>STATUS</div>
                  <div className="cr-status">● READY TO DISPATCH</div>
                </div>
              </div>

              {/* Membership upsell — only when this isn't already a membership signup */}
              {data.service !== 'CB Gold Card Membership' && !memberAdd && (
                <button className="member-upsell" onClick={() => setMemberAdd(true)} type="button">
                  <span className="member-upsell-badge">+ ADD</span>
                  <span className="member-upsell-body">
                    <span className="member-upsell-h">Make this job a member job — waive your fee today.</span>
                    <span className="member-upsell-d">The <strong>CB Gold Card ($19/mo)</strong> is coming soon — waived service fees, $25 off repairs, plus Busterz Family rates at Floodbusterz &amp; Lawn Busterz. Tap to join the early list and we'll let you know the moment it launches.</span>
                  </span>
                </button>
              )}
              {memberAdd && (
                <div className="member-added">
                  <span className="member-added-check">✓</span>
                  <span>You're on the CB Gold Card early list — we'll reach out when it launches. <button className="member-undo" onClick={() => setMemberAdd(false)} type="button">undo</button></span>
                </div>
              )}

              {bookError && (
                <div className="book-error" role="alert" style={{ margin: '10px 0', padding: '10px 12px', borderRadius: '6px', background: '#fdeaea', border: '1px solid #f0bcbc', color: '#7a1c1c', fontSize: '14px', lineHeight: 1.4 }}>
                  ⚠️ {bookError}
                </div>
              )}
              <div className="confirm-cta">
                <button className="btn btn-primary big-btn" disabled={submitting} onClick={async () => {
                  if (submittingRef.current) return;       // synchronous double-submit guard → no duplicate bookings
                  if (hp) { setStep(4); return; }          // honeypot tripped → silently drop the bot
                  submittingRef.current = true;
                  setSubmitting(true);
                  setBookError('');
                  const idempotencyKey = idemKeyRef.current || '';
                  // ── 1. SHEETZ DISPATCH APP — books the exact picked window (or holds for the office).
                  let primaryOk = false;
                  let res = { ok: false, status: 0, message: '' };
                  if (window.CB_DISPATCH) {
                    try {
                      res = await window.CB_DISPATCH.sendLead({
                        source: 'booking-modal',
                        idempotencyKey,                 // backend can dedupe retries by this key
                        name: data.name,
                        phone: data.phone,
                        email: data.email,
                        address: fullAddress,
                        city: data.city,
                        location: data.city,            // qualifying: where it's located
                        homeAge: data.homeAge,          // qualifying: how old the home is
                        service: data.service,
                        urgency: urgencyForBoard,
                        emergency: urgencyForBoard === 'emergency',
                        notes: notesForBoard,
                        date: data.date,                // exact slot (blank = office confirms)
                        window: data.window,
                        platePhoto: data.platePhoto,    // optional data-plate photo for OCR
                        referralCode: (window.CB_DISPATCH.refCode ? window.CB_DISPATCH.refCode() : ''),
                        promo: 'ONLINE25',
                        smsConsent: data.smsConsent ? 'yes' : 'no',
                        emailConsent: data.emailConsent ? 'yes' : 'no',
                      });
                      primaryOk = !!(res && res.ok !== false && res.status !== 409);
                    } catch (e) { primaryOk = false; }
                  }
                  // The picked window just filled → bounce back to choose another.
                  if (res && res.status === 409) {
                    submittingRef.current = false;
                    setSubmitting(false);
                    setResultMsg('');
                    setData(d => ({ ...d, when: '', whenISO: '', date: '', window: '' }));
                    setSlots(undefined);
                    if (window.CB_DISPATCH) window.CB_DISPATCH.loadSlots(r => setSlots(r));
                    setStep(1);
                    return;
                  }
                  // ── 2. BACKUP — if the primary didn't clearly succeed (API down / no dispatch bridge),
                  //    capture the lead via Formspree so it's NEVER lost. This email also alerts the office.
                  let backupOk = false;
                  if (!primaryOk) {
                    try {
                      const fr = await fetch('https://formspree.io/f/mbdekjeo', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({
                          _subject: `⚠️ CB Booking BACKUP · ${data.service || 'General'} · ${data.city || 'KY'}`,
                          _cc: 'ronniem@clogbusterzplumbing.com,devin@clogbusterzplumbing.com',
                          idempotencyKey,
                          name: data.name,
                          phone: data.phone,
                          email: data.email,
                          address: fullAddress,
                          service: data.service,
                          when: data.when,
                          notes: notesForBoard,
                          requestedWindow: requestedISO,
                          membership: memberAdd ? 'GOLD CARD EARLY LIST (not launched yet)' : 'no',
                          promo: 'ONLINE25',
                          smsConsent: data.smsConsent ? 'yes' : 'no',
                          emailConsent: data.emailConsent ? 'yes' : 'no',
                          consentText: window.CB_DISPATCH ? window.CB_DISPATCH.CONSENT_TEXT : '',
                          alert: 'PRIMARY DISPATCH SEND FAILED — please follow up with this customer manually.',
                          source: 'Main Site — Book Online (backup path)',
                        })
                      });
                      backupOk = !!(fr && fr.ok);
                    } catch (e) { console.warn('Booking backup error', e); }
                  }
                  // ── 3. Both paths failed → don't fake success; keep them on Confirm to retry or call.
                  if (!primaryOk && !backupOk) {
                    submittingRef.current = false;
                    setSubmitting(false);
                    setBookError("We couldn't reach dispatch just now — please call 859-408-3382 so we don't miss you, or tap Confirm to try again. Your details are saved here.");
                    return;
                  }
                  setResultMsg(
                    primaryOk ? (res && res.message ? res.message : '')
                    : "Got it — your request is in. If you don't hear from us shortly, call 859-408-3382."
                  );
                  // 📊 Conversion (analytics.js listens; no-op until GA4 / Ads / Pixel ids are set)
                  try { window.dispatchEvent(new CustomEvent('cb-booked', { detail: { service: data.service, backup: !primaryOk } })); } catch (e) {}
                  submittingRef.current = false;
                  setSubmitting(false);
                  setStep(4);
                }}>
                  {submitting ? 'SENDING…' : 'SEND MY REQUEST'}
                </button>
                <p>A dispatcher will call or text to confirm your time.</p>
              </div>
            </div>
          )}

          {step === 4 && (
            <div className="success">
              <div className="success-mark">
                <svg width="64" height="64" viewBox="0 0 64 64" fill="none">
                  <path d="M14 33 L27 46 L51 18" stroke="currentColor" strokeWidth="4" strokeLinecap="square" strokeLinejoin="miter"/>
                </svg>
              </div>
              <h3>REQUEST RECEIVED</h3>
              <p>{resultMsg || `Your request${data.when ? ` for ${data.when}` : ''} is in. A dispatcher will call or text ${data.phone || "you"} to confirm your time.`}</p>
              <p className="success-promo">Your $25 online-booking discount (ONLINE25) is attached.</p>
              {isWaterDamage && (
                <div className="success-flood" style={{ margin: '16px 0 4px', padding: '14px 16px', borderRadius: '10px', background: 'rgba(56,132,255,.08)', border: '1px solid rgba(56,132,255,.35)', textAlign: 'left' }}>
                  <div style={{ fontWeight: 800, fontSize: '14px', letterSpacing: '.02em', marginBottom: '6px' }}>🌊 Did water already cause damage?</div>
                  <div style={{ fontSize: '13px', lineHeight: 1.45, opacity: .9 }}>If this leak soaked drywall, flooring, or a ceiling, our sister company <strong>Floodbusterz</strong> handles water extraction, structural drying, and insurance-claim paperwork — separate crews, same family, one call covers both.</div>
                  <button type="button" className="btn btn-ghost" style={{ marginTop: '10px' }} onClick={() => { onClose(); setTimeout(() => { try { window.dispatchEvent(new CustomEvent('cb-open-flood')); } catch (e) {} }, 80); }}>See Floodbusterz →</button>
                </div>
              )}
              <button className="btn btn-ghost" onClick={onClose}>Done</button>
            </div>
          )}
        </div>

        {step < 3 && (
          <div className="modal-foot">
            <button className="btn btn-text" onClick={back} disabled={step === 0}>← Back</button>
            <button className="btn btn-primary" disabled={!canNext} onClick={onStepNext}>
              {step === 2 && addrCheck && addrCheck.status === 'review' ? 'Continue → (we’ll review)' : 'Next →'}
            </button>
          </div>
        )}
        {step === 3 && (
          <div className="modal-foot">
            <button className="btn btn-text" onClick={back}>← Back</button>
          </div>
        )}
      </div>
    </div>
  );
}

function Field({ label, value, onChange, placeholder, textarea, full, onBlur, type, name, autoComplete, required, inputMode }) {
  const id = 'bf-' + (name || label).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
  return (
    <label className={`field ${full ? 'full' : ''}`} htmlFor={id}>
      <span>{label}{required ? ' *' : ''}</span>
      {textarea ? (
        <textarea id={id} name={name || id} value={value} onChange={e => onChange(e.target.value)} onBlur={onBlur} placeholder={placeholder} rows={3} autoComplete={autoComplete} required={required} />
      ) : (
        <input id={id} name={name || id} type={type || 'text'} inputMode={inputMode} value={value} onChange={e => onChange(e.target.value)} onBlur={onBlur} placeholder={placeholder} autoComplete={autoComplete} required={required} />
      )}
    </label>
  );
}

function ConfirmRow({ k, v }) {
  return (
    <div className="cr-row">
      <span className="cr-k">{k}</span>
      <span className="cr-v">{v}</span>
    </div>
  );
}

window.BookingModal = BookingModal;
