// pantry-sheets.jsx — bottom sheets and modal flows

const { useState: useStateS, useEffect: useEffectS, useRef: useRefS } = React;

// ─────────────────────────────────────────────────────────────
// Sheet shell with backdrop + slide-up animation
// ─────────────────────────────────────────────────────────────
function Sheet({ open, onClose, palette, children, maxHeight = '85%', web = false }) {
  const [render, setRender] = useStateS(false);
  const [shown, setShown] = useStateS(false);
  useEffectS(() => {
    let t1, t2;
    if (open) {
      setRender(true);
      t1 = setTimeout(() => setShown(true), 10);
    } else {
      setShown(false);
      t2 = setTimeout(() => setRender(false), 250);
    }
    return () => { clearTimeout(t1); clearTimeout(t2); };
  }, [open]);
  if (!render) return null;
  return (
    <div style={{
      position: 'absolute', inset: 0, zIndex: 100,
      display: 'flex', flexDirection: 'column', justifyContent: 'flex-end',
      pointerEvents: 'auto',
    }}>
      <div onClick={onClose} style={{
        position: 'absolute', inset: 0,
        background: palette.overlay,
        opacity: shown ? 1 : 0, transition: 'opacity 220ms ease',
      }} />
      <div style={{
        position: 'relative',
        background: palette.bg,
        borderTopLeftRadius: web ? 24 : 28, borderTopRightRadius: web ? 24 : 28,
        maxHeight, display: 'flex', flexDirection: 'column',
        transform: shown ? 'translateY(0)' : 'translateY(100%)',
        transition: 'transform 280ms cubic-bezier(0.2, 0.8, 0.2, 1)',
        boxShadow: '0 -8px 32px rgba(0,0,0,0.18)',
        overflow: 'hidden',
      }}>
        {/* grabber */}
        <div style={{ display: 'flex', justifyContent: 'center', padding: '10px 0 4px' }}>
          <div style={{ width: 36, height: 4, borderRadius: 2, background: palette.line }} />
        </div>
        <div style={{ flex: 1, overflowY: 'auto', overflowX: 'hidden' }}>
          {children}
        </div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Add Method Picker
// ─────────────────────────────────────────────────────────────
function AddMethodPicker({ palette, lang, onPick, onClose }) {
  const methods = [
    { id: 'manual', icon: 'pen', title: t(lang, 'manual'), desc: t(lang, 'manualDesc'), tone: palette.primary },
    { id: 'photo', icon: 'camera', title: t(lang, 'photoAI'), desc: t(lang, 'photoAIDesc'), tone: palette.amber, ai: true },
    { id: 'receipt', icon: 'cart', title: lang === 'fr' ? 'Scanner un reçu' : 'Scan a receipt', desc: lang === 'fr' ? 'Photo du ticket → tous les articles d\'un coup' : 'Photo of the receipt → all items at once', tone: palette.primary, ai: true },
    { id: 'barcode', icon: 'barcode', title: t(lang, 'barcode'), desc: t(lang, 'barcodeDesc'), tone: palette.blue },
    { id: 'quicklog', icon: 'fire', title: t(lang, 'quickLog'), desc: t(lang, 'quickLogSub'), tone: palette.red },
  ];
  return (
    <div style={{ padding: '8px 20px 28px' }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '4px 0 18px' }}>
        <div>
          <div style={{ fontSize: 20, fontWeight: 600, letterSpacing: -0.4, color: palette.ink, lineHeight: 1.15 }}>
            {t(lang, 'quickAdd')}
          </div>
          <div style={{ fontSize: 13, color: palette.ink3, marginTop: 3 }}>{t(lang, 'chooseMethod')}</div>
        </div>
        <div onClick={onClose} style={{
          width: 32, height: 32, borderRadius: 10, background: palette.cardSub,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          cursor: 'pointer', border: `1px solid ${palette.line}`, color: palette.ink2,
        }}>
          <Icon name="close" size={16} />
        </div>
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
        {methods.map(m => (
          <div key={m.id} onClick={() => onPick(m.id)} style={{
            background: palette.card, border: `1px solid ${palette.line}`,
            borderRadius: 18, padding: 16, cursor: 'pointer',
            display: 'flex', alignItems: 'center', gap: 14, position: 'relative',
          }}>
            <div style={{
              width: 46, height: 46, borderRadius: 14,
              background: hexToSoft(m.tone), color: m.tone,
              display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
              border: palette.isDark ? `1px solid ${m.tone}40` : 'none',
            }}>
              <Icon name={m.icon} size={22} style="line" color={m.tone} />
            </div>
            <div style={{ flex: 1 }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                <div style={{ fontSize: 16, fontWeight: 600, color: palette.ink, letterSpacing: -0.2 }}>{m.title}</div>
                {m.ai && (
                  <span style={{
                    fontSize: 9.5, fontFamily: 'Geist Mono, monospace', fontWeight: 600,
                    padding: '2px 6px', borderRadius: 5, background: palette.amber, color: '#fff', letterSpacing: 0.06,
                  }}>AI</span>
                )}
              </div>
              <div style={{ fontSize: 12.5, color: palette.ink3, marginTop: 2 }}>{m.desc}</div>
            </div>
            <Icon name="chevR" size={18} color={palette.ink3} />
          </div>
        ))}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Manual Add — search field + suggestions
// ─────────────────────────────────────────────────────────────
function ManualAdd({ palette, lang, onBack, onPick, onBulkAdd }) {
  const [q, setQ] = useStateS('');
  const [listening, setListening] = useStateS(false);
  const SR = typeof window !== 'undefined' && (window.SpeechRecognition || window.webkitSpeechRecognition);
  const startVoice = () => {
    if (!SR) { alert(lang === 'fr' ? 'La saisie vocale n\'est pas supportée sur ce navigateur.' : 'Voice input isn\'t supported on this browser.'); return; }
    try {
      const rec = new SR();
      rec.lang = lang === 'fr' ? 'fr-CA' : 'en-CA';
      rec.interimResults = false; rec.maxAlternatives = 1;
      rec.onresult = (e) => { const txt = e.results[0][0].transcript || ''; setQ(txt.replace(/[.,!?]+$/, '').trim()); };
      rec.onend = () => setListening(false);
      rec.onerror = () => setListening(false);
      setListening(true); rec.start();
    } catch (e) { setListening(false); }
  };
  const CATALOG = (typeof window !== 'undefined' && window.ADD_CATALOG) || ITEM_SUGGESTIONS;
  const filtered = CATALOG.filter(s =>
    localized(lang, s.name).toLowerCase().includes(q.toLowerCase())
  ).slice(0, 40);
  const exact = CATALOG.some(s => localized(lang, s.name).toLowerCase() === q.trim().toLowerCase());
  // Build a custom item for anything not already in the catalog.
  const makeCustom = () => {
    const v = q.trim();
    const n = v.toLowerCase();
    const g = (typeof window !== 'undefined' && window.ADD_CATALOG || []).find(s =>
      n.includes(localized('fr', s.name).toLowerCase()) || n.includes(localized('en', s.name).toLowerCase()));
    return {
      id: 'custom_' + Date.now(), name: { fr: v, en: v }, emoji: g ? g.emoji : '🛒',
      mono: (v[0] || '?').toUpperCase(), tone: '#9CA98C',
      defaultDays: 7, defaultLoc: 'fridge', defaultQty: '1', defaultUnit: { fr: 'pcs', en: 'pcs' }, custom: true,
    };
  };
  return (
    <div style={{ padding: '8px 20px 28px' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '4px 0 14px' }}>
        <div onClick={onBack} style={{
          width: 32, height: 32, borderRadius: 10, background: palette.cardSub,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          cursor: 'pointer', border: `1px solid ${palette.line}`, color: palette.ink2, flexShrink: 0,
        }}>
          <Icon name="chevL" size={16} />
        </div>
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: 18, fontWeight: 600, letterSpacing: -0.3, color: palette.ink }}>
            {t(lang, 'pickItem')}
          </div>
        </div>
      </div>
      {/* search */}
      <div style={{
        background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 14,
        padding: '10px 14px', display: 'flex', alignItems: 'center', gap: 10,
      }}>
        <Icon name="search" size={18} color={palette.ink3} />
        <input
          value={q}
          onChange={e => setQ(e.target.value)}
          placeholder={t(lang, 'searchPh')}
          autoFocus
          style={{
            flex: 1, border: 'none', outline: 'none', background: 'transparent',
            fontSize: 15, color: palette.ink, fontFamily: 'inherit',
          }}
        />
        {SR && (
          <div onClick={startVoice} title={lang === 'fr' ? 'Dicter' : 'Speak'} style={{
            width: 32, height: 32, borderRadius: 9, flexShrink: 0, cursor: 'pointer',
            background: listening ? palette.primary : palette.cardSub,
            border: `1px solid ${listening ? palette.primary : palette.line}`,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            fontSize: 15, animation: listening ? 'aiScan 1s linear infinite' : 'none',
          }}>🎤</div>
        )}
      </div>
      {/* Bulk-add the common kitchen staples in one tap */}
      {!q && onBulkAdd && (
        <div onClick={onBulkAdd} style={{
          marginTop: 14, padding: '12px 14px', borderRadius: 12,
          background: palette.cardSub, border: `1px dashed ${palette.line}`,
          display: 'flex', alignItems: 'center', gap: 12, cursor: 'pointer',
        }}>
          <div style={{ fontSize: 22 }}>🧂</div>
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 13.5, fontWeight: 600, color: palette.ink }}>
              {lang === 'fr' ? 'Ajouter le garde-manger de base' : 'Add kitchen staples'}
            </div>
            <div style={{ fontSize: 11, color: palette.ink3, marginTop: 1 }}>
              {lang === 'fr' ? 'Sel, huile, farine, riz, épices…' : 'Salt, oil, flour, rice, spices…'}
            </div>
          </div>
          <Icon name="plus" size={16} color={palette.ink2} />
        </div>
      )}
      <div style={{
        marginTop: 18, fontSize: 11, fontFamily: 'Geist Mono, monospace',
        color: palette.ink3, letterSpacing: 0.06, textTransform: 'uppercase',
      }}>
        {q ? t(lang, 'suggestions') : t(lang, 'common')}
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginTop: 10 }}>
        {/* Add whatever you typed, even if it's not in the catalog */}
        {q.trim() && !exact && (
          <div onClick={() => onPick(makeCustom())} style={{
            display: 'flex', alignItems: 'center', gap: 12, padding: '10px 12px',
            background: palette.primarySoft, border: `1px solid ${palette.primary}40`, borderRadius: 12,
            cursor: 'pointer',
          }}>
            <div style={{
              width: 34, height: 34, borderRadius: 10, background: palette.primary, color: palette.primaryInk,
              display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
            }}>
              <Icon name="plus" size={18} color={palette.primaryInk} />
            </div>
            <div style={{ flex: 1 }}>
              <div style={{ fontSize: 14, fontWeight: 600, color: palette.primarySoftInk }}>
                {lang === 'fr' ? `Ajouter « ${q.trim()} »` : `Add "${q.trim()}"`}
              </div>
              <div style={{ fontSize: 11, color: palette.primarySoftInk, opacity: 0.8, marginTop: 1, fontFamily: 'Geist Mono, monospace' }}>
                {lang === 'fr' ? 'aliment personnalisé' : 'custom item'}
              </div>
            </div>
            <Icon name="chevR" size={16} color={palette.primarySoftInk} />
          </div>
        )}
        {filtered.map(s => (
          <div key={s.id} onClick={() => onPick(s)} style={{
            display: 'flex', alignItems: 'center', gap: 12, padding: '10px 12px',
            background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 12,
            cursor: 'pointer',
          }}>
            <FoodChip item={s} size={34} style="emoji" palette={palette} />
            <div style={{ flex: 1 }}>
              <div style={{ fontSize: 14, fontWeight: 500, color: palette.ink, letterSpacing: -0.1 }}>{localized(lang, s.name)}</div>
              <div style={{ fontSize: 11, color: palette.ink3, marginTop: 1, fontFamily: 'Geist Mono, monospace' }}>
                {t(lang, 'autoFilled')} · {t(lang, s.defaultLoc)} · {s.defaultDays}{t(lang, 'days')}
              </div>
            </div>
            <Icon name="chevR" size={16} color={palette.ink3} />
          </div>
        ))}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// AI Photo Add — real camera (getUserMedia) + Claude vision
