/* order-modal.jsx - multi-step order request wired to Cloudflare D1 APIs. */

const SIZE_INFO = {
  large: { id: 'large', name: 'Large loaf', w: 9, l: 13, perFirst: 30, perMore: 20 },
  small: { id: 'small', name: 'Regular loaf', w: 9, l: 5,  perFirst: 15, perMore: 10 },
};

const MAX_PER_SIZE = 5;
const ORDER_TEXT_LAUREL_HREF = 'sms:+17174915845?&body=' + encodeURIComponent('Hey Laurel! I loved your banana bread. Can I get more info?');
const LAUREL_PHONE_DISPLAY = '(717) 491-5845';

const FLAVORS = [
  { id: 'original',          name: 'Original',          sub: 'Classic banana bread.',                     image: 'assets/flavor-original.png'  },
  { id: 'cinnamon_crumble',  name: 'Cinnamon Crumble',  sub: 'Crumb topping, no internal swirl.',          image: 'assets/flavor-cinnamon.png'  },
  { id: 'pumpkin_spice',     name: 'Pumpkin Spice',     sub: 'Seasonal pumpkin spice flavor.',             image: 'assets/flavor-pumpkin.png'   },
  { id: 'chocolate_chip',    name: 'Chocolate Chip',    sub: 'Chocolate chips or chunks mixed in.',        image: 'assets/flavor-chocchip.png'  },
  { id: 'nutella',           name: 'Nutella',           sub: 'Swirl baked through.',                       image: 'assets/flavor-nutella.png'   },
];
const FLAVOR_BY_ID = Object.fromEntries(FLAVORS.map((f) => [f.id, f]));

const PAYMENT_METHODS = [
  { id: 'cash_app',  name: 'Cash App',  hint: 'Easiest', bg: '#00C244', fg: '#FFFFFF', glyph: 'cashapp' },
  { id: 'apple_pay', name: 'Apple Pay', hint: '',        bg: '#000000', fg: '#FFFFFF', glyph: 'apple'   },
  { id: 'paypal',    name: 'PayPal',    hint: '',        bg: '#003087', fg: '#FFFFFF', glyph: 'paypal'  },
  { id: 'cash',      name: 'Cash',      hint: 'On hand', bg: '#1F5E2E', fg: '#FFFFFF', glyph: 'cash'    },
];

function PayGlyph({ kind }) {
  const fontProps = { fontFamily: 'system-ui, -apple-system, "Helvetica Neue", sans-serif', fontWeight: 700 };
  switch (kind) {
    case 'cashapp':
      return <span style={{ ...fontProps, fontSize: 17, fontStyle: 'italic' }}>$</span>;
    case 'apple':
      return (
        <svg width="16" height="18" viewBox="0 0 24 26" fill="currentColor" aria-hidden="true">
          <path d="M16.5 13.5c0-2.6 2.1-3.9 2.2-3.9-1.2-1.7-3-2-3.7-2-1.6-.2-3 .9-3.8.9-.7 0-2-.9-3.3-.9-1.7 0-3.3 1-4.2 2.5-1.8 3.1-.5 7.7 1.3 10.3.9 1.3 1.9 2.7 3.2 2.6 1.3 0 1.8-.8 3.4-.8s2 .8 3.4.8c1.4 0 2.3-1.3 3.1-2.6.6-.9 1.1-2 1.3-3-2.8-.8-2.9-3.9-2.9-3.9zM14 5c.7-.9 1.2-2.1 1.1-3.3-1 0-2.3.7-3 1.5-.7.8-1.3 2-1.1 3.2 1.1.1 2.3-.6 3-1.4z"/>
        </svg>
      );
    case 'paypal':
      return <span style={{ ...fontProps, fontStyle: 'italic', fontSize: 16, letterSpacing: '-0.04em' }}>PP</span>;
    case 'cash':
      return (
        <svg width="20" height="14" viewBox="0 0 24 14" fill="none" stroke="currentColor" strokeWidth="1.6" aria-hidden="true">
          <rect x="1" y="1" width="22" height="12" rx="1.5" />
          <circle cx="12" cy="7" r="2.6" />
        </svg>
      );
    default: return null;
  }
}

