// tab-email.jsx — the Email tab: a 3-pane mail client (folders · list · reading pane)
// over the MAIL store. Threading, search, labels, inline reply, reply-detection that
// pauses the sequence. Honors the rule: you press Send; the engine never auto-sends.
const { useState: useStateEm, useEffect: useEffectEm, useRef: useRefEm, useMemo: useMemoEm } = React;
const RSem = window.RaiseShell;

const FOLDERS = [
  { id: 'inbox',     label: 'Inbox',     d: 'M2 8.5l2-5h8l2 5M2 8.5v4h12v-4M2 8.5h3l1 1.5h4l1-1.5h3' },
  { id: 'starred',   label: 'Flagged',   d: 'M4 2.4v11.2M4 3.4h7.4l-1.6 2 1.6 2H4' },
  { id: 'snoozed',   label: 'Snoozed',   d: 'M8 4.2v4l2.6 1.5M8 1.8a6.2 6.2 0 100 12.4A6.2 6.2 0 008 1.8z' },
  { id: 'sent',      label: 'Sent',      d: 'M2.5 8l11-5-4 11-2.5-4.5L2.5 8z' },
  { id: 'drafts',    label: 'Drafts',    d: 'M3 2.5h7l3 3v8H3zM10 2.5v3h3' },
  { id: 'scheduled', label: 'Scheduled', d: 'M8 4v4l2.5 1.5M8 1.8a6.2 6.2 0 100 12.4A6.2 6.2 0 008 1.8z' },
  { id: 'spam',      label: 'Spam',      d: 'M8 1.8a6.2 6.2 0 100 12.4A6.2 6.2 0 008 1.8zM8 5v3.4M8 10.6v.2' },
  { id: 'all',       label: 'All mail',  d: 'M2.5 4.5h11v7h-11zM2.5 4.5l5.5 4 5.5-4' },
];
const CALENDLY_URL = 'https://calendly.com/austin-fund2/intro-call';
const SNOOZE_OPTS = ['Later today · 4:00 PM', 'Tomorrow · 8:00 AM', 'This weekend', 'Next week · Mon 8:00 AM'];
const LABEL_HUES = { Reply: '#3f9560', Warm: '#c96442', Hot: '#c4504f', Question: '#4a78c8', 'Not now': '#c08a2a', Outreach: '#8159bf', Draft: '#9b9285', Scheduled: '#c08a2a' };
const labelColor = (l) => LABEL_HUES[l] || '#9b9285';
const STAGE_ORDER = ['identified', 'outreach', 'meeting', 'dataroom', 'terms', 'verbal', 'wired', 'passed'];

function relTime(at) {
  const s = (Date.now() - at) / 1000;
  if (s < 60) return 'now';
  if (s < 3600) return Math.floor(s / 60) + 'm';
  const d = new Date(at), now = new Date();
  if (d.toDateString() === now.toDateString()) return d.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
  const days = Math.floor(s / 86400);
  if (days < 7) return days + 'd';
  return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
}
function EmIcon({ d }) { return <svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"><path d={d} /></svg>; }

// ── live Gmail helpers (parse headers, decode bodies) ───────────────────────
function parseAddr(h) { // "Name <a@b.com>" → { name, email }
  const s = String(h || '').trim();
  const m = s.match(/^\s*"?([^"<]*?)"?\s*<([^>]+)>\s*$/);
  const email = (m ? m[2] : s).trim();
  let name = (m ? m[1] : '').trim();
  if (!name) name = email.split('@')[0];
  return { name, email };
}
function b64urlDecode(d) {
  try { return decodeURIComponent(escape(atob(String(d).replace(/-/g, '+').replace(/_/g, '/')))); }
  catch (e) { try { return atob(String(d).replace(/-/g, '+').replace(/_/g, '/')); } catch (_) { return ''; } }
}
function gmailBodyText(payload) { // walk MIME tree → plain text (fallback: stripped HTML)
  if (!payload) return '';
  const find = (p, mime) => {
    if (p.mimeType === mime && p.body && p.body.data) return b64urlDecode(p.body.data);
    if (p.parts) { for (const c of p.parts) { const r = find(c, mime); if (r) return r; } }
    return '';
  };
  let txt = find(payload, 'text/plain');
  if (!txt) {
    const html = find(payload, 'text/html');
    txt = html.replace(/<style[\s\S]*?<\/style>/gi, '').replace(/<\/(p|div|br)>/gi, '\n').replace(/<[^>]+>/g, '').replace(/&nbsp;/g, ' ').replace(/\n{3,}/g, '\n\n').trim();
  }
  return txt;
}
// Return the raw text/html part of a Gmail message (for rich rendering), or '' if none.
function gmailBodyHtml(payload) {
  if (!payload) return '';
  const find = (p, mime) => {
    if (p.mimeType === mime && p.body && p.body.data) return b64urlDecode(p.body.data);
    if (p.parts) { for (const c of p.parts) { const r = find(c, mime); if (r) return r; } }
    return '';
  };
  return find(payload, 'text/html');
}
// Light sanitize: drop scripts/event handlers; force links to open in a new tab.
function sanitizeMailHtml(html) {
  return String(html)
    .replace(/<script[\s\S]*?<\/script>/gi, '')
    .replace(/\son\w+\s*=\s*"[^"]*"/gi, '')
    .replace(/\son\w+\s*=\s*'[^']*'/gi, '')
    .replace(/<a\s/gi, '<a target="_blank" rel="noopener noreferrer" ');
}
// Renders email HTML inside an isolated, sandboxed iframe so styles don't leak into
// the app and images load. Auto-sizes height to the content.
function HtmlMail({ html }) {
  const ref = useRefEm(null);
  const doc = '<!doctype html><html><head><meta charset="utf-8">'
    + '<base target="_blank">'
    + '<style>html,body{margin:0;padding:0;}body{font:14px/1.55 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;color:#2b2622;word-break:break-word;overflow-wrap:anywhere;}img{max-width:100%;height:auto;}a{color:#c96442;}table{max-width:100%;}</style>'
    + '</head><body>' + sanitizeMailHtml(html) + '</body></html>';
  const fit = () => { const f = ref.current; if (!f || !f.contentWindow) return; try { const h = f.contentWindow.document.body.scrollHeight; f.style.height = Math.max(80, h + 24) + 'px'; } catch (e) {} };
  return (
    <iframe ref={ref} title="message" sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin"
      srcDoc={doc} onLoad={() => { fit(); setTimeout(fit, 300); }}
      style={{ width: '100%', border: 0, display: 'block' }} />
  );
}

// ── consistent compose font: everything the composer sends uses this unless the
//    user deliberately changes a selection (which then carries its own inline style).
const MAIL_FONT = 'Arial, Helvetica, sans-serif';
const MAIL_SIZE = '14px';
const MAIL_INK = '#2b2622';
function escapeHtml(s) { return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); }
// plain text (with \n) → HTML for the editor; linkifies bare URLs.
function textToHtml(s) {
  return escapeHtml(s || '')
    .replace(/(https?:\/\/[^\s<]+)/g, '<a href="$1">$1</a>')
    .replace(/\n/g, '<br>');
}
// HTML → plain text, for the local Sent log + the plain-text MIME fallback.
function htmlToText(html) {
  return String(html || '')
    .replace(/<style[\s\S]*?<\/style>/gi, '')
    .replace(/<br\s*\/?>/gi, '\n')
    .replace(/<\/(p|div|li|h[1-6])>/gi, '\n')
    .replace(/<li[^>]*>/gi, '• ')
    .replace(/<[^>]+>/g, '')
    .replace(/&nbsp;/g, ' ').replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>')
    .replace(/\n{3,}/g, '\n\n').trim();
}
// insert an HTML snippet just before the signature block if present, else append.
function insertHtmlBeforeSig(html, snippet, sigHtml) {
  const i = sigHtml ? html.lastIndexOf(sigHtml) : -1;
  return i >= 0 ? html.slice(0, i) + snippet + '<br><br>' + html.slice(i)
                : html.replace(/(<br>\s*)*$/, '') + '<br><br>' + snippet;
}