// Falls back to mock detection if camera/vision unavailable
// ─────────────────────────────────────────────────────────────
function AIPhotoAdd({ palette, lang, onBack, onAddAll, mode }) {
  const isReceipt = mode === 'receipt';
  const [phase, setPhase] = useStateS('aim'); // aim → scanning → detected | error
  const [stream, setStream] = useStateS(null);
  const [cameraReady, setCameraReady] = useStateS(false);
  const [cameraError, setCameraError] = useStateS(null);
  const [snapshot, setSnapshot] = useStateS(null); // dataURL
  const [detected, setDetected] = useStateS(null);
  const [usingMock, setUsingMock] = useStateS(false);
  const videoRef = React.useRef(null);
  const canvasRef = React.useRef(null);

  // Start camera on mount
  useEffectS(() => {
    let mounted = true;
    async function start() {
      if (!navigator.mediaDevices?.getUserMedia) {
        setCameraError('no-api');
        return;
      }
      try {
        const s = await navigator.mediaDevices.getUserMedia({
          video: { facingMode: { ideal: 'environment' }, width: { ideal: 1280 }, height: { ideal: 960 } },
          audio: false,
        });
        if (!mounted) { s.getTracks().forEach(t => t.stop()); return; }
        setStream(s);
        if (videoRef.current) {
          videoRef.current.srcObject = s;
          videoRef.current.onloadedmetadata = () => setCameraReady(true);
        }
      } catch (e) {
        setCameraError(e.name || 'denied');
      }
    }
    start();
    return () => { mounted = false; };
  }, []);

  // Stop camera tracks when leaving / when snapshot taken
  useEffectS(() => {
    return () => { if (stream) stream.getTracks().forEach(t => t.stop()); };
  }, [stream]);

  async function handleSnap() {
    setPhase('scanning');
    let dataUrl = null;
    if (videoRef.current && cameraReady) {
      const v = videoRef.current;
      const c = canvasRef.current || document.createElement('canvas');
      const w = v.videoWidth || 640, h = v.videoHeight || 480;
      c.width = w; c.height = h;
      c.getContext('2d').drawImage(v, 0, 0, w, h);
      try { dataUrl = c.toDataURL('image/jpeg', 0.82); } catch (e) {}
      setSnapshot(dataUrl);
      if (stream) { stream.getTracks().forEach(t => t.stop()); }
    }
    // Normalize a raw [{name,emoji,qty,unit,loc,days}] list to suggestion shape.
    const normalize = (parsed) => parsed.map((it, i) => ({
      id: 'det_' + Date.now() + '_' + i,
      name: { fr: it.name, en: it.name },
      emoji: it.emoji || '🥗',
      mono: (it.name || 'X').charAt(0).toUpperCase(),
      tone: ['#7FB575','#E8A04C','#D86B5C','#6B9FD8','#B89A6C'][i % 5],
      conf: 85 + Math.floor(Math.random() * 13),
      defaultQty: String(it.qty || '1'),
      defaultUnit: { fr: it.unit || 'unité', en: it.unit || 'unit' },
      defaultLoc: it.loc || 'fridge',
      defaultDays: typeof it.days === 'number' ? it.days : 5,
    }));

    let items = null;
    const base64 = dataUrl ? dataUrl.split(',')[1] : null;

    // 1) Real backend endpoint (production): /api/ai-scan → Claude Vision.
    if (base64 && window.PWAuth?.api?.aiScan && window.PWAuth?.current?.user) {
      try {
        const { items: parsed } = await window.PWAuth.api.aiScan(base64, 'image/jpeg', mode || 'photo');
        if (Array.isArray(parsed) && parsed.length > 0) items = normalize(parsed);
      } catch (e) { /* fall through */ }
    }

    // 2) Claude artifact preview environment (window.claude).
    if (!items && base64 && window.claude?.complete) {
      try {
        const prompt = `Look at this photo of a fridge/pantry/groceries and identify the food items visible. Return ONLY a JSON array (no prose, no markdown) of up to 6 items: [{"name":"english food name","emoji":"single food emoji","qty":"1","unit":"unit","loc":"fridge|counter|freezer|pantry","days":7}]. Use 'days' as a realistic shelf-life estimate. If you cannot see clear food items, return [].`;
        const resp = await window.claude.complete({
          messages: [{
            role: 'user',
            content: [
              { type: 'image', source: { type: 'base64', media_type: 'image/jpeg', data: base64 } },
              { type: 'text', text: prompt },
            ],
          }],
        });
        const match = (typeof resp === 'string' ? resp : '').match(/\[[\s\S]*\]/);
        if (match) {
          const parsed = JSON.parse(match[0]);
          if (Array.isArray(parsed) && parsed.length > 0) items = normalize(parsed);
        }
      } catch (e) { /* fall through to mock */ }
    }

    // 3) Mock fallback (offline / AI not configured).
    if (!items || items.length === 0) {
      items = AI_DETECTED;
      setUsingMock(true);
    }
    setDetected(items);
    setPhase('detected');
  }

  function handleRetry() {
    setPhase('aim');
    setSnapshot(null);
    setDetected(null);
    setUsingMock(false);
    setCameraError(null);
    setCameraReady(false);
    // restart camera
    (async () => {
      try {
        const s = await navigator.mediaDevices.getUserMedia({ video: { facingMode: { ideal: 'environment' } } });
        setStream(s);
        if (videoRef.current) {
          videoRef.current.srcObject = s;
          videoRef.current.onloadedmetadata = () => setCameraReady(true);
        }
      } catch (e) { setCameraError(e.name || 'denied'); }
    })();
  }

  const items = detected || AI_DETECTED;

  return (
    <div style={{ padding: '8px 20px 28px' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '4px 0 14px' }}>
        <div onClick={onBack} style={{
          width: 32, height: 32, borderRadius: 10, background: palette.cardSub,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          cursor: 'pointer', border: `1px solid ${palette.line}`, color: palette.ink2,
        }}>
          <Icon name="chevL" size={16} />
        </div>
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: 18, fontWeight: 600, letterSpacing: -0.3, color: palette.ink, display: 'flex', alignItems: 'center', gap: 8 }}>
            {t(lang, 'photoAI')}
            <span style={{
              fontSize: 9.5, fontFamily: 'Geist Mono, monospace', fontWeight: 600,
              padding: '2px 6px', borderRadius: 5, background: palette.amber, color: '#fff', letterSpacing: 0.06,
            }}>AI</span>
          </div>
        </div>
      </div>

      {/* Camera viewfinder */}
      <div style={{
        position: 'relative', borderRadius: 20, overflow: 'hidden',
        aspectRatio: '4/3', background: '#1A211D',
      }}>
        {/* Live camera feed */}
        {!snapshot && (
          <video
            ref={videoRef}
            autoPlay
            playsInline
            muted
            style={{
              position: 'absolute', inset: 0, width: '100%', height: '100%',
              objectFit: 'cover',
              opacity: cameraReady ? 1 : 0,
              transition: 'opacity 220ms',
            }}
          />
        )}
        {/* Captured snapshot */}
        {snapshot && (
          <img src={snapshot} alt="" style={{
            position: 'absolute', inset: 0, width: '100%', height: '100%',
            objectFit: 'cover',
          }} />
        )}
        <canvas ref={canvasRef} style={{ display: 'none' }} />

        {/* Loading / permission state */}
        {!cameraReady && !cameraError && !snapshot && (
          <div style={{
            position: 'absolute', inset: 0, display: 'flex',
            alignItems: 'center', justifyContent: 'center', flexDirection: 'column', gap: 12,
            color: 'rgba(255,255,255,0.8)', fontSize: 13,
          }}>
            <div style={{
              width: 36, height: 36, borderRadius: 18, border: '2.5px solid rgba(255,255,255,0.25)',
              borderTopColor: 'rgba(255,255,255,0.9)', animation: 'aiScan 1s linear infinite',
            }} />
            <div style={{ fontFamily: 'Geist Mono, monospace', fontSize: 11, letterSpacing: 0.08 }}>
              {lang === 'fr' ? 'CAMÉRA…' : 'CAMERA…'}
            </div>
          </div>
        )}

        {/* Camera permission denied / unavailable */}
        {cameraError && (
          <div style={{
            position: 'absolute', inset: 0, display: 'flex',
            alignItems: 'center', justifyContent: 'center', flexDirection: 'column', gap: 10,
            padding: 24, textAlign: 'center', color: 'rgba(255,255,255,0.9)',
          }}>
            <Icon name="camera" size={28} color="rgba(255,255,255,0.5)" />
            <div style={{ fontSize: 13, fontWeight: 500, maxWidth: 240 }}>
              {lang === 'fr'
                ? 'Caméra non disponible. Tu peux quand même essayer le mode démo.'
                : 'Camera not available. You can still try the demo mode.'}
            </div>
            <div style={{ fontSize: 10.5, fontFamily: 'Geist Mono, monospace', color: 'rgba(255,255,255,0.5)' }}>
              {cameraError}
            </div>
          </div>
        )}

        {/* Aim overlay */}
        {phase === 'aim' && cameraReady && !cameraError && (
          <>
            <div style={{
              position: 'absolute', inset: 24, borderRadius: 14,
              border: '2px dashed rgba(255,255,255,0.55)',
              pointerEvents: 'none',
            }} />
            <div style={{
              position: 'absolute', left: 0, right: 0, top: 16,
              textAlign: 'center', color: '#fff', fontSize: 12.5, fontWeight: 500,
              textShadow: '0 1px 4px rgba(0,0,0,0.6)',
            }}>{isReceipt ? (lang === 'fr' ? 'Cadre ton reçu d\'épicerie' : 'Frame your grocery receipt') : t(lang, 'snapShelf')}</div>
          </>
        )}

        {/* Scanning overlay */}
        {phase === 'scanning' && (
          <>
            <div style={{ position: 'absolute', inset: 0, background: 'rgba(0,0,0,0.25)' }} />
            <div className="ai-scan-bar" style={{
              position: 'absolute', left: 0, right: 0, height: 60,
              background: `linear-gradient(180deg, transparent 0%, ${palette.amber}40 50%, transparent 100%)`,
              borderTop: `1.5px solid ${palette.amber}`,
              pointerEvents: 'none',
            }} />
            <div style={{
              position: 'absolute', left: '50%', top: '50%', transform: 'translate(-50%,-50%)',
              padding: '8px 14px', borderRadius: 999,
              background: 'rgba(255,255,255,0.95)', color: '#1F2A24',
              fontSize: 12, fontFamily: 'Geist Mono, monospace', fontWeight: 500,
              display: 'flex', alignItems: 'center', gap: 8,
            }}>
              <Icon name="sparkle" size={14} color={palette.amber} />
              {t(lang, 'detecting')}
            </div>
          </>
        )}

        {/* Detected overlays */}
        {phase === 'detected' && items.slice(0, 3).map((d, i) => {
          const positions = [{ left: '18%', top: '28%' }, { left: '52%', top: '24%' }, { left: '34%', top: '62%' }];
          const p = positions[i] || positions[0];
          return (
            <div key={d.id || i} style={{
              position: 'absolute', left: p.left, top: p.top,
              padding: '4px 9px', borderRadius: 999,
              background: 'rgba(255,255,255,0.95)',
              color: '#1F2A24', fontSize: 11, fontFamily: 'Geist Mono, monospace', fontWeight: 500,
              display: 'flex', alignItems: 'center', gap: 5,
              border: `2px solid ${palette.amber}`,
            }}>
              <span style={{ fontSize: 14 }}>{d.emoji}</span>
              {localized(lang, d.name)}
              <span style={{ color: palette.amber, fontWeight: 600 }}>{d.conf}%</span>
            </div>
          );
        })}
      </div>

      {/* CTA */}
      {phase === 'aim' && (
        <div onClick={handleSnap} style={{
          marginTop: 16, padding: '14px', borderRadius: 14,
          background: palette.ink, color: palette.bg,
          textAlign: 'center', fontWeight: 500, fontSize: 14, cursor: 'pointer',
          display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
          opacity: (cameraReady || cameraError) ? 1 : 0.5,
          pointerEvents: (cameraReady || cameraError) ? 'auto' : 'none',
        }}>
          <Icon name="camera" size={18} color={palette.bg} style="filled" />
          {cameraError ? (lang === 'fr' ? 'Démarrer la démo' : 'Start demo') : t(lang, 'tapToScan')}
        </div>
      )}

      {phase === 'detected' && (
        <>
          <div style={{
            marginTop: 16, fontSize: 11, fontFamily: 'Geist Mono, monospace',
            color: palette.ink3, letterSpacing: 0.06, textTransform: 'uppercase',
            display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          }}>
            <span>{t(lang, 'detected')} · {items.length}</span>
            {usingMock && (
              <span style={{ color: palette.amberSoftInk, background: palette.amberSoft, padding: '2px 7px', borderRadius: 5 }}>
                {lang === 'fr' ? 'DÉMO' : 'DEMO'}
              </span>
            )}
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginTop: 8 }}>
            {items.map(d => (
              <div key={d.id} style={{
                display: 'flex', alignItems: 'center', gap: 12, padding: '10px 12px',
                background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 12,
              }}>
                <FoodChip item={d} size={34} style="emoji" palette={palette} />
                <div style={{ flex: 1 }}>
                  <div style={{ fontSize: 14, fontWeight: 500, color: palette.ink }}>{localized(lang, d.name)}</div>
                  <div style={{ fontSize: 11, color: palette.ink3, marginTop: 1, fontFamily: 'Geist Mono, monospace' }}>
                    {d.defaultQty} {localized(lang, d.defaultUnit)} · {t(lang, d.defaultLoc)}
                  </div>
                </div>
                <Icon name="check" size={18} color={palette.primary} />
              </div>
            ))}
          </div>
          <div style={{ display: 'flex', gap: 8, marginTop: 14 }}>
            <div onClick={handleRetry} style={{
              padding: '14px 18px', borderRadius: 14,
              background: palette.cardSub, color: palette.ink2,
              border: `1px solid ${palette.line}`,
              textAlign: 'center', fontWeight: 500, fontSize: 14, cursor: 'pointer',
              display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
            }}>
              <Icon name="camera" size={15} color={palette.ink2} />
              {lang === 'fr' ? 'Reprendre' : 'Retake'}
            </div>
            <div onClick={() => onAddAll(items)} style={{
              flex: 1, padding: '14px', borderRadius: 14,
              background: palette.primary, color: palette.primaryInk,
              textAlign: 'center', fontWeight: 500, fontSize: 14, cursor: 'pointer',
            }}>
              {t(lang, 'addAll')} ({items.length})
            </div>
          </div>
        </>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Barcode Add — real camera scan (BarcodeDetector) + Open Food Facts