function SizeIcon({ wIn, lIn, active }) {
  const MAX_LONG = 60;
  const MAX_REF = 13;
  const scale = MAX_LONG / MAX_REF;
  const rectW = lIn * scale;
  const rectH = wIn * scale;
  const padTop = 18, padLeft = 26, padRight = 8, padBottom = 12;
  const boxW = padLeft + rectW + padRight;
  const boxH = padTop + rectH + padBottom;
  const rectX = padLeft, rectY = padTop;
  const stroke = active ? 'var(--lb-mulberry-wine)' : 'var(--lb-vintage-clay)';
  const fill = active ? 'rgba(97,24,41,0.06)' : 'rgba(178,96,96,0.05)';
  return (
    <svg width={boxW} height={boxH} viewBox={`0 0 ${boxW} ${boxH}`} aria-hidden="true">
      <rect x={rectX} y={rectY} width={rectW} height={rectH} rx="2.5" fill={fill} stroke={stroke} strokeWidth="1.3" />
      <line x1={rectX} y1={rectY - 8} x2={rectX + rectW} y2={rectY - 8} stroke={stroke} strokeWidth="1" strokeDasharray="2 3" strokeLinecap="round" />
      <line x1={rectX} y1={rectY - 11} x2={rectX} y2={rectY - 5} stroke={stroke} strokeWidth="1" />
      <line x1={rectX + rectW} y1={rectY - 11} x2={rectX + rectW} y2={rectY - 5} stroke={stroke} strokeWidth="1" />
      <text x={rectX + rectW / 2} y={rectY - 12} textAnchor="middle" style={{ font: '500 8.5px "Cardo", Georgia, serif' }} fill={stroke}>{lIn}″</text>
      <line x1={rectX - 8} y1={rectY} x2={rectX - 8} y2={rectY + rectH} stroke={stroke} strokeWidth="1" strokeDasharray="2 3" strokeLinecap="round" />
      <line x1={rectX - 11} y1={rectY} x2={rectX - 5} y2={rectY} stroke={stroke} strokeWidth="1" />
      <line x1={rectX - 11} y1={rectY + rectH} x2={rectX - 5} y2={rectY + rectH} stroke={stroke} strokeWidth="1" />
      <text x={rectX - 12} y={rectY + rectH / 2 + 3} textAnchor="end" style={{ font: '500 8.5px "Cardo", Georgia, serif' }} fill={stroke}>{wIn}″</text>
    </svg>
  );
}

function priceForSize(sizeId, count) {
  if (count === 0) return 0;
  const s = SIZE_INFO[sizeId];
  return s.perFirst + (count - 1) * s.perMore;
}
function grossForSize(sizeId, count) {
  if (count === 0) return 0;
  return count * SIZE_INFO[sizeId].perFirst;
}
function computeTotals({ largeCount, smallCount, mixIns, loafSelections }) {
  const loaves = largeCount + smallCount;
  const mixInCount = loafSelections ? loafSelections.reduce((sum, loaf) => sum + ((loaf.mixIns && loaf.mixIns.length) || 0), 0) : ((mixIns && mixIns.length) || 0) * loaves;
  const extrasFee = mixInCount * 2;
  const gross = grossForSize('large', largeCount) + grossForSize('small', smallCount) + extrasFee;
  const net = priceForSize('large', largeCount) + priceForSize('small', smallCount) + extrasFee;
  return { gross, net, saved: gross - net, loaves, extrasOn: mixInCount > 0, mixInCount, extrasFee };
}

function buildLoafSlots(largeCount, smallCount) {
  const slots = [];
  for (let i = 0; i < largeCount; i++) slots.push({ key: `large-${i + 1}`, size: 'large', label: largeCount > 1 ? `Large loaf ${i + 1}` : 'Large loaf' });
  for (let i = 0; i < smallCount; i++) slots.push({ key: `regular-${i + 1}`, size: 'small', label: smallCount > 1 ? `Regular loaf ${i + 1}` : 'Regular loaf' });
  return slots;
}

function defaultLoafChoice() {
  return { baseFlavor: 'original', mixIns: [] };
}

function flavorSummaryForChoice(choice) {
  const base = FLAVOR_BY_ID[choice.baseFlavor]?.name || 'Original';
  const extras = (choice.mixIns || []).map((id) => FLAVOR_BY_ID[id]?.name).filter(Boolean);
  return [base, ...extras].join(' + ');
}

function formatLoafChoicesSummary(loafSelections) {
  if (!loafSelections || !loafSelections.length) return '';
  const grouped = new Map();
  loafSelections.forEach((loaf) => {
    const size = loaf.size === 'large' ? 'large' : 'regular';
    const flavor = flavorSummaryForChoice(loaf);
    const key = `${size}|${flavor}`;
    const current = grouped.get(key);
    if (current) current.count += 1;
    else grouped.set(key, { count: 1, size, flavor });
  });
  return Array.from(grouped.values()).map((item) => `${item.count}× ${item.size} ${item.flavor}`).join('; ');
}