// ── rich-text editor (contentEditable + toolbar) ───────────────────────────────
function RichEditor({ html, onChange, minHeight }) {
  const ref = useRefEm(null);
  // seed / re-seed from prop only when not actively editing (so external inserts apply
  // but typing is never clobbered).
  useEffectEm(() => {
    const el = ref.current;
    if (el && document.activeElement !== el && el.innerHTML !== (html || '')) el.innerHTML = html || '';
  }, [html]);
  const fire = () => { if (ref.current) onChange(ref.current.innerHTML); };
  const exec = (cmd, val) => {
    ref.current && ref.current.focus();
    try { document.execCommand('styleWithCSS', false, true); } catch (e) {}
    document.execCommand(cmd, false, val);
    fire();
  };
  const link = () => { const u = window.prompt('Link URL'); if (u) exec('createLink', /^https?:/.test(u) ? u : 'https://' + u); };
  const SIZES = [['Small', '2'], ['Normal', '3'], ['Large', '5'], ['Huge', '6']];
  const Btn = ({ cmd, val, label, title, fn }) => (
    <button type="button" className="rte-btn" title={title} onMouseDown={(e) => e.preventDefault()}
      onClick={() => (fn ? fn() : exec(cmd, val))}>{label}</button>
  );
  return (
    <div className="rte" style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
      <div className="rte-bar" style={{ display: 'flex', alignItems: 'center', gap: 2, flexWrap: 'wrap', padding: '6px 12px', borderBottom: '1px solid var(--line,#f0ebe3)' }}>
        <Btn cmd="bold" label={<b>B</b>} title="Bold (⌘B)" />
        <Btn cmd="italic" label={<i>I</i>} title="Italic (⌘I)" />
        <Btn cmd="underline" label={<u>U</u>} title="Underline (⌘U)" />
        <span className="rte-sep" style={{ width: 1, height: 18, background: 'var(--line,#e6dfd4)', margin: '0 4px' }} />
        <Btn cmd="insertUnorderedList" label="•" title="Bulleted list" />
        <Btn cmd="insertOrderedList" label="1." title="Numbered list" />
        <Btn fn={link} label="🔗" title="Insert link" />
        <span className="rte-sep" style={{ width: 1, height: 18, background: 'var(--line,#e6dfd4)', margin: '0 4px' }} />
        <select className="rte-size" title="Font size" defaultValue="3" onMouseDown={(e) => e.stopPropagation()}
          onChange={(e) => { exec('fontSize', e.target.value); e.target.value = '3'; }}
          style={{ border: '1px solid var(--line,#e6dfd4)', borderRadius: 6, fontSize: 12, padding: '2px 4px', background: 'transparent', color: 'inherit', cursor: 'pointer' }}>
          {SIZES.map(([n, v]) => <option key={v} value={v}>{n}</option>)}
        </select>
        <Btn cmd="removeFormat" label="⌫T" title="Clear formatting" />
      </div>
      <div ref={ref} className="rte-body scroll" contentEditable suppressContentEditableWarning
        onInput={fire} onBlur={fire} spellCheck
        style={{ flex: 1, minHeight: minHeight || 120, overflowY: 'auto', outline: 'none', padding: '14px 16px',
          fontFamily: MAIL_FONT, fontSize: MAIL_SIZE, lineHeight: 1.5, color: MAIL_INK, background: 'transparent' }} />
    </div>
  );
}

// quick suggested replies (Smart-Reply style) — context-aware off the last incoming message
function suggestReplies(thread, contact) {
  const first = window.OX.firstName(contact ? contact.name : 'there');
  const sig = window.OX.Store.signature();
  const lastIn = [...thread.messages].reverse().find((m) => m.dir === 'in');
  const t = (lastIn ? lastIn.body : '').toLowerCase();
  const wrap = (mid) => `Hi ${first},\n\n${mid}\n\n${sig}`;
  const S = [];
  if (/\b(work|works|time|invite|slot|schedule|tuesday|thursday|week of)\b/.test(t)) {
    S.push({ label: 'Send times', body: wrap(`Great — I’m open Tue 2–4pm CT or Thu morning CT. Easiest is to grab a slot directly: ${CALENDLY_URL}`) });
    S.push({ label: 'Confirm + invite', body: wrap(`Perfect, locking it in — a calendar invite is on its way. Looking forward to it.`) });
  }
  if (/\b(data.?room|link|resend|deck|one.?pager|materials)\b/.test(t)) {
    S.push({ label: 'Send data room', body: wrap(`Here’s the data room — the two seed deals and the fund docs are inside. Shout with any questions, or we can walk it live.`) });
  }
  if (/\b(minimum|commit|gp|fee|terms|check)\b/.test(t)) {
    S.push({ label: 'Share terms', body: wrap(`Good questions. Minimum is $100K, the GP commit is meaningful, and I’m happy to walk the fee math line by line on a quick call.`) });
  }
  if (/\b(circle back|q3|not now|later|busy|heads.?down|timing)\b/.test(t)) {
    S.push({ label: 'Graceful hold', body: wrap(`Totally understand — I’ll keep you on a light quarterly update so you can see how it tracks, and we’ll revisit when the timing’s better. No selling in between.`) });
  }
  S.push({ label: 'Propose a call', body: wrap(`Would a quick 20 minutes be useful? Grab whatever works here: ${CALENDLY_URL}`) });
  S.push({ label: 'Quick thanks', body: wrap(`Really appreciate you getting back to me — means a lot. Let me know how you’d like to take it from here.`) });
  const seen = new Set();
  return S.filter((s) => (seen.has(s.label) ? false : seen.add(s.label))).slice(0, 3);
}

// resizable column divider (drag to set the panel width; persists)
function ColResizer({ value, set, min, max, lsKey }) {
  const startDrag = (e) => {
    e.preventDefault();
    const x0 = e.clientX, w0 = value; let latest = w0;
    const move = (ev) => { latest = Math.max(min, Math.min(max, w0 + (ev.clientX - x0))); set(latest); };
    const up = () => { document.removeEventListener('mousemove', move); document.removeEventListener('mouseup', up); document.body.style.userSelect = ''; try { localStorage.setItem(lsKey, String(latest)); } catch (e) {} };
    document.body.style.userSelect = 'none';
    document.addEventListener('mousemove', move); document.addEventListener('mouseup', up);
  };
  return <div className="em-colresizer" onMouseDown={startDrag} title="Drag to resize"><span /></div>;
}