// lookup, with manual code entry fallback (iOS Safari has no detector).
// ─────────────────────────────────────────────────────────────
// Map an Open Food Facts product to the onPick() suggestion shape.
function offToSuggestion(prod, code, lang) {
  const name = prod.product_name_fr || prod.product_name || prod.generic_name || (lang === 'fr' ? 'Produit' : 'Product');
  const brand = (prod.brands || '').split(',')[0] || '';
  const qtyStr = prod.quantity || '';
  // crude location/shelf-life guess from categories
  const cats = (prod.categories_tags || []).join(' ');
  let loc = 'pantry', days = 30;
  if (/frais|fresh|dairy|laitier|meat|viande|fish|poisson|charcut/.test(cats)) { loc = 'fridge'; days = 7; }
  if (/frozen|surgel/.test(cats)) { loc = 'freezer'; days = 90; }
  if (/fruit|vegetable|légume|legume/.test(cats)) { loc = 'fridge'; days = 7; }
  return {
    id: 'bc_' + code,
    name: { fr: name, en: name },
    emoji: '🛒',
    mono: (name || 'X').charAt(0).toUpperCase(),
    tone: '#6B9FD8',
    defaultQty: '1',
    defaultUnit: { fr: 'unité', en: 'unit' },
    defaultLoc: loc,
    defaultDays: days,
    _brand: brand,
    _qtyStr: qtyStr,
  };
}