function SizeRow({ sizeId, count, onChange, max = MAX_PER_SIZE }) {
  const s = SIZE_INFO[sizeId];
  const active = count > 0;
  return (
    <div className="lb-sizerow" data-active={active ? 'true' : 'false'}>
      <div className="lb-sizerow__icon"><SizeIcon wIn={s.w} lIn={s.l} active={active} /></div>
      <div className="lb-sizerow__copy">
        <div className="lb-sizerow__name">{s.name}</div>
        <div className="lb-sizerow__price"><span>${s.perFirst} first</span><Heart size={7} color="var(--lb-vintage-clay)" /><span>+${s.perMore} each more</span></div>
      </div>
      <div className="lb-stepper" aria-label={`${s.name} quantity`}>
        <button type="button" className="lb-stepper__btn" onClick={() => onChange(Math.max(0, count - 1))} disabled={count === 0} aria-label="One fewer">−</button>
        <span className="lb-stepper__value" aria-live="polite">{count}</span>
        <button type="button" className="lb-stepper__btn" onClick={() => onChange(Math.min(max, count + 1))} disabled={count >= max} aria-label="One more">+</button>
      </div>
    </div>
  );
}

function Summary({ largeCount, smallCount, mixIns, loafSelections }) {
  const { gross, net, saved, loaves, extrasOn, mixInCount, extrasFee } = computeTotals({ largeCount, smallCount, mixIns, loafSelections });
  if (loaves === 0) {
    return <div className="lb-summary"><div style={{ fontFamily: 'var(--lb-font-serif)', fontStyle: 'italic', fontSize: 13, color: 'var(--lb-vintage-clay)', textAlign: 'center', padding: '2px 0' }}>Add at least one loaf to get started.</div></div>;
  }
  return (
    <div className="lb-summary">
      {largeCount > 0 && <div className="lb-summary__row"><span>{largeCount}× large loaf</span><span>${priceForSize('large', largeCount)}</span></div>}
      {smallCount > 0 && <div className="lb-summary__row"><span>{smallCount}× regular loaf</span><span>${priceForSize('small', smallCount)}</span></div>}
      {extrasOn && <div className="lb-summary__row"><span>Extra mix-ins{mixInCount > 1 ? ` (${mixInCount})` : ''}</span><span>+${extrasFee}</span></div>}
      <div className="lb-summary__total">
        <div><div className="lb-summary__total-label">Total</div>{saved > 0 && <div className="lb-summary__saved"><Heart size={9} color="var(--lb-rust-petal)" />You save ${saved}</div>}</div>
        <div className="lb-summary__total-value">{saved > 0 && <span className="lb-summary__gross">${gross}</span>}<span className="lb-summary__net">${net}</span></div>
      </div>
    </div>
  );
}

function FlavorPicker({ baseFlavor, onBaseFlavorChange, mixIns, onMixInsChange, loafCount }) {
  const extrasOn = mixIns.length > 0;
  const selectBase = (id) => {
    onBaseFlavorChange(id);
    if (mixIns.includes(id)) onMixInsChange(mixIns.filter((m) => m !== id));
  };
  const toggleExtras = () => {
    if (extrasOn) onMixInsChange([]);
    else {
      const seed = FLAVORS.find((f) => f.id !== baseFlavor);
      onMixInsChange(seed ? [seed.id] : []);
    }
  };
  const toggleMixIn = (id) => {
    if (mixIns.includes(id)) onMixInsChange(mixIns.filter((m) => m !== id));
    else onMixInsChange([...mixIns, id]);
  };
  return (
    <React.Fragment>
      <div className="lb-flavor-list" role="radiogroup" aria-label="Flavor">
        {FLAVORS.map((f) => (
          <div key={f.id} role="radio" tabIndex={0} aria-checked={baseFlavor === f.id} className="lb-flavor" onClick={() => selectBase(f.id)} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') selectBase(f.id); }}>
            <div className="lb-flavor__swatch"><img src={f.image} alt="" aria-hidden="true" /></div>
            <div style={{ minWidth: 0 }}><div className="lb-flavor__name">{f.name}</div><div className="lb-flavor__sub">{f.sub}</div></div>
            <span className="lb-flavor__radio" aria-hidden="true" />
          </div>
        ))}
      </div>
      <div className="lb-toggle-row" role="checkbox" tabIndex={0} aria-checked={extrasOn} onClick={toggleExtras} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') toggleExtras(); }}>
        <div className="lb-toggle-row__copy"><div className="lb-toggle-row__name">Extra mix-ins</div><div className="lb-toggle-row__sub">Fold another flavor in. $2 per loaf.</div></div>
        {extrasOn && loafCount > 0 && <div className="lb-toggle-row__price">+${mixIns.length * loafCount * 2}</div>}
        <span className="lb-toggle-switch" />
      </div>
      {extrasOn && <div className="lb-mixins" role="group" aria-label="Mix in another flavor">{FLAVORS.filter((f) => f.id !== baseFlavor).map((f) => { const on = mixIns.includes(f.id); return <button key={f.id} type="button" className="lb-mixin-chip" aria-checked={on} onClick={(e) => { e.stopPropagation(); toggleMixIn(f.id); }}><span className="lb-mixin-chip__swatch"><img src={f.image} alt="" aria-hidden="true" /></span>{f.name}</button>; })}</div>}
    </React.Fragment>
  );
}