// ── compact From-account selector for replies ──
function MailFrom({ value, onChange }) {
  const [open, setOpen] = useStateEm(false);
  const OX = window.OX;
  const cur = OX.ACCOUNTS.find((a) => a.id === value) || OX.ACCOUNTS[0];
  return (
    <div className="em-from">
      <button className="em-from-btn" title="Change sending account" onClick={() => setOpen((o) => !o)}>
        <span className="em-from-dot" style={{ background: cur.id === 'fund' ? 'var(--primary)' : 'var(--green)' }} />
        {cur.email}<span className="em-cv">▾</span>
      </button>
      {open && (
        <>
          <div className="combo-back" onClick={() => setOpen(false)} />
          <div className="em-from-menu" onClick={(e) => e.stopPropagation()}>
            {OX.ACCOUNTS.map((a) => (
              <button key={a.id} className={'em-from-opt' + (a.id === value ? ' on' : '')} onClick={() => { onChange(a.id); setOpen(false); }}>
                <span className="em-from-dot" style={{ background: a.id === 'fund' ? 'var(--primary)' : 'var(--green)' }} />
                <span><b>{a.email}</b><small>{a.label}</small></span>
              </button>
            ))}
          </div>
        </>
      )}
    </div>
  );
}

// ── inline reply composer (lives at the bottom of the reading pane) ──
// ── compose toolbar: shared Scripts + Attach combos ───────────────────────────
const POP = { position: 'absolute', bottom: 'calc(100% + 6px)', left: 0, zIndex: 210, width: 300, maxHeight: 320,
  display: 'flex', flexDirection: 'column', background: 'var(--surface,#fff)', border: '1px solid var(--line,#e6dfd4)',
  borderRadius: 10, boxShadow: '0 12px 32px rgba(0,0,0,.16)', overflow: 'hidden' };
const POP_HD = { padding: '9px 12px', fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '.05em', color: 'var(--ink-3)', borderBottom: '1px solid var(--line,#f0ebe3)' };
const POP_NEW = { padding: '9px 12px', fontSize: 13, fontWeight: 600, color: 'var(--primary,#c96442)', background: 'transparent', border: 'none', borderTop: '1px solid var(--line,#f0ebe3)', cursor: 'pointer', textAlign: 'left' };
const POP_INP = { margin: '8px 10px 4px', padding: '6px 9px', fontSize: 13, border: '1px solid var(--line,#e6dfd4)', borderRadius: 7, outline: 'none', background: 'transparent', color: 'inherit' };

// modal to write a brand-new script and save it into the Outreach script library
function ScriptEditorModal({ initialName, onSaved, onClose }) {
  const S = window.OX.Store;
  const cats = S.scriptCats();
  const [name, setName] = useStateEm(initialName || '');
  const [cat, setCat] = useStateEm(cats[0] || 'Email');
  const [text, setText] = useStateEm('');
  const save = () => {
    if (!name.trim() || !text.trim()) { RSem.toast('Add a name and some text'); return; }
    const s = S.addScript({ name: name.trim(), cat: cat.trim() || 'Email', use: '', body: [{ h: '', t: text }] });
    RSem.toast('Saved to script library');
    onSaved && onSaved(s); onClose();
  };
  const fld = { width: '100%', padding: '8px 10px', fontSize: 13, border: '1px solid var(--line,#e6dfd4)', borderRadius: 7, outline: 'none', background: 'transparent', color: 'inherit', boxSizing: 'border-box' };
  return (
    <>
      <div className="combo-back" onClick={onClose} style={{ zIndex: 210 }} />
      <div onClick={(e) => e.stopPropagation()} style={{ position: 'fixed', zIndex: 211, top: '50%', left: '50%', transform: 'translate(-50%,-50%)', width: 460, maxWidth: '92vw', background: 'var(--surface,#fff)', border: '1px solid var(--line,#e6dfd4)', borderRadius: 12, boxShadow: '0 20px 50px rgba(0,0,0,.22)', padding: 18 }}>
        <div style={{ fontSize: 15, fontWeight: 700, marginBottom: 12 }}>New script</div>
        <div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
          <input style={{ ...fld, flex: 2 }} placeholder="Script name" value={name} autoFocus onChange={(e) => setName(e.target.value)} />
          <input style={{ ...fld, flex: 1 }} placeholder="Category" list="oxscriptcats" value={cat} onChange={(e) => setCat(e.target.value)} />
          <datalist id="oxscriptcats">{cats.map((c) => <option key={c} value={c} />)}</datalist>
        </div>
        <textarea style={{ ...fld, minHeight: 150, resize: 'vertical', fontFamily: 'inherit', lineHeight: 1.5 }}
          placeholder="Write the script… tokens like {first} and {firm} are filled in when inserted." value={text} onChange={(e) => setText(e.target.value)} />
        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 12 }}>
          <button className="snd-tool" onClick={onClose}>Cancel</button>
          <button className="snd-send" onClick={save}>Save &amp; insert</button>
        </div>
      </div>
    </>
  );
}

// Scripts combo: filter + insert an existing script, or create a new one (saved to library)
function ScriptCombo({ onPick, contact }) {
  const S = window.OX.Store;
  const [, bump] = useStateEm(0);
  useEffectEm(() => S.subscribe(() => bump((x) => x + 1)), []);
  const [open, setOpen] = useStateEm(false);
  const [edit, setEdit] = useStateEm(false);
  const [q, setQ] = useStateEm('');
  const scripts = S.scripts();
  const filt = scripts.filter((s) => !q || (s.name + ' ' + (s.cat || '') + ' ' + (s.use || '')).toLowerCase().includes(q.toLowerCase()));
  return (
    <div className="chpick-wrap" style={{ position: 'relative' }}>
      <button className="snd-tool" onClick={() => setOpen((o) => !o)} data-tip="Insert or create a script">⤵ Scripts ▾</button>
      {open && (<>
        <div className="combo-back" onClick={() => setOpen(false)} />
        <div onClick={(e) => e.stopPropagation()} style={POP}>
          <div style={POP_HD}>Insert a script</div>
          <input style={POP_INP} placeholder="Filter scripts…" value={q} autoFocus onChange={(e) => setQ(e.target.value)} />
          <div className="spk-list scroll" style={{ flex: 1, overflowY: 'auto', padding: '2px 0' }}>
            {filt.map((s) => (
              <button key={s.id} className="spk-opt" onClick={() => { onPick(s); setOpen(false); }}>
                <span className="spk-cat">{s.cat}</span>
                <span className="spk-nm">{s.name}<small>{s.use || s.blurb || ''}</small></span>
              </button>
            ))}
            {filt.length === 0 && <div className="muted" style={{ padding: 12, fontSize: 12 }}>{scripts.length ? 'No match.' : 'No saved scripts yet — create one below.'}</div>}
          </div>
          <button style={POP_NEW} onClick={() => { setOpen(false); setEdit(true); }}>✎ New script…</button>
        </div>
      </>)}
      {edit && <ScriptEditorModal onSaved={(s) => onPick(s)} onClose={() => setEdit(false)} />}
    </div>
  );
}

