// shopping.jsx — 購物清單功能

// ── helpers ──────────────────────────────────────────────────

function _shopApi(method, path, body) {
  const opts = { method, headers: { Authorization: `Bearer ${window.tabiToken()}` } };
  if (body) { opts.headers['Content-Type'] = 'application/json'; opts.body = JSON.stringify(body); }
  return fetch('/api/shopping' + path, opts).then(r => {
    if (r.status === 204) return null;
    return r.ok ? r.json() : r.json().then(d => Promise.reject(d.detail || '操作失敗'));
  });
}

function _fmtShopAmt(price, currency, qty) {
  if (!price) return null;
  const sym = currency === 'JPY' ? '¥' : currency === 'TWD' ? 'NT$' : currency;
  const total = price * (qty || 1);
  return `${sym}${total.toLocaleString()}`;
}

const SHOP_EMOJIS = ['🛍️','💊','📷','👗','👟','🎮','🍫','🧴','🏠','🎁','✏️','🎯'];
const PRIORITY_COLOR = { must: '#B94A35', want: '#574B40' };

// ── Image Lightbox ────────────────────────────────────────────

function ImageLightbox({ src, onClose }) {
  return ReactDOM.createPortal(
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, zIndex: 9500,
      background: 'rgba(0,0,0,0.88)', display: 'flex', alignItems: 'center', justifyContent: 'center',
      animation: 'tabiFade 0.18s ease', cursor: 'zoom-out',
    }}>
      <img src={src} alt="" style={{
        maxWidth: '94vw', maxHeight: '88vh', objectFit: 'contain',
        borderRadius: 12, boxShadow: '0 24px 60px rgba(0,0,0,0.5)',
      }} onClick={e => e.stopPropagation()} />
      <button onClick={onClose} style={{
        position: 'absolute', top: 20, right: 20, width: 38, height: 38,
        borderRadius: 19, border: '1px solid rgba(255,255,255,0.3)', background: 'rgba(0,0,0,0.5)',
        color: '#fff', fontSize: 18, cursor: 'pointer', display: 'grid', placeItems: 'center',
      }}>×</button>
    </div>,
    document.body
  );
}

// ── AddEditListSheet ──────────────────────────────────────────

function AddEditListSheet({ existing, onClose, onSaved }) {
  const [title, setTitle]   = React.useState(existing?.title || '');
  const [emoji, setEmoji]   = React.useState(existing?.emoji || '🛍️');
  const [saving, setSaving] = React.useState(false);
  const [err, setErr]       = React.useState('');

  async function save() {
    if (!title.trim()) { setErr('請輸入清單名稱'); return; }
    setSaving(true);
    try {
      const body = { title: title.trim(), emoji };
      await _shopApi(existing ? 'PATCH' : 'POST', existing ? `/lists/${existing.id}` : '/lists', body);
      onSaved();
    } catch (e) { setErr(String(e)); setSaving(false); }
  }

  const inputStyle = {
    width: '100%', padding: '10px 12px', borderRadius: 10,
    border: `1px solid ${TABI_COLORS.line}`, background: TABI_COLORS.bg,
    fontSize: 16, color: TABI_COLORS.ink, outline: 'none', fontFamily: 'inherit', boxSizing: 'border-box',
  };

  return ReactDOM.createPortal(
    <div style={{ position:'fixed', inset:0, zIndex:9100, background:'rgba(24,20,15,0.48)', display:'flex', alignItems:'flex-end', animation:'tabiFade 0.18s ease' }}
      onClick={e => e.target === e.currentTarget && onClose()}>
      <div style={{ width:'100%', maxWidth:480, margin:'0 auto', background:TABI_COLORS.bg, borderRadius:'24px 24px 0 0', maxHeight:'90dvh', display:'flex', flexDirection:'column', boxShadow:'0 -22px 50px rgba(24,20,15,0.20)', animation:'tabiSheetUp 0.22s ease' }}>
        {/* drag handle */}
        <div style={{ width:36, height:4, borderRadius:2, background:TABI_COLORS.line2, margin:'12px auto 0', flexShrink:0 }} />
        {/* header */}
        <div style={{ display:'flex', alignItems:'center', padding:'14px 18px 12px', borderBottom:`1px solid ${TABI_COLORS.line}`, flexShrink:0 }}>
          <div style={{ fontFamily:tabiSerif, fontSize:18, fontWeight:700, color:TABI_COLORS.ink, flex:1 }}>{existing ? '編輯清單' : '新增清單'}</div>
          <button onClick={onClose} style={{ width:32, height:32, borderRadius:16, border:`1px solid ${TABI_COLORS.line}`, background:'transparent', fontSize:18, cursor:'pointer', color:TABI_COLORS.ink3, display:'grid', placeItems:'center' }}>×</button>
        </div>
        {/* content */}
        <div style={{ flex:1, overflowY:'auto', padding:'16px 18px' }}>
          <div style={{ fontSize:11.5, color:TABI_COLORS.ink3, marginBottom:5, fontWeight:600 }}>清單名稱</div>
          <input value={title} onChange={e => setTitle(e.target.value)} placeholder="例：藥妝必買、電器清單" style={inputStyle} />
          <div style={{ fontSize:11.5, color:TABI_COLORS.ink3, margin:'14px 0 8px', fontWeight:600 }}>圖示</div>
          <div style={{ display:'grid', gridTemplateColumns:'repeat(6, 1fr)', gap:8 }}>
            {SHOP_EMOJIS.map(e => (
              <button key={e} onClick={() => setEmoji(e)} style={{
                height:50, borderRadius:10, fontSize:24, lineHeight:1,
                border:`2px solid ${emoji === e ? TABI_COLORS.red : TABI_COLORS.line}`,
                background: emoji === e ? `${TABI_COLORS.red}12` : TABI_COLORS.bg,
                cursor:'pointer', display:'grid', placeItems:'center',
                boxShadow: emoji === e ? `0 0 0 3px ${TABI_COLORS.red}20` : 'none',
                transition:'border-color 0.15s, background 0.15s',
              }}>{e}</button>
            ))}
          </div>
          {err && <div style={{ color:TABI_COLORS.red, fontSize:12.5, marginTop:10 }}>{err}</div>}
        </div>
        {/* footer */}
        <div style={{ padding:'12px 18px calc(env(safe-area-inset-bottom,0px)+16px)', borderTop:`1px solid ${TABI_COLORS.line}`, flexShrink:0, display:'flex', gap:10 }}>
          <button onClick={onClose} style={{ flex:1, padding:'13px', borderRadius:12, border:`1px solid ${TABI_COLORS.line}`, background:'transparent', fontSize:14, cursor:'pointer', color:TABI_COLORS.ink2 }}>取消</button>
          <button onClick={save} disabled={saving} style={{ flex:2, padding:'13px', borderRadius:12, border:'none', background:saving?TABI_COLORS.line2:TABI_COLORS.ink, color:'#fff', fontSize:14, fontWeight:700, cursor:saving?'not-allowed':'pointer' }}>
            {saving ? '儲存中…' : existing ? '儲存' : '建立清單'}
          </button>
        </div>
      </div>
    </div>,
    document.body
  );
}

// ── AddChoiceSheet (手動 vs AI) ───────────────────────────────

function AddChoiceSheet({ onManual, onAI, onClose }) {
  const fileRef = React.useRef();
  return ReactDOM.createPortal(
    <div style={{ position:'fixed', inset:0, zIndex:9100, background:'rgba(24,20,15,0.48)', display:'flex', alignItems:'flex-end', animation:'tabiFade 0.18s ease' }}
      onClick={e => e.target === e.currentTarget && onClose()}>
      <div style={{ width:'100%', maxWidth:480, margin:'0 auto', background:TABI_COLORS.bg, borderRadius:'24px 24px 0 0', padding:'12px 18px calc(env(safe-area-inset-bottom,0px)+22px)', boxShadow:'0 -22px 50px rgba(24,20,15,0.20)', animation:'tabiSheetUp 0.22s ease' }}>
        <div style={{ width:36, height:4, borderRadius:2, background:TABI_COLORS.line2, margin:'0 auto 14px' }} />
        <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:14 }}>
          <button onClick={onManual} style={{
            minHeight:134, padding:'18px 10px', borderRadius:18, border:`1px solid ${TABI_COLORS.line}`,
            background:TABI_COLORS.card, cursor:'pointer', textAlign:'center', boxShadow:TABI_COLORS.shadowSoft,
            display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center',
          }}>
            <div style={{ width:44, height:44, borderRadius:15, display:'grid', placeItems:'center', background:`${TABI_COLORS.red}10`, fontSize:24, marginBottom:10 }}>✏️</div>
            <div style={{ fontSize:15, fontWeight:800, color:TABI_COLORS.ink }}>手動輸入</div>
            <div style={{ fontSize:11.5, color:TABI_COLORS.ink3, marginTop:5, lineHeight:1.4 }}>自行填寫品項資訊</div>
          </button>
          <button onClick={() => fileRef.current?.click()} style={{
            minHeight:134, padding:'18px 10px', borderRadius:18, border:`1px solid ${TABI_COLORS.blue}35`,
            background:`${TABI_COLORS.blue}08`, cursor:'pointer', textAlign:'center', boxShadow:TABI_COLORS.shadowSoft,
            display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center',
          }}>
            <div style={{ width:44, height:44, borderRadius:15, display:'grid', placeItems:'center', background:`${TABI_COLORS.blue}12`, fontSize:24, marginBottom:10 }}>📷</div>
            <div style={{ fontSize:15, fontWeight:800, color:TABI_COLORS.blue }}>照片 AI 分析</div>
            <div style={{ fontSize:11.5, color:TABI_COLORS.ink3, marginTop:5, lineHeight:1.4 }}>拍照 / 圖庫 / 檔案</div>
          </button>
        </div>
        {/* no capture attr → iOS native picker: Take Photo / Photo Library / Files */}
        <input ref={fileRef} type="file" accept="image/*" style={{ display:'none' }}
          onChange={e => { const f = e.target.files[0]; if (f) onAI(f); }} />
      </div>
    </div>,
    document.body
  );
}

// ── ImageAnalysisSheet ────────────────────────────────────────