function BarcodeAdd({ palette, lang, onBack, onPick }) {
  const tt = (fr, en) => (lang === 'fr' ? fr : en);
  // phase: aim → scanning → found | notfound ; or manual entry
  const [phase, setPhase] = useStateS('aim');
  const [product, setProduct] = useStateS(null);
  const [manual, setManual] = useStateS(false);
  const [code, setCode] = useStateS('');
  const [error, setError] = useStateS('');
  const videoRef = useRefS(null);
  const streamRef = useRefS(null);
  const detectorRef = useRefS(null);
  const rafRef = useRefS(null);

  const hasDetector = typeof window !== 'undefined' && 'BarcodeDetector' in window;

  const stopCamera = () => {
    if (rafRef.current) cancelAnimationFrame(rafRef.current);
    if (streamRef.current) { try { streamRef.current.getTracks().forEach(t => t.stop()); } catch (e) {} }
    streamRef.current = null;
  };
  useEffectS(() => () => stopCamera(), []);

  // Look up a barcode via Open Food Facts (free, no key). 8s timeout.
  const lookup = async (barcode) => {
    setError('');
    setPhase('scanning');
    stopCamera();
    try {
      const ctrl = new AbortController();
      const to = setTimeout(() => ctrl.abort(), 8000);
      const resp = await fetch(`https://world.openfoodfacts.org/api/v2/product/${encodeURIComponent(barcode)}.json?fields=product_name,product_name_fr,generic_name,brands,quantity,categories_tags`, { signal: ctrl.signal });
      clearTimeout(to);
      const data = await resp.json();
      if (data && data.status === 1 && data.product) {
        setProduct(offToSuggestion(data.product, barcode, lang));
        setPhase('found');
      } else {
        setPhase('notfound');
      }
    } catch (e) {
      setError(t(lang, 'barcodeNotFound'));
      setPhase('notfound');
    }
  };

  const startScan = async () => {
    if (!hasDetector) { setManual(true); return; }
    setError('');
    try {
      const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: { ideal: 'environment' } } });
      streamRef.current = stream;
      if (videoRef.current) {
        videoRef.current.srcObject = stream;
        await videoRef.current.play().catch(() => {});
      }
      detectorRef.current = new window.BarcodeDetector({ formats: ['ean_13', 'ean_8', 'upc_a', 'upc_e', 'code_128'] });
      setPhase('scanning');
      const tick = async () => {
        if (!videoRef.current || !detectorRef.current) return;
        try {
          const codes = await detectorRef.current.detect(videoRef.current);
          if (codes && codes.length) { lookup(codes[0].rawValue); return; }
        } catch (e) {}
        rafRef.current = requestAnimationFrame(tick);
      };
      rafRef.current = requestAnimationFrame(tick);
    } catch (e) {
      // Camera denied / unavailable → manual entry
      setManual(true);
    }
  };

  const Header = (
    <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '4px 0 14px' }}>
      <div onClick={() => { stopCamera(); onBack(); }} style={{
        width: 32, height: 32, borderRadius: 10, background: palette.cardSub,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        cursor: 'pointer', border: `1px solid ${palette.line}`, color: palette.ink2,
      }}>
        <Icon name="chevL" size={16} />
      </div>
      <div style={{ flex: 1, fontSize: 18, fontWeight: 600, color: palette.ink, letterSpacing: -0.3 }}>
        {t(lang, 'barcode')}
      </div>
    </div>
  );

  // ── Found product card ──
  if (phase === 'found' && product) {
    return (
      <div style={{ padding: '8px 20px 28px' }}>
        {Header}
        <div style={{
          fontSize: 11, fontFamily: 'Geist Mono, monospace',
          color: palette.primary, letterSpacing: 0.06, textTransform: 'uppercase',
          display: 'flex', alignItems: 'center', gap: 6,
        }}>
          <Icon name="check" size={14} color={palette.primary} />
          {t(lang, 'barcodeFound')}
        </div>
        <div style={{
          marginTop: 8, background: palette.card, border: `1px solid ${palette.line}`,
          borderRadius: 16, padding: 14, display: 'flex', alignItems: 'center', gap: 12,
        }}>
          <FoodChip item={product} size={48} style="emoji" palette={palette} />
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 15, fontWeight: 600, color: palette.ink, letterSpacing: -0.2 }}>{localized(lang, product.name)}</div>
            {(product._brand || product._qtyStr) && (
              <div style={{ fontSize: 11.5, color: palette.ink3, marginTop: 2, fontFamily: 'Geist Mono, monospace', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                {[product._brand, product._qtyStr].filter(Boolean).join(' · ')}
              </div>
            )}
          </div>
        </div>
        <div onClick={() => onPick(product)} style={{
          marginTop: 12, padding: '14px', borderRadius: 14,
          background: palette.primary, color: palette.primaryInk,
          textAlign: 'center', fontWeight: 500, fontSize: 14, cursor: 'pointer',
        }}>
          {t(lang, 'confirm')}
        </div>
        <div onClick={() => { setProduct(null); setManual(false); setPhase('aim'); }} style={{
          marginTop: 10, textAlign: 'center', fontSize: 13, color: palette.ink3, cursor: 'pointer',
        }}>
          {tt('Scanner un autre', 'Scan another')}
        </div>
      </div>
    );
  }

  // ── Manual entry (iOS Safari / no camera / not found) ──
  if (manual || phase === 'notfound') {
    return (
      <div style={{ padding: '8px 20px 28px' }}>
        {Header}
        {phase === 'notfound' && (
          <div style={{
            marginBottom: 12, background: palette.redSoft, color: palette.redSoftInk,
            padding: '10px 12px', borderRadius: 10, fontSize: 13, lineHeight: 1.4,
          }}>{error || t(lang, 'barcodeNotFound')}</div>
        )}
        {!hasDetector && phase !== 'notfound' && (
          <div style={{ marginBottom: 12, fontSize: 12.5, color: palette.ink3, lineHeight: 1.45 }}>
            {t(lang, 'barcodeUnsupported')}
          </div>
        )}
        <Field palette={palette} label={t(lang, 'barcodeEnter')}>
          <input value={code} onChange={e => setCode(e.target.value.replace(/[^0-9]/g, ''))}
            placeholder="0 64711 28392 4" inputMode="numeric" autoFocus style={{
            width: '100%', height: 46, background: palette.card, border: `1px solid ${palette.line}`,
            borderRadius: 12, padding: '0 14px', fontSize: 16, color: palette.ink, outline: 'none',
            fontFamily: 'Geist Mono, monospace', letterSpacing: 1, boxSizing: 'border-box',
          }} />
        </Field>
        <div onClick={() => code.length >= 6 && lookup(code)} style={{
          marginTop: 14, padding: '14px', borderRadius: 14,
          background: code.length >= 6 ? palette.primary : palette.line,
          color: code.length >= 6 ? palette.primaryInk : palette.ink3,
          textAlign: 'center', fontWeight: 500, fontSize: 14, cursor: code.length >= 6 ? 'pointer' : 'default',
        }}>
          {phase === 'scanning' ? t(lang, 'scanning') : t(lang, 'barcodeLookup')}
        </div>
        {hasDetector && (
          <div onClick={() => { setManual(false); setPhase('aim'); }} style={{
            marginTop: 10, textAlign: 'center', fontSize: 13, color: palette.ink3, cursor: 'pointer',
          }}>
            {tt('← Utiliser la caméra', '← Use the camera')}
          </div>
        )}
      </div>
    );
  }

  // ── Camera viewfinder (aim / scanning) ──
  return (
    <div style={{ padding: '8px 20px 28px' }}>
      {Header}
      <div style={{
        position: 'relative', borderRadius: 20, overflow: 'hidden', aspectRatio: '4/3',
        background: '#181C1A',
      }}>
        <video ref={videoRef} autoPlay playsInline muted style={{
          width: '100%', height: '100%', objectFit: 'cover',
          display: phase === 'scanning' ? 'block' : 'none',
        }} />
        {phase !== 'scanning' && (
          <div style={{
            position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column',
            alignItems: 'center', justifyContent: 'center', gap: 10, color: palette.ink3,
          }}>
            <Icon name="barcode" size={40} color="rgba(255,255,255,0.6)" />
          </div>
        )}
        <div style={{
          position: 'absolute', left: '12%', right: '12%', top: '34%', bottom: '34%',
          border: `2px solid ${palette.amber}`, borderRadius: 8,
        }} />
        {phase === 'scanning' && (
          <div className="ai-scan-bar" style={{
            position: 'absolute', left: '12%', right: '12%',
            height: 2, background: palette.amber, boxShadow: `0 0 12px ${palette.amber}`,
          }} />
        )}
      </div>

      {phase === 'aim' && (
        <div onClick={startScan} style={{
          marginTop: 16, padding: '14px', borderRadius: 14,
          background: palette.ink, color: palette.bg,
          textAlign: 'center', fontWeight: 500, fontSize: 14, cursor: 'pointer',
          display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
        }}>
          <Icon name="camera" size={17} color={palette.bg} />
          {t(lang, 'tapToScan')}
        </div>
      )}
      {phase === 'scanning' && (
        <div style={{ marginTop: 16, textAlign: 'center', fontSize: 13, color: palette.ink3 }}>
          {t(lang, 'scanning')}
        </div>
      )}
      <div onClick={() => { stopCamera(); setManual(true); }} style={{
        marginTop: 12, textAlign: 'center', fontSize: 13, color: palette.primary, cursor: 'pointer',
      }}>
        {t(lang, 'barcodeManual')}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Confirm sheet — qty / location / expiry
// ─────────────────────────────────────────────────────────────
function ConfirmAdd({ palette, lang, suggestion, onBack, onConfirm }) {
  const [qty, setQty] = useStateS(suggestion.defaultQty);
  const [loc, setLoc] = useStateS(suggestion.defaultLoc);
  const [days, setDays] = useStateS(suggestion.defaultDays);
  const [variant, setVariant] = useStateS(suggestion.variants ? suggestion.variants[0].id : null);
  const locOptions = [
    { id: 'fridge', label: t(lang, 'fridge'), emoji: '❄️' },
    { id: 'freezer', label: t(lang, 'freezer'), emoji: '🧊' },
    { id: 'pantry', label: t(lang, 'pantry'), emoji: '🗄️' },
  ];
  return (
    <div style={{ padding: '8px 20px 28px' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '4px 0 18px' }}>
        <div onClick={onBack} style={{
          width: 32, height: 32, borderRadius: 10, background: palette.cardSub,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          cursor: 'pointer', border: `1px solid ${palette.line}`, color: palette.ink2,
        }}>
          <Icon name="chevL" size={16} />
        </div>
        <div style={{ flex: 1, fontSize: 18, fontWeight: 600, color: palette.ink, letterSpacing: -0.3 }}>
          {localized(lang, suggestion.name)}
        </div>
        <FoodChip item={suggestion} size={36} style="emoji" palette={palette} />
      </div>

      {/* Variants (sorte) */}
      {suggestion.variants && (
        <Field palette={palette} label={t(lang, 'sort')}>
          <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
            {suggestion.variants.map(v => {
              const a = variant === v.id;
              return (
                <div key={v.id} onClick={() => setVariant(v.id)} style={{
                  padding: '7px 12px', borderRadius: 10,
                  background: a ? palette.ink : palette.card,
                  color: a ? palette.bg : palette.ink2,
                  border: `1px solid ${a ? palette.ink : palette.line}`,
                  fontSize: 12.5, fontWeight: 500, cursor: 'pointer',
                }}>{localized(lang, v.name)}</div>
              );
            })}
          </div>
        </Field>
      )}

      {/* Qty */}
      <Field palette={palette} label={t(lang, 'setQty')}>
        <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
          <div onClick={() => setQty(String(Math.max(1, parseInt(qty) - 1)))} style={stepBtn(palette)}>−</div>
          <input value={qty} onChange={e => setQty(e.target.value)} style={{
            flex: 1, height: 44, background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 12,
            padding: '0 14px', fontSize: 17, color: palette.ink, outline: 'none', fontFamily: 'Geist Mono, monospace',
            fontWeight: 500, textAlign: 'center',
          }} />
          <div onClick={() => setQty(String(parseInt(qty) + 1))} style={stepBtn(palette)}>+</div>
          <div style={{
            padding: '0 12px', height: 44, display: 'flex', alignItems: 'center',
            background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 12,
            fontSize: 13, color: palette.ink2, fontFamily: 'Geist Mono, monospace',
          }}>{localized(lang, suggestion.defaultUnit)}</div>
        </div>
      </Field>

      {/* Location segmented */}
      <Field palette={palette} label={t(lang, 'location')}>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 6 }}>
          {locOptions.map(o => {
            const a = loc === o.id;
            return (
              <div key={o.id} onClick={() => setLoc(o.id)} style={{
                padding: '10px 8px', borderRadius: 12, textAlign: 'center',
                background: a ? palette.primary : palette.card,
                color: a ? palette.primaryInk : palette.ink2,
                border: `1px solid ${a ? palette.primary : palette.line}`,
                fontSize: 13, fontWeight: a ? 500 : 400, cursor: 'pointer',
                display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 3,
              }}>
                <span style={{ fontSize: 18 }}>{o.emoji}</span>
                {o.label}
              </div>
            );
          })}
        </div>
      </Field>

      {/* Expiry quick picker + exact date */}
      <Field palette={palette} label={t(lang, 'expiry')} hint={t(lang, 'autoFilled')}>
        <div style={{ display: 'flex', gap: 6 }}>
          {[1, 3, 7, 14, 30].map(d => {
            const a = days === d;
            return (
              <div key={d} onClick={() => setDays(d)} style={{
                flex: 1, padding: '10px 4px', borderRadius: 10, textAlign: 'center',
                background: a ? palette.ink : palette.card,
                color: a ? palette.bg : palette.ink2,
                border: `1px solid ${a ? palette.ink : palette.line}`,
                fontSize: 12, fontWeight: 500, cursor: 'pointer',
                fontFamily: 'Geist Mono, monospace',
              }}>
                {d}{t(lang, 'days')}
              </div>
            );
          })}
        </div>
        {/* Pick an exact date — converted to days remaining for the algorithm */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 8 }}>
          <span style={{ fontSize: 11.5, color: palette.ink3, fontFamily: 'Geist Mono, monospace', whiteSpace: 'nowrap' }}>
            {lang === 'fr' ? 'ou date :' : 'or date:'}
          </span>
          <input type="date" min={todayISO()} value={addDays(todayISO(), Math.max(0, days))}
            onChange={e => {
              const picked = e.target.value; if (!picked) return;
              const diff = Math.round((parseISO(picked).getTime() - parseISO(todayISO()).getTime()) / 86400000);
              setDays(Math.max(0, diff));
            }}
            style={{
              flex: 1, height: 40, background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 12,
              padding: '0 12px', fontSize: 14, color: palette.ink, outline: 'none', fontFamily: 'inherit',
            }} />
        </div>
      </Field>

      <div onClick={() => {
        const baseName = localized(lang, suggestion.name);
        const v = suggestion.variants && suggestion.variants.find(x => x.id === variant);
        const finalName = v ? { fr: `${baseName} · ${v.name.fr || v.name}`, en: `${baseName} · ${v.name.en || v.name}` } : suggestion.name;
        onConfirm({ ...suggestion, name: finalName, qty, loc, daysLeft: days });
      }} style={{
        marginTop: 22, padding: '15px', borderRadius: 14,
        background: palette.primary, color: palette.primaryInk,
        textAlign: 'center', fontWeight: 500, fontSize: 15, cursor: 'pointer',
        display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
      }}>
        <Icon name="plus" size={18} color={palette.primaryInk} />
        {t(lang, 'confirm')}
      </div>
    </div>
  );
}