// Attach combo: insert a library file directly, or browse local files
function AttachCombo({ attach, setAttach, files, setFiles }) {
  const OX = window.OX;
  const [open, setOpen] = useStateEm(false);
  const inputRef = useRefEm(null);
  const lib = OX.ATTACHMENTS;
  const onPickFiles = (e) => {
    Array.from(e.target.files || []).forEach((f) => {
      const reader = new FileReader();
      reader.onload = () => setFiles((prev) => [...prev, { name: f.name, size: f.size, type: f.type || 'application/octet-stream', b64: String(reader.result).split(',')[1] || '' }]);
      reader.readAsDataURL(f);
    });
    e.target.value = ''; setOpen(false);
  };
  return (
    <div className="chpick-wrap" style={{ position: 'relative' }}>
      <button className="snd-tool" onClick={() => setOpen((o) => !o)} data-tip="Attach a file">📎 Attach ▾</button>
      {open && (<>
        <div className="combo-back" onClick={() => setOpen(false)} />
        <div onClick={(e) => e.stopPropagation()} style={POP}>
          <div style={POP_HD}>Attach a file</div>
          <div className="spk-list scroll" style={{ flex: 1, overflowY: 'auto', padding: '2px 0' }}>
            {lib.map((a) => { const on = attach.includes(a.id); return (
              <button key={a.id} className="spk-opt" onClick={() => setAttach(on ? attach.filter((x) => x !== a.id) : [...attach, a.id])}>
                <span className="spk-cat">{on ? '✓' : '＋'}</span>
                <span className="spk-nm">{a.name}<small>{a.size || 'from library'}</small></span>
              </button>); })}
            {lib.length === 0 && <div className="muted" style={{ padding: 12, fontSize: 12 }}>No saved files — browse your computer below.</div>}
          </div>
          <button style={POP_NEW} onClick={() => inputRef.current && inputRef.current.click()}>＋ Browse local files…</button>
          <input ref={inputRef} type="file" multiple style={{ display: 'none' }} onChange={onPickFiles} />
        </div>
      </>)}
    </div>
  );
}

// compact strip of currently-selected attachments (removable)
function AttachChips({ attach, setAttach, files, setFiles, style }) {
  const OX = window.OX;
  if (!files.length && !attach.length) return null;
  const x = { marginLeft: 6, border: 'none', background: 'none', cursor: 'pointer', color: 'inherit' };
  return (
    <div className="em-reply-att" style={style}>
      {files.map((f, i) => (
        <span key={'f' + i} className="snd-att-chip on"><span className="att-ic">📎</span>{f.name}<small>{Math.max(1, Math.round(f.size / 1024))}KB</small>
          <button onClick={() => setFiles(files.filter((_, j) => j !== i))} style={x}>✕</button></span>
      ))}
      {attach.map((id) => { const a = OX.ATTACHMENTS.find((a) => a.id === id); if (!a) return null;
        return <span key={id} className="snd-att-chip on"><span className="att-ic">📎</span>{a.name}<button onClick={() => setAttach(attach.filter((y) => y !== id))} style={x}>✕</button></span>; })}
    </div>
  );
}

function ReplyBox({ thread, contact, onSent }) {
  const OX = window.OX;
  const ES = OX.Store.emailSettings();
  const first = OX.firstName(contact ? contact.name : 'there');
  const [acct, setAcct] = useStateEm(ES.fromDefault || 'fund');
  const [body, setBody] = useStateEm(`Hi ${first},\n\n\n\n${OX.Store.signature()}`);
  const [attach, setAttach] = useStateEm([]);
  const [files, setFiles] = useStateEm([]);
  const [track, setTrack] = useStateEm(ES.trackOpens);
  const [sched, setSched] = useStateEm(false);
  const [boxH, setBoxH] = useStateEm(() => { const v = +localStorage.getItem('raiseos.mail.replyH'); return v >= 80 ? v : 150; });
  const hRef = useRefEm(boxH);
  const startDrag = (e) => {
    e.preventDefault();
    const startY = e.clientY, startH = hRef.current;
    const move = (ev) => { const nh = Math.max(90, Math.min(560, startH + (startY - ev.clientY))); hRef.current = nh; setBoxH(nh); };
    const up = () => { document.removeEventListener('mousemove', move); document.removeEventListener('mouseup', up); document.body.style.userSelect = ''; try { localStorage.setItem('raiseos.mail.replyH', String(hRef.current)); } catch (e) {} };
    document.body.style.userSelect = 'none';
    document.addEventListener('mousemove', move); document.addEventListener('mouseup', up);
  };
  useEffectEm(() => { setBody(`Hi ${first},\n\n\n\n${OX.Store.signature()}`); setAttach([]); setFiles([]); }, [thread.id]);
  const sig = OX.Store.signature();
  const insertBeforeSig = (b, text) => { const i = b.lastIndexOf(sig); return i >= 0 ? b.slice(0, i) + text + '\n\n' + b.slice(i) : b.replace(/\s*$/, '') + '\n\n' + text; };
  const applySuggestion = (s) => setBody(s.body);
  const insertCalendly = () => { setBody((b) => insertBeforeSig(b, 'Easiest is to grab a time that works for you here: ' + CALENDLY_URL)); RSem.toast('Calendly link inserted'); };
  const insertScript = (sv) => { setBody((b) => insertBeforeSig(b, window.OX.scriptText(sv, contact))); RSem.toast('Inserted “' + sv.name + '”'); };
  const suggestions = suggestReplies(thread, contact);

  const send = (status, when) => {
    const accEmail = (OX.ACCOUNTS.find((a) => a.id === acct) || {}).email;
    window.MAIL.reply(thread.id, {
      body, account: accEmail, track, status: status || 'sent', when,
      attachments: [...attach.map((id) => (OX.ATTACHMENTS.find((a) => a.id === id) || {}).name).filter(Boolean), ...files.map((f) => f.name)],
    });
    if (status === 'scheduled') RSem.toast('Reply scheduled — ' + when);
    else RSem.toast('Reply sent to ' + first);
    if (ES.logTouch) {/* touch logged silently */}
    onSent && onSent();
  };
  const schedOpts = ['In 1 hour', 'This evening · 6:00 PM', 'Tomorrow · 8:00 AM'];
  return (
    <div className="em-reply">
      <div className="em-reply-grip" onMouseDown={startDrag} title="Drag to resize"><span /></div>
      <div className="em-reply-hd">
        <span className="em-reply-k">Reply</span>
        <MailFrom value={acct} onChange={setAcct} />
        <span className="em-reply-to">to {contact ? contact.name : thread.subject}</span>
      </div>
      {suggestions.length > 0 && (
        <div className="em-suggest">
          <span className="em-suggest-k">Suggested</span>
          {suggestions.map((s, i) => <button key={i} className="em-suggest-chip" onClick={() => applySuggestion(s)}>{s.label}</button>)}
        </div>
      )}
      <textarea className="em-reply-body" style={{ height: boxH }} value={body} onChange={(e) => setBody(e.target.value)} spellCheck={false} />
      <AttachChips attach={attach} setAttach={setAttach} files={files} setFiles={setFiles} />
      <div className="em-reply-foot">
        <div className="snd-sendsplit">
          <button className="snd-send" onClick={() => send('sent')}>Send</button>
          <button className="snd-send-caret" title="Schedule send" onClick={() => setSched((s) => !s)}>▾</button>
          {sched && (
            <>
              <div className="combo-back" onClick={() => setSched(false)} />
              <div className="snd-sched-menu" onClick={(e) => e.stopPropagation()}>
                <div className="snd-sched-hd">Schedule send</div>
                {schedOpts.map((o) => <button key={o} className="snd-sched-opt" onClick={() => { send('scheduled', o); setSched(false); }}>{o}</button>)}
              </div>
            </>
          )}
        </div>
        <ScriptCombo onPick={insertScript} contact={contact} />
        <AttachCombo attach={attach} setAttach={setAttach} files={files} setFiles={setFiles} />
        <button className="snd-tool" onClick={insertCalendly} data-tip="Insert your Calendly link">🗓 Calendly</button>
        <label className="snd-logtouch" title="Embed a tracking pixel"><input type="checkbox" checked={track} onChange={(e) => setTrack(e.target.checked)} /><span>Track opens</span></label>
      </div>
    </div>
  );
}