function ImageAnalysisSheet({ listId, initialFile, onClose, onSaved }) {
  const [step, setStep] = React.useState(initialFile ? 'analyzing' : 'choose');
  const [imgPreview, setImgPreview] = React.useState(null);
  const [result, setResult] = React.useState(null);
  const [errMsg, setErrMsg] = React.useState('');
  const [showForm, setShowForm] = React.useState(false);

  React.useEffect(() => {
    if (initialFile) analyze(initialFile);
  }, []);
  const fileRef = React.useRef();

  async function analyze(file) {
    setImgPreview(URL.createObjectURL(file));
    setStep('analyzing');
    try {
      const fd = new FormData();
      fd.append('image', file);
      const r = await fetch('/api/shopping/analyze-image', {
        method: 'POST',
        headers: { Authorization: `Bearer ${window.tabiToken()}`, ...window.tabiLLMHeaders() },
        body: fd,
      });
      const data = await r.json();
      if (!r.ok) throw new Error(data.detail || '分析失敗');
      setResult(data);
      setStep('result');
    } catch (e) {
      setErrMsg(String(e.message || e));
      setStep('error');
    }
  }

  if (showForm && result) {
    return (
      <AddEditItemSheet
        listId={listId}
        prefill={result}
        onClose={onClose}
        onSaved={onSaved}
      />
    );
  }

  if (step === 'choose') {
    return ReactDOM.createPortal(
      <div style={{ position:'fixed', inset:0, zIndex:9100, background:'rgba(24,20,15,0.48)', display:'flex', alignItems:'center', justifyContent:'center', padding:20 }}
        onClick={e => e.target === e.currentTarget && onClose()}>
        <div style={{ background:TABI_COLORS.bg, borderRadius:18, padding:24, maxWidth:320, width:'100%' }}>
          <div style={{ fontFamily:tabiSerif, fontSize:17, fontWeight:700, color:TABI_COLORS.ink, marginBottom:14 }}>選擇圖片</div>
          <button onClick={() => fileRef.current?.click()} style={{
            width:'100%', padding:'14px', borderRadius:12, border:`1px solid ${TABI_COLORS.blue}40`,
            background:`${TABI_COLORS.blue}08`, cursor:'pointer', fontSize:14, fontWeight:700, color:TABI_COLORS.blue, marginBottom:10,
          }}>📷 拍照</button>
          <button onClick={() => { const i = document.createElement('input'); i.type='file'; i.accept='image/*'; i.onchange=e=>{ const f=e.target.files[0]; if(f) analyze(f); }; i.click(); }} style={{
            width:'100%', padding:'14px', borderRadius:12, border:`1px solid ${TABI_COLORS.line}`,
            background:TABI_COLORS.card, cursor:'pointer', fontSize:14, fontWeight:700, color:TABI_COLORS.ink2, marginBottom:10,
          }}>🖼️ 從相簿選取</button>
          <button onClick={onClose} style={{ width:'100%', padding:'11px', borderRadius:10, border:'none', background:'transparent', color:TABI_COLORS.ink3, fontSize:13, cursor:'pointer' }}>取消</button>
          <input ref={fileRef} type="file" accept="image/*" capture="environment" style={{ display:'none' }}
            onChange={e => { const f = e.target.files[0]; if (f) analyze(f); }} />
        </div>
      </div>,
      document.body
    );
  }

  if (step === 'analyzing') {
    return ReactDOM.createPortal(
      <div style={{ position:'fixed', inset:0, zIndex:9100, background:'rgba(24,20,15,0.55)', display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center', gap:16 }}>
        {imgPreview && <img src={imgPreview} alt="" style={{ width:160, height:160, objectFit:'cover', borderRadius:16, boxShadow:'0 16px 40px rgba(0,0,0,0.3)' }} />}
        <div style={{ background:TABI_COLORS.bg, borderRadius:14, padding:'14px 22px', textAlign:'center' }}>
          <div style={{ fontSize:14, fontWeight:700, color:TABI_COLORS.ink, marginBottom:4 }}>AI 分析中…</div>
          <div style={{ fontSize:12, color:TABI_COLORS.ink3 }}>透過大語言模型辨識商品並搜尋資訊</div>
        </div>
      </div>,
      document.body
    );
  }

  if (step === 'error') {
    return ReactDOM.createPortal(
      <div style={{ position:'fixed', inset:0, zIndex:9100, background:'rgba(24,20,15,0.48)', display:'flex', alignItems:'center', justifyContent:'center', padding:20 }}>
        <div style={{ background:TABI_COLORS.bg, borderRadius:18, padding:24, maxWidth:300, width:'100%', textAlign:'center' }}>
          <div style={{ fontSize:36, marginBottom:10 }}>😕</div>
          <div style={{ fontSize:15, fontWeight:700, color:TABI_COLORS.ink, marginBottom:6 }}>無法辨識商品</div>
          <div style={{ fontSize:13, color:TABI_COLORS.ink3, marginBottom:20 }}>{errMsg}</div>
          <div style={{ display:'flex', gap:8 }}>
            <button onClick={onClose} style={{ flex:1, padding:'11px', borderRadius:10, border:`1px solid ${TABI_COLORS.line}`, background:'transparent', fontSize:13, cursor:'pointer', color:TABI_COLORS.ink2 }}>關閉</button>
            <button onClick={() => setStep('choose')} style={{ flex:1, padding:'11px', borderRadius:10, border:'none', background:TABI_COLORS.ink, color:'#fff', fontSize:13, fontWeight:700, cursor:'pointer' }}>重試</button>
          </div>
        </div>
      </div>,
      document.body
    );
  }

  // step === 'result'
  const conf = result?.confidence;
  const confColor = conf === 'high' ? TABI_COLORS.green : conf === 'medium' ? TABI_COLORS.warn : TABI_COLORS.red;
  const confLabel = conf === 'high' ? '辨識可信度高' : conf === 'medium' ? '辨識可信度中等' : '辨識可信度低，請確認';

  return ReactDOM.createPortal(
    <div style={{ position:'fixed', inset:0, zIndex:9100, background:'rgba(24,20,15,0.52)', display:'flex', alignItems:'flex-end', animation:'tabiFade 0.18s ease' }}>
      <div style={{ width:'100%', maxWidth:480, margin:'0 auto', background:TABI_COLORS.bg, borderRadius:'24px 24px 0 0', maxHeight:'90dvh', display:'flex', flexDirection:'column', boxShadow:'0 -22px 50px rgba(24,20,15,0.20)', animation:'tabiSheetUp 0.22s ease' }}>
        <div style={{ width:36, height:4, borderRadius:2, background:TABI_COLORS.line2, margin:'12px auto 0', flexShrink:0 }} />
        <div style={{ flex:1, overflowY:'auto', padding:'16px 18px' }}>
          {/* AI 警示 */}
          <div style={{ background:`${confColor}10`, border:`1px solid ${confColor}30`, borderRadius:10, padding:'8px 12px', marginBottom:14, display:'flex', gap:8, alignItems:'center' }}>
            <span style={{ fontSize:14 }}>🤖</span>
            <div style={{ flex:1 }}>
              <div style={{ fontSize:12, fontWeight:700, color:confColor }}>{confLabel}</div>
              <div style={{ fontSize:11, color:TABI_COLORS.ink3 }}>AI 辨識結果，資訊可能有誤，請自行確認後加入</div>
            </div>
          </div>

          {/* 商品摘要 */}
          <div style={{ display:'flex', gap:12, alignItems:'flex-start', marginBottom:14 }}>
            {imgPreview && <img src={imgPreview} alt="" style={{ width:72, height:72, objectFit:'cover', borderRadius:12, flexShrink:0 }} />}
            <div style={{ flex:1, minWidth:0 }}>
              <div style={{ fontFamily:tabiSerif, fontSize:17, fontWeight:700, color:TABI_COLORS.ink }}>{result?.name || '—'}</div>
              {result?.brand && <div style={{ fontSize:12, color:TABI_COLORS.ink3, marginTop:2 }}>{result.brand}</div>}
              <div style={{ display:'flex', gap:8, marginTop:6, flexWrap:'wrap' }}>
                {result?.price && <span style={{ fontSize:12, fontWeight:700, color:TABI_COLORS.ink }}>≈¥{result.price.toLocaleString()}</span>}
                {result?.category && <span style={{ fontSize:11, padding:'2px 7px', borderRadius:5, background:TABI_COLORS.line2, color:TABI_COLORS.ink2 }}>{result.category}</span>}
              </div>
              {result?.note && <div style={{ fontSize:12, color:TABI_COLORS.ink3, marginTop:4 }}>{result.note}</div>}
              {result?.location && <div style={{ fontSize:11, color:TABI_COLORS.ink3, marginTop:2 }}>📍 {result.location}</div>}
            </div>
          </div>

          {/* 搜尋結果 */}
          {result?.search_results?.length > 0 && (
            <div>
              <div style={{ fontSize:11.5, fontWeight:700, color:TABI_COLORS.ink3, marginBottom:8, letterSpacing:'0.04em' }}>
                🔍 網路搜尋結果（{result.search_results.length} 筆）
              </div>
              {result.search_results.map((sr, i) => (
                <a key={i} href={sr.url} target="_blank" rel="noopener noreferrer" style={{
                  display:'block', padding:'9px 12px', borderRadius:10,
                  border:`1px solid ${i === 0 ? TABI_COLORS.blue + '40' : TABI_COLORS.line}`,
                  background: i < 2 ? `${TABI_COLORS.blue}06` : TABI_COLORS.card,
                  marginBottom:6, textDecoration:'none',
                }}>
                  <div style={{ display:'flex', alignItems:'center', gap:6, marginBottom:3 }}>
                    {i < 2 && <span style={{ fontSize:9.5, fontWeight:800, padding:'1px 6px', borderRadius:4, background:`${TABI_COLORS.blue}15`, color:TABI_COLORS.blue }}>爬取</span>}
                    <div style={{ fontSize:13, fontWeight:600, color:TABI_COLORS.blue, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', flex:1 }}>{sr.title}</div>
                  </div>
                  {/* show crawled snippet if available, else SearXNG content */}
                  {(sr.crawled || sr.content) && (
                    <div style={{ fontSize:11, color:TABI_COLORS.ink3, marginTop:2, overflow:'hidden', display:'-webkit-box', WebkitLineClamp:2, WebkitBoxOrient:'vertical' }}>
                      {sr.crawled || sr.content}
                    </div>
                  )}
                  <div style={{ fontSize:10, color:TABI_COLORS.ink3, marginTop:3, opacity:0.6 }}>{sr.url.replace(/^https?:\/\/(www\.)?/,'').slice(0,55)}</div>
                </a>
              ))}
            </div>
          )}
          {result?.search_results?.length === 0 && (
            <div style={{ fontSize:12, color:TABI_COLORS.ink3, textAlign:'center', padding:'8px 0' }}>
              未找到網路搜尋結果
            </div>
          )}
        </div>
        <div style={{ padding:'12px 18px calc(env(safe-area-inset-bottom,0px)+16px)', borderTop:`1px solid ${TABI_COLORS.line}`, flexShrink:0, display:'flex', gap:10 }}>
          <button onClick={onClose} style={{ flex:1, padding:'13px', borderRadius:12, border:`1px solid ${TABI_COLORS.line}`, background:'transparent', fontSize:14, cursor:'pointer', color:TABI_COLORS.ink2 }}>取消</button>
          <button onClick={() => setShowForm(true)} style={{ flex:2, padding:'13px', borderRadius:12, border:'none', background:TABI_COLORS.ink, color:'#fff', fontSize:14, fontWeight:700, cursor:'pointer' }}>
            確認並填入清單 →
          </button>
        </div>
      </div>
    </div>,
    document.body
  );
}

// ── AddEditItemSheet ──────────────────────────────────────────

function AddEditItemSheet({ listId, existing, prefill, onClose, onSaved }) {
  // prefill comes from AI analysis; existing from edit mode
  const src = existing || prefill || {};
  const [name, setName]         = React.useState(src.name || '');
  const [price, setPrice]       = React.useState(src.price ? String(src.price) : '');
  const [currency, setCurrency] = React.useState(src.currency || 'JPY');
  const [qty, setQty]           = React.useState(String(src.quantity || 1));
  const [location, setLocation] = React.useState(src.location || '');
  const [note, setNote]         = React.useState(src.note || '');
  const [url, setUrl]           = React.useState(src.url || '');
  const [priority, setPriority] = React.useState(src.priority || 'want');
  const [forWhomType, setForWhomType] = React.useState(src.for_whom ? 'other' : 'self');
  const [forWhomName, setForWhomName] = React.useState(src.for_whom || '');
  const [saving, setSaving]     = React.useState(false);
  const [err, setErr]           = React.useState('');
  const [imgFile, setImgFile]   = React.useState(null);
  // use existing URL, or prefill URL from AI analysis (already uploaded to server)
  const [imgPreview, setImgPreview] = React.useState(existing?.image_url || prefill?.image_url || null);
  const fileRef = React.useRef();

  function pickImg(e) {
    const f = e.target.files[0];
    if (!f) return;
    setImgFile(f);
    setImgPreview(URL.createObjectURL(f));
  }

  async function save() {
    if (!name.trim()) { setErr('請輸入品項名稱'); return; }
    if (forWhomType === 'other' && !forWhomName.trim()) { setErr('請輸入代買對象的名字'); return; }
    setSaving(true);
    try {
      const body = {
        name: name.trim(), price: price ? parseInt(price) : null,
        currency, quantity: parseInt(qty) || 1,
        location: location.trim() || null, note: note.trim() || null,
        url: url.trim() || null, priority,
        for_whom: forWhomType === 'other' ? forWhomName.trim() : null,
        // include server-side image URL from AI analysis (no re-upload needed)
        image_url: !imgFile && prefill?.image_url ? prefill.image_url : undefined,
      };
      let item;
      if (existing) {
        item = await _shopApi('PATCH', `/lists/${listId}/items/${existing.id}`, body);
      } else {
        item = await _shopApi('POST', `/lists/${listId}/items`, body);
      }
      if (imgFile && item?.id) {
        const fd = new FormData();
        fd.append('image', imgFile);
        await fetch(`/api/shopping/lists/${listId}/items/${item.id}/upload`, {
          method: 'POST', headers: { Authorization: `Bearer ${window.tabiToken()}` }, body: fd,
        });
      }
      onSaved();
    } catch (e) { setErr(String(e)); setSaving(false); }
  }

  const inputStyle = {
    width:'100%', padding:'10px 12px', borderRadius:10, minHeight:42,
    border:`1px solid ${TABI_COLORS.line}`, background:TABI_COLORS.bg,
    fontSize:16, color:TABI_COLORS.ink, outline:'none', fontFamily:'inherit', boxSizing:'border-box',
  };
  const selectStyle = {
    ...inputStyle,
    appearance:'none', WebkitAppearance:'none',
    backgroundImage:`url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%238D8071' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E")`,
    backgroundRepeat:'no-repeat', backgroundPosition:'right 10px center', paddingRight:28, cursor:'pointer',
  };

  return ReactDOM.createPortal(
    <div style={{ position:'fixed', inset:0, zIndex:9100, background:'rgba(24,20,15,0.48)', display:'flex', alignItems:'flex-end', animation:'tabiFade 0.18s ease' }}
      onClick={e => e.target === e.currentTarget && onClose()}>
      <div style={{ width:'100%', maxWidth:480, margin:'0 auto', background:TABI_COLORS.bg, borderRadius:'24px 24px 0 0', maxHeight:'90dvh', display:'flex', flexDirection:'column', boxShadow:'0 -22px 50px rgba(24,20,15,0.20)', animation:'tabiSheetUp 0.22s ease' }}>
        <div style={{ width:36, height:4, borderRadius:2, background:TABI_COLORS.line2, margin:'12px auto 0', flexShrink:0 }} />
        <div style={{ display:'flex', alignItems:'center', padding:'14px 18px 12px', borderBottom:`1px solid ${TABI_COLORS.line}`, flexShrink:0 }}>
          <div style={{ fontFamily:tabiSerif, fontSize:18, fontWeight:700, color:TABI_COLORS.ink, flex:1 }}>{existing ? '編輯品項' : '新增品項'}</div>
          <button onClick={onClose} style={{ width:32, height:32, borderRadius:16, border:`1px solid ${TABI_COLORS.line}`, background:'transparent', fontSize:18, cursor:'pointer', color:TABI_COLORS.ink3, display:'grid', placeItems:'center' }}>×</button>
        </div>
        <div style={{ flex:1, overflowY:'auto', padding:'14px 18px' }}>
          {/* AI prefill warning */}
          {prefill && (
            <div style={{ background:`${TABI_COLORS.warn}10`, border:`1px solid ${TABI_COLORS.warn}30`, borderRadius:10, padding:'8px 12px', marginBottom:12, display:'flex', gap:6, alignItems:'center' }}>
              <span style={{ fontSize:13 }}>🤖</span>
              <span style={{ fontSize:12, color:TABI_COLORS.warn, fontWeight:600 }}>AI 自動填入・資訊可能有誤，請確認後儲存</span>
            </div>
          )}

          {/* 優先度 */}
          <div style={{ display:'flex', gap:8, marginBottom:14 }}>
            {[['must','非買不可 🔥'],['want','想買看看']].map(([v,label]) => (
              <button key={v} onClick={() => setPriority(v)} style={{
                flex:1, padding:'9px 8px', borderRadius:10, fontSize:12.5, fontWeight:700, cursor:'pointer',
                border:`2px solid ${priority===v ? PRIORITY_COLOR[v] : TABI_COLORS.line}`,
                background: priority===v ? `${PRIORITY_COLOR[v]}12` : 'transparent',
                color: priority===v ? PRIORITY_COLOR[v] : TABI_COLORS.ink2,
              }}>{label}</button>
            ))}
          </div>

          <div style={{ fontSize:11.5, color:TABI_COLORS.ink3, marginBottom:5, fontWeight:600 }}>品項名稱 *</div>
          <input value={name} onChange={e => setName(e.target.value)} placeholder="例：龍角散喉糖、Shiseido 防曬" style={inputStyle} />

          {/* 金額 + 幣別 + 數量 */}
          <div style={{ display:'flex', gap:8, marginTop:12 }}>
            <div style={{ flex:2 }}>
              <div style={{ fontSize:11.5, color:TABI_COLORS.ink3, marginBottom:5, fontWeight:600 }}>預估金額（選填）</div>
              <input type="number" inputMode="numeric" value={price} onChange={e => setPrice(e.target.value)} placeholder="0" style={inputStyle} />
            </div>
            <div style={{ flex:1 }}>
              <div style={{ fontSize:11.5, color:TABI_COLORS.ink3, marginBottom:5, fontWeight:600 }}>幣別</div>
              <select value={currency} onChange={e => setCurrency(e.target.value)} style={selectStyle}>
                <option value="JPY">¥ 日圓</option>
                <option value="TWD">NT$ 台幣</option>
                <option value="USD">$ 美元</option>
              </select>
            </div>
            <div style={{ flex:1 }}>
              <div style={{ fontSize:11.5, color:TABI_COLORS.ink3, marginBottom:5, fontWeight:600 }}>數量</div>
              <input type="number" inputMode="numeric" value={qty} onChange={e => setQty(e.target.value)} min="1" style={inputStyle} />
            </div>
          </div>

          {/* 幫誰買 */}
          <div style={{ marginTop:12 }}>
            <div style={{ fontSize:11.5, color:TABI_COLORS.ink3, marginBottom:8, fontWeight:600 }}>幫誰買</div>
            <div style={{ display:'flex', gap:8, marginBottom: forWhomType==='other' ? 8 : 0 }}>
              {[['self','自己 🙋'],['other','代買給別人 🎁']].map(([v,label]) => (
                <button key={v} onClick={() => setForWhomType(v)} style={{
                  flex:1, padding:'9px 8px', borderRadius:10, fontSize:12.5, fontWeight:700, cursor:'pointer',
                  border:`2px solid ${forWhomType===v ? TABI_COLORS.blue : TABI_COLORS.line}`,
                  background: forWhomType===v ? `${TABI_COLORS.blue}12` : 'transparent',
                  color: forWhomType===v ? TABI_COLORS.blue : TABI_COLORS.ink2,
                }}>{label}</button>
              ))}
            </div>
            {forWhomType === 'other' && (
              <input value={forWhomName} onChange={e => setForWhomName(e.target.value)}
                placeholder="輸入對方名字" style={inputStyle} />
            )}
          </div>

          <div style={{ fontSize:11.5, color:TABI_COLORS.ink3, margin:'12px 0 5px', fontWeight:600 }}>購買地點（選填）</div>
          <input value={location} onChange={e => setLocation(e.target.value)} placeholder="例：松本清、唐吉訶德" style={inputStyle} />

          <div style={{ fontSize:11.5, color:TABI_COLORS.ink3, margin:'12px 0 5px', fontWeight:600 }}>備註（尺寸、顏色、品牌…）</div>
          <input value={note} onChange={e => setNote(e.target.value)} placeholder="例：粉色款、L號、要買2盒" style={inputStyle} />

          <div style={{ fontSize:11.5, color:TABI_COLORS.ink3, margin:'12px 0 5px', fontWeight:600 }}>商品連結（選填）</div>
          <input value={url} onChange={e => setUrl(e.target.value)} placeholder="https://..." style={inputStyle} />

          <div style={{ fontSize:11.5, color:TABI_COLORS.ink3, margin:'12px 0 8px', fontWeight:600 }}>商品圖片（選填）</div>
          <div style={{ display:'flex', gap:10, alignItems:'center' }}>
            {imgPreview && (
              <img src={imgPreview} alt="" style={{ width:64, height:64, objectFit:'cover', borderRadius:10, flexShrink:0, border:`1px solid ${TABI_COLORS.line}`, cursor:'pointer' }} />
            )}
            <button onClick={() => fileRef.current?.click()} style={{
              flex:1, padding:'10px', borderRadius:10, border:`1.5px dashed ${TABI_COLORS.line2}`,
              background:'transparent', cursor:'pointer', fontSize:12.5, color:TABI_COLORS.ink2, fontWeight:600,
            }}>{imgPreview ? '更換圖片' : '📷 上傳圖片'}</button>
            <input ref={fileRef} type="file" accept="image/*" style={{ display:'none' }} onChange={pickImg} />
          </div>

          {err && <div style={{ color:TABI_COLORS.red, fontSize:12.5, marginTop:10 }}>{err}</div>}
        </div>

        <div style={{ padding:'12px 18px calc(env(safe-area-inset-bottom,0px)+16px)', borderTop:`1px solid ${TABI_COLORS.line}`, flexShrink:0, display:'flex', gap:10 }}>
          <button onClick={onClose} style={{ flex:1, padding:'13px', borderRadius:12, border:`1px solid ${TABI_COLORS.line}`, background:'transparent', fontSize:14, cursor:'pointer', color:TABI_COLORS.ink2 }}>取消</button>
          <button onClick={save} disabled={saving} style={{ flex:2, padding:'13px', borderRadius:12, border:'none', background:saving?TABI_COLORS.line2:TABI_COLORS.ink, color:'#fff', fontSize:14, fontWeight:700, cursor:saving?'not-allowed':'pointer' }}>
            {saving ? '儲存中…' : existing ? '儲存變更' : '新增品項'}
          </button>
        </div>
      </div>
    </div>,
    document.body
  );
}

// ── RequestsReviewSheet ───────────────────────────────────────

function RequestsReviewSheet({ listId, requests, onClose, onRefresh }) {
  const [rejectId, setRejectId] = React.useState(null);
  const [loading, setLoading]   = React.useState(null);
  const [expanded, setExpanded] = React.useState(() => new Set());
  const [lightbox, setLightbox] = React.useState(null);

  const pending = requests.filter(r => r.status === 'pending');
  const done    = requests.filter(r => r.status !== 'pending');

  function toggleExpand(key) {
    setExpanded(prev => {
      const next = new Set(prev);
      if (next.has(key)) next.delete(key); else next.add(key);
      return next;
    });
  }

  async function approve(r) {
    setLoading(r.id);
    try { await _shopApi('PATCH', `/requests/${r.id}/approve`); onRefresh(); }
    catch (e) { alert(String(e)); }
    finally { setLoading(null); }
  }

  async function reject(id) {
    setLoading(id);
    try { await _shopApi('PATCH', `/requests/${id}/reject`); setRejectId(null); onRefresh(); }
    catch (e) { alert(String(e)); }
    finally { setLoading(null); }
  }

  return ReactDOM.createPortal(
    <div style={{ position:'fixed', inset:0, zIndex:9200, background:'rgba(24,20,15,0.52)', display:'flex', alignItems:'flex-end', animation:'tabiFade 0.18s ease' }}
      onClick={e => e.target === e.currentTarget && onClose()}>
      <div style={{ width:'100%', maxWidth:480, margin:'0 auto', background:TABI_COLORS.bg, borderRadius:'24px 24px 0 0', maxHeight:'85dvh', display:'flex', flexDirection:'column', animation:'tabiSheetUp 0.22s ease' }}>
        <div style={{ padding:'20px 18px 14px', borderBottom:`1px solid ${TABI_COLORS.line}`, flexShrink:0, display:'flex', alignItems:'center' }}>
          <div style={{ flex:1, fontFamily:tabiSerif, fontSize:18, fontWeight:700, color:TABI_COLORS.ink }}>代購申請審核</div>
          <button onClick={onClose} style={{ width:34, height:34, borderRadius:17, border:`1px solid ${TABI_COLORS.line}`, background:'transparent', fontSize:18, cursor:'pointer', color:TABI_COLORS.ink3 }}>×</button>
        </div>
        <div style={{ flex:1, overflowY:'auto', padding:'12px 16px' }}>
          {pending.length === 0 && (
            <div style={{ textAlign:'center', padding:'36px 0', color:TABI_COLORS.ink3 }}>
              <div style={{ fontSize:36, marginBottom:8 }}>✓</div>
              <div>目前沒有待審核的申請</div>
            </div>
          )}
          {pending.map(r => (
            <div key={r.id} style={{ background:TABI_COLORS.card, borderRadius:14, padding:'14px', marginBottom:10, border:`1px solid ${TABI_COLORS.line}` }}>
              <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:10 }}>
                <div style={{ width:32, height:32, borderRadius:16, background:`${TABI_COLORS.warn}20`, display:'grid', placeItems:'center', fontSize:16 }}>🙋</div>
                <div>
                  <div style={{ fontSize:14, fontWeight:700, color:TABI_COLORS.ink }}>{r.requester_name}</div>
                  <div style={{ fontSize:11, color:TABI_COLORS.ink3 }}>申請 {r.items.length} 項 · {new Date(r.created_at).toLocaleDateString('zh-TW')}</div>
                </div>
                <div style={{ marginLeft:'auto', fontSize:10, padding:'3px 8px', borderRadius:6, background:`${TABI_COLORS.warn}15`, color:'#8a6010', fontWeight:700 }}>待審核</div>
              </div>
              {r.items.map((it, i) => {
                const key = `${r.id}-${i}`;
                const hasDetails = !!(it.location || it.note || it.url || it.image_url);
                const isOpen = expanded.has(key);
                return (
                  <div key={i} style={{ borderTop: i===0 ? `1px solid ${TABI_COLORS.line}` : 'none' }}>
                    <div onClick={() => hasDetails && toggleExpand(key)} style={{
                      display:'flex', gap:8, alignItems:'center', padding:'8px 0',
                      cursor: hasDetails ? 'pointer' : 'default',
                    }}>
                      <div style={{ flex:1, minWidth:0 }}>
                        <span style={{ fontSize:13, fontWeight:600, color:TABI_COLORS.ink }}>{it.name}</span>
                        {it.priority === 'must' && <span style={{ marginLeft:6, fontSize:10, padding:'1px 6px', borderRadius:4, background:`${TABI_COLORS.red}15`, color:TABI_COLORS.red, fontWeight:700 }}>必買</span>}
                      </div>
                      {it.price ? <span style={{ fontSize:12, color:TABI_COLORS.ink3 }}>{_fmtShopAmt(it.price, it.currency, it.quantity)}</span> : null}
                      {it.quantity > 1 && <span style={{ fontSize:11, color:TABI_COLORS.ink3 }}>×{it.quantity}</span>}
                      {hasDetails && (
                        <span style={{ fontSize:10, color:TABI_COLORS.ink3, transition:'transform 0.15s', transform: isOpen ? 'rotate(90deg)' : 'none', flexShrink:0 }}>▸</span>
                      )}
                    </div>
                    {isOpen && (
                      <div style={{ display:'flex', gap:10, padding:'0 0 10px', alignItems:'flex-start' }}>
                        {it.image_url && (
                          <img src={it.image_url} alt="" onClick={() => setLightbox(it.image_url)} style={{
                            width:56, height:56, objectFit:'cover', borderRadius:8, flexShrink:0, cursor:'zoom-in',
                            border:`1px solid ${TABI_COLORS.line}`,
                          }} />
                        )}
                        <div style={{ flex:1, minWidth:0, fontSize:12, color:TABI_COLORS.ink3, lineHeight:1.7 }}>
                          {it.location && <div>📍 {it.location}</div>}
                          {it.note && <div>📝 {it.note}</div>}
                          {it.url && (
                            <div>
                              <a href={it.url} target="_blank" rel="noopener noreferrer" style={{ color:TABI_COLORS.blue, wordBreak:'break-all' }}>🔗 {it.url}</a>
                            </div>
                          )}
                        </div>
                      </div>
                    )}
                  </div>
                );
              })}
              <div style={{ display:'flex', gap:8, marginTop:12 }}>
                <button onClick={() => setRejectId(r.id)} disabled={loading===r.id} style={{
                  flex:1, padding:'10px', borderRadius:10, border:`1px solid ${TABI_COLORS.red}40`,
                  background:`${TABI_COLORS.red}08`, color:TABI_COLORS.red, fontSize:13, fontWeight:700, cursor:'pointer',
                }}>拒絕</button>
                <button onClick={() => approve(r)} disabled={loading===r.id} style={{
                  flex:2, padding:'10px', borderRadius:10, border:'none',
                  background:loading===r.id ? TABI_COLORS.line2 : TABI_COLORS.green, color:'#fff', fontSize:13, fontWeight:700, cursor:'pointer',
                }}>{loading===r.id ? '處理中…' : '✓ 同意並匯入'}</button>
              </div>
            </div>
          ))}

          {done.length > 0 && (
            <div style={{ marginTop:16 }}>
              <div style={{ fontSize:11.5, color:TABI_COLORS.ink3, fontWeight:600, marginBottom:8 }}>已處理</div>
              {done.map(r => (
                <div key={r.id} style={{ display:'flex', alignItems:'center', gap:8, padding:'10px 12px', background:TABI_COLORS.card, borderRadius:10, marginBottom:6, border:`1px solid ${TABI_COLORS.line}`, opacity:0.65 }}>
                  <span style={{ fontSize:13, fontWeight:600, color:TABI_COLORS.ink, flex:1 }}>{r.requester_name} ({r.items.length} 項)</span>
                  <span style={{ fontSize:11, padding:'2px 8px', borderRadius:5, fontWeight:700,
                    background: r.status==='approved' ? `${TABI_COLORS.green}15` : `${TABI_COLORS.red}12`,
                    color: r.status==='approved' ? TABI_COLORS.green : TABI_COLORS.red,
                  }}>{r.status==='approved' ? '已同意' : '已拒絕'}</span>
                </div>
              ))}
            </div>
          )}
        </div>
      </div>

      {/* Reject confirm dialog */}
      {rejectId && ReactDOM.createPortal(
        <div style={{ position:'fixed', inset:0, zIndex:9300, background:'rgba(0,0,0,0.54)', display:'flex', alignItems:'center', justifyContent:'center', padding:20 }}
          onClick={() => setRejectId(null)}>
          <div onClick={e => e.stopPropagation()} style={{ background:TABI_COLORS.bg, borderRadius:18, padding:'22px 20px', maxWidth:320, width:'100%', boxShadow:'0 24px 60px rgba(0,0,0,0.25)' }}>
            <div style={{ fontSize:15, fontWeight:700, color:TABI_COLORS.ink, marginBottom:8 }}>確定拒絕這筆申請？</div>
            <div style={{ fontSize:13, color:TABI_COLORS.ink3, marginBottom:20, lineHeight:1.5 }}>拒絕後對方需要重新填寫申請，請確認後再操作。</div>
            <div style={{ display:'flex', gap:10 }}>
              <button onClick={() => setRejectId(null)} style={{ flex:1, padding:'12px', borderRadius:10, border:`1px solid ${TABI_COLORS.line}`, background:'transparent', fontSize:14, cursor:'pointer', color:TABI_COLORS.ink2 }}>取消</button>
              <button onClick={() => reject(rejectId)} disabled={loading===rejectId} style={{ flex:1, padding:'12px', borderRadius:10, border:'none', background:TABI_COLORS.red, color:'#fff', fontSize:14, fontWeight:700, cursor:'pointer' }}>
                {loading===rejectId ? '處理中…' : '確認拒絕'}
              </button>
            </div>
          </div>
        </div>,
        document.body
      )}

      {lightbox && <ImageLightbox src={lightbox} onClose={() => setLightbox(null)} />}
    </div>,
    document.body
  );
}

// ── ShareLinkSheet ────────────────────────────────────────────

function ShareLinkSheet({ listId, hasLink, appBaseUrl, onClose, onTokenChange }) {
  const [token, setToken]   = React.useState(null);
  const [loading, setLoading] = React.useState(hasLink);  // if link exists, fetch token on mount
  const [copied, setCopied]   = React.useState(false);

  React.useEffect(() => {
    if (!hasLink) return;
    _shopApi('POST', `/lists/${listId}/request-token`)
      .then(d => { setToken(d.token); setLoading(false); })
      .catch(() => setLoading(false));
  }, []);

  const link = token ? `${appBaseUrl}/shopping-request?token=${token}` : null;
  const shareText = link ? [
    '想請你幫我填寫 Tabi 代購清單。',
    '打開連結後可以直接新增想買的商品，不需要登入。',
    '送出後會先進入待審核，我確認後才會加入購物清單。',
    link,
  ].join('\n') : '';

  async function generate() {
    setLoading(true);
    try {
      const d = await _shopApi('POST', `/lists/${listId}/request-token`);
      setToken(d.token);
      onTokenChange(d.token);
    } catch (e) { alert(String(e)); }
    finally { setLoading(false); }
  }

  async function revoke() {
    if (!confirm('停用後舊連結將失效，確定嗎？')) return;
    setLoading(true);
    try {
      await _shopApi('DELETE', `/lists/${listId}/request-token`);
      setToken(null);
      onTokenChange(null);
    } catch (e) { alert(String(e)); }
    finally { setLoading(false); }
  }

  function copy() {
    if (!link) return;
    navigator.clipboard?.writeText(link).then(() => {
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    });
  }

  function share() {
    if (!link) return;
    if (navigator.share) {
      navigator.share({
        title: '幫我填寫 Tabi 代購清單',
        text: shareText,
        url: link,
      });
    } else {
      copy();
    }
  }

  return ReactDOM.createPortal(
    <div style={{ position:'fixed', inset:0, zIndex:9200, background:'rgba(24,20,15,0.52)', display:'flex', alignItems:'flex-end', animation:'tabiFade 0.18s ease' }}
      onClick={e => e.target === e.currentTarget && onClose()}>
      <div style={{ width:'100%', maxWidth:480, margin:'0 auto', background:TABI_COLORS.bg, borderRadius:'24px 24px 0 0', maxHeight:'90dvh', display:'flex', flexDirection:'column', boxShadow:'0 -22px 50px rgba(24,20,15,0.20)', animation:'tabiSheetUp 0.22s ease' }}>
        <div style={{ width:36, height:4, borderRadius:2, background:TABI_COLORS.line2, margin:'12px auto 0', flexShrink:0 }} />
        <div style={{ display:'flex', alignItems:'center', padding:'14px 18px 12px', borderBottom:`1px solid ${TABI_COLORS.line}`, flexShrink:0 }}>
          <div style={{ fontFamily:tabiSerif, fontSize:18, fontWeight:700, color:TABI_COLORS.ink, flex:1 }}>邀請填寫代購清單</div>
          <button onClick={onClose} style={{ width:32, height:32, borderRadius:16, border:`1px solid ${TABI_COLORS.line}`, background:'transparent', fontSize:18, cursor:'pointer', color:TABI_COLORS.ink3, display:'grid', placeItems:'center' }}>×</button>
        </div>
        <div style={{ flex:1, overflowY:'auto', padding:'16px 18px calc(env(safe-area-inset-bottom,0px)+20px)' }}>
          <div style={{ fontSize:13, color:TABI_COLORS.ink3, marginBottom:18, lineHeight:1.6 }}>
            把連結傳給對方，對方直接在網頁填寫想買的商品，不需要登入。
            填完後出現在「待審核」，你同意後才加入清單。
          </div>

          {loading && !token ? (
            <div style={{ textAlign:'center', color:TABI_COLORS.ink3, padding:'20px 0' }}>產生中…</div>
          ) : !token ? (
            <button onClick={generate} disabled={loading} style={{ width:'100%', padding:'14px', borderRadius:12, border:'none', background:TABI_COLORS.ink, color:'#fff', fontSize:15, fontWeight:700, cursor:'pointer' }}>
              🔗 產生分享連結
            </button>
          ) : (
            <>
              <div style={{ background:TABI_COLORS.bg2, borderRadius:12, padding:'12px 14px', border:`1px solid ${TABI_COLORS.line}`, marginBottom:12 }}>
                <div style={{ fontSize:11, color:TABI_COLORS.ink3, marginBottom:5, fontWeight:600 }}>分享連結</div>
                <div style={{ fontSize:13, color:TABI_COLORS.ink2, wordBreak:'break-all', lineHeight:1.6, userSelect:'all' }}>{link}</div>
              </div>
              <div style={{ display:'flex', gap:8, marginBottom:12 }}>
                <button onClick={copy} style={{ flex:2, padding:'13px', borderRadius:10, border:'none', background:copied?TABI_COLORS.green:TABI_COLORS.ink, color:'#fff', fontSize:14, fontWeight:700, cursor:'pointer', transition:'background 0.2s' }}>
                  {copied ? '✓ 已複製！' : '📋 複製連結'}
                </button>
                <button onClick={share} style={{ flex:1, padding:'13px', borderRadius:10, border:`1px solid ${TABI_COLORS.line}`, background:'transparent', color:TABI_COLORS.ink2, fontSize:14, fontWeight:700, cursor:'pointer' }}>
                  分享
                </button>
              </div>
              <button onClick={revoke} disabled={loading} style={{ width:'100%', padding:'11px', borderRadius:10, border:`1px solid rgba(185,74,53,0.3)`, background:`rgba(185,74,53,0.06)`, color:TABI_COLORS.red, fontSize:13, fontWeight:600, cursor:'pointer' }}>
                停用連結（舊連結將失效）
              </button>
            </>
          )}
        </div>
      </div>
    </div>,
    document.body
  );
}

// ── ShoppingListDetailScreen ──────────────────────────────────

// ── MoveItemsSheet ───────────────────────────────────────────

function MoveItemsSheetReal({ fromListId, selectedIds, onClose, onMoved }) {
  const [allLists, setAllLists] = React.useState([]);
  const [loading, setLoading]   = React.useState(true);
  const [moving, setMoving]     = React.useState(null);  // list id being moved to
  const [tripChoice, setTripChoice] = React.useState({});

  React.useEffect(() => {
    _shopApi('GET', '/lists')
      .then(d => setAllLists(Array.isArray(d) ? d.filter(l => l.id !== fromListId) : []))
      .catch(() => {})
      .finally(() => setLoading(false));
  }, [fromListId]);

  const standalone = allLists.filter(l => !l.trip_id && l.is_owner);
  const tripGroups = {};
  allLists.filter(l => l.trip_id).forEach(l => {
    if (!tripGroups[l.trip_id]) tripGroups[l.trip_id] = [];
    tripGroups[l.trip_id].push(l);
  });

  async function moveTo(toListId) {
    setMoving(toListId);
    try {
      await _shopApi('POST', `/lists/${fromListId}/items/move`, {
        item_ids: selectedIds,
        to_list_id: toListId,
      });
      onMoved();
    } catch (e) { alert(String(e)); setMoving(null); }
  }

  const ListOption = ({ lst, label }) => (
    <button onClick={() => moveTo(lst.id)} disabled={!!moving} style={{
      width:'100%', padding:'12px 14px', borderRadius:12, border:`1px solid ${TABI_COLORS.line}`,
      background: moving === lst.id ? `${TABI_COLORS.blue}12` : TABI_COLORS.card,
      display:'flex', alignItems:'center', gap:10, cursor:'pointer', marginBottom:8, textAlign:'left',
      opacity: moving && moving !== lst.id ? 0.5 : 1,
    }}>
      <span style={{ fontSize:20 }}>{lst.emoji || '🛍️'}</span>
      <div style={{ flex:1, minWidth:0 }}>
        <div style={{ fontSize:14, fontWeight:600, color:TABI_COLORS.ink, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{lst.title}</div>
        {label && <div style={{ fontSize:11, color:TABI_COLORS.ink3, marginTop:1 }}>{label}</div>}
      </div>
      {moving === lst.id && <span style={{ fontSize:13, color:TABI_COLORS.blue }}>移動中…</span>}
    </button>
  );

  return ReactDOM.createPortal(
    <div style={{ position:'fixed', inset:0, zIndex:9300, background:'rgba(24,20,15,0.52)', display:'flex', alignItems:'flex-end', animation:'tabiFade 0.18s ease' }}
      onClick={e => e.target === e.currentTarget && onClose()}>
      <div style={{ width:'100%', maxWidth:480, margin:'0 auto', background:TABI_COLORS.bg, borderRadius:'24px 24px 0 0', maxHeight:'85dvh', display:'flex', flexDirection:'column', boxShadow:'0 -22px 50px rgba(24,20,15,0.20)', animation:'tabiSheetUp 0.22s ease' }}>
        <div style={{ width:36, height:4, borderRadius:2, background:TABI_COLORS.line2, margin:'12px auto 0', flexShrink:0 }} />
        <div style={{ display:'flex', alignItems:'center', padding:'14px 18px 12px', borderBottom:`1px solid ${TABI_COLORS.line}`, flexShrink:0 }}>
          <div style={{ flex:1 }}>
            <div style={{ fontFamily:tabiSerif, fontSize:18, fontWeight:700, color:TABI_COLORS.ink }}>移動到哪個清單？</div>
            <div style={{ fontSize:12, color:TABI_COLORS.ink3, marginTop:2 }}>{selectedIds.length} 個品項將被移動</div>
          </div>
          <button onClick={onClose} style={{ width:32, height:32, borderRadius:16, border:`1px solid ${TABI_COLORS.line}`, background:'transparent', fontSize:18, cursor:'pointer', color:TABI_COLORS.ink3, display:'grid', placeItems:'center' }}>×</button>
        </div>
        <div style={{ flex:1, overflowY:'auto', padding:'14px 16px' }}>
          {loading && <div style={{ textAlign:'center', color:TABI_COLORS.ink3, padding:'24px 0' }}>載入中…</div>}

          {/* Standalone personal lists */}
          {standalone.length > 0 && (
            <>
              <div style={{ fontSize:11, color:TABI_COLORS.ink3, fontWeight:700, letterSpacing:'0.06em', textTransform:'uppercase', marginBottom:8 }}>個人清單</div>
              {standalone.map(lst => <ListOption key={lst.id} lst={lst} />)}
            </>
          )}

          {/* Trip lists */}
          {Object.entries(tripGroups).map(([tripId, tripLs]) => {
            const tripName = tripLs[0]?.title?.split(' · ')[0] || '旅程';
            const personal = tripLs.find(l => l.visibility === 'personal' && l.is_owner);
            const shared   = tripLs.find(l => l.visibility === 'shared');
            return (
              <div key={tripId} style={{ marginBottom:12 }}>
                <div style={{ fontSize:11, color:TABI_COLORS.ink3, fontWeight:700, letterSpacing:'0.06em', textTransform:'uppercase', marginBottom:8 }}>
                  ✈️ {tripName}
                </div>
                {personal && <ListOption lst={personal} label="🙋 個人採購 · 只有你看得到" />}
                {shared   && <ListOption lst={shared}   label="👥 共同採購 · 全員可見" />}
              </div>
            );
          })}

          {!loading && standalone.length === 0 && Object.keys(tripGroups).length === 0 && (
            <div style={{ textAlign:'center', color:TABI_COLORS.ink3, padding:'24px 0' }}>沒有其他清單可以移動</div>
          )}
        </div>
      </div>
    </div>,
    document.body
  );
}

// ── ActionsMenu ──────────────────────────────────────────────
// A ⋯ button that opens a bottom action sheet

function ActionsMenu({ actions }) {
  // actions: [{ label, icon, color?, onClick, hidden? }]
  const [open, setOpen] = React.useState(false);
  const visible = actions.filter(a => !a.hidden);
  if (!visible.length) return null;
  return (
    <>
      <button onClick={() => setOpen(true)} style={{
        minHeight:34, width:34, borderRadius:10, border:`1px solid ${TABI_COLORS.line}`,
        background:'transparent', cursor:'pointer', fontSize:17, color:TABI_COLORS.ink2,
        display:'grid', placeItems:'center',
      }}>⋯</button>
      {open && ReactDOM.createPortal(
        <div style={{ position:'fixed', inset:0, zIndex:9400, background:'rgba(24,20,15,0.48)', display:'flex', alignItems:'flex-end', animation:'tabiFade 0.15s ease' }}
          onClick={() => setOpen(false)}>
          <div style={{ width:'100%', maxWidth:480, margin:'0 auto', background:TABI_COLORS.bg, borderRadius:'20px 20px 0 0', padding:'10px 10px calc(env(safe-area-inset-bottom,0px)+12px)', boxShadow:'0 -16px 40px rgba(24,20,15,0.18)', animation:'tabiSheetUp 0.2s ease' }}
            onClick={e => e.stopPropagation()}>
            <div style={{ width:32, height:3, borderRadius:2, background:TABI_COLORS.line2, margin:'0 auto 10px' }} />
            {visible.map((a, i) => (
              <button key={i} onClick={() => { setOpen(false); a.onClick(); }} style={{
                width:'100%', padding:'13px 16px', borderRadius:12, border:'none', background:'transparent',
                display:'flex', alignItems:'center', gap:12, cursor:'pointer', textAlign:'left',
                color: a.color || TABI_COLORS.ink,
              }}>
                {a.icon && <span style={{ fontSize:18, width:24, textAlign:'center' }}>{a.icon}</span>}
                <span style={{ fontSize:15, fontWeight:600 }}>{a.label}</span>
              </button>
            ))}
          </div>
        </div>,
        document.body
      )}
    </>
  );
}

// ── HistorySheet ──────────────────────────────────────────────

const ACTION_LABEL = { add:'新增', edit:'編輯', delete:'刪除', toggle:'勾選', revert:'還原' };
const ACTION_COLOR = { add: '#55705A', edit: '#365A77', delete: '#B94A35', toggle: '#8D8071', revert: '#BE8A35' };

function _snapSummary(snap) {
  if (!snap) return null;
  const parts = [];
  const sym = snap.currency === 'JPY' ? '¥' : snap.currency === 'TWD' ? 'NT$' : snap.currency || '¥';
  if (snap.price) parts.push(`${sym}${Number(snap.price).toLocaleString()}${snap.quantity > 1 ? ` ×${snap.quantity}` : ''}`);
  if (snap.priority === 'must') parts.push('非買不可🔥');
  if (snap.location) parts.push(`📍${snap.location}`);
  if (snap.note) parts.push(snap.note.slice(0, 20) + (snap.note.length > 20 ? '…' : ''));
  if (snap.for_whom) parts.push(`代買給 ${snap.for_whom}`);
  if (typeof snap.is_bought === 'boolean') parts.push(snap.is_bought ? '✓ 已購買' : '未購買');
  return parts.length ? parts.join(' · ') : null;
}

function HistorySheet({ listId, history, onClose, onRevert }) {
  const [reverting, setReverting] = React.useState(null);

  async function revert(h) {
    if (!h.item_id) return;
    const label = h.action === 'delete' ? `還原已刪除的「${h.item_name}」？` : `還原「${h.item_name}」到此時間點的狀態？`;
    if (!confirm(label)) return;
    setReverting(h.id);
    try { await onRevert(h.item_id, h.id); }
    catch (e) { alert(String(e)); }
    finally { setReverting(null); }
  }

  function fmtTime(iso) {
    const d = new Date(iso);
    return `${d.getMonth()+1}/${d.getDate()} ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`;
  }

  // Action descriptions
  const ACTION_DESC = {
    add:    (h) => '新增了這個品項',
    delete: (h) => `刪除前：${_snapSummary(h.snapshot) || h.item_name}`,
    edit:   (h) => `修改前：${_snapSummary(h.snapshot) || '—'}`,
    toggle: (h) => h.snapshot?.is_bought === true ? '取消購買' : '標記已購買',
    revert: (h) => '還原到先前狀態',
  };

  return ReactDOM.createPortal(
    <div style={{ position:'fixed', inset:0, zIndex:9200, background:'rgba(24,20,15,0.52)', display:'flex', alignItems:'flex-end', animation:'tabiFade 0.18s ease' }}
      onClick={e => e.target === e.currentTarget && onClose()}>
      <div style={{ width:'100%', maxWidth:480, margin:'0 auto', background:TABI_COLORS.bg, borderRadius:'24px 24px 0 0', maxHeight:'85dvh', display:'flex', flexDirection:'column', boxShadow:'0 -22px 50px rgba(24,20,15,0.20)', animation:'tabiSheetUp 0.22s ease' }}>
        <div style={{ width:36, height:4, borderRadius:2, background:TABI_COLORS.line2, margin:'12px auto 0', flexShrink:0 }} />
        <div style={{ display:'flex', alignItems:'center', padding:'14px 18px 12px', borderBottom:`1px solid ${TABI_COLORS.line}`, flexShrink:0 }}>
          <div style={{ fontFamily:tabiSerif, fontSize:18, fontWeight:700, color:TABI_COLORS.ink, flex:1 }}>編輯紀錄</div>
          <button onClick={onClose} style={{ width:32, height:32, borderRadius:16, border:`1px solid ${TABI_COLORS.line}`, background:'transparent', fontSize:18, cursor:'pointer', color:TABI_COLORS.ink3, display:'grid', placeItems:'center' }}>×</button>
        </div>
        <div style={{ flex:1, overflowY:'auto', padding:'12px 16px' }}>
          {history.length === 0 && (
            <div style={{ textAlign:'center', padding:'40px 0', color:TABI_COLORS.ink3 }}>
              <div style={{ fontSize:32, marginBottom:8 }}>📋</div>
              <div>尚無編輯紀錄</div>
            </div>
          )}
          {history.map(h => {
            const desc = (ACTION_DESC[h.action] || (() => ''))(h);
            const canRevert = h.item_id && h.action !== 'revert';
            return (
              <div key={h.id} style={{ display:'flex', alignItems:'flex-start', gap:10, padding:'12px 0', borderBottom:`1px solid ${TABI_COLORS.line}` }}>
                <div style={{ width:40, flexShrink:0, marginTop:1 }}>
                  <div style={{ padding:'3px 0', borderRadius:6, background:`${ACTION_COLOR[h.action]}15`, textAlign:'center' }}>
                    <span style={{ fontSize:10.5, fontWeight:800, color:ACTION_COLOR[h.action] }}>{ACTION_LABEL[h.action] || h.action}</span>
                  </div>
                </div>
                <div style={{ flex:1, minWidth:0 }}>
                  <div style={{ fontSize:13.5, fontWeight:700, color:TABI_COLORS.ink }}>{h.item_name || '—'}</div>
                  {desc && <div style={{ fontSize:11.5, color:TABI_COLORS.ink3, marginTop:3, lineHeight:1.4 }}>{desc}</div>}
                  <div style={{ fontSize:11, color:TABI_COLORS.ink3, marginTop:3, opacity:0.7 }}>
                    {h.user_display_name && <span style={{ fontWeight:600, color:TABI_COLORS.ink3 }}>{h.user_display_name} · </span>}
                    {fmtTime(h.created_at)}
                  </div>
                </div>
                {canRevert && (
                  <button onClick={() => revert(h)} disabled={reverting === h.id} style={{
                    padding:'6px 11px', borderRadius:8, border:`1px solid ${TABI_COLORS.warn}50`,
                    background:`${TABI_COLORS.warn}10`, color:TABI_COLORS.warn,
                    fontSize:11.5, fontWeight:700, cursor:'pointer', flexShrink:0, whiteSpace:'nowrap',
                  }}>{reverting===h.id ? '…' : h.action==='delete' ? '🔄 還原' : '還原'}</button>
                )}
              </div>
            );
          })}
        </div>
      </div>
    </div>,
    document.body
  );
}

function ShoppingListDetailScreen({ go, listId }) {
  const [lst, setLst]             = React.useState(null);
  const [editList, setEditList]   = React.useState(false);
  const [addItem, setAddItem]     = React.useState(false);  // manual
  const [addChoice, setAddChoice] = React.useState(false);  // choice sheet
  const [aiAnalysis, setAiAnalysis] = React.useState(false); // AI sheet
  const [editItem, setEditItem]   = React.useState(null);
  const [showBought, setShowBought] = React.useState(false);
  const [lightbox, setLightbox]       = React.useState(null);
  const [showRequests, setShowRequests] = React.useState(false);
  const [showShare, setShowShare]     = React.useState(false);
  const [showHistory, setShowHistory] = React.useState(false);
  const [history, setHistory]         = React.useState([]);
  const [moveMode, setMoveMode]       = React.useState(false);
  const [selectedIds, setSelectedIds] = React.useState([]);
  const [showMove, setShowMove]       = React.useState(false);

  async function loadHistory() {
    try {
      const d = await _shopApi('GET', `/lists/${listId}/history`);
      setHistory(Array.isArray(d) ? d : []);
    } catch {}
  }
  const [showMust, setShowMust]   = React.useState(true);
  const [showWant, setShowWant]   = React.useState(true);
  const [appBase] = React.useState(() => window.location.origin);

  async function load() {
    try { setLst(await _shopApi('GET', `/lists/${listId}`)); }
    catch { go('shopping'); }
  }

  React.useEffect(() => { load(); }, [listId]);

  async function toggleItem(item) { await _shopApi('PATCH', `/lists/${listId}/items/${item.id}/toggle`); load(); }
  async function deleteItem(item) {
    if (!confirm(`刪除「${item.name}」？`)) return;
    await _shopApi('DELETE', `/lists/${listId}/items/${item.id}`);
    load();
    // Refresh history if it's open
    if (showHistory) {
      _shopApi('GET', `/lists/${listId}/history`)
        .then(d => setHistory(Array.isArray(d) ? d : []))
        .catch(() => {});
    }
  }

  if (!lst) return (
    <div style={{ background:TABI_COLORS.bg, minHeight:'100%', display:'grid', placeItems:'center' }}>
      <div style={{ color:TABI_COLORS.ink3 }}>載入中…</div>
    </div>
  );

  const mustItems = lst.items.filter(i => !i.is_bought && i.priority === 'must').sort((a,b) => a.sort_order - b.sort_order);
  const wantItems = lst.items.filter(i => !i.is_bought && i.priority !== 'must').sort((a,b) => a.sort_order - b.sort_order);
  const bought    = lst.items.filter(i => i.is_bought);
  const unbought  = [...mustItems, ...wantItems];
  const totalEst  = unbought.filter(i => i.price).reduce((s, i) => s + i.price * i.quantity, 0);
  const mustEst   = mustItems.filter(i => i.price).reduce((s, i) => s + i.price * i.quantity, 0);
  const mustCount = mustItems.length;
  const pendingRequests = (lst.requests || []).filter(r => r.status==='pending').length;

  return (
    <div style={{ background:TABI_COLORS.bg, minHeight:'100%', paddingBottom:'calc(env(safe-area-inset-bottom,0px)+100px)' }}>
      <TabiTopBar
        title={`${lst.emoji || '🛍️'} ${lst.title}`}
        subtitle={`${unbought.length} 項待購・${bought.length} 項已買`}
        onBack={() => go('shopping')}
        right={
          <div style={{ display:'flex', gap:6, alignItems:'center' }}>
            {pendingRequests > 0 && (
              <button onClick={() => setShowRequests(true)} style={{
                minHeight:34, padding:'6px 10px', borderRadius:10, border:'none',
                background:TABI_COLORS.red, color:'#fff', fontSize:11.5, fontWeight:700,
                cursor:'pointer', display:'flex', alignItems:'center', gap:4, flexShrink:0,
              }}>
                申請<span style={{ background:'rgba(255,255,255,0.25)', borderRadius:8, padding:'0 5px', fontSize:11 }}>{pendingRequests}</span>
              </button>
            )}
            {/* 編輯 = 進入選取模式（個人清單），在選取模式顯示「取消」 */}
            {lst.visibility !== 'shared' && (
              <button onClick={() => { setMoveMode(v => !v); setSelectedIds([]); }} style={{
                minHeight:34, padding:'6px 11px', borderRadius:10, fontSize:12, fontWeight:700, cursor:'pointer', flexShrink:0,
                border:`1px solid ${moveMode ? TABI_COLORS.blue : TABI_COLORS.line}`,
                background: moveMode ? `${TABI_COLORS.blue}10` : 'transparent',
                color: moveMode ? TABI_COLORS.blue : TABI_COLORS.ink2,
              }}>{moveMode ? '取消' : '編輯'}</button>
            )}
            {!moveMode && <ActionsMenu actions={[
              { label:'邀請填單', icon:'🔗', hidden: !lst.is_owner, onClick: () => setShowShare(true) },
              { label:'歷史紀錄', icon:'📋', hidden: lst.visibility !== 'shared', onClick: () => { loadHistory(); setShowHistory(true); } },
            ]} />}
          </div>
        }
      />

      <div style={{ padding:'12px 16px' }}>

        {/* ── stats row ── */}
        {lst.items.length > 0 && (
          <div style={{ display:'grid', gridTemplateColumns:'repeat(3, 1fr)', gap:8, marginBottom:14 }}>
            <div style={{ background:`${TABI_COLORS.red}10`, border:`1px solid ${TABI_COLORS.red}25`, borderRadius:12, padding:'10px 8px', textAlign:'center' }}>
              <div style={{ fontSize:22, fontWeight:800, fontFamily:tabiSerif, color:TABI_COLORS.red }}>{mustItems.length}</div>
              <div style={{ fontSize:10.5, color:TABI_COLORS.red, fontWeight:700, marginTop:1 }}>非買不可</div>
            </div>
            <div style={{ background:TABI_COLORS.card, border:`1px solid ${TABI_COLORS.line}`, borderRadius:12, padding:'10px 8px', textAlign:'center' }}>
              <div style={{ fontSize:22, fontWeight:800, fontFamily:tabiSerif, color:TABI_COLORS.ink }}>{wantItems.length}</div>
              <div style={{ fontSize:10.5, color:TABI_COLORS.ink3, fontWeight:700, marginTop:1 }}>想買看看</div>
            </div>
            <div style={{ background:`${TABI_COLORS.green}10`, border:`1px solid ${TABI_COLORS.green}25`, borderRadius:12, padding:'10px 8px', textAlign:'center' }}>
              <div style={{ fontSize:22, fontWeight:800, fontFamily:tabiSerif, color:TABI_COLORS.green }}>{bought.length}</div>
              <div style={{ fontSize:10.5, color:TABI_COLORS.green, fontWeight:700, marginTop:1 }}>已購買</div>
            </div>
          </div>
        )}

        {/* ── empty state ── */}
        {lst.items.length === 0 && (
          <div style={{ textAlign:'center', padding:'52px 20px', color:TABI_COLORS.ink3 }}>
            <div style={{ fontSize:44, marginBottom:12 }}>🛍️</div>
            <div style={{ fontSize:15, fontFamily:tabiSerif, color:TABI_COLORS.ink2, marginBottom:6 }}>清單還是空的</div>
            <div style={{ fontSize:12.5 }}>點右下角 + 開始加入想買的東西</div>
          </div>
        )}

        {/* ── 非買不可 section ── */}
        {mustItems.length > 0 && (
          <div style={{ marginBottom:8 }}>
            <button onClick={() => setShowMust(v => !v)} style={{
              display:'flex', alignItems:'center', gap:8, width:'100%', padding:'6px 0 8px',
              background:'transparent', border:'none', cursor:'pointer',
            }}>
              <span style={{ fontSize:16 }}>🔥</span>
              <span style={{ fontSize:13, fontWeight:800, color:TABI_COLORS.red }}>非買不可</span>
              <span style={{ background:`${TABI_COLORS.red}15`, color:TABI_COLORS.red, fontSize:11.5, fontWeight:700, padding:'1px 9px', borderRadius:999 }}>{mustItems.length}</span>
              {mustEst > 0 && <span style={{ fontSize:11, color:TABI_COLORS.ink3 }}>預估 ¥{mustEst.toLocaleString()}</span>}
              <div style={{ flex:1, height:1, background:`${TABI_COLORS.red}20` }} />
              <span style={{ fontSize:12, color:TABI_COLORS.ink3 }}>{showMust ? '▾' : '▸'}</span>
            </button>
            {showMust && mustItems.map(item => (
              <ShoppingItemCard key={item.id} item={item} hidePriority showContributor={lst.visibility==='shared'}
                selectable={moveMode} selected={selectedIds.includes(item.id)}
                onSelect={() => setSelectedIds(prev => prev.includes(item.id) ? prev.filter(id=>id!==item.id) : [...prev, item.id])}
                onToggle={() => toggleItem(item)} onEdit={() => setEditItem(item)}
                onDelete={() => deleteItem(item)} onImageClick={() => item.image_url && setLightbox(item.image_url)} />
            ))}
          </div>
        )}

        {/* ── 想買看看 section ── */}
        {wantItems.length > 0 && (
          <div style={{ marginBottom:8 }}>
            <button onClick={() => setShowWant(v => !v)} style={{
              display:'flex', alignItems:'center', gap:8, width:'100%', padding:'6px 0 8px',
              background:'transparent', border:'none', cursor:'pointer',
            }}>
              <span style={{ fontSize:16 }}>💭</span>
              <span style={{ fontSize:13, fontWeight:800, color:TABI_COLORS.ink2 }}>想買看看</span>
              <span style={{ background:TABI_COLORS.line2, color:TABI_COLORS.ink2, fontSize:11.5, fontWeight:700, padding:'1px 9px', borderRadius:999 }}>{wantItems.length}</span>
              <div style={{ flex:1, height:1, background:TABI_COLORS.line }} />
              <span style={{ fontSize:12, color:TABI_COLORS.ink3 }}>{showWant ? '▾' : '▸'}</span>
            </button>
            {showWant && wantItems.map(item => (
              <ShoppingItemCard key={item.id} item={item} hidePriority showContributor={lst.visibility==='shared'}
                selectable={moveMode} selected={selectedIds.includes(item.id)}
                onSelect={() => setSelectedIds(prev => prev.includes(item.id) ? prev.filter(id=>id!==item.id) : [...prev, item.id])}
                onToggle={() => toggleItem(item)} onEdit={() => setEditItem(item)}
                onDelete={() => deleteItem(item)} onImageClick={() => item.image_url && setLightbox(item.image_url)} />
            ))}
          </div>
        )}

        {/* ── 已購買 section ── */}
        {bought.length > 0 && (
          <div style={{ marginTop:8 }}>
            <button onClick={() => setShowBought(v => !v)} style={{
              display:'flex', alignItems:'center', gap:8, width:'100%', padding:'6px 0 8px',
              background:'transparent', border:'none', cursor:'pointer',
            }}>
              <span style={{ fontSize:16 }}>✅</span>
              <span style={{ fontSize:13, fontWeight:800, color:TABI_COLORS.green }}>已購買</span>
              <span style={{ background:`${TABI_COLORS.green}15`, color:TABI_COLORS.green, fontSize:11.5, fontWeight:700, padding:'1px 9px', borderRadius:999 }}>{bought.length}</span>
              <div style={{ flex:1, height:1, background:`${TABI_COLORS.green}25` }} />
              <span style={{ fontSize:12, color:TABI_COLORS.ink3 }}>{showBought ? '▾' : '▸'}</span>
            </button>
            {showBought && bought.map(item => (
              <ShoppingItemCard key={item.id} item={item} dimmed hidePriority showContributor={lst.visibility==='shared'}
                selectable={moveMode} selected={selectedIds.includes(item.id)}
                onSelect={() => setSelectedIds(prev => prev.includes(item.id) ? prev.filter(id=>id!==item.id) : [...prev, item.id])}
                onToggle={() => toggleItem(item)} onEdit={() => setEditItem(item)}
                onDelete={() => deleteItem(item)} onImageClick={() => item.image_url && setLightbox(item.image_url)} />
            ))}
          </div>
        )}
      </div>

      {editList && <AddEditListSheet existing={lst} onClose={() => setEditList(false)} onSaved={() => { setEditList(false); load(); }} />}
      {addChoice && <AddChoiceSheet
        onManual={() => { setAddChoice(false); setAddItem(true); }}
        onAI={f => { setAddChoice(false); setAiAnalysis(f || true); }}
        onClose={() => setAddChoice(false)} />}
      {addItem  && <AddEditItemSheet listId={listId} onClose={() => setAddItem(false)} onSaved={() => { setAddItem(false); load(); }} />}
      {aiAnalysis && <ImageAnalysisSheet listId={listId} initialFile={aiAnalysis || null}
        onClose={() => setAiAnalysis(false)}
        onSaved={() => { setAiAnalysis(false); load(); }} />}
      {editItem && <AddEditItemSheet listId={listId} existing={editItem} onClose={() => setEditItem(null)} onSaved={() => { setEditItem(null); load(); }} />}
      {lightbox && <ImageLightbox src={lightbox} onClose={() => setLightbox(null)} />}
      {showRequests && <RequestsReviewSheet listId={listId} requests={lst.requests||[]} onClose={() => setShowRequests(false)} onRefresh={() => { setShowRequests(false); load(); }} />}
      {showHistory && <HistorySheet listId={listId} history={history} onClose={() => setShowHistory(false)}
        onRevert={(itemId, histId) => _shopApi('POST', `/lists/${listId}/items/${itemId}/revert/${histId}`).then(() => { load(); loadHistory(); })} />}
      {showShare && <ShareLinkSheet listId={listId} hasLink={lst.has_request_link} appBaseUrl={appBase}
        onClose={() => setShowShare(false)}
        onTokenChange={() => load()} />}
      {showMove && <MoveItemsSheetReal fromListId={listId} selectedIds={selectedIds}
        onClose={() => setShowMove(false)}
        onMoved={() => { setShowMove(false); setMoveMode(false); setSelectedIds([]); load(); }} />}

      {/* Edit mode bottom action bar */}
      {moveMode && ReactDOM.createPortal(
        <div style={{
          position:'fixed', bottom:'calc(env(safe-area-inset-bottom,0px) + 76px)', left:'50%', transform:'translateX(-50%)',
          background:TABI_COLORS.ink, color:'#fff', borderRadius:16, padding:'10px 16px',
          display:'flex', alignItems:'center', gap:10, boxShadow:'0 12px 34px rgba(24,20,15,0.3)', zIndex:200,
          maxWidth:340, width:'calc(100% - 32px)',
        }}>
          <span style={{ fontSize:12, fontWeight:600, flex:1, whiteSpace:'nowrap' }}>
            {selectedIds.length === 0 ? '點品項選取' : `已選 ${selectedIds.length} 項`}
          </span>
          {selectedIds.length > 0 && (<>
            <button onClick={async () => {
              if (!confirm(`確定刪除選取的 ${selectedIds.length} 個品項？`)) return;
              await Promise.all(selectedIds.map(id => _shopApi('DELETE', `/lists/${listId}/items/${id}`)));
              setMoveMode(false); setSelectedIds([]); load();
            }} style={{
              padding:'7px 13px', borderRadius:9, border:'none', background:`${TABI_COLORS.red}cc`,
              color:'#fff', fontSize:12, fontWeight:700, cursor:'pointer', flexShrink:0,
            }}>🗑 刪除</button>
            <button onClick={() => setShowMove(true)} style={{
              padding:'7px 13px', borderRadius:9, border:'none', background:TABI_COLORS.blue,
              color:'#fff', fontSize:12, fontWeight:700, cursor:'pointer', flexShrink:0,
            }}>移動 →</button>
          </>)}
        </div>,
        document.body
      )}

      {!moveMode && ReactDOM.createPortal(
        <button onClick={() => setAddChoice(true)} style={{
          position:'fixed', bottom:'calc(env(safe-area-inset-bottom,0px) + 76px)', right:16,
          width:52, height:52, borderRadius:26, border:'none', cursor:'pointer',
          background:TABI_COLORS.red, color:'#fff', fontSize:26, lineHeight:1,
          boxShadow:'0 12px 30px rgba(200,85,61,0.4)', zIndex:200,
        }}>+</button>,
        document.body
      )}
    </div>
  );
}

// ── ShoppingItemCard ──────────────────────────────────────────

function ShoppingItemCard({ item, dimmed, hidePriority, showContributor, selectable, selected, onSelect, onToggle, onEdit, onDelete, onImageClick }) {
  const amtText = _fmtShopAmt(item.price, item.currency, item.quantity);
  return (
    <div onClick={selectable ? onSelect : undefined} style={{
      background: selected ? `${TABI_COLORS.blue}08` : TABI_COLORS.card,
      borderRadius:16, padding:'12px 14px', marginBottom:8,
      border:`2px solid ${selected ? TABI_COLORS.blue : TABI_COLORS.line}`,
      display:'flex', alignItems:'flex-start', gap:10, opacity:dimmed ? 0.55 : 1,
      boxShadow:dimmed ? 'none' : TABI_COLORS.shadowSoft,
      cursor: selectable ? 'pointer' : 'default',
      transition: 'border-color 0.12s, background 0.12s',
    }}>
      {selectable ? (
        <div style={{
          width:26, height:26, borderRadius:8, flexShrink:0, marginTop:2,
          border:`2px solid ${selected ? TABI_COLORS.blue : TABI_COLORS.line2}`,
          background: selected ? TABI_COLORS.blue : 'transparent',
          display:'grid', placeItems:'center', color:'#fff', fontSize:13,
        }}>{selected ? '✓' : ''}</div>
      ) : (
      <button onClick={onToggle} style={{
        width:26, height:26, borderRadius:8, flexShrink:0, cursor:'pointer', marginTop:2,
        border:`2px solid ${item.is_bought ? TABI_COLORS.green : TABI_COLORS.line2}`,
        background:item.is_bought ? TABI_COLORS.green : 'transparent',
        display:'grid', placeItems:'center', color:'#fff', fontSize:13,
      }}>{item.is_bought ? '✓' : ''}</button>
      )}

      {item.image_url && (
        <img src={item.image_url} alt="" onClick={onImageClick}
          style={{ width:52, height:52, objectFit:'cover', borderRadius:10, flexShrink:0, border:`1px solid ${TABI_COLORS.line}`, cursor:'zoom-in' }} />
      )}

      <div style={{ flex:1, minWidth:0 }}>
        <div style={{ display:'flex', alignItems:'center', gap:6, flexWrap:'wrap' }}>
          {item.priority==='must' && !item.is_bought && !hidePriority && (
            <span style={{ fontSize:9.5, fontWeight:800, padding:'2px 7px', borderRadius:5, background:`${TABI_COLORS.red}15`, color:TABI_COLORS.red }}>必買</span>
          )}
          {item.for_whom && (
            <span style={{ fontSize:9.5, fontWeight:700, padding:'2px 7px', borderRadius:5, background:`${TABI_COLORS.blue}12`, color:TABI_COLORS.blue }}>代買給 {item.for_whom}</span>
          )}
          <span style={{ fontSize:14.5, fontWeight:600, color:TABI_COLORS.ink, textDecoration:item.is_bought?'line-through':'none', wordBreak:'break-word' }}>{item.name}</span>
        </div>
        <div style={{ display:'flex', gap:8, marginTop:4, flexWrap:'wrap', alignItems:'center' }}>
          {amtText && <span style={{ fontSize:13, fontWeight:700, fontFamily:tabiSerif, color:item.is_bought?TABI_COLORS.ink3:TABI_COLORS.ink }}>{amtText}</span>}
          {item.quantity > 1 && <span style={{ fontSize:11, color:TABI_COLORS.ink3 }}>×{item.quantity}</span>}
          {item.location && <span style={{ fontSize:11, color:TABI_COLORS.ink3 }}>📍 {item.location}</span>}
        </div>
        {item.note && <div style={{ fontSize:11.5, color:TABI_COLORS.ink3, marginTop:3, lineHeight:1.4 }}>{item.note}</div>}
        {item.url && (
          <a href={item.url} target="_blank" rel="noopener noreferrer" style={{ fontSize:11, color:TABI_COLORS.blue, marginTop:3, display:'block', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
            🔗 {item.url.replace(/^https?:\/\/(www\.)?/, '')}
          </a>
        )}
        {/* contributor info: only shown for shared lists */}
        {showContributor && (
          <div style={{ display:'flex', gap:10, marginTop:5, flexWrap:'wrap' }}>
            {item.added_by_display_name && !item.is_bought && (
              <span style={{ fontSize:10.5, color:TABI_COLORS.ink3 }}>
                ✏️ {item.added_by_display_name} 新增
              </span>
            )}
            {item.is_bought && item.bought_by_display_name && (
              <span style={{ fontSize:10.5, color:TABI_COLORS.green, fontWeight:600 }}>
                ✓ {item.bought_by_display_name} 已購買
              </span>
            )}
            {item.is_bought && !item.bought_by_display_name && (
              <span style={{ fontSize:10.5, color:TABI_COLORS.green }}>✓ 已購買</span>
            )}
          </div>
        )}
        {/* personal list: simple bought indicator */}
        {!showContributor && item.is_bought && (
          <div style={{ fontSize:10.5, color:TABI_COLORS.green, marginTop:3 }}>✓ 已購買</div>
        )}
      </div>

      {!selectable && (
        <div style={{ display:'flex', flexDirection:'column', gap:4, flexShrink:0 }}>
          <button onClick={onEdit} style={{ width:30, height:30, borderRadius:8, border:`1px solid ${TABI_COLORS.line}`, background:'transparent', cursor:'pointer', fontSize:13, color:TABI_COLORS.ink2 }}>✏️</button>
          <button onClick={onDelete} style={{ width:30, height:30, borderRadius:8, border:`1px solid ${TABI_COLORS.line}`, background:'transparent', cursor:'pointer', fontSize:13, color:TABI_COLORS.red }}>🗑</button>
        </div>
      )}
    </div>
  );
}

// ── ShoppingScreen (list of lists) ───────────────────────────

// ── ListCard ──────────────────────────────────────────────────

function ListCard({ lst, go, onDelete, onEdit }) {
  const remain = lst.item_count - lst.bought_count;
  const progress = lst.item_count > 0 ? lst.bought_count / lst.item_count : 0;
  return (
    <div onClick={() => go('shopping_detail', lst.id)} style={{
      background:'rgba(255,250,242,0.96)', borderRadius:20, padding:'14px 15px', marginBottom:10,
      border:`1px solid ${TABI_COLORS.line}`, cursor:'pointer', boxShadow:TABI_COLORS.shadowSoft,
      display:'flex', alignItems:'center', gap:12,
    }}>
      <div style={{ width:50, height:50, borderRadius:16, background: lst.visibility==='shared' ? `${TABI_COLORS.blue}12` : `${TABI_COLORS.red}12`, display:'grid', placeItems:'center', fontSize:22, flexShrink:0, position:'relative', boxShadow:`inset 0 0 0 1px ${lst.visibility==='shared' ? TABI_COLORS.blue : TABI_COLORS.red}16` }}>
        {lst.emoji || '🛍️'}
        {lst.pending_requests > 0 && (
          <div style={{ position:'absolute', top:-4, right:-4, width:16, height:16, borderRadius:8, background:TABI_COLORS.red, color:'#fff', fontSize:9, fontWeight:800, display:'grid', placeItems:'center' }}>{lst.pending_requests}</div>
        )}
      </div>
      <div style={{ flex:1, minWidth:0 }}>
        <div style={{ display:'flex', alignItems:'center', gap:6, marginBottom:2, flexWrap:'wrap' }}>
          {lst.visibility === 'shared' && (
            <span style={{ fontSize:9.5, fontWeight:700, padding:'1px 6px', borderRadius:4, background:`${TABI_COLORS.blue}12`, color:TABI_COLORS.blue }}>共同</span>
          )}
          {!lst.is_owner && (
            <span style={{ fontSize:9.5, fontWeight:700, padding:'1px 6px', borderRadius:4, background:`${TABI_COLORS.green}12`, color:TABI_COLORS.green }}>旅伴清單</span>
          )}
          <span style={{ fontFamily:tabiSerif, fontSize:15, fontWeight:700, color:TABI_COLORS.ink, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{lst.title}</span>
        </div>
        <div style={{ fontSize:11.8, color:TABI_COLORS.ink3, marginBottom:lst.item_count>0?7:0, lineHeight:1.4 }}>
          {lst.item_count === 0 ? '尚無品項，點進去開始加入想買的東西' : `${remain} 項待買・${lst.bought_count} 項已買`}
        </div>
        {lst.item_count > 0 && (
          <div style={{ height:5, borderRadius:3, background:TABI_COLORS.line2, overflow:'hidden' }}>
            <div style={{ height:'100%', width:`${progress*100}%`, background:TABI_COLORS.green, borderRadius:3 }} />
          </div>
        )}
      </div>
      {lst.is_owner && (
        <div style={{ display:'flex', gap:4, flexShrink:0 }}>
          {onEdit && (
            <button onClick={e => { e.stopPropagation(); onEdit(lst); }} style={{
              width:30, height:30, borderRadius:8, border:`1px solid ${TABI_COLORS.line}`,
              background:'transparent', cursor:'pointer', fontSize:13, color:TABI_COLORS.ink2,
            }}>✏️</button>
          )}
          {onDelete && (
            <button onClick={e => { e.stopPropagation(); onDelete(lst); }} style={{
              width:30, height:30, borderRadius:8, border:`1px solid ${TABI_COLORS.line}`,
              background:'transparent', cursor:'pointer', fontSize:13, color:TABI_COLORS.red,
            }}>🗑</button>
          )}
        </div>
      )}
    </div>
  );
}

// ── ShoppingScreen ────────────────────────────────────────────

function ShoppingScreen({ go }) {
  const [lists, setLists]     = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [creating, setCreating]   = React.useState(false);
  const [editingList, setEditingList] = React.useState(null);

  async function load() {
    setLoading(true);
    try {
      const d = await _shopApi('GET', '/lists');
      setLists(Array.isArray(d) ? d : []);
    } catch {} finally { setLoading(false); }
  }

  React.useEffect(() => { load(); }, []);

  async function deleteList(lst) {
    if (!confirm(`刪除「${lst.title}」清單？清單內所有品項也會一起刪除。`)) return;
    await _shopApi('DELETE', `/lists/${lst.id}`);
    load();
  }

  // Group lists
  const myLists     = lists.filter(l => l.is_owner && !l.trip_id);
  const tripLists   = lists.filter(l => l.trip_id);
  // Group trip lists by trip_id
  const tripGroups  = {};
  tripLists.forEach(l => {
    if (!tripGroups[l.trip_id]) tripGroups[l.trip_id] = [];
    tripGroups[l.trip_id].push(l);
  });

  const totalPending = lists.reduce((s, l) => s + (l.pending_requests || 0), 0);
  const totalItems = lists.reduce((s, l) => s + (l.item_count || 0), 0);
  const totalBought = lists.reduce((s, l) => s + (l.bought_count || 0), 0);
  const totalRemain = Math.max(0, totalItems - totalBought);

  return (
    <div style={{ background:`linear-gradient(180deg,${TABI_COLORS.bg} 0%,#F2E7D8 100%)`, minHeight:'100%', paddingBottom:'calc(env(safe-area-inset-bottom,0px)+100px)' }}>
      <TabiTopBar
        title="購物清單"
        subtitle={`SHOPPING · ${lists.length} 個清單${totalPending > 0 ? ` · ${totalPending} 待審核` : ''}`}
        onBack={() => go('home')}
        right={
          <button onClick={() => setCreating(true)} style={{
            minHeight:36, padding:'8px 14px', borderRadius:12, border:'none',
            background:TABI_COLORS.ink, color:'#fff', fontSize:12, fontWeight:700, cursor:'pointer',
          }}>+ 新清單</button>
        }
      />

      <div style={{ padding:'14px 16px' }}>
        {loading && <div style={{ textAlign:'center', padding:'60px 0', color:TABI_COLORS.ink3 }}>載入中…</div>}

        {!loading && lists.length === 0 && (
          <div style={{ textAlign:'center', padding:'52px 20px', background:'rgba(255,250,242,0.74)', border:`1px solid ${TABI_COLORS.line}`, borderRadius:18, boxShadow:TABI_COLORS.shadowSoft }}>
            <div style={{ fontSize:48, marginBottom:12 }}>🛍️</div>
            <div style={{ fontSize:15, fontFamily:tabiSerif, color:TABI_COLORS.ink2, marginBottom:6 }}>還沒有購物清單</div>
            <div style={{ fontSize:12.5, color:TABI_COLORS.ink3 }}>建立清單，或新增旅程後會自動建立清單</div>
            <button onClick={() => setCreating(true)} style={{ marginTop:18, padding:'12px 24px', borderRadius:12, border:'none', background:TABI_COLORS.ink, color:'#fff', fontSize:14, fontWeight:700, cursor:'pointer' }}>建立第一個清單</button>
          </div>
        )}

        {!loading && lists.length > 0 && (
          <div style={{
            marginBottom:14, borderRadius:20, overflow:'hidden',
            border:`1px solid ${TABI_COLORS.line}`, background:TABI_COLORS.ink,
            color:'#fff', boxShadow:'0 16px 36px rgba(55,42,31,0.16)',
          }}>
            <div style={{ padding:'16px 16px 13px', display:'flex', alignItems:'flex-start', gap:12 }}>
              <div style={{
                width:48, height:48, borderRadius:16, flexShrink:0,
                display:'grid', placeItems:'center', background:'rgba(255,255,255,0.12)', fontSize:23,
              }}>🛍️</div>
              <div style={{ flex:1, minWidth:0 }}>
                <div style={{ fontSize:10.5, letterSpacing:'0.14em', fontWeight:800, opacity:0.58, marginBottom:5 }}>TRIP SHOPPING</div>
                <div style={{ fontFamily:tabiSerif, fontSize:22, fontWeight:800, lineHeight:1.15 }}>今天要買什麼？</div>
                <div style={{ fontSize:12, opacity:0.62, marginTop:5, lineHeight:1.45 }}>
                  個人清單和旅程採買集中管理，先買必需品，再看伴手禮。
                </div>
              </div>
            </div>
            <div style={{ display:'grid', gridTemplateColumns:'repeat(3, 1fr)', borderTop:'1px solid rgba(255,255,255,0.11)' }}>
              {[
                ['待買', totalRemain],
                ['已買', totalBought],
                ['待審核', totalPending],
              ].map(([label, value], idx) => (
                <div key={label} style={{
                  padding:'11px 8px 12px', textAlign:'center',
                  borderLeft: idx ? '1px solid rgba(255,255,255,0.1)' : 'none',
                }}>
                  <div style={{ fontFamily:tabiSerif, fontSize:18, fontWeight:800 }}>{value}</div>
                  <div style={{ fontSize:10.5, opacity:0.58, fontWeight:700, marginTop:1 }}>{label}</div>
                </div>
              ))}
            </div>
          </div>
        )}

        {/* 我的清單 (no trip) */}
        {myLists.length > 0 && (
          <div style={{ marginBottom:14 }}>
            <div style={{ display:'flex', alignItems:'baseline', gap:8, marginBottom:8, padding:'2px 1px' }}>
              <div style={{ fontFamily:tabiSerif, fontSize:16, fontWeight:800, color:TABI_COLORS.ink }}>個人清單</div>
              <div style={{ fontSize:10.5, fontWeight:800, color:TABI_COLORS.ink3, letterSpacing:'0.12em' }}>MY LISTS</div>
              <div style={{ marginLeft:'auto', fontSize:11, color:TABI_COLORS.ink3 }}>{myLists.length} 份</div>
            </div>
            {myLists.map(lst => <ListCard key={lst.id} lst={lst} go={go} onDelete={deleteList} onEdit={lst => setEditingList(lst)} />)}
          </div>
        )}

        {/* Trip-linked lists → ONE card per trip */}
        {Object.keys(tripGroups).length > 0 && (
          <div style={{ display:'flex', alignItems:'baseline', gap:8, margin:'4px 1px 8px' }}>
            <div style={{ fontFamily:tabiSerif, fontSize:16, fontWeight:800, color:TABI_COLORS.ink }}>旅程採買</div>
            <div style={{ fontSize:10.5, fontWeight:800, color:TABI_COLORS.ink3, letterSpacing:'0.12em' }}>TRIP LISTS</div>
          </div>
        )}
        {Object.entries(tripGroups).map(([tripId, tripLs]) => {
          const tripName     = tripLs[0]?.title?.split(' · ')[0] || '旅程清單';
          const totalItems   = tripLs.reduce((s, l) => s + l.item_count, 0);
          const totalBought  = tripLs.reduce((s, l) => s + l.bought_count, 0);
          const pending      = tripLs.reduce((s, l) => s + (l.pending_requests || 0), 0);
          const progress     = totalItems > 0 ? totalBought / totalItems : 0;
          return (
            <div key={tripId} onClick={() => go('shopping_trip', tripId)} style={{
              background:'rgba(255,250,242,0.96)', borderRadius:20, padding:'15px 16px', marginBottom:11,
              border:`1px solid ${pending > 0 ? 'rgba(185,74,53,0.24)' : TABI_COLORS.line}`,
              cursor:'pointer', boxShadow:TABI_COLORS.shadowSoft,
              display:'flex', alignItems:'center', gap:13,
            }}>
              <div style={{ width:50, height:50, borderRadius:16, background:`${TABI_COLORS.blue}12`, display:'grid', placeItems:'center', fontSize:23, flexShrink:0, position:'relative', boxShadow:`inset 0 0 0 1px ${TABI_COLORS.blue}16` }}>
                🛒
                {pending > 0 && (
                  <div style={{ position:'absolute', top:-4, right:-4, width:16, height:16, borderRadius:8, background:TABI_COLORS.red, color:'#fff', fontSize:9, fontWeight:800, display:'grid', placeItems:'center' }}>{pending}</div>
                )}
              </div>
              <div style={{ flex:1, minWidth:0 }}>
                <div style={{ display:'flex', alignItems:'center', gap:6, marginBottom:2 }}>
                  <span style={{ fontSize:10, color:TABI_COLORS.ink3 }}>✈️</span>
                  <span style={{ fontFamily:tabiSerif, fontSize:15, fontWeight:700, color:TABI_COLORS.ink, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{tripName}</span>
                </div>
                <div style={{ fontSize:11.8, color:TABI_COLORS.ink3, marginBottom:totalItems>0?7:0, lineHeight:1.4 }}>
                  {totalItems === 0 ? '尚無品項，進入後新增旅程採買' : `${totalItems - totalBought} 項待買・${totalBought} 項已買${pending > 0 ? `・${pending} 待審核` : ''}`}
                </div>
                {totalItems > 0 && (
                  <div style={{ height:5, borderRadius:3, background:TABI_COLORS.line2, overflow:'hidden' }}>
                    <div style={{ height:'100%', width:`${progress*100}%`, background:`linear-gradient(90deg, ${TABI_COLORS.green}, ${TABI_COLORS.blue})`, borderRadius:3 }} />
                  </div>
                )}
              </div>
              <span style={{ fontSize:18, color:TABI_COLORS.ink3, flexShrink:0 }}>›</span>
            </div>
          );
        })}
      </div>

      {creating && <AddEditListSheet onClose={() => setCreating(false)} onSaved={() => { setCreating(false); load(); }} />}
      {editingList && <AddEditListSheet existing={editingList} onClose={() => setEditingList(null)} onSaved={() => { setEditingList(null); load(); }} />}
    </div>
  );
}

// ── TripShoppingScreen ────────────────────────────────────────
// Combined view: personal section + shared section, shown inside the trip's 清單 tab

function TripShoppingScreen({ tripId, onTopBarRight }) {
  const [lists, setLists]         = React.useState([]);
  const [loading, setLoading]     = React.useState(true);
  const [tab, setTab]             = React.useState('personal'); // 'personal' | 'shared'
  const [addSheet, setAddSheet]     = React.useState(false);
  const [addChoice, setAddChoice]   = React.useState(false);
  const [aiAnalysis, setAiAnalysis] = React.useState(false);
  const [editItem, setEditItem]   = React.useState(null);
  const [lightbox, setLightbox]   = React.useState(null);
  const [showMust, setShowMust]     = React.useState(true);
  const [showWant, setShowWant]     = React.useState(true);
  const [showBought, setShowBought] = React.useState(false);
  const [detail, setDetail]         = React.useState(null);
  const [showHistory, setShowHistory] = React.useState(false);
  const [history, setHistory]         = React.useState([]);
  const [moveMode, setMoveMode]       = React.useState(false);
  const [selectedIds, setSelectedIds] = React.useState([]);
  const [showMove, setShowMove]       = React.useState(false);
  const [showShare, setShowShare]     = React.useState(false);
  const [showRequests, setShowRequests] = React.useState(false);
  const [appBase]                     = React.useState(() => window.location.origin);

  async function loadLists() {
    setLoading(true);
    try {
      const d = await _shopApi('GET', `/trips/${tripId}/lists`);
      setLists(Array.isArray(d) ? d : []);
    } catch {} finally { setLoading(false); }
  }

  React.useEffect(() => {
    let cancelled = false;
    setLoading(true);
    _shopApi('GET', `/trips/${tripId}/lists`)
      .then(d => { if (!cancelled) setLists(Array.isArray(d) ? d : []); })
      .catch(() => {})
      .finally(() => { if (!cancelled) setLoading(false); });
    return () => { cancelled = true; };
  }, [tripId]);

  const myPersonalList = lists.find(l => l.visibility === 'personal' && l.is_owner);
  const sharedList     = lists.find(l => l.visibility === 'shared');
  const activeList     = tab === 'personal' ? myPersonalList : sharedList;

  React.useEffect(() => {
    if (!activeList) { setDetail(null); return; }
    let cancelled = false;
    _shopApi('GET', `/lists/${activeList.id}`)
      .then(d => { if (!cancelled) setDetail(d); })
      .catch(() => {});
    return () => { cancelled = true; };
  }, [activeList?.id]);

  function reload() {
    if (!activeList) return;
    _shopApi('GET', `/lists/${activeList.id}`).then(setDetail).catch(() => {});
  }

  function openHistory() {
    if (!activeList) return;
    _shopApi('GET', `/lists/${activeList.id}/history`)
      .then(d => { setHistory(Array.isArray(d) ? d : []); setShowHistory(true); })
      .catch(() => {});
  }

  const btnStyle = (active) => ({
    minHeight:34, padding:'6px 11px', borderRadius:10, fontSize:12, fontWeight:700, cursor:'pointer',
    border:`1px solid ${active ? TABI_COLORS.blue : TABI_COLORS.line}`,
    background: active ? `${TABI_COLORS.blue}10` : 'transparent',
    color: active ? TABI_COLORS.blue : TABI_COLORS.ink2,
  });

  // Push action buttons to parent TopBar when onTopBarRight is provided
  const pendingCount = activeList?.pending_requests || 0;

  React.useEffect(() => {
    if (!onTopBarRight) return;
    onTopBarRight(
      <div style={{ display:'flex', gap:6, alignItems:'center' }}>
        {/* 申請 badge */}
        {pendingCount > 0 && !moveMode && (
          <button onClick={() => setShowRequests(true)} style={{
            minHeight:34, padding:'6px 10px', borderRadius:10, border:'none', flexShrink:0,
            background:TABI_COLORS.red, color:'#fff', fontSize:11.5, fontWeight:700,
            cursor:'pointer', display:'flex', alignItems:'center', gap:4,
          }}>
            申請<span style={{ background:'rgba(255,255,255,0.25)', borderRadius:8, padding:'0 5px', fontSize:11 }}>{pendingCount}</span>
          </button>
        )}
        {/* 編輯 — both personal and shared */}
        {activeList && (
          <button onClick={() => { setMoveMode(v => !v); setSelectedIds([]); }} style={{
            ...btnStyle(moveMode), flexShrink:0,
          }}>{moveMode ? '取消' : '編輯'}</button>
        )}
        {/* ⋯ secondary actions */}
        {!moveMode && <ActionsMenu actions={[
          { label:'邀請填單', icon:'🔗', hidden: !activeList, onClick: () => setShowShare(true) },
          { label:'歷史紀錄', icon:'📋', hidden: tab !== 'shared', onClick: openHistory },
        ]} />}
      </div>
    );
  }, [tab, moveMode, activeList?.id, pendingCount, onTopBarRight]);

  async function toggleItem(item) {
    await _shopApi('PATCH', `/lists/${activeList.id}/items/${item.id}/toggle`);
    reload();
  }
  async function deleteItem(item) {
    if (!confirm(`刪除「${item.name}」？`)) return;
    await _shopApi('DELETE', `/lists/${activeList.id}/items/${item.id}`);
    reload();
    if (showHistory) openHistory();
  }

  if (loading) return <div style={{ textAlign:'center', padding:'48px 0', color:TABI_COLORS.ink3 }}>載入中…</div>;

  const items      = detail?.items || [];
  const mustItems  = items.filter(i => !i.is_bought && i.priority === 'must').sort((a,b) => a.sort_order-b.sort_order);
  const wantItems  = items.filter(i => !i.is_bought && i.priority !== 'must').sort((a,b) => a.sort_order-b.sort_order);
  const boughtItems = items.filter(i => i.is_bought);
  const accentColor = tab === 'personal' ? TABI_COLORS.red : TABI_COLORS.blue;

  // summary counts for the toggle badges
  const pCount = (myPersonalList?.item_count || 0) - (myPersonalList?.bought_count || 0);
  const sCount = (sharedList?.item_count || 0) - (sharedList?.bought_count || 0);

  return (
    <div style={{ paddingBottom:'calc(env(safe-area-inset-bottom,0px)+90px)' }}>

      {/* ── Tab toggle ── */}
      <div style={{ display:'flex', gap:8, padding:'10px 16px 0', alignItems:'stretch' }}>
        {[
          { id:'personal', label:'🙋 個人', desc:'只有你', count: pCount },
          { id:'shared',   label:'👥 共同', desc:'全員可見', count: sCount },
        ].map(t => (
          <button key={t.id} onClick={() => setTab(t.id)} style={{
            flex:1, padding:'10px 8px', borderRadius:12, cursor:'pointer',
            border:`2px solid ${tab===t.id ? accentColor : TABI_COLORS.line}`,
            background: tab===t.id ? `${tab==='personal'?TABI_COLORS.red:TABI_COLORS.blue}10` : 'transparent',
            textAlign:'center', transition:'all 0.15s',
          }}>
            <div style={{ fontSize:13, fontWeight:800, color: tab===t.id ? accentColor : TABI_COLORS.ink2 }}>
              {t.label}
              {t.count > 0 && <span style={{ marginLeft:5, fontSize:11, fontWeight:700, background:`${tab===t.id?accentColor:TABI_COLORS.line2}30`, padding:'1px 6px', borderRadius:8, color: tab===t.id ? accentColor : TABI_COLORS.ink3 }}>{t.count}</span>}
            </div>
            <div style={{ fontSize:10.5, color: tab===t.id ? accentColor : TABI_COLORS.ink3, marginTop:1, opacity:0.8 }}>{t.desc}</div>
          </button>
        ))}
        {/* Inline fallback buttons (used when embedded in trip tab, no external TopBar) */}
        {!onTopBarRight && (
          <>
            {pendingCount > 0 && !moveMode && (
              <button onClick={() => setShowRequests(true)} style={{
                padding:'6px 10px', borderRadius:10, border:'none', background:TABI_COLORS.red,
                color:'#fff', fontSize:11.5, fontWeight:700, cursor:'pointer', flexShrink:0,
                display:'flex', alignItems:'center', gap:4,
              }}>
                申請<span style={{ background:'rgba(255,255,255,0.25)', borderRadius:8, padding:'0 5px', fontSize:11 }}>{pendingCount}</span>
              </button>
            )}
            {activeList && (
              <button onClick={() => { setMoveMode(v => !v); setSelectedIds([]); }} style={{
                padding:'6px 10px', borderRadius:10, fontSize:11.5, fontWeight:700, cursor:'pointer', flexShrink:0,
                border:`1px solid ${moveMode ? TABI_COLORS.blue : TABI_COLORS.line}`,
                background: moveMode ? `${TABI_COLORS.blue}10` : 'transparent',
                color: moveMode ? TABI_COLORS.blue : TABI_COLORS.ink2,
              }}>{moveMode ? '取消' : '編輯'}</button>
            )}
            {!moveMode && <ActionsMenu actions={[
              { label:'邀請填單', icon:'🔗', hidden: !activeList, onClick: () => setShowShare(true) },
              { label:'歷史紀錄', icon:'📋', hidden: tab !== 'shared', onClick: openHistory },
            ]} />}
          </>
        )}
      </div>

      {/* ── Content ── */}
      <div style={{ padding:'12px 16px' }}>
        {!activeList ? (
          <div style={{ textAlign:'center', padding:'40px 0', color:TABI_COLORS.ink3 }}>清單建立中…</div>
        ) : !detail ? (
          <div style={{ textAlign:'center', padding:'40px 0', color:TABI_COLORS.ink3 }}>載入中…</div>
        ) : items.length === 0 ? (
          <div style={{ textAlign:'center', padding:'48px 20px', color:TABI_COLORS.ink3 }}>
            <div style={{ fontSize:40, marginBottom:10 }}>🛍️</div>
            <div style={{ fontSize:14, fontFamily:tabiSerif, color:TABI_COLORS.ink2, marginBottom:4 }}>
              {tab==='personal' ? '個人清單還是空的' : '共同清單還是空的'}
            </div>
            <div style={{ fontSize:12.5 }}>點右下角 + 新增品項</div>
          </div>
        ) : (
          <>
            {mustItems.length > 0 && (
              <>
                <button onClick={() => setShowMust(v=>!v)} style={{ display:'flex', alignItems:'center', gap:6, width:'100%', padding:'6px 0 8px', background:'transparent', border:'none', cursor:'pointer' }}>
                  <span style={{ fontSize:13 }}>🔥</span>
                  <span style={{ fontSize:13, fontWeight:800, color:TABI_COLORS.red }}>非買不可</span>
                  <span style={{ background:`${TABI_COLORS.red}15`, color:TABI_COLORS.red, fontSize:11.5, fontWeight:700, padding:'1px 9px', borderRadius:999 }}>{mustItems.length}</span>
                  <div style={{ flex:1, height:1, background:`${TABI_COLORS.red}20` }} />
                  <span style={{ fontSize:12, color:TABI_COLORS.ink3 }}>{showMust?'▾':'▸'}</span>
                </button>
                {showMust && mustItems.map(item => (
                  <ShoppingItemCard key={item.id} item={item} hidePriority showContributor={tab==='shared'}
                    selectable={moveMode} selected={selectedIds.includes(item.id)}
                    onSelect={() => setSelectedIds(prev => prev.includes(item.id) ? prev.filter(id=>id!==item.id) : [...prev, item.id])}
                    onToggle={() => toggleItem(item)} onEdit={() => setEditItem(item)}
                    onDelete={() => deleteItem(item)} onImageClick={() => item.image_url && setLightbox(item.image_url)} />
                ))}
              </>
            )}
            {wantItems.length > 0 && (
              <>
                <button onClick={() => setShowWant(v=>!v)} style={{ display:'flex', alignItems:'center', gap:6, width:'100%', padding:'6px 0 8px', background:'transparent', border:'none', cursor:'pointer' }}>
                  <span style={{ fontSize:13 }}>💭</span>
                  <span style={{ fontSize:13, fontWeight:800, color:TABI_COLORS.ink2 }}>想買看看</span>
                  <span style={{ background:TABI_COLORS.line2, color:TABI_COLORS.ink2, fontSize:11.5, fontWeight:700, padding:'1px 9px', borderRadius:999 }}>{wantItems.length}</span>
                  <div style={{ flex:1, height:1, background:TABI_COLORS.line }} />
                  <span style={{ fontSize:12, color:TABI_COLORS.ink3 }}>{showWant?'▾':'▸'}</span>
                </button>
                {showWant && wantItems.map(item => (
                  <ShoppingItemCard key={item.id} item={item} hidePriority showContributor={tab==='shared'}
                    selectable={moveMode} selected={selectedIds.includes(item.id)}
                    onSelect={() => setSelectedIds(prev => prev.includes(item.id) ? prev.filter(id=>id!==item.id) : [...prev, item.id])}
                    onToggle={() => toggleItem(item)} onEdit={() => setEditItem(item)}
                    onDelete={() => deleteItem(item)} onImageClick={() => item.image_url && setLightbox(item.image_url)} />
                ))}
              </>
            )}
            {boughtItems.length > 0 && (
              <>
                <button onClick={() => setShowBought(v=>!v)} style={{ display:'flex', alignItems:'center', gap:6, width:'100%', padding:'6px 0 8px', background:'transparent', border:'none', cursor:'pointer', marginTop:4 }}>
                  <span style={{ fontSize:13 }}>✅</span>
                  <span style={{ fontSize:13, fontWeight:800, color:TABI_COLORS.green }}>已購買</span>
                  <span style={{ background:`${TABI_COLORS.green}15`, color:TABI_COLORS.green, fontSize:11.5, fontWeight:700, padding:'1px 9px', borderRadius:999 }}>{boughtItems.length}</span>
                  <div style={{ flex:1, height:1, background:`${TABI_COLORS.green}25` }} />
                  <span style={{ fontSize:12, color:TABI_COLORS.ink3 }}>{showBought?'▾':'▸'}</span>
                </button>
                {showBought && boughtItems.map(item => (
                  <ShoppingItemCard key={item.id} item={item} dimmed hidePriority showContributor={tab==='shared'}
                    selectable={moveMode} selected={selectedIds.includes(item.id)}
                    onSelect={() => setSelectedIds(prev => prev.includes(item.id) ? prev.filter(id=>id!==item.id) : [...prev, item.id])}
                    onToggle={() => toggleItem(item)} onEdit={() => setEditItem(item)}
                    onDelete={() => deleteItem(item)} onImageClick={() => item.image_url && setLightbox(item.image_url)} />
                ))}
              </>
            )}
          </>
        )}
      </div>

      {addSheet && activeList && (
        <AddEditItemSheet listId={activeList.id} onClose={() => setAddSheet(false)} onSaved={() => { setAddSheet(false); reload(); }} />
      )}
      {addChoice && activeList && <AddChoiceSheet
        onManual={() => { setAddChoice(false); setAddSheet(true); }}
        onAI={f => { setAddChoice(false); setAiAnalysis(f || true); }}
        onClose={() => setAddChoice(false)} />}
      {aiAnalysis && activeList && <ImageAnalysisSheet listId={activeList.id} initialFile={aiAnalysis || null}
        onClose={() => setAiAnalysis(false)}
        onSaved={() => { setAiAnalysis(false); reload(); }} />}
      {editItem && activeList && (
        <AddEditItemSheet listId={activeList.id} existing={editItem} onClose={() => setEditItem(null)} onSaved={() => { setEditItem(null); reload(); }} />
      )}
      {lightbox && <ImageLightbox src={lightbox} onClose={() => setLightbox(null)} />}
      {showRequests && activeList && detail && (
        <RequestsReviewSheet listId={activeList.id} requests={detail.requests||[]}
          onClose={() => setShowRequests(false)}
          onRefresh={() => {
            setShowRequests(false);
            reload();
            // reload lists to update pending_requests count
            _shopApi('GET', `/trips/${tripId}/lists`).then(d => setLists(Array.isArray(d) ? d : [])).catch(() => {});
          }} />
      )}
      {showHistory && activeList && (
        <HistorySheet listId={activeList.id} history={history} onClose={() => setShowHistory(false)}
          onRevert={(itemId, histId) => _shopApi('POST', `/lists/${activeList.id}/items/${itemId}/revert/${histId}`).then(() => { reload(); openHistory(); })} />
      )}
      {showMove && activeList && (
        <MoveItemsSheetReal fromListId={activeList.id} selectedIds={selectedIds}
          onClose={() => setShowMove(false)}
          onMoved={() => { setShowMove(false); setMoveMode(false); setSelectedIds([]); reload(); }} />
      )}
      {showShare && activeList && (
        <ShareLinkSheet listId={activeList.id} hasLink={activeList.has_request_link} appBaseUrl={appBase}
          onClose={() => setShowShare(false)}
          onTokenChange={() => {
            _shopApi('GET', `/trips/${tripId}/lists`).then(d => setLists(Array.isArray(d) ? d : [])).catch(() => {});
          }} />
      )}

      {/* Edit mode action bar */}
      {moveMode && ReactDOM.createPortal(
        <div style={{
          position:'fixed', bottom:'calc(env(safe-area-inset-bottom,0px) + 76px)', left:'50%', transform:'translateX(-50%)',
          background:TABI_COLORS.ink, color:'#fff', borderRadius:16, padding:'10px 16px',
          display:'flex', alignItems:'center', gap:10, boxShadow:'0 12px 34px rgba(24,20,15,0.3)', zIndex:200,
          maxWidth:340, width:'calc(100% - 32px)',
        }}>
          <span style={{ fontSize:12, fontWeight:600, flex:1, whiteSpace:'nowrap' }}>
            {selectedIds.length === 0 ? '點品項選取' : `已選 ${selectedIds.length} 項`}
          </span>
          {selectedIds.length > 0 && (<>
            <button onClick={async () => {
              if (!confirm(`確定刪除選取的 ${selectedIds.length} 個品項？`)) return;
              await Promise.all(selectedIds.map(id => _shopApi('DELETE', `/lists/${activeList.id}/items/${id}`)));
              setMoveMode(false); setSelectedIds([]); reload();
            }} style={{
              padding:'7px 13px', borderRadius:9, border:'none', background:`${TABI_COLORS.red}cc`,
              color:'#fff', fontSize:12, fontWeight:700, cursor:'pointer', flexShrink:0,
            }}>🗑 刪除</button>
            {/* 移動 only available for personal lists (not shared) */}
            {tab === 'personal' && (
              <button onClick={() => setShowMove(true)} style={{
                padding:'7px 13px', borderRadius:9, border:'none', background:TABI_COLORS.blue,
                color:'#fff', fontSize:12, fontWeight:700, cursor:'pointer', flexShrink:0,
              }}>移動 →</button>
            )}
          </>)}
        </div>,
        document.body
      )}

      {/* FAB (hidden in move mode) */}
      {activeList && !moveMode && ReactDOM.createPortal(
        <button onClick={() => setAddChoice(true)} style={{
          position:'fixed', bottom:'calc(env(safe-area-inset-bottom,0px) + 76px)', right:16,
          width:52, height:52, borderRadius:26, border:'none', cursor:'pointer',
          background:accentColor, color:'#fff', fontSize:26, lineHeight:1,
          boxShadow:`0 12px 30px ${accentColor}60`, zIndex:200,
        }}>+</button>,
        document.body
      )}
    </div>
  );
}

// ── TripShoppingPage (standalone wrapper with TopBar) ─────────

function TripShoppingPage({ go, tripId }) {
  const [tripName, setTripName] = React.useState('採購清單');
  const [topBarRight, setTopBarRight] = React.useState(null);

  React.useEffect(() => {
    _shopApi('GET', `/trips/${tripId}/lists`)
      .then(d => {
        if (Array.isArray(d) && d.length) {
          const name = d[0]?.title?.split(' · ')[0];
          if (name) setTripName(name);
        }
      })
      .catch(() => {});
  }, [tripId]);

  return (
    <div style={{ background: TABI_COLORS.bg, minHeight: '100%' }}>
      <TabiTopBar
        title={tripName}
        subtitle="採購清單 · 個人 + 共同"
        onBack={() => go('shopping')}
        right={topBarRight}
      />
      <TripShoppingScreen tripId={tripId} onTopBarRight={setTopBarRight} />
    </div>
  );
}