function PaymentPicker({ value, onChange }) {
  return (
    <React.Fragment>
      <div className="lb-pay-grid" role="radiogroup" aria-label="Preferred payment">
        {PAYMENT_METHODS.map((p) => <div key={p.id} role="radio" tabIndex={0} aria-checked={value === p.id} className="lb-pay-opt" onClick={() => onChange(p.id)} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') onChange(p.id); }}><span className="lb-pay-opt__glyph" style={{ background: p.bg, color: p.fg }}><PayGlyph kind={p.glyph} /></span><span className="lb-pay-opt__name">{p.name}</span></div>)}
      </div>
      <div className="lb-paynote"><span className="lb-paynote__heart"><Heart size={10} color="var(--lb-vintage-clay)" /></span>You pay on delivery, not now. Delivery is included in town. Laurel will text once your loaves are confirmed. Cash is welcome too, easiest if exact.</div>
    </React.Fragment>
  );
}

function OrderForm({ values, errors, onChange }) {
  const set = (key) => (e) => onChange({ ...values, [key]: e.target.value });
  return <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}><div className={'lb-field' + (errors.name ? ' lb-field--error' : '')}><label>Your name<span style={{ color: 'var(--lb-rust-petal)' }}> *</span></label><input value={values.name} onChange={set('name')} placeholder="First and last" autoComplete="name" />{errors.name && <div className="lb-field-error">{errors.name}</div>}</div><div className={'lb-field' + (errors.phone ? ' lb-field--error' : '')}><label>Phone<span style={{ color: 'var(--lb-rust-petal)' }}> *</span></label><input value={values.phone} onChange={set('phone')} placeholder="(717) 491-5845" type="tel" inputMode="tel" autoComplete="tel" />{errors.phone && <div className="lb-field-error">{errors.phone}</div>}</div><div className="lb-field"><label>Preferred delivery address</label><input value={values.pickup} onChange={set('pickup')} placeholder="Street address, apartment, or landmark" /><div style={{ fontFamily: 'var(--lb-font-serif)', fontStyle: 'italic', fontSize: 12, color: 'var(--lb-vintage-clay)' }}>Delivery is included in town.</div></div><div className="lb-field"><label>A little note</label><textarea value={values.notes} onChange={set('notes')} placeholder="Anything Laurel should know" rows={2} style={{ resize: 'none', minHeight: 52 }} /></div></div>;
}

function CustomOrderForm({ values, errors, onChange }) {
  const set = (key) => (e) => onChange({ ...values, [key]: e.target.value });
  return <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}><div className={'lb-field' + (errors.name ? ' lb-field--error' : '')}><label>Your name<span style={{ color: 'var(--lb-rust-petal)' }}> *</span></label><input value={values.name} onChange={set('name')} placeholder="First and last" />{errors.name && <div className="lb-field-error">{errors.name}</div>}</div><div className={'lb-field' + (errors.phone ? ' lb-field--error' : '')}><label>Phone<span style={{ color: 'var(--lb-rust-petal)' }}> *</span></label><input value={values.phone} onChange={set('phone')} placeholder="(717) 491-5845" type="tel" inputMode="tel" />{errors.phone && <div className="lb-field-error">{errors.phone}</div>}</div><div className={'lb-field' + (errors.idea ? ' lb-field--error' : '')}><label>What did you have in mind?<span style={{ color: 'var(--lb-rust-petal)' }}> *</span></label><textarea value={values.idea} onChange={set('idea')} placeholder="A different size, a special occasion, a flavor twist" rows={4} style={{ resize: 'none', minHeight: 96 }} />{errors.idea && <div className="lb-field-error">{errors.idea}</div>}</div></div>;
}