// ── one message bubble in the thread ──
function MsgBubble({ m, contact, now }) {
  const OX = window.OX;
  const out = m.dir === 'out';
  const who = out ? 'You' : (contact ? contact.name : 'Them');
  const tracked = out && m.status === 'sent' && m.openPlan != null;
  const opens = tracked ? m.openPlan.filter((t) => t <= now).length : 0;
  return (
    <div className={'em-msg ' + (out ? 'out' : 'in')}>
      <div className="em-msg-hd">
        <span className="em-msg-who">{who}</span>
        {m.status === 'draft' && <span className="em-msg-tag draft">Draft</span>}
        {m.status === 'scheduled' && <span className="em-msg-tag sched">Scheduled · {m.when}</span>}
        <span className="em-msg-time">{m.status === 'scheduled' ? '' : relTime(m.at)}</span>
      </div>
      <div className="em-msg-body">{m.body}</div>
      {m.attachments && m.attachments.length > 0 && (
        <div className="em-msg-atts">{m.attachments.map((a, i) => <span key={i} className="em-msg-att">📎 {a}</span>)}</div>
      )}
      {out && m.status === 'sent' && (
        tracked
          ? (opens > 0
              ? <div className="em-receipt opened"><span className="dot" />Opened{opens > 1 ? ' · ' + opens + '×' : ''}</div>
              : <div className="em-receipt pending"><span className="dot" />Sent · not opened yet</div>)
          : <div className="em-receipt off">Sent · tracking off</div>
      )}
    </div>
  );
}

// ── reading pane ──
function ReadingPane({ thread, onChanged }) {
  const OX = window.OX, R = window.RAISE;
  const [now, setNow] = useStateEm(Date.now());
  const [snoozeOpen, setSnoozeOpen] = useStateEm(false);
  useEffectEm(() => { const id = setInterval(() => setNow(Date.now()), 4000); return () => clearInterval(id); }, []);
  const bodyRef = useRefEm(null);
  const contact = thread.pid ? R.contactById(thread.pid) : null;
  const lastIn = [...thread.messages].reverse().find((m) => m.dir === 'in');
  const replied = lastIn && thread.messages.indexOf(lastIn) === thread.messages.length - 1;
  const isUnread = window.MAIL.unread(thread);
  useEffectEm(() => { if (bodyRef.current) bodyRef.current.scrollTop = bodyRef.current.scrollHeight; }, [thread.id, thread.messages.length]);

  const advanceStage = () => {
    if (!contact) return;
    const i = STAGE_ORDER.indexOf(contact.stage);
    const next = STAGE_ORDER[Math.min(i + 1, STAGE_ORDER.length - 1)];
    const nextContact = { ...contact, stage: next };
    const step = window.BRAIN ? window.BRAIN.spawnTodo(nextContact) : null;
    RSem.toast(OX.firstName(contact.name) + ' → ' + R.stage(next).label + (step ? ' · next step queued' : ' (logged)'));
  };

  return (
    <div className="em-read">
      <div className="em-read-hd">
        <div className="em-read-subjwrap">
          <div className="em-read-subj">{thread.subject}</div>
          <div className="em-read-meta">
            {contact && <button className="em-read-person" onClick={() => window.RaiseStore.openPerson(contact.id)}>{contact.name} · {contact.firm}</button>}
            {(thread.labels || []).map((l) => <span key={l} className="em-label" style={{ color: labelColor(l), background: labelColor(l) + '1c', borderColor: labelColor(l) + '44' }}>{l}</span>)}
          </div>
        </div>
        <div className="em-read-tools">
          <button className={'em-tool' + (thread.flagged ? ' star' : '')} data-tip={thread.flagged ? 'Unflag' : 'Flag'} onClick={() => { window.MAIL.toggleFlag(thread.id); onChanged && onChanged(); }}>{thread.flagged ? '⚑' : '⚐'}</button>
          <div className="em-snoozewrap">
            <button className="em-tool" data-tip="Snooze" onClick={() => setSnoozeOpen((o) => !o)}>⏰</button>
            {snoozeOpen && (
              <>
                <div className="combo-back" onClick={() => setSnoozeOpen(false)} />
                <div className="em-snooze-menu" onClick={(e) => e.stopPropagation()}>
                  <div className="snd-sched-hd">Snooze until</div>
                  {SNOOZE_OPTS.map((o) => <button key={o} className="snd-sched-opt" onClick={() => { window.MAIL.snooze(thread.id, o); setSnoozeOpen(false); RSem.toast('Snoozed — ' + o); onChanged && onChanged(true); }}>{o}</button>)}
                </div>
              </>
            )}
          </div>
          <button className="em-tool" data-tip="Report spam" onClick={() => { const s = window.MAIL.toggleSpam(thread.id); RSem.toast(s ? 'Reported as spam' : 'Marked not spam'); onChanged && onChanged(true); }}>⊘</button>
          <button className="em-tool" data-tip={isUnread ? 'Mark read' : 'Mark unread'} onClick={() => { window.MAIL.toggleRead(thread.id); onChanged && onChanged(); }}>{isUnread ? '✉' : '●'}</button>
          {!thread.fromOutbox && <button className="em-tool" data-tip="Delete" onClick={() => { window.MAIL.deleteThread(thread.id); onChanged && onChanged(true); }}>🗑</button>}
        </div>
      </div>

      {replied && (
        <div className="em-replied">
          <span className="em-replied-ic">↩</span>
          <span><b>Reply detected</b> — sequence paused</span>
          <div className="em-replied-acts">
            <button onClick={() => RSem.toast('Touch logged on ' + (contact ? contact.name : 'thread'))}>Log</button>
            <button onClick={advanceStage}>Advance</button>
            <button onClick={() => RSem.toast('Sequence resumed')}>Resume</button>
          </div>
        </div>
      )}

      {thread.snoozedUntil && (
        <div className="em-snoozedbar"><span>⏰ Snoozed · {thread.snoozedUntil}</span><button onClick={() => { window.MAIL.unsnooze(thread.id); onChanged && onChanged(); }}>Unsnooze</button></div>
      )}
      <div className="em-read-body scroll" ref={bodyRef}>
        {thread.messages.map((m) => <MsgBubble key={m.id} m={m} contact={contact} now={now} />)}
      </div>

      {!thread.fromOutbox || thread.pid
        ? <ReplyBox thread={thread} contact={contact} onSent={() => onChanged && onChanged()} />
        : null}
    </div>
  );
}