function Field({ label, hint, palette, children }) {
  return (
    <div style={{ marginBottom: 14 }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 8 }}>
        <div style={{
          fontSize: 11, fontFamily: 'Geist Mono, monospace', color: palette.ink3,
          letterSpacing: 0.06, textTransform: 'uppercase',
        }}>{label}</div>
        {hint && <div style={{ fontSize: 10.5, color: palette.ink3, fontStyle: 'italic' }}>{hint}</div>}
      </div>
      {children}
    </div>
  );
}

function stepBtn(palette) {
  return {
    width: 44, height: 44, borderRadius: 12,
    background: palette.card, border: `1px solid ${palette.line}`,
    display: 'flex', alignItems: 'center', justifyContent: 'center',
    cursor: 'pointer', fontSize: 20, fontWeight: 400, color: palette.ink, userSelect: 'none',
  };
}

// ─────────────────────────────────────────────────────────────
// Recipe Detail Sheet
// ─────────────────────────────────────────────────────────────
function RecipeDetail({ palette, lang, recipe, inventory, onClose, onCooked, onAddMissingToList, onAddToPlan, onDeleteRecipe, onEditRecipe, autoAdd, setAutoAdd }) {
  const basePortions = (recipe && recipe.portions) || 2;
  const [servings, setServings] = useStateS(basePortions);
  // Reset the serving count whenever a different recipe opens.
  useEffectS(() => { setServings((recipe && recipe.portions) || 2); }, [recipe && recipe.id]);
  if (!recipe) return null;
  const expanded = recipe.steps && recipe.steps.length > 0;
  const hasNutrition = !!recipe.nutrition;
  const hasSplitTime = recipe.prepTime != null && recipe.cookTime != null;
  // Culinary-aware scaling factor for the chosen number of servings.
  const factor = servings / (basePortions || 1);
  const scaler = (typeof window !== 'undefined' && window.scaleQty) ? window.scaleQty : null;
  const scaleIngQty = (ing) => {
    const base = localized(lang, ing.qty);
    if (!scaler || factor === 1) return base;
    return scaler(base, factor, localized(lang, ing.name || {}) + ' ' + localized('en', ing.name || {}));
  };
  // Unified inventory match (by id or ingredient name) for every recipe source.
  const mm = (typeof window !== 'undefined' && window.matchRecipe)
    ? window.matchRecipe(recipe, inventory)
    : { matched: 0, total: (recipe.ingredients || []).length, expiringUsed: 0, missing: [] };
  const missingSet = new Set(mm.missing);
  const missingCount = mm.missing.length;
  // Regional cost estimate, scaled to the chosen servings.
  let _region = 'mtl'; try { _region = localStorage.getItem('pw_region') || 'mtl'; } catch (e) {}
  const baseCost = (typeof window !== 'undefined' && window.estimateRecipeCost) ? window.estimateRecipeCost(recipe, _region, lang) : null;
  const estCost = baseCost != null ? baseCost * factor : null;
  return (
    <div>
      {/* Hero */}
      <div style={{
        height: 180, position: 'relative',
        background: `linear-gradient(135deg, ${recipe.bg1 || palette.primary}, ${recipe.bg2 || palette.amber})`,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
      }}>
        {recipe.image
          ? <img src={recipe.image} alt="" style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover' }} />
          : <div style={{ fontSize: 90, filter: 'drop-shadow(0 6px 16px rgba(0,0,0,0.22))' }}>{recipe.hero}</div>}
        <div onClick={onClose} style={{
          position: 'absolute', top: 14, right: 14,
          width: 36, height: 36, borderRadius: 12,
          background: 'rgba(255,255,255,0.85)', backdropFilter: 'blur(8px)',
          display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
          color: '#1F2A24',
        }}>
          <Icon name="close" size={16} color="#1F2A24" />
        </div>
      </div>

      <div style={{ padding: '18px 20px 28px' }}>
        <div style={{ fontSize: 22, fontWeight: 600, color: palette.ink, letterSpacing: -0.4, lineHeight: 1.15 }}>
          {localized(lang, recipe.name)}
        </div>
        <div style={{ fontSize: 14, color: palette.ink3, marginTop: 4 }}>{localized(lang, recipe.tagline)}</div>

        {/* Time breakdown — prep / cook / total */}
        {hasSplitTime ? (
          <div style={{
            display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8,
            marginTop: 14,
          }}>
            {[
              { label: t(lang, 'prepTime'), val: recipe.prepTime + 'm', icon: 'pen' },
              { label: t(lang, 'cookTime'), val: recipe.cookTime + 'm', icon: 'fire' },
              { label: t(lang, 'portions'), val: servings, icon: 'user' },
            ].map((c, i) => (
              <div key={i} style={{
                background: palette.cardSub, border: `1px solid ${palette.line2}`,
                borderRadius: 12, padding: '10px 8px', textAlign: 'center',
              }}>
                <div style={{ color: palette.ink3, marginBottom: 3, display: 'flex', justifyContent: 'center' }}>
                  <Icon name={c.icon} size={13} color={palette.ink3} />
                </div>
                <div style={{ fontSize: 15, fontWeight: 600, color: palette.ink, letterSpacing: -0.3, fontFamily: 'Geist, sans-serif' }}>
                  {c.val}
                </div>
                <div style={{ fontSize: 10, color: palette.ink3, fontFamily: 'Geist Mono, monospace', textTransform: 'uppercase', letterSpacing: 0.06, marginTop: 1 }}>
                  {c.label}
                </div>
              </div>
            ))}
          </div>
        ) : (
          <div style={{ display: 'flex', gap: 14, marginTop: 14, fontSize: 12.5, color: palette.ink2, fontFamily: 'Geist Mono, monospace' }}>
            <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
              <Icon name="clock" size={14} color={palette.ink3} />{recipe.minutes} {t(lang, 'minutes')}
            </span>
            <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
              <Icon name="user" size={14} color={palette.ink3} />{servings} {t(lang, 'portions')}
            </span>
            <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
              <Icon name="fire" size={14} color={palette.ink3} />{localized(lang, recipe.difficulty)}
            </span>
          </div>
        )}

        {/* Estimated cost (regional, scales with servings) */}
        {estCost != null && (
          <div style={{ marginTop: 12, display: 'inline-flex', alignItems: 'center', gap: 8, padding: '7px 12px', borderRadius: 10, background: palette.cardSub, border: `1px solid ${palette.line2}` }}>
            <span style={{ fontSize: 14 }}>💰</span>
            <span style={{ fontSize: 13, fontWeight: 600, color: palette.ink }}>~{estCost.toFixed(2)} $</span>
            <span style={{ fontSize: 11, color: palette.ink3, fontFamily: 'Geist Mono, monospace' }}>
              · {(estCost / Math.max(1, servings)).toFixed(2)} $/{lang === 'fr' ? 'pers' : 'serving'}
            </span>
          </div>
        )}

        {/* Servings stepper — quantities rescale (culinary-aware, not flat ×) */}
        <div style={{
          marginTop: 14, padding: '10px 12px', borderRadius: 14,
          background: palette.cardSub, border: `1px solid ${palette.line2}`,
          display: 'flex', alignItems: 'center', gap: 10,
        }}>
          <Icon name="user" size={16} color={palette.ink2} />
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 13, fontWeight: 500, color: palette.ink }}>
              {lang === 'fr' ? 'Pour combien de personnes' : 'How many servings'}
            </div>
            {factor !== 1 && (
              <div style={{ fontSize: 10.5, color: palette.primary, fontFamily: 'Geist Mono, monospace', marginTop: 1 }}>
                {lang === 'fr' ? `ajusté depuis ${basePortions}` : `adjusted from ${basePortions}`}
              </div>
            )}
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <div onClick={() => setServings(s => Math.max(1, s - 1))} style={{
              width: 30, height: 30, borderRadius: 9, cursor: 'pointer',
              background: palette.card, border: `1px solid ${palette.line}`,
              display: 'flex', alignItems: 'center', justifyContent: 'center', color: palette.ink,
              fontSize: 18, fontWeight: 600, userSelect: 'none',
            }}>−</div>
            <div style={{ minWidth: 22, textAlign: 'center', fontSize: 16, fontWeight: 600, color: palette.ink, fontFamily: 'Geist Mono, monospace' }}>{servings}</div>
            <div onClick={() => setServings(s => Math.min(24, s + 1))} style={{
              width: 30, height: 30, borderRadius: 9, cursor: 'pointer',
              background: palette.card, border: `1px solid ${palette.line}`,
              display: 'flex', alignItems: 'center', justifyContent: 'center', color: palette.ink,
              fontSize: 18, fontWeight: 600, userSelect: 'none',
            }}>+</div>
          </div>
        </div>

        {/* "Why" callout */}
        <div style={{
          marginTop: 16, padding: '12px 14px', borderRadius: 14,
          background: palette.primarySoft, color: palette.primarySoftInk,
          display: 'flex', alignItems: 'flex-start', gap: 10,
        }}>
          <Icon name="sparkle" size={18} color={palette.primarySoftInk} />
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 12, fontWeight: 600, letterSpacing: -0.1 }}>{t(lang, 'why')}</div>
            <div style={{ fontSize: 12, marginTop: 2, opacity: 0.85 }}>{t(lang, 'whyDesc')}</div>
          </div>
        </div>

        {/* Nutrition */}
        {hasNutrition && (
          <>
            <div style={{
              marginTop: 22, fontSize: 11, fontFamily: 'Geist Mono, monospace',
              color: palette.ink3, letterSpacing: 0.06, textTransform: 'uppercase',
            }}>
              {t(lang, 'nutrition')}
            </div>
            <div style={{
              marginTop: 8, display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 6,
            }}>
              {[
                { v: recipe.nutrition.cal, l: t(lang, 'calories') },
                { v: recipe.nutrition.p + 'g', l: t(lang, 'protein') },
                { v: recipe.nutrition.c + 'g', l: t(lang, 'carbs') },
                { v: recipe.nutrition.f + 'g', l: t(lang, 'fat') },
              ].map((n, i) => (
                <div key={i} style={{
                  background: palette.card, border: `1px solid ${palette.line}`,
                  borderRadius: 10, padding: '8px 6px', textAlign: 'center',
                }}>
                  <div style={{ fontSize: 14, fontWeight: 600, color: palette.ink, fontFamily: 'Geist, sans-serif', letterSpacing: -0.2 }}>
                    {n.v}
                  </div>
                  <div style={{ fontSize: 9.5, color: palette.ink3, marginTop: 1, fontFamily: 'Geist Mono, monospace', textTransform: 'uppercase', letterSpacing: 0.06 }}>
                    {n.l}
                  </div>
                </div>
              ))}
            </div>
          </>
        )}

        {/* Ingredients */}
        <div style={{
          marginTop: 22, fontSize: 11, fontFamily: 'Geist Mono, monospace',
          color: palette.ink3, letterSpacing: 0.06, textTransform: 'uppercase',
        }}>
          {t(lang, 'ingredients')} · {mm.matched}/{recipe.ingredients.length} {t(lang, 'haveIt').toLowerCase()}
        </div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginTop: 10 }}>
          {recipe.ingredients.map((ing, i) => {
            const isMissing = missingSet.has(ing);
            const have = !isMissing && !ing.pantry;
            const inv = ing.id ? inventory.find(x => x.id === ing.id) : null;
            const resolved = (typeof window !== 'undefined' && window.resolveIngredient)
              ? window.resolveIngredient(ing) : { name: ing.name || { fr: '', en: '' }, emoji: ing.emoji || '🍽' };
            const name = inv ? localized(lang, inv.name) : localized(lang, resolved.name);
            const emoji = (inv ? inv.emoji : resolved.emoji) || '🍽';
            return (
              <div key={i} style={{
                display: 'flex', alignItems: 'center', gap: 12, padding: '10px 12px',
                background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 12,
                opacity: have || ing.pantry ? 1 : 0.95,
              }}>
                <div style={{
                  width: 32, height: 32, borderRadius: 16,
                  background: have ? palette.primarySoft : ing.pantry ? palette.cardSub : palette.amberSoft,
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                  fontSize: 17, flexShrink: 0,
                }}>{emoji}</div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 14, fontWeight: 500, color: palette.ink, letterSpacing: -0.1 }}>{name}</div>
                  <div style={{ fontSize: 11, color: factor !== 1 ? palette.primary : palette.ink3, marginTop: 1, fontFamily: 'Geist Mono, monospace' }}>
                    {scaleIngQty(ing)}
                  </div>
                </div>
                {have ? (
                  <span style={{
                    fontSize: 10.5, fontWeight: 500, color: palette.primarySoftInk,
                    background: palette.primarySoft, padding: '3px 8px', borderRadius: 6,
                    fontFamily: 'Geist Mono, monospace', display: 'inline-flex', alignItems: 'center', gap: 4,
                  }}>
                    <Icon name="check" size={12} color={palette.primarySoftInk} />
                    {t(lang, 'haveIt')}
                  </span>
                ) : ing.pantry ? (
                  <span style={{
                    fontSize: 10.5, color: palette.ink3,
                    fontFamily: 'Geist Mono, monospace',
                  }}>—</span>
                ) : (
                  <span style={{
                    fontSize: 10.5, fontWeight: 500, color: palette.amberSoftInk,
                    background: palette.amberSoft, padding: '3px 8px', borderRadius: 6,
                    fontFamily: 'Geist Mono, monospace',
                  }}>{t(lang, 'needIt')}</span>
                )}
              </div>
            );
          })}
        </div>

        {/* Steps */}
        {expanded && (
          <>
            <div style={{
              marginTop: 22, fontSize: 11, fontFamily: 'Geist Mono, monospace',
              color: palette.ink3, letterSpacing: 0.06, textTransform: 'uppercase',
            }}>
              {t(lang, 'steps')}
            </div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginTop: 10 }}>
              {recipe.steps.map((step, i) => (
                <div key={i} style={{
                  display: 'flex', gap: 12, padding: '10px 12px',
                  background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 12,
                }}>
                  <div style={{
                    width: 22, height: 22, borderRadius: 11,
                    background: palette.primary, color: palette.primaryInk,
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                    fontSize: 11, fontWeight: 600, fontFamily: 'Geist Mono, monospace', flexShrink: 0,
                  }}>{i + 1}</div>
                  <div style={{ fontSize: 13.5, color: palette.ink2, lineHeight: 1.45 }}>{localized(lang, step)}</div>
                </div>
              ))}
            </div>
          </>
        )}

        {/* Auto-add missing toggle */}
        {setAutoAdd != null && (
          <div onClick={() => setAutoAdd(!autoAdd)} style={{
            marginTop: 18, padding: '11px 14px', borderRadius: 12,
            background: autoAdd ? palette.primarySoft : palette.cardSub,
            border: `1px solid ${autoAdd ? palette.primary + '40' : palette.line}`,
            display: 'flex', alignItems: 'center', gap: 12, cursor: 'pointer',
          }}>
            <Icon name="cart" size={16} color={autoAdd ? palette.primarySoftInk : palette.ink2} />
            <div style={{ flex: 1, fontSize: 13, color: autoAdd ? palette.primarySoftInk : palette.ink2, fontWeight: 500 }}>
              {t(lang, 'autoAdd')}
              <div style={{ fontSize: 11, fontWeight: 400, opacity: 0.75, marginTop: 1 }}>
                {autoAdd ? t(lang, 'autoAddOn') : (lang === 'fr' ? 'Manuel' : 'Manual')}
              </div>
            </div>
            <div style={{
              width: 36, height: 22, borderRadius: 12, padding: 2,
              background: autoAdd ? palette.primary : palette.line, display: 'flex',
              transition: 'background 180ms',
            }}>
              <div style={{
                width: 18, height: 18, borderRadius: 9, background: '#fff',
                transform: autoAdd ? 'translateX(14px)' : 'translateX(0)',
                transition: 'transform 180ms',
              }} />
            </div>
          </div>
        )}

        {/* Actions */}
        {/* Always offer to add the missing ingredients to the grocery list —
            works for catalog AND online recipes (matched by name). */}
        {missingCount > 0 && (
          <div onClick={() => onAddMissingToList && onAddMissingToList(mm.missing)} style={{
            marginTop: 16, padding: '14px', borderRadius: 14,
            background: palette.primarySoft, border: `1px solid ${palette.primary}40`, color: palette.primarySoftInk,
            textAlign: 'center', fontWeight: 600, fontSize: 14, cursor: 'pointer',
            display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
          }}>
            <Icon name="cart" size={17} color={palette.primarySoftInk} />
            {lang === 'fr'
              ? `Ajouter ${missingCount} manquant${missingCount > 1 ? 's' : ''} à la liste`
              : `Add ${missingCount} missing to list`}
          </div>
        )}
        <div style={{ display: 'flex', gap: 8, marginTop: 10 }}>
          {onAddToPlan && (
            <div onClick={() => onAddToPlan(recipe)} style={{
              flex: 1, padding: '14px', borderRadius: 14,
              background: palette.card, border: `1px solid ${palette.line}`, color: palette.ink,
              textAlign: 'center', fontWeight: 500, fontSize: 13.5, cursor: 'pointer',
              display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
            }}>
              <Icon name="calendar" size={17} color={palette.ink} />
              {lang === 'fr' ? 'Au calendrier' : 'To calendar'}
            </div>
          )}
          <div onClick={onCooked} style={{
            flex: 1, padding: '14px', borderRadius: 14,
            background: palette.primary, color: palette.primaryInk,
            textAlign: 'center', fontWeight: 500, fontSize: 13.5, cursor: 'pointer',
            display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
          }}>
            <Icon name="check" size={17} color={palette.primaryInk} />
            {t(lang, 'cook')}
          </div>
        </div>
        {/* Custom recipe management */}
        {recipe.custom && (onEditRecipe || onDeleteRecipe) && (
          <div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
            {onEditRecipe && (
              <div onClick={() => onEditRecipe(recipe)} style={{
                flex: 1, padding: '11px', borderRadius: 12, background: palette.cardSub, border: `1px solid ${palette.line}`,
                color: palette.ink2, textAlign: 'center', fontSize: 13, fontWeight: 500, cursor: 'pointer',
                display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
              }}>
                <Icon name="pen" size={14} color={palette.ink2} />{lang === 'fr' ? 'Modifier' : 'Edit'}
              </div>
            )}
            {onDeleteRecipe && (
              <div onClick={() => onDeleteRecipe(recipe)} style={{
                flex: 1, padding: '11px', borderRadius: 12, background: palette.redSoft, border: `1px solid transparent`,
                color: palette.redSoftInk, textAlign: 'center', fontSize: 13, fontWeight: 500, cursor: 'pointer',
                display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
              }}>
                <Icon name="trash" size={14} color={palette.redSoftInk} />{lang === 'fr' ? 'Supprimer' : 'Delete'}
              </div>
            )}
          </div>
        )}
      </div>
    </div>
  );
}