function OrderSuccess({ name, custom, result, onClose }) {
  const fallbackBody = encodeURIComponent(custom ? `Hi Laurel, this is ${name || '[your name]'}. I just sent a custom order request through the site. Just confirming!` : `Hi Laurel, this is ${name || '[your name]'}. I just submitted a banana bread request through the site. Just confirming!`);
  const confirmHref = (result && result.sms_confirm_link) || `sms:+17174915845?&body=${fallbackBody}`;
  const handleTextClick = () => { if (result && result.id && window.LaurelAPI) window.LaurelAPI.recordSmsConfirm(result.id).catch(() => {}); };
  return <div style={{ textAlign: 'center', padding: '4px 4px 8px' }}><div style={{ width: 64, height: 64, borderRadius: 999, background: 'rgba(97,24,41,0.06)', color: 'var(--lb-mulberry-wine)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', margin: '6px auto 12px' }}><Heart size={28} /></div><h2 className="lb-modal-title" style={{ fontSize: 26 }}>{custom ? <React.Fragment>Your idea<br />was sent</React.Fragment> : <React.Fragment>Your request<br />was saved</React.Fragment>}</h2><div className="lb-modal-sub">Laurel will text back to confirm{custom ? ' and figure out the details' : ' your loaves and delivery details'}.</div><div style={{ background: 'var(--lb-paper)', border: '1px solid var(--lb-rule)', borderRadius: 14, padding: '14px 16px', margin: '4px 0 18px', textAlign: 'left' }}><div style={{ fontFamily: 'var(--lb-font-display)', fontSize: 9, letterSpacing: '0.32em', textTransform: 'uppercase', color: 'var(--lb-vintage-clay)', marginBottom: 6 }}>Tap to confirm by text</div><div style={{ fontFamily: 'var(--lb-font-serif)', fontStyle: 'italic', fontSize: 13, color: 'var(--lb-midnight-espresso)', lineHeight: 1.5 }}>{result && result.display_id ? `Order #${result.display_id} is saved as ${result.item_summary || 'your request'}.` : 'Your request is saved.'}</div><div style={{ fontFamily: 'var(--lb-font-body)', fontSize: 12, color: 'var(--lb-vintage-clay)', marginTop: 8 }}>If texting does not open, call or text {LAUREL_PHONE_DISPLAY}.</div></div><div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}><a href={confirmHref} onClick={handleTextClick} className="lb-btn lb-btn--primary"><Icon.message size={14} />Text Laurel to confirm</a><button onClick={onClose} className="lb-btn lb-btn--ghost">Close</button></div></div>;
}

function OrderFailure({ message, onClose }) {
  return <div style={{ textAlign: 'center', padding: '4px 4px 8px' }}><div style={{ width: 64, height: 64, borderRadius: 999, background: 'rgba(97,24,41,0.06)', color: 'var(--lb-mulberry-wine)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', margin: '6px auto 12px' }}><Icon.message size={24} /></div><h2 className="lb-modal-title" style={{ fontSize: 24 }}>Please text Laurel</h2><div className="lb-modal-sub">{message || 'Order form is unavailable right now. Please text Laurel directly.'}</div><div style={{ background: 'var(--lb-paper)', border: '1px solid var(--lb-rule)', borderRadius: 14, padding: '14px 16px', margin: '4px 0 18px', textAlign: 'left', fontFamily: 'var(--lb-font-body)', fontSize: 13, color: 'var(--lb-midnight-espresso)' }}>Phone: {LAUREL_PHONE_DISPLAY}</div><div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}><a href={ORDER_TEXT_LAUREL_HREF} className="lb-btn lb-btn--primary"><Icon.message size={14} />Text Laurel</a><button onClick={onClose} className="lb-btn lb-btn--ghost">Close</button></div></div>;
}

function StepDots({ current, total }) {
  return <div className="lb-step-dots">{Array.from({ length: total }).map((_, i) => <span key={i} className={'lb-step-dot' + (i < current ? ' is-on' : '')} />)}</div>;
}

function ModalNote() { return <div className="lb-note">This is an order request. Laurel will confirm by text.</div>; }