// ── thread row in the list ──
function ThreadRow({ thread, active, onClick }) {
  const R = window.RAISE;
  const c = thread.pid ? R.contactById(thread.pid) : null;
  const last = thread.messages[thread.messages.length - 1];
  const unread = window.MAIL.unread(thread);
  const snippet = (last.body || '').replace(/\s+/g, ' ').replace(/\[draft[^\]]*\]/i, '').trim().slice(0, 90);
  const hasOpen = thread.messages.some((m) => m.dir === 'out' && m.status === 'sent' && m.openPlan && m.openPlan.some((t) => t <= Date.now()));
  return (
    <button className={'em-row' + (active ? ' on' : '') + (unread ? ' unread' : '')} onClick={onClick}>
      {unread && <span className="em-unreaddot" />}
      <span className={'em-av mono' + (c && c.anchor ? ' anchor' : '')}>{RSem.initials(c ? c.name : thread.subject)}</span>
      <div className="em-rowmeta">
        <div className="em-rowtop">
          <span className="em-rowname">{c ? c.name : thread.subject}</span>
          <span className="em-rowtime">{relTime(last.at)}</span>
        </div>
        <div className="em-rowsubj">{thread.subject}{thread.messages.length > 1 && <span className="em-count"> · {thread.messages.length}</span>}</div>
        <div className="em-rowsnip">
          {last.dir === 'out' && last.status === 'draft' && <span className="em-chip draft">Draft</span>}
          {last.dir === 'out' && last.status === 'scheduled' && <span className="em-chip sched">Scheduled</span>}
          {last.dir === 'out' && last.status === 'sent' && <span className="em-rowarrow">↗ </span>}
          {snippet}
        </div>
      </div>
      <div className="em-rowtags">
        {thread.flagged && <span className="em-star" title="Flagged">⚑</span>}
        {thread.snoozedUntil && <span className="em-snoozepip" title={'Snoozed · ' + thread.snoozedUntil}>⏰</span>}
        {hasOpen && <span className="em-openpip" title="Opened">●</span>}
        {(thread.labels || []).slice(0, 1).map((l) => <span key={l} className="em-dotlbl" style={{ background: labelColor(l) }} title={l} />)}
      </div>
    </button>
  );
}

// ── live Gmail row (maps a gmail.list message → the em-row look) ──
// `who` is the address header to show as the counterparty (From for inbox,
// To for sent/drafts).
function LiveThreadRow({ msg, who, active, onClick }) {
  const { name } = parseAddr(who || msg.from);
  const unread = (msg.labelIds || []).includes('UNREAD');
  const at = msg.date ? Date.parse(msg.date) : Date.now();
  const snippet = String(msg.snippet || '').replace(/\s+/g, ' ').trim().slice(0, 90);
  return (
    <button className={'em-row' + (active ? ' on' : '') + (unread ? ' unread' : '')} onClick={onClick}>
      {unread && <span className="em-unreaddot" />}
      <span className="em-av mono">{RSem.initials(name)}</span>
      <div className="em-rowmeta">
        <div className="em-rowtop">
          <span className="em-rowname">{name}</span>
          <span className="em-rowtime">{relTime(at)}</span>
        </div>
        <div className="em-rowsubj">{msg.subject || '(no subject)'}</div>
        <div className="em-rowsnip">{snippet}</div>
      </div>
    </button>
  );
}

// ── live Gmail reader: fetches the full message body on open ──
// `box` = 'inbox' | 'sent' | 'drafts'. Drafts get a "Continue" action that
// re-opens the composer with the draft's content (Gmail draft edit isn't
// exposed by the edge fn, so it continues as a fresh message).
function LiveReader({ msg, box, onReply }) {
  const [full, setFull] = useStateEm(null);
  const [err, setErr] = useStateEm(null);
  useEffectEm(() => {
    let alive = true; setFull(null); setErr(null);
    window.RaiseGoogle.getMessage(msg.id)
      .then((m) => { if (alive) setFull(m); })
      .catch((e) => { if (alive) setErr(e.message || 'Could not load message'); });
    return () => { alive = false; };
  }, [msg.id]);
  const isDraft = box === 'drafts';
  const counterparty = (box === 'inbox') ? msg.from : msg.to;        // who the message is with
  const { name, email } = parseAddr(counterparty);
  const at = msg.date ? Date.parse(msg.date) : Date.now();
  const body = full ? gmailBodyText(full.payload) : '';
  const bodyHtml = full ? gmailBodyHtml(full.payload) : '';
  const act = () => isDraft
    ? onReply({ to: email, subject: msg.subject || '', body: body || '' })
    : onReply({ to: email, subject: /^re:/i.test(msg.subject || '') ? msg.subject : 'Re: ' + (msg.subject || ''),
        body: '\n\n\n— On ' + new Date(at).toLocaleString() + ', ' + name + ' wrote:\n' + (body || msg.snippet || '').split('\n').map((l) => '> ' + l).join('\n') });
  const actLabel = isDraft ? '✎ Continue' : '↩ Reply';
  const metaLabel = (box === 'inbox') ? '' : (isDraft ? 'Draft · To ' : 'To ');
  return (
    <div className="em-read">
      <div className="em-read-hd">
        <div className="em-read-subjwrap">
          <div className="em-read-subj">{msg.subject || '(no subject)'}</div>
          <div className="em-read-meta"><span className="mono" style={{ fontSize: 12, color: 'var(--ink-3)' }}>{metaLabel}{name} · {email}</span></div>
        </div>
        <div className="em-read-tools">
          <button className="em-tool" data-tip={isDraft ? 'Continue draft' : 'Reply'} onClick={act}>{isDraft ? '✎' : '↩'}</button>
        </div>
      </div>
      <div className="em-msg-list scroll" style={{ flex: 1, overflowY: 'auto', padding: '16px' }}>
        {err ? <div className="em-list-empty">{err}</div>
          : !full ? <div className="em-list-empty">Loading…</div>
          : bodyHtml ? <HtmlMail html={bodyHtml} />
          : <pre style={{ whiteSpace: 'pre-wrap', fontFamily: 'inherit', fontSize: 14, lineHeight: 1.55, margin: 0, color: 'var(--ink)' }}>{body || msg.snippet}</pre>}
      </div>
      <div style={{ padding: '10px 16px', borderTop: '1px solid var(--border)' }}>
        <button className="btn sm" style={{ background: 'var(--primary)', color: '#fff', borderColor: 'transparent' }} onClick={act}>{actLabel}</button>
      </div>
    </div>
  );
}