Object.assign(window, {
  Sheet, AddMethodPicker, ManualAdd, AIPhotoAdd, BarcodeAdd, ConfirmAdd, RecipeDetail,
  DotsActionSheet, QuickLogSheet, BuyConfirmSheet, SavedExplainerSheet,
});

// ─────────────────────────────────────────────────────────────
// DotsActionSheet — actions for an inventory item
// ─────────────────────────────────────────────────────────────
function DotsActionSheet({ palette, lang, item, inventory, onClose, onEdit, onMove, onDelete, onUsedSome, onSeeRecipes, onTogglePrivate }) {
  if (!item) return null;
  // Recipes that use this item
  const matchingRecipes = RECIPES.filter(r => r.uses.includes(item.id));
  const actions = [
    { id: 'edit', icon: 'pen', label: t(lang, 'edit'), onClick: onEdit },
    { id: 'used', icon: 'fire', label: t(lang, 'usedSome'), onClick: onUsedSome },
    { id: 'move', icon: 'box', label: t(lang, 'move'), onClick: onMove },
    { id: 'recipes', icon: 'chef', label: t(lang, 'seeRecipes'), onClick: onSeeRecipes,
      sub: `${matchingRecipes.length} ${t(lang, 'nRecipesUse')}` },
    onTogglePrivate && { id: 'private', icon: 'user', label: item.private
        ? (lang === 'fr' ? 'Rendre disponible à la famille' : 'Share with family')
        : (lang === 'fr' ? 'Marquer comme personnel' : 'Mark as personal'),
      sub: item.private ? (lang === 'fr' ? 'Actuellement bloqué de l\'inventaire familial' : 'Currently private') : (lang === 'fr' ? 'Visible mais non utilisable par la famille' : 'Visible but not shared'),
      onClick: onTogglePrivate },
    { id: 'delete', icon: 'trash', label: t(lang, 'delete'), onClick: onDelete, danger: true },
  ].filter(Boolean);
  return (
    <div style={{ padding: '8px 20px 28px' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '4px 0 16px' }}>
        <FoodChip item={item} size={42} style="emoji" palette={palette} />
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 17, fontWeight: 600, color: palette.ink, letterSpacing: -0.3 }}>
            {localized(lang, item.name)}
          </div>
          <div style={{ fontSize: 11.5, color: palette.ink3, marginTop: 2, fontFamily: 'Geist Mono, monospace' }}>
            {item.qty} {localized(lang, item.unit)} · {t(lang, item.location)} · {expiryLabel(lang, item.daysLeft)}
          </div>
        </div>
        <div onClick={onClose} style={{
          width: 32, height: 32, borderRadius: 10, background: palette.cardSub,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          cursor: 'pointer', border: `1px solid ${palette.line}`, color: palette.ink2, flexShrink: 0,
        }}>
          <Icon name="close" size={15} />
        </div>
      </div>
      <div style={{ background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 14, overflow: 'hidden' }}>
        {actions.map((a, i) => (
          <div key={a.id} onClick={a.onClick} style={{
            display: 'flex', alignItems: 'center', gap: 12,
            padding: '13px 14px', cursor: 'pointer',
            borderBottom: i < actions.length - 1 ? `1px solid ${palette.line2}` : 'none',
          }}>
            <div style={{
              width: 32, height: 32, borderRadius: 10,
              background: a.danger ? palette.redSoft : palette.cardSub,
              color: a.danger ? palette.redSoftInk : palette.ink2,
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              border: `1px solid ${a.danger ? palette.red + '40' : palette.line}`,
            }}>
              <Icon name={a.icon} size={16} color={a.danger ? palette.redSoftInk : palette.ink2} />
            </div>
            <div style={{ flex: 1 }}>
              <div style={{ fontSize: 14, fontWeight: 500, color: a.danger ? palette.red : palette.ink }}>{a.label}</div>
              {a.sub && (
                <div style={{ fontSize: 11.5, color: palette.ink3, marginTop: 1, fontFamily: 'Geist Mono, monospace' }}>
                  {a.sub}
                </div>
              )}
            </div>
            <Icon name="chevR" size={15} color={palette.ink3} />
          </div>
        ))}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// QuickLogSheet — "I ate this" — quick inventory deduction