function OrderModal({ open, initialStep = 'sizes', onClose }) {
  const [step, setStep] = React.useState(initialStep);
  const [largeCount, setLargeCount] = React.useState(1);
  const [smallCount, setSmallCount] = React.useState(0);
  const [flavor, setFlavor] = React.useState('original');
  const [mixIns, setMixIns] = React.useState([]);
  const [currentFlavorIndex, setCurrentFlavorIndex] = React.useState(0);
  const [loafChoices, setLoafChoices] = React.useState({ 'large-1': defaultLoafChoice() });
  const [paymentPref, setPaymentPref] = React.useState('cash_app');
  const [form, setForm] = React.useState({ name: '', phone: '', pickup: '', notes: '' });
  const [customForm, setCustomForm] = React.useState({ name: '', phone: '', idea: '' });
  const [errors, setErrors] = React.useState({});
  const [wasCustom, setWasCustom] = React.useState(false);
  const [submitting, setSubmitting] = React.useState(false);
  const [successResult, setSuccessResult] = React.useState(null);
  const [submitError, setSubmitError] = React.useState('');
  const [motionDirection, setMotionDirection] = React.useState('next');
  const [idempotencyKey, setIdempotencyKey] = React.useState(() => window.LaurelAPI?.makeIdempotencyKey?.() || String(Date.now()));

  const [closing, setClosing] = React.useState(false);
  const [dragY, setDragY] = React.useState(0);
  const dragging = React.useRef(false);
  const CLOSE_MS = 320;

  const startClose = React.useCallback(() => {
    if (closing) return;
    setClosing(true);
    setTimeout(() => { onClose && onClose(); setClosing(false); setDragY(0); }, CLOSE_MS);
  }, [closing, onClose]);

  React.useEffect(() => {
    if (open) {
      setStep(initialStep);
      setErrors({});
      setSubmitError('');
      setSuccessResult(null);
      setIdempotencyKey(window.LaurelAPI?.makeIdempotencyKey?.() || String(Date.now()));
      setCurrentFlavorIndex(0);
      setMotionDirection('next');
      setClosing(false);
      setDragY(0);
    }
  }, [open, initialStep]);

  React.useEffect(() => {
    const slots = buildLoafSlots(largeCount, smallCount);
    setLoafChoices((prev) => {
      const next = {};
      slots.forEach((slot) => {
        next[slot.key] = prev[slot.key] || defaultLoafChoice();
      });
      return next;
    });
    if (currentFlavorIndex >= slots.length) setCurrentFlavorIndex(Math.max(0, slots.length - 1));
  }, [largeCount, smallCount, currentFlavorIndex]);

  const onHandlePointerDown = (e) => {
    if (closing) return;
    if (e.pointerType === 'mouse' && e.button !== 0) return;
    const startY = e.clientY;
    const startT = performance.now();
    let lastY = startY, lastT = startT, velocity = 0;
    dragging.current = true;
    const move = (ev) => { const dy = Math.max(0, ev.clientY - startY); const now = performance.now(); const dt = Math.max(1, now - lastT); velocity = 0.7 * velocity + 0.3 * ((ev.clientY - lastY) / dt); lastY = ev.clientY; lastT = now; setDragY(dy); };
    const up = () => { document.removeEventListener('pointermove', move); document.removeEventListener('pointerup', up); document.removeEventListener('pointercancel', up); dragging.current = false; const dy = Math.max(0, lastY - startY); if (dy > 110 || velocity > 0.9) startClose(); else setDragY(0); };
    document.addEventListener('pointermove', move);
    document.addEventListener('pointerup', up);
    document.addEventListener('pointercancel', up);
  };

  if (!open) return null;

  const loafSlots = buildLoafSlots(largeCount, smallCount);
  const getLoafChoice = (slot) => loafChoices[slot.key] || defaultLoafChoice();
  const loafSelections = loafSlots.map((slot) => ({ ...getLoafChoice(slot), size: slot.size, label: slot.label }));
  const currentSlot = loafSlots[currentFlavorIndex] || loafSlots[0];
  const currentChoice = currentSlot ? getLoafChoice(currentSlot) : defaultLoafChoice();
  const totalSteps = Math.max(3, loafSlots.length + 2);
  const updateLoafChoice = (slotKey, patch) => {
    setLoafChoices((prev) => {
      const current = prev[slotKey] || defaultLoafChoice();
      return { ...prev, [slotKey]: { ...current, ...patch } };
    });
  };
  const totals = computeTotals({ largeCount, smallCount, loafSelections });
  const hasLoaves = totals.loaves > 0;
  const isCustom = step === 'custom';
  const goStep = (nextStep, direction = 'next') => {
    setMotionDirection(direction);
    setStep(nextStep);
  };
  const goFlavorIndex = (nextIndex, direction = 'next') => {
    setMotionDirection(direction);
    setCurrentFlavorIndex(nextIndex);
  };

  const submit = async () => {
    if (submitting) return;
    const next = {};
    if (!form.name.trim()) next.name = 'Please add a name.';
    if (!form.phone.trim()) next.phone = 'Phone number, please.';
    else if (form.phone.replace(/\D/g, '').length < 7) next.phone = 'That phone looks short.';
    if (Object.keys(next).length) { setErrors(next); return; }
    setSubmitting(true);
    setSubmitError('');
    try {
      const firstLoaf = loafSelections[0] || defaultLoafChoice();
      const result = await window.LaurelAPI.submitOrder({ ...form, largeCount, smallCount, flavor: firstLoaf.baseFlavor, mixIns: firstLoaf.mixIns || [], loafSelections, paymentPref, idempotencyKey });
      setSuccessResult(result);
      setWasCustom(false);
      goStep('success', 'next');
    } catch (err) {
      setSubmitError('Order form is unavailable right now. Please text Laurel directly.');
      goStep('failure', 'next');
    } finally {
      setSubmitting(false);
    }
  };

  const submitCustom = async () => {
    if (submitting) return;
    const next = {};
    if (!customForm.name.trim()) next.name = 'Please add a name.';
    if (!customForm.phone.trim()) next.phone = 'Phone number, please.';
    else if (customForm.phone.replace(/\D/g, '').length < 7) next.phone = 'That phone looks short.';
    if (!customForm.idea.trim()) next.idea = 'Tell Laurel a little about what you want.';
    if (Object.keys(next).length) { setErrors(next); return; }
    setSubmitting(true);
    setSubmitError('');
    try {
      const result = await window.LaurelAPI.submitCustomOrder({ ...customForm, idempotencyKey });
      setSuccessResult(result);
      setWasCustom(true);
      goStep('success', 'next');
    } catch (err) {
      setSubmitError('Order form is unavailable right now. Please text Laurel directly.');
      goStep('failure', 'next');
    } finally {
      setSubmitting(false);
    }
  };

  const modalStyle = { transform: closing ? 'translateY(110%)' : `translateY(${dragY}px)`, transition: dragging.current ? 'none' : `transform ${CLOSE_MS}ms var(--lb-ease)`, opacity: closing ? 0 : 1 };
  const scrimStyle = { opacity: closing ? 0 : Math.max(0, 1 - dragY / 280), transition: dragging.current ? 'none' : `opacity ${CLOSE_MS}ms var(--lb-ease)` };

  return (
    <div className="lb-modal-scrim" style={scrimStyle} onClick={startClose}>
      <div className={'lb-modal' + (isCustom ? ' lb-modal--custom' : '')} style={modalStyle} onClick={(e) => e.stopPropagation()} role="dialog" aria-modal="true" aria-label="Place an order">
        <div className="lb-modal-swipe" onPointerDown={onHandlePointerDown}><div className="lb-modal-handle" /></div>
        <div key={step === 'flavor' ? `${step}-${currentFlavorIndex}-${loafSlots.length}` : step} className="lb-modal-step" data-motion={motionDirection}>
          {step === 'sizes' && <React.Fragment><div className="lb-modal-eyebrow">Order request <Heart size={8} /> step 1 of {totalSteps}</div><h2 className="lb-modal-title">Your loaves</h2><div className="lb-modal-sub">Pick how many loaves. You will choose each flavor next.</div><div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}><SizeRow sizeId="large" count={largeCount} onChange={setLargeCount} /><SizeRow sizeId="small" count={smallCount} onChange={setSmallCount} /></div><Summary largeCount={largeCount} smallCount={smallCount} loafSelections={loafSelections} /><button type="button" className="lb-textlink" onClick={() => { setErrors({}); goStep('custom', 'next'); }}>Want something else? Request a custom order</button><ModalNote /><div style={{ marginTop: 14, display: 'flex', flexDirection: 'column', gap: 10 }}><button className="lb-btn lb-btn--primary" onClick={() => { if (!hasLoaves) return; setErrors({}); setCurrentFlavorIndex(0); goStep('flavor', 'next'); }} disabled={!hasLoaves} style={!hasLoaves ? { opacity: 0.45, cursor: 'not-allowed' } : null}>Choose flavors<Icon.arrowRight size={14} /></button></div><StepDots current={1} total={totalSteps} /></React.Fragment>}
          {step === 'flavor' && currentSlot && <React.Fragment><div className="lb-modal-eyebrow">Order request <Heart size={8} /> step {currentFlavorIndex + 2} of {totalSteps}</div><h2 className="lb-modal-title">Pick a flavor</h2><div className="lb-loaf-step" aria-live="polite"><span><span className="lb-loaf-step__dot" aria-hidden="true" />{currentSlot.label}</span><span>Choosing {currentFlavorIndex + 1} of {loafSlots.length}</span></div><div className="lb-modal-sub">Choose the bread and extra mix-ins for this loaf.</div><FlavorPicker baseFlavor={currentChoice.baseFlavor} onBaseFlavorChange={(baseFlavor) => updateLoafChoice(currentSlot.key, { baseFlavor })} mixIns={currentChoice.mixIns || []} onMixInsChange={(nextMixIns) => updateLoafChoice(currentSlot.key, { mixIns: nextMixIns })} loafCount={1} /><div className="lb-flavor-total"><span>Total so far</span><strong>${totals.net}</strong></div><div style={{ marginTop: 10, display: 'flex', gap: 10 }}><button onClick={() => { if (currentFlavorIndex === 0) goStep('sizes', 'prev'); else goFlavorIndex(currentFlavorIndex - 1, 'prev'); }} className="lb-btn lb-btn--secondary" style={{ flex: '0 0 auto', padding: '16px 18px' }} aria-label="Back"><Icon.arrowLeft size={14} /></button><button className="lb-btn lb-btn--primary" onClick={() => { setErrors({}); if (currentFlavorIndex >= loafSlots.length - 1) goStep('details', 'next'); else goFlavorIndex(currentFlavorIndex + 1, 'next'); }} style={{ flex: 1 }}>{currentFlavorIndex >= loafSlots.length - 1 ? 'Continue' : 'Next loaf'}<Icon.arrowRight size={14} /></button></div><StepDots current={currentFlavorIndex + 2} total={totalSteps} /></React.Fragment>}
          {step === 'details' && <React.Fragment><div className="lb-modal-eyebrow">Order request <Heart size={8} /> step {totalSteps} of {totalSteps}</div><h2 className="lb-modal-title" style={{ fontSize: 24, marginBottom: 4 }}>You <Heart size={18} color="var(--lb-vintage-clay)" /> payment</h2><div className="lb-modal-sub" style={{ margin: '4px 0 12px' }}>{formatLoafChoicesSummary(loafSelections)} <Heart size={8} color="var(--lb-vintage-clay)" /> ${totals.net}</div><div className="lb-details"><OrderForm values={form} errors={errors} onChange={setForm} /></div><div className="lb-details-paylabel">Preferred payment</div><PaymentPicker value={paymentPref} onChange={setPaymentPref} /><div style={{ marginTop: 12, display: 'flex', gap: 10 }}><button onClick={() => { setCurrentFlavorIndex(Math.max(0, loafSlots.length - 1)); goStep('flavor', 'prev'); }} className="lb-btn lb-btn--secondary" style={{ flex: '0 0 auto', padding: '14px 18px' }} aria-label="Back" disabled={submitting}><Icon.arrowLeft size={14} /></button><button className="lb-btn lb-btn--primary lb-btn--summary" onClick={submit} disabled={submitting} style={{ flex: 1, opacity: submitting ? 0.72 : 1 }}><span className="lb-btn-summary__line1">{submitting ? 'Submitting' : 'Submit request'}</span><span className="lb-btn-summary__line2">{formatLoafChoicesSummary(loafSelections)}<span className="lb-btn-summary__dot"> · </span>${totals.net}<span className="lb-btn-summary__dot"> · </span>{PAYMENT_METHODS.find((p) => p.id === paymentPref)?.name}</span></button></div><StepDots current={totalSteps} total={totalSteps} /></React.Fragment>}
          {step === 'custom' && <React.Fragment><div className="lb-modal-eyebrow">Custom request <Heart size={8} /> off the menu</div><h2 className="lb-modal-title">Something<br/>special?</h2><div className="lb-modal-sub">For unusual sizes, mixed flavors, or anything else. Laurel will figure out pricing with you over text.</div><CustomOrderForm values={customForm} errors={errors} onChange={setCustomForm} /><ModalNote /><div style={{ marginTop: 14, display: 'flex', gap: 10 }}><button onClick={() => goStep('sizes', 'prev')} className="lb-btn lb-btn--secondary" style={{ flex: '0 0 auto', padding: '16px 18px' }} aria-label="Back" disabled={submitting}><Icon.arrowLeft size={14} /></button><button className="lb-btn lb-btn--primary" onClick={submitCustom} disabled={submitting} style={{ flex: 1, opacity: submitting ? 0.72 : 1 }}>{submitting ? 'Sending' : 'Send the idea'}<Heart size={11} /></button></div></React.Fragment>}
          {step === 'success' && <OrderSuccess name={wasCustom ? customForm.name : form.name} custom={wasCustom} result={successResult} onClose={startClose} />}
          {step === 'failure' && <OrderFailure message={submitError} onClose={startClose} />}
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { OrderModal, SizeRow, FlavorPicker, PaymentPicker, OrderForm, OrderSuccess, SIZE_INFO, FLAVORS, FLAVOR_BY_ID, PAYMENT_METHODS, computeTotals, priceForSize });