// ── inline compose: takes over the reading pane (full new-message surface) ──
function ComposeInline({ onClose, init }) {
  const OX = window.OX;
  const ES = OX.Store.emailSettings();
  const sig = OX.Store.signature();
  const sigHtml = textToHtml(sig);
  const [acct, setAcct] = useStateEm(ES.fromDefault || 'fund');
  const [to, setTo] = useStateEm((init && init.to) || '');
  const [subject, setSubject] = useStateEm((init && init.subject) || '');
  // body is now HTML (rich text). Seed from a plain-text init (reply/draft) by converting.
  const [html, setHtml] = useStateEm((init && init.body != null) ? textToHtml(init.body) : '<br><br>' + sigHtml);
  const [attach, setAttach] = useStateEm([]);
  const [track, setTrack] = useStateEm(ES.trackOpens);
  const [sched, setSched] = useStateEm(false);
  const accEmail = (OX.ACCOUNTS.find((a) => a.id === acct) || {}).email;
  const [cc, setCc] = useStateEm('');
  const [bcc, setBcc] = useStateEm('');
  const [showCc, setShowCc] = useStateEm(false);
  const [files, setFiles] = useStateEm([]);
  const insertCalendly = () => { setHtml((h) => insertHtmlBeforeSig(h, 'Easiest is to grab a time that works for you here: <a href="' + CALENDLY_URL + '">' + CALENDLY_URL + '</a>', sigHtml)); RSem.toast('Calendly link inserted'); };
  const insertScript = (sv) => { setHtml((h) => insertHtmlBeforeSig(h, textToHtml(window.OX.scriptText(sv, null)), sigHtml)); RSem.toast('Inserted “' + sv.name + '”'); };
  const OPENERS = [
    { label: 'Warm intro', body: `Hi there,\n\nQuick note — [referrer] suggested we connect. We back emerging managers out of a $10M fund and I thought our paths should cross.\n\n${sig}` },
    { label: 'Value-first', body: `Hi there,\n\nNot a pitch — just sharing something I thought you'd find useful given what you're building.\n\n${sig}` },
    { label: 'Propose a call', body: `Hi there,\n\nWould a quick 20 minutes be useful? Grab whatever works here: ${CALENDLY_URL}\n\n${sig}` },
  ];
  const finish = (status, when) => {
    if (!to.trim()) { RSem.toast('Add a recipient email'); return; }
    const text = htmlToText(html);                                   // plain-text fallback + local log
    const htmlBody = '<div style="font-family:' + MAIL_FONT + ';font-size:' + MAIL_SIZE + ';color:' + MAIL_INK + ';line-height:1.5;">' + html + '</div>';
    const attNames = [...attach.map((id) => (OX.ATTACHMENTS.find((a) => a.id === id) || {}).name).filter(Boolean), ...files.map((f) => f.name)];
    window.MAIL.compose({ to: to.trim(), subject: subject.trim() || '(no subject)', body: text, account: accEmail, status, when, track, attachments: attNames });
    if (status === 'sent' && window.RaiseGoogle) {
      window.RaiseGoogle.sendEmail({ to: to.trim(), cc: cc.trim(), bcc: bcc.trim(), subject: subject.trim(), body: text, html: htmlBody, attachments: [
        ...files.map((f) => ({ filename: f.name, mimeType: f.type, data: f.b64 })),
        ...attach.map((id) => OX.ATTACHMENTS.find((a) => a.id === id)).filter((a) => a && a.b64).map((a) => ({ filename: a.name, mimeType: a.type || 'application/octet-stream', data: a.b64 })),
      ] })
        .then(() => RSem.toast('Sent via Gmail to ' + to.trim()))
        .catch((err) => RSem.toast(
          err.message === 'paused' ? 'Safe mode — saved to Sent, not emailed. Turn Live on in Settings.'
          : err.message === 'not_connected' ? 'Saved to Sent — connect Google to send for real'
          : 'Gmail send failed: ' + err.message));
    } else if (status === 'draft') RSem.toast('Draft saved');
    else if (status === 'scheduled') RSem.toast('Scheduled — ' + when);
    onClose();
  };
  const schedOpts = ['In 1 hour', 'This evening · 6:00 PM', 'Tomorrow · 8:00 AM'];
  const kSt = { fontSize: 12, color: 'var(--ink-3)', width: 56, flex: '0 0 auto' };
  const rowSt = { display: 'flex', alignItems: 'center', gap: 8, padding: '9px 16px', borderBottom: '1px solid var(--line,#f0ebe3)' };
  const inpSt = { flex: 1, border: 'none', outline: 'none', fontSize: 14, background: 'transparent', color: 'inherit' };
  return (
    <div className="em-read">
      <div className="em-read-hd">
        <div className="em-read-subjwrap">
          <div className="em-read-subj">New message</div>
          <div className="em-read-meta"><span className="mono" style={{ fontSize: 12, color: 'var(--ink-3)' }}>Compose · sends as {accEmail}</span></div>
        </div>
        <div className="em-read-tools">
          <button className="em-tool" data-tip="Discard" onClick={onClose}>✕</button>
        </div>
      </div>

      <div style={rowSt}><span style={kSt}>From</span><MailFrom value={acct} onChange={setAcct} /></div>
      <div style={rowSt}><span style={kSt}>To</span><input value={to} onChange={(e) => setTo(e.target.value)} placeholder="name@firm.com" autoFocus style={inpSt} />
        {!showCc && <button className="snd-tool" style={{ padding: '4px 10px' }} onClick={() => setShowCc(true)}>Cc / Bcc</button>}</div>
      {showCc && <div style={rowSt}><span style={kSt}>Cc</span><input value={cc} onChange={(e) => setCc(e.target.value)} placeholder="cc@firm.com, …" style={inpSt} /></div>}
      {showCc && <div style={rowSt}><span style={kSt}>Bcc</span><input value={bcc} onChange={(e) => setBcc(e.target.value)} placeholder="bcc@firm.com, …" style={inpSt} /></div>}
      <div style={rowSt}><span style={kSt}>Subject</span><input value={subject} onChange={(e) => setSubject(e.target.value)} placeholder="Subject" style={inpSt} /></div>

      <div className="em-suggest">
        <span className="em-suggest-k">Openers</span>
        {OPENERS.map((s, i) => <button key={i} className="em-suggest-chip" onClick={() => setHtml(textToHtml(s.body))}>{s.label}</button>)}
      </div>

      <RichEditor html={html} onChange={setHtml} minHeight={120} />

      <AttachChips attach={attach} setAttach={setAttach} files={files} setFiles={setFiles} style={{ padding: '6px 16px 0' }} />

      <div className="em-reply-foot" style={{ padding: '10px 16px 14px' }}>
        <div className="snd-sendsplit">
          <button className="snd-send" onClick={() => finish('sent')}>Send</button>
          <button className="snd-send-caret" title="Schedule send" onClick={() => setSched((s) => !s)}>▾</button>
          {sched && (<><div className="combo-back" onClick={() => setSched(false)} />
            <div className="snd-sched-menu" onClick={(e) => e.stopPropagation()}>
              <div className="snd-sched-hd">Schedule send</div>
              {schedOpts.map((o) => <button key={o} className="snd-sched-opt" onClick={() => finish('scheduled', o)}>{o}</button>)}
            </div></>)}
        </div>
        <button className="snd-tool" onClick={() => finish('draft')}>Save draft</button>
        <ScriptCombo onPick={insertScript} />
        <AttachCombo attach={attach} setAttach={setAttach} files={files} setFiles={setFiles} />
        <button className="snd-tool" onClick={insertCalendly} data-tip="Insert your Calendly link">🗓 Calendly</button>
        <label className="snd-logtouch" title="Embed a tracking pixel"><input type="checkbox" checked={track} onChange={(e) => setTrack(e.target.checked)} /><span>Track opens</span></label>
      </div>
    </div>
  );
}