// ─────────────────────────────────────────────────────────────
function QuickLogSheet({ palette, lang, inventory, onClose, onLog }) {
  const [mealType, setMealType] = useStateS('snack');
  const [picked, setPicked] = useStateS(new Set());
  const meals = ['breakfast', 'lunch', 'dinner', 'snack'];
  // pick from inventory most-likely to be eaten right now (closest to expiry)
  const candidates = [...inventory].sort((a, b) => a.daysLeft - b.daysLeft).slice(0, 12);
  const toggle = (id) => setPicked(prev => {
    const n = new Set(prev); if (n.has(id)) n.delete(id); else n.add(id); return n;
  });
  return (
    <div style={{ padding: '8px 20px 28px' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '4px 0 14px' }}>
        <div style={{
          width: 44, height: 44, borderRadius: 12,
          background: `linear-gradient(135deg, ${palette.amber}, ${palette.primary})`,
          color: '#fff',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
        }}>
          <Icon name="fire" size={20} color="#fff" style="filled" />
        </div>
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: 17, fontWeight: 600, color: palette.ink, letterSpacing: -0.3 }}>
            {t(lang, 'quickLog')}
          </div>
          <div style={{ fontSize: 12, color: palette.ink3, marginTop: 2 }}>{t(lang, 'quickLogSub')}</div>
        </div>
        <div onClick={onClose} style={{
          width: 32, height: 32, borderRadius: 10, background: palette.cardSub,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          cursor: 'pointer', border: `1px solid ${palette.line}`, color: palette.ink2,
        }}>
          <Icon name="close" size={15} />
        </div>
      </div>

      {/* Meal type chips */}
      <div style={{ display: 'flex', gap: 6, marginBottom: 14, flexWrap: 'wrap' }}>
        {meals.map(m => (
          <div key={m} onClick={() => setMealType(m)} style={{
            padding: '7px 12px', borderRadius: 999,
            background: mealType === m ? palette.ink : palette.card,
            color: mealType === m ? palette.bg : palette.ink2,
            border: `1px solid ${mealType === m ? palette.ink : palette.line}`,
            fontSize: 12.5, fontWeight: 500, cursor: 'pointer',
          }}>{t(lang, m)}</div>
        ))}
      </div>

      <div style={{ fontSize: 11, fontFamily: 'Geist Mono, monospace', color: palette.ink3, letterSpacing: 0.06, textTransform: 'uppercase', marginBottom: 10 }}>
        {lang === 'fr' ? 'Qu\'as-tu mangé ?' : 'What did you eat?'}
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8 }}>
        {candidates.map(item => {
          const sel = picked.has(item.id);
          return (
            <div key={item.id} onClick={() => toggle(item.id)} style={{
              aspectRatio: '1 / 1', borderRadius: 14,
              background: sel ? palette.primarySoft : palette.card,
              border: `1.5px solid ${sel ? palette.primary : palette.line}`,
              display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
              cursor: 'pointer', position: 'relative', padding: 6,
            }}>
              {sel && (
                <div style={{
                  position: 'absolute', top: 6, right: 6,
                  width: 18, height: 18, borderRadius: 9, background: palette.primary,
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                }}>
                  <Icon name="check" size={10} color={palette.primaryInk} />
                </div>
              )}
              <div style={{ fontSize: 28 }}>{item.emoji}</div>
              <div style={{ fontSize: 10.5, color: sel ? palette.primarySoftInk : palette.ink, marginTop: 4, fontWeight: 500, textAlign: 'center', lineHeight: 1.15 }}>
                {localized(lang, item.name)}
              </div>
            </div>
          );
        })}
      </div>

      {/* AI assist hint */}
      <div style={{
        marginTop: 14, padding: '11px 12px', borderRadius: 12,
        background: palette.amberSoft, color: palette.amberSoftInk,
        display: 'flex', alignItems: 'center', gap: 10,
      }}>
        <Icon name="sparkle" size={16} color={palette.amberSoftInk} />
        <div style={{ flex: 1, fontSize: 12, lineHeight: 1.4 }}>
          {lang === 'fr' ? 'Décris ce que tu as mangé, l\'IA ajuste l\'inventaire' : 'Describe what you ate, AI adjusts inventory'}
          <div style={{ fontSize: 10.5, opacity: 0.75, marginTop: 2, fontFamily: 'Geist Mono, monospace' }}>
            {lang === 'fr' ? '« Sandwich, 2 tranches de pain, fromage »' : '"Sandwich, 2 slices bread, cheese"'}
          </div>
        </div>
      </div>

      <div onClick={() => onLog({ mealType, items: [...picked] })} style={{
        marginTop: 16, padding: '14px', borderRadius: 14,
        background: picked.size > 0 ? palette.primary : palette.line,
        color: picked.size > 0 ? palette.primaryInk : palette.ink3,
        textAlign: 'center', fontWeight: 500, fontSize: 14,
        cursor: picked.size > 0 ? 'pointer' : 'default',
        display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
      }}>
        <Icon name="check" size={17} color={picked.size > 0 ? palette.primaryInk : palette.ink3} />
        {picked.size > 0
          ? (lang === 'fr' ? `Ajuster (${picked.size})` : `Adjust (${picked.size})`)
          : (lang === 'fr' ? 'Choisis un aliment' : 'Pick something')}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// BuyConfirmSheet — when a grocery item is checked off,