function TabEmail() {
  const { TopBar } = RSem;
  const [compose, setCompose] = useStateEm(false);
  const [, bump] = useStateEm(0);
  useEffectEm(() => {
    const u1 = window.MAIL.subscribe(() => bump((x) => x + 1));
    const u2 = window.OX && window.OX.Store ? window.OX.Store.subscribe(() => bump((x) => x + 1)) : null;
    return () => { u1(); u2 && u2(); };
  }, []);
  const [folder, setFolder] = useStateEm('inbox');
  const [labelF, setLabelF] = useStateEm(null);
  const [q, setQ] = useStateEm('');
  const [foldersW, setFoldersW] = useStateEm(() => { const v = +localStorage.getItem('raiseos.mail.fw'); return v >= 150 ? v : 198; });
  const [listW, setListW] = useStateEm(() => { const v = +localStorage.getItem('raiseos.mail.lw'); return v >= 260 ? v : 340; });
  const counts = window.MAIL.counts();
  const labels = window.MAIL.allLabels();

  // ── live Gmail inbox (real data when connected & not paused) ──
  const RL = window.RaiseLive;
  const [conns] = RL.useConnections();
  const [paused] = RL.useLivePaused();
  const liveOn = !!(conns.gmail && conns.gmail.status === 'connected') && !paused;
  // folders we can serve from live Gmail (edge fn supports list/get/send)
  const LIVE_Q = { inbox: 'in:inbox', sent: 'in:sent', drafts: 'in:drafts' };
  const liveFolderQ = (!labelF && LIVE_Q[folder]) ? LIVE_Q[folder] : null;
  const liveActive = liveOn && !!liveFolderQ;
  const liveBadge = RL.useLiveInbox('in:inbox', { enabled: liveOn });               // inbox unread count (always)
  const liveList = RL.useLiveInbox(liveFolderQ || 'in:inbox', { enabled: liveActive }); // the active live folder
  const [selLiveId, setSelLiveId] = useStateEm(null);
  const [composeInit, setComposeInit] = useStateEm(null);
  let liveMsgs = liveList.messages || [];
  if (liveActive && q.trim()) {
    const ql = q.toLowerCase();
    liveMsgs = liveMsgs.filter((m) => ((m.subject || '') + ' ' + (m.from || '') + ' ' + (m.to || '') + ' ' + (m.snippet || '')).toLowerCase().includes(ql));
  }
  const liveUnread = (liveBadge.messages || []).filter((m) => (m.labelIds || []).includes('UNREAD')).length;
  const liveWho = (m) => (folder === 'inbox') ? m.from : m.to;  // counterparty to display
  const openLive = (m) => { setCompose(false); setComposeInit(null); setSelLiveId(m.id); };

  let list = labelF ? window.MAIL.threads().filter((t) => (t.labels || []).includes(labelF)) : window.MAIL.folderThreads(folder);
  if (labelF) list = [...list].sort((a, b) => window.MAIL.lastMsg(b).at - window.MAIL.lastMsg(a).at);
  if (q.trim()) {
    const ql = q.toLowerCase();
    list = list.filter((t) => {
      const c = t.pid ? window.RAISE.contactById(t.pid) : null;
      return (t.subject + ' ' + (c ? c.name + ' ' + c.firm : '') + ' ' + t.messages.map((m) => m.body).join(' ')).toLowerCase().includes(ql);
    });
  }

  const [selId, setSelId] = useStateEm(() => list[0] && list[0].id);
  const selected = window.MAIL.thread(selId) || list[0];
  const liveSel = liveActive ? (liveMsgs.find((m) => m.id === selLiveId) || liveMsgs[0]) : null;
  useEffectEm(() => { if (!list.some((t) => t.id === selId)) setSelId(list[0] && list[0].id); }, [folder, labelF, q, list.length]);

  const open = (t) => { setCompose(false); setSelId(t.id); window.MAIL.markRead(t.id); };
  const pickFolder = (f) => { setFolder(f); setLabelF(null); };
  const curFolderLabel = labelF ? labelF : (FOLDERS.find((f) => f.id === folder) || {}).label;

  return (
    <>
      <TopBar title="Email" sub="inbox · sent · drafts · scheduled">
        <div className="em-search">
          <svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="1.5"><circle cx="7" cy="7" r="4.5" /><path d="M10.5 10.5L14 14" strokeLinecap="round" /></svg>
          <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search mail" />
          {q && <button onClick={() => setQ('')}>✕</button>}
        </div>
        <button className="btn sm" onClick={() => RSem.toast('Gmail synced · 7:02 AM')}>Synced ↻</button>
      </TopBar>

      <div className="email">
        {/* FOLDERS */}
        <div className="em-folders" style={{ width: foldersW }}>
          <button className="em-compose" onClick={() => { setComposeInit(null); setCompose(true); }}>✎ New message</button>
          {FOLDERS.map((f) => {
            const ct = liveOn
              ? (f.id === 'inbox' ? liveUnread : (LIVE_Q[f.id] ? null : counts[f.id]))
              : counts[f.id];
            return (
            <button key={f.id} title={f.label} className={'em-folder' + (folder === f.id && !labelF ? ' on' : '')} onClick={() => pickFolder(f.id)}>
              <EmIcon d={f.d} /><span className="em-folder-lbl">{f.label}</span>
              {ct > 0 && <span className={'em-folder-ct' + (f.id === 'inbox' ? ' accent' : '')}>{ct}</span>}
            </button>
            );
          })}
          {labels.length > 0 && <div className="em-lblhd">Labels</div>}
          {labels.map((l) => (
            <button key={l} title={'Label · ' + l} className={'em-folder lbl' + (labelF === l ? ' on' : '')} onClick={() => { setLabelF(l); }}>
              <span className="em-lbldot" style={{ background: labelColor(l) }} /><span className="em-folder-lbl">{l}</span>
            </button>
          ))}
        </div>

        <ColResizer value={foldersW} set={setFoldersW} min={150} max={320} lsKey="raiseos.mail.fw" />

        {/* THREAD LIST */}
        <div className="em-list" style={{ width: listW }}>
          <div className="em-list-hd"><span className="em-list-title">{curFolderLabel}{liveActive && <span className="mono" style={{ fontSize: 10, color: 'var(--green)', marginLeft: 6 }}>● LIVE</span>}</span><span className="em-list-ct">{liveActive ? liveMsgs.length : list.length}</span></div>
          <div className="em-list-scroll scroll">
            {liveActive
              ? (liveMsgs.length
                  ? liveMsgs.map((m) => <LiveThreadRow key={m.id} msg={m} who={liveWho(m)} active={liveSel && liveSel.id === m.id} onClick={() => openLive(m)} />)
                  : liveList.loading
                    ? <div className="em-list-empty">Loading {curFolderLabel.toLowerCase()}…</div>
                    : liveList.error
                      ? <div className="em-list-empty">Couldn’t load Gmail — {liveList.error}. Reconnect in Settings → Google.</div>
                      : <div className="em-list-empty">{curFolderLabel} empty{q ? ' for “' + q + '”' : ''}.</div>)
              : (list.length === 0
                  ? <div className="em-list-empty">No messages{q ? ' match “' + q + '”' : ' here'}.</div>
                  : list.map((t) => <ThreadRow key={t.id} thread={t} active={selected && selected.id === t.id} onClick={() => open(t)} />))}
          </div>
        </div>

        <ColResizer value={listW} set={setListW} min={260} max={520} lsKey="raiseos.mail.lw" />

        {/* READING PANE */}
        {compose
          ? <ComposeInline init={composeInit} onClose={() => { setCompose(false); setComposeInit(null); }} />
          : liveActive
            ? (liveSel
                ? <LiveReader key={liveSel.id} msg={liveSel} box={folder} onReply={(init) => { setComposeInit(init); setCompose(true); }} />
                : <div className="em-read em-read-empty"><div className="cons-empty-ic">✉</div><div className="cons-empty-t">No message selected</div><div className="cons-empty-s">Pick a message on the left to read and reply.</div></div>)
            : selected
              ? <ReadingPane key={selected.id} thread={selected} onChanged={(deleted) => { if (deleted) setSelId(null); bump((x) => x + 1); }} />
              : <div className="em-read em-read-empty"><div className="cons-empty-ic">✉</div><div className="cons-empty-t">No message selected</div><div className="cons-empty-s">Pick a thread on the left to read and reply.</div></div>}
      </div>
    </>
  );
}

window.TabEmail = TabEmail;