// confirm qty/unit → add to inventory
// ─────────────────────────────────────────────────────────────
function BuyConfirmSheet({ palette, lang, groceryItem, onClose, onConfirm }) {
  const [count, setCount] = useStateS(1);
  const [size, setSize] = useStateS('1');
  const [unit, setUnit] = useStateS('L');
  const units = ['ml', 'L', 'g', 'kg', 'pcs'];
  if (!groceryItem) return null;
  const itemName = typeof groceryItem.name === 'string' ? groceryItem.name : localized(lang, groceryItem.name);
  return (
    <div style={{ padding: '8px 20px 28px' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '4px 0 18px' }}>
        <div style={{
          width: 44, height: 44, borderRadius: 12,
          background: palette.primarySoft, color: palette.primarySoftInk,
          display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 22,
        }}>{groceryItem.emoji || '🛒'}</div>
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: 17, fontWeight: 600, color: palette.ink, letterSpacing: -0.3 }}>
            {t(lang, 'boughtIt')} — {itemName}
          </div>
          <div style={{ fontSize: 12, color: palette.ink3, marginTop: 2 }}>{t(lang, 'boughtItSub')}</div>
        </div>
        <div onClick={onClose} style={{
          width: 32, height: 32, borderRadius: 10, background: palette.cardSub,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          cursor: 'pointer', border: `1px solid ${palette.line}`, color: palette.ink2,
        }}>
          <Icon name="close" size={15} />
        </div>
      </div>

      {/* "2 × 1 L" composer */}
      <Field palette={palette} label={lang === 'fr' ? 'Combien ?' : 'How many?'}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <div onClick={() => setCount(Math.max(1, count - 1))} style={stepBtn(palette)}>−</div>
          <div style={{
            flex: '0 0 60px', height: 44, background: palette.card,
            border: `1px solid ${palette.line}`, borderRadius: 12,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            fontSize: 18, fontWeight: 600, color: palette.ink, fontFamily: 'Geist Mono, monospace',
          }}>{count}</div>
          <div onClick={() => setCount(count + 1)} style={stepBtn(palette)}>+</div>
          <div style={{ fontSize: 18, fontWeight: 400, color: palette.ink3, padding: '0 6px' }}>×</div>
          <input value={size} onChange={e => setSize(e.target.value)} style={{
            flex: 1, height: 44, background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 12,
            padding: '0 14px', fontSize: 17, color: palette.ink, outline: 'none', fontFamily: 'Geist Mono, monospace',
            fontWeight: 500, textAlign: 'center',
          }} />
          <div style={{
            display: 'flex', gap: 4,
            background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 12,
            padding: 4,
          }}>
            {units.map(u => (
              <div key={u} onClick={() => setUnit(u)} style={{
                padding: '4px 8px', borderRadius: 8,
                background: unit === u ? palette.ink : 'transparent',
                color: unit === u ? palette.bg : palette.ink3,
                fontSize: 11, fontWeight: 500, cursor: 'pointer',
                fontFamily: 'Geist Mono, monospace',
              }}>{u}</div>
            ))}
          </div>
        </div>
        <div style={{ fontSize: 11, color: palette.ink3, marginTop: 8, fontFamily: 'Geist Mono, monospace' }}>
          → {count} × {size} {unit} {lang === 'fr' ? 'de' : 'of'} {itemName}
        </div>
      </Field>

      <div onClick={() => onConfirm({ count, size, unit })} style={{
        marginTop: 18, padding: '15px', borderRadius: 14,
        background: palette.primary, color: palette.primaryInk,
        textAlign: 'center', fontWeight: 500, fontSize: 15, cursor: 'pointer',
        display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
      }}>
        <Icon name="plus" size={18} color={palette.primaryInk} />
        {t(lang, 'addToInv')}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// SavedExplainerSheet — how the "$ saved this month" works
// ─────────────────────────────────────────────────────────────
function SavedExplainerSheet({ palette, lang, onClose }) {
  return (
    <div style={{ padding: '8px 20px 28px' }}>
      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, padding: '4px 0 18px' }}>
        <div style={{
          width: 44, height: 44, borderRadius: 12,
          background: palette.primary, color: palette.primaryInk,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
        }}>
          <Icon name="sparkle" size={20} color={palette.primaryInk} />
        </div>
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: 17, fontWeight: 600, color: palette.ink, letterSpacing: -0.3 }}>
            {t(lang, 'savedHow')}
          </div>
        </div>
        <div onClick={onClose} style={{
          width: 32, height: 32, borderRadius: 10, background: palette.cardSub,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          cursor: 'pointer', border: `1px solid ${palette.line}`, color: palette.ink2,
        }}>
          <Icon name="close" size={15} />
        </div>
      </div>

      <div style={{
        background: palette.primarySoft, color: palette.primarySoftInk,
        borderRadius: 16, padding: '18px 16px', textAlign: 'center', marginBottom: 14,
      }}>
        <div style={{ fontSize: 44, fontWeight: 600, letterSpacing: -1.5, fontFamily: 'Geist, sans-serif', lineHeight: 1 }}>47$</div>
        <div style={{ fontSize: 12, marginTop: 4, opacity: 0.85 }}>{lang === 'fr' ? 'économisé ce mois-ci' : 'saved this month'}</div>
      </div>

      <div style={{ fontSize: 13.5, color: palette.ink2, lineHeight: 1.55, marginBottom: 16 }}>
        {t(lang, 'savedExpl')}
      </div>

      <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
        {[
          { icon: 'leaf', label: lang === 'fr' ? 'Aliments cuisinés à temps' : 'Cooked in time', val: '12' },
          { icon: 'cart', label: lang === 'fr' ? 'Prix moyen $/aliment' : 'Avg price per item', val: '~3.90$' },
          { icon: 'fire', label: lang === 'fr' ? 'Gaspillage évité' : 'Waste avoided', val: '47$' },
        ].map((r, i) => (
          <div key={i} style={{
            display: 'flex', alignItems: 'center', gap: 12, padding: '12px 14px',
            background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 12,
          }}>
            <div style={{
              width: 30, height: 30, borderRadius: 9,
              background: palette.cardSub, color: palette.ink2,
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              border: `1px solid ${palette.line}`,
            }}>
              <Icon name={r.icon} size={15} color={palette.ink2} />
            </div>
            <div style={{ flex: 1, fontSize: 13, color: palette.ink, fontWeight: 500 }}>{r.label}</div>
            <div style={{ fontSize: 13, color: palette.primary, fontFamily: 'Geist Mono, monospace', fontWeight: 600 }}>{r.val}</div>
          </div>
        ))}
      </div>

      <div style={{
        marginTop: 14, padding: '11px 12px', borderRadius: 12,
        background: palette.amberSoft, color: palette.amberSoftInk,
        display: 'flex', alignItems: 'center', gap: 10, fontSize: 12,
      }}>
        <Icon name="sparkle" size={15} color={palette.amberSoftInk} />
        <div style={{ flex: 1 }}>
          {lang === 'fr' ? 'Bientôt : prix réels depuis les circulaires de ton quartier (IGA, Metro, Maxi…)' : 'Soon: real flyer prices from local stores (IGA, Metro, Maxi…)'}
        </div>
      </div>
    </div>
  );
}
