// ============ Views ============

// ---- Task detail modal ----

// Build Outlook deep link - resolves via server to handle moved emails
// mailbox param: 'shared:tenders' for tenders@ shared mailbox, omit for personal
function outlookLink(webLink, graphId, messageId, mailbox) {
  if (messageId) {
    const params = new URLSearchParams({ messageId, fallback: webLink||'' });
    if (mailbox) params.set('mailbox', mailbox);
    return `/api/outlook-link?${params}`;
  }
  // Direct webLink from Graph (already has correct mailbox context)
  if (webLink) return webLink;
  if (graphId) return 'https://outlook.office365.com/owa/?ItemID=' + encodeURIComponent(graphId) + '&exvsurl=1&viewmodel=ReadMessageItem';
  return null;
}

function openInOutlook(webLink, graphId, messageId, mailbox) {
  // If we have a direct webLink from Graph, use it - it already targets the right mailbox
  const link = webLink || outlookLink(webLink, graphId, messageId, mailbox);
  if (link) window.open(link, '_blank');
}



// Strip markdown formatting from AI-generated text
function stripMd(text) {
  if (!text) return text;
  return text.replace(/\*\*([^*]+)\*\*/g, '$1').replace(/\*([^*]+)\*/g, '$1').replace(/^#+\s*/gm, '').trim();
}


function ReplyCard({ reply, idx, copied, setCopied, sending, sent, onCopy, onSend, canSend }) {
  const [editing, setEditing] = useState(false);
  const [editText, setEditText] = useState(reply.text);

  const handleEdit = () => {
    setEditText(reply.text);
    setEditing(true);
  };

  const sendText = editing ? editText : reply.text;

  return (
    <div className="reply-card">
      <div className="rl-head">
        <span className="rl-label">Option {idx+1} · {reply.label}</span>
        <div style={{display:'flex', gap:6, flexWrap:'wrap'}}>
          <button className="btn tiny ghost" onClick={() => onCopy(sendText, 'r'+idx)}>
            <I.copy/> {copied === 'r'+idx ? 'Copied' : 'Copy'}
          </button>
          <button className="btn tiny ghost" onClick={handleEdit} title="Edit before sending" style={{color:'#6366f1'}}>
            ✏ Edit
          </button>
          {canSend && (
            <button className="btn tiny primary" onClick={() => onSend({...reply, text: sendText}, idx)} disabled={sending === idx}>
              <I.send/> {sending === idx ? 'Sending...' : sent === idx ? 'Sent ✓' : 'Send reply'}
            </button>
          )}
        </div>
      </div>
      {editing ? (
        <div>
          <textarea value={editText} onChange={e => setEditText(e.target.value)}
            style={{width:'100%', minHeight:160, padding:'10px 12px', border:'1px solid #e2e8f0', borderRadius:6,
              fontSize:13, lineHeight:1.6, fontFamily:'inherit', resize:'vertical', boxSizing:'border-box'}}
          />
          <div style={{display:'flex', gap:6, marginTop:6}}>
            <button className="btn tiny ghost" onClick={() => setEditing(false)}>Done editing</button>
            <button className="btn tiny ghost" onClick={() => setEditText(reply.text)} style={{color:'#9ca3af'}}>Reset</button>
          </div>
        </div>
      ) : (
        <pre style={{cursor:'pointer'}} onClick={handleEdit} title="Click to edit">{reply.text}</pre>
      )}
    </div>
  );
}


function TaskModal({ task, thread, onClose, onCheck, onSend }) {
  const [copied, setCopied] = useState(null);
  const [sending, setSending] = useState(null);
  const [sent, setSent]     = useState(null);
  const [body, setBody]     = useState(null);
  const [loadingBody, setLoadingBody] = useState(false);

  // On-demand replies (Task 4)
  const [showReplies, setShowReplies]     = useState(false);
  const [loadingReplies, setLoadingReplies] = useState(false);
  const [replies, setReplies]             = useState(task.replies?.length > 0 ? task.replies : null);

  // Fetch full email body on open
  useEffect(() => {
    if (!task.graphId) return;
    setLoadingBody(true);
    api.get('/api/email/body?graphId=' + encodeURIComponent(task.graphId))
      .then(d => setBody(d.body))
      .catch(() => setBody(null))
      .finally(() => setLoadingBody(false));
  }, [task.graphId]);

  const handleGenerateReplies = async () => {
    if (replies?.length > 0) { setShowReplies(true); return; }
    setLoadingReplies(true);
    try {
      const { replies: fetched } = await api.post('/api/ai/replies', {
        graphId: task.graphId,
        subject: task.subject,
        from: task.from,
        snippet: task.snippet,
      });
      setReplies(fetched);
      setShowReplies(true);
    } catch (err) {
      alert('Could not generate replies: ' + err.message);
    } finally {
      setLoadingReplies(false);
    }
  };

  const copy = (text, label) => {
    navigator.clipboard?.writeText(text);
    setCopied(label);
    setTimeout(() => setCopied(null), 1500);
  };

  const handleSend = async (reply, idx) => {
    if (!task.graphId && !task.internetMessageId && !task.messageId) { copy(reply.text, 'r'+idx); return; }
    setSending(idx);
    try {
      await api.post('/api/email/reply', { graphId: task.graphId, internetMessageId: task.internetMessageId || task.messageId, replyText: reply.text });
    } catch (err) {
      // Graph /reply returns 204 No Content which may cause parse errors - treat as success if no network error
      if (!err.message?.includes('HTTP') && !err.message?.includes('network') && !err.message?.includes('failed')) {
        // Likely a successful send with empty response - continue
      } else {
        setSending(null);
        alert('Send failed: ' + err.message);
        return;
      }
    }
    setSent(idx);
    setTimeout(() => setSent(null), 3000);
    onSend?.();
    setSending(null);
  };

  return (
    <Modal onClose={onClose}>
      <div className="modal-head">
        <div style={{display:'flex', alignItems:'center', gap:8, marginBottom:8}}>
          <Badge kind={thread.tag}>{thread.tag.toUpperCase()}</Badge>
          <Badge>{stripMd(thread.title)}</Badge>
          {task.hasAttachments && <Badge><I.clip/> Attachment</Badge>}
        </div>
        <h2>{task.subject}</h2>
        <div className="from-line">From {task.from} &lt;{task.fromEmail}&gt; · {formatDate(task.date)}</div>
      </div>

      <div className="modal-body">
        <div className="modal-section">
          <div className="label">Email body</div>
          {loadingBody
            ? <div style={{color:'#888',padding:'8px 0',fontSize:13}}>Loading...</div>
            : body
              ? <div style={{fontSize:13,lineHeight:1.6,maxHeight:320,overflowY:'auto',background:'#f9f8f6',border:'1px solid #e8e7e3',borderRadius:6,padding:'10px 14px',whiteSpace:'pre-wrap'}} dangerouslySetInnerHTML={{__html: body}} />
              : <div className="snippet">{task.snippet || '(no preview available)'}</div>
          }
        </div>

        {(task.tags?.[0] || task.confidence != null || task.classificationReason) && (
          <div className="modal-section">
            <div className="label">Classification</div>
            <div style={{display:'flex', flexWrap:'wrap', alignItems:'center', gap:8, marginBottom: task.classificationReason ? 8 : 0}}>
              {task.tags?.[0] && (
                <Badge kind={task.tags[0].toLowerCase().replace(/[^a-z]/g,'')}>{task.tags[0].toUpperCase()}</Badge>
              )}
              {task.confidence != null && (
                <span style={{fontSize:12, color:'var(--ink-2)', fontFamily:'var(--mono)'}}>
                  {Math.round((task.confidence <= 1 ? task.confidence * 100 : task.confidence))}% confidence
                </span>
              )}
              {task.confidence != null && (task.confidence <= 1 ? task.confidence : task.confidence / 100) < 0.75 && (
                <span style={{fontSize:11, fontWeight:700, padding:'2px 8px', borderRadius:4, background:'#fef3c7', color:'#92400e', border:'1px solid #fde68a'}}>
                  ⚠ Low confidence - review manually
                </span>
              )}
            </div>
            {task.classificationReason && (
              <div className="snippet" style={{fontSize:12.5, color:'var(--ink-2)', marginTop:0}}>{task.classificationReason}</div>
            )}
          </div>
        )}

        <div className="modal-section">
          <div className="label">Suggested action <span style={{color:'var(--violet)', textTransform:'none', letterSpacing:0}}><I.ai/> AI generated</span></div>
          <div className="snippet" style={{background:'var(--violet-bg)', borderColor:'transparent'}}>{task.action}</div>
        </div>

        <div className="modal-section">
          <div className="label">On done → move to folder</div>
          <div className="folder-pill"><I.folder className="ic"/> {task.folder || thread.folder || '- stays in inbox -'}</div>
        </div>

        <div className="modal-section">
          {!showReplies ? (
            <button onClick={handleGenerateReplies} disabled={loadingReplies}
              style={{display:'inline-flex',alignItems:'center',justifyContent:'center',gap:8,width:'100%',padding:'12px 20px',fontSize:14,fontWeight:700,borderRadius:8,background:loadingReplies?'#94a3b8':'#1a1a18',color:'#fff',border:'none',cursor:loadingReplies?'default':'pointer',transition:'background .15s'}}>
              <I.sparkle/> {loadingReplies ? 'Generating reply options...' : '✨ Generate AI Reply Options'}
            </button>
          ) : (
            <>
              <div className="label"><I.sparkle/>&nbsp; AI reply suggestions</div>
              {(replies || []).map((r, i) => (
                <ReplyCard key={i} reply={r} idx={i}
                  copied={copied} setCopied={setCopied}
                  sending={sending} sent={sent}
                  onCopy={copy}
                  onSend={handleSend}
                  canSend={!!(task.graphId || task.internetMessageId || task.messageId)}
                />
              ))}
            </>
          )}
        </div>
      </div>

      <div className="modal-foot">
        <button className="btn ghost" onClick={onClose}>Close</button>
        <div style={{display:'flex', gap:8}}>
          {(task.webLink || task.graphId) && (
            <a className="btn" href={outlookLink(task.webLink, task.graphId, task.messageId)} onClick={e=>{e.preventDefault();openInOutlook(task.webLink, task.graphId, task.messageId);}} target="_blank" rel="noreferrer"
              style={{background:'#1e40af', color:'#fff', border:'none', display:'inline-flex', alignItems:'center', gap:6}}>
              📧 Open in Outlook
            </a>
          )}
          <button className="btn primary" onClick={() => { onCheck(); onClose(); }}>
            <I.check/> Mark done
          </button>
        </div>
      </div>
    </Modal>
  );
}

// ---- Follow-up task modal ----
function AddFollowUpModal({ threadId, threadTitle, onClose, onAdd }) {
  const [subject, setSubject] = useState('Follow-up required');
  const [note, setNote]       = useState('');
  const [busy, setBusy]       = useState(false);

  const submit = async () => {
    setBusy(true);
    try {
      await api.post('/api/task/followup', { threadId, subject, note });
      onAdd();
      onClose();
    } catch (err) {
      alert(err.message);
      setBusy(false);
    }
  };

  return (
    <Modal onClose={onClose}>
      <div className="modal-head">
        <h2>Add follow-up task</h2>
        <div className="from-line">Thread: {threadTitle}</div>
      </div>
      <div className="modal-body">
        <div className="modal-section">
          <div className="label">Task label</div>
          <input type="text" value={subject} onChange={e => setSubject(e.target.value)} />
        </div>
        <div className="modal-section">
          <div className="label">Notes</div>
          <textarea rows="3" value={note} onChange={e => setNote(e.target.value)} style={{width:'100%', resize:'vertical'}} />
        </div>
      </div>
      <div className="modal-foot">
        <button className="btn ghost" onClick={onClose}>Cancel</button>
        <button className="btn primary" onClick={submit} disabled={busy || !subject.trim()}>
          {busy ? 'Adding...' : 'Add task'}
        </button>
      </div>
    </Modal>
  );
}

// ---- TaskRow ----
function TaskRow({ task, thread, onCheck, onOpen, onRefresh }) {
  const [editing, setEditing] = useState(false);
  const [editSubject, setEditSubject] = useState(task.subject || '');
  const [editAction, setEditAction] = useState(task.action || '');
  const [saving, setSaving] = useState(false);
  const [confirmDelete, setConfirmDelete] = useState(false);
  const [previewExpanded, setPreviewExpanded] = useState(false);

  // ---- Manual task card (distinct amber style) ----
  if (task.isManual) {
    const isStandalone = !!task.isStandalone;
    const updateRoute = isStandalone ? '/api/task/standalone/update' : '/api/task/manual/update';
    const deleteRoute = isStandalone ? '/api/task/standalone/delete' : '/api/task/manual/delete';
    const updatePayload = isStandalone
      ? { id: task.id, subject: editSubject, action: editAction }
      : { threadId: thread.id, taskId: task.id, subject: editSubject, action: editAction };
    const deletePayload = isStandalone
      ? { id: task.id }
      : { threadId: thread.id, taskId: task.id };

    const handleSaveEdit = async () => {
      setSaving(true);
      try {
        await api.post(updateRoute, updatePayload);
        onRefresh?.();
        setEditing(false);
      } catch (err) {
        alert('Save failed: ' + err.message);
      } finally {
        setSaving(false);
      }
    };

    const handleDelete = (e) => {
      e.stopPropagation();
      setConfirmDelete(true);
    };

    return (
      <>
        <div className={`task${task.doneAt ? ' done' : ''}`}
          style={{ borderLeft: '4px solid #f59e0b', background: '#fffbeb', gridTemplateColumns: '28px 1fr' }}>
          <div style={{paddingTop:2}}>
            <Check checked={!!task.doneAt} onClick={onCheck} />
          </div>
          <div className="task-body" style={{ flex: 1 }}>
            {editing ? (
              <div>
                <input type="text" value={editSubject}
                  onChange={e => setEditSubject(e.target.value)}
                  style={{ width: '100%', marginBottom: 6 }}
                  placeholder="Task label" autoFocus />
                <input type="text" value={editAction}
                  onChange={e => setEditAction(e.target.value)}
                  style={{ width: '100%', marginBottom: 8 }}
                  placeholder="Notes / action" />
                <div style={{ display: 'flex', gap: 6 }}>
                  <button className="btn tiny primary" onClick={handleSaveEdit}
                    disabled={saving || !editSubject.trim()}>
                    {saving ? 'Saving...' : 'Save'}
                  </button>
                  <button className="btn tiny ghost" onClick={() => {
                    setEditing(false);
                    setEditSubject(task.subject || '');
                    setEditAction(task.action || '');
                  }}>Cancel</button>
                </div>
              </div>
            ) : (
              <>
                <div style={{ display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
                  <span className="task-title">{task.subject}</span>
                  <span style={{
                    display: 'inline-block', background: '#f59e0b', color: '#fff',
                    fontSize: 10, fontWeight: 700, padding: '1px 6px', borderRadius: 4,
                    letterSpacing: '0.06em', textTransform: 'uppercase',
                  }}>User Task</span>
                </div>
                {task.action && <div className="task-action">{task.action}</div>}
                <div className="task-row">
                  <span style={{ fontFamily: 'var(--mono)', fontSize: 11 }}>{formatDate(task.date || task.createdAt)}</span>
                  {task.rolledOver && <Badge kind="rolled">↻ rolled over</Badge>}
                </div>
              </>
            )}
          </div>
          {!editing && (
            <div style={{ display: 'flex', gap: 4, flexShrink: 0, paddingTop: 2 }}>
              <button className="btn tiny ghost" title="Edit"
                onClick={e => { e.stopPropagation(); setEditing(true); }}>✏️</button>
              <button className="btn tiny ghost" title="Delete"
                onClick={handleDelete}>🗑️</button>
            </div>
          )}
        </div>
        {confirmDelete && (
          <ConfirmDialog
            title="Are you sure?"
            body="This will remove this task from your list."
            confirmLabel="Yes, remove"
            danger
            onCancel={() => setConfirmDelete(false)}
            onConfirm={async () => {
              setConfirmDelete(false);
              try {
                await api.post(deleteRoute, deletePayload);
                onRefresh?.();
              } catch (err) {
                alert('Delete failed: ' + err.message);
              }
            }}
          />
        )}
      </>
    );
  }

  // ---- Standard email task card ----
  return (
    <div className={`task ${task.doneAt ? 'done' : ''}`} onClick={() => onOpen(task, thread)}>
      <div>
        <Check checked={!!task.doneAt} onClick={onCheck} />
      </div>
      <div className="task-body">
        <div className="task-title">{task.subject}</div>
        {task.action && <div className="task-action">{task.action}</div>}
        <div className="task-row">
          <span>{task.from}</span>
          <span className="sep">·</span>
          <span style={{fontFamily:'var(--mono)', fontSize:11}}>{formatDate(task.date)}</span>
          {task.hasAttachments && <><span className="sep">·</span><span style={{display:'inline-flex', alignItems:'center', gap:3}}><I.clip/> attachment</span></>}
          {task.tags?.map(t => <Badge key={t} kind={t.toLowerCase().replace(/[^a-z]/g,'')}>{t}</Badge>)}
          {task.isNew && <Badge kind="new">NEW</Badge>}
          {task.rolledOver && <Badge kind="rolled">↻ rolled over</Badge>}
          {(task.webLink || task.graphId) && <><span className="sep">·</span><a href={outlookLink(task.webLink, task.graphId, task.messageId)} onClick={e=>{e.preventDefault();openInOutlook(task.webLink, task.graphId, task.messageId);}} target="_blank" rel="noreferrer" style={{color:'#2563eb',fontWeight:600}}>Open in Outlook ↗</a></>}
        </div>
      </div>
    </div>
  );
}

// ---- Thread ----
function Thread({ thread, onCheck, onOpenTask, onToggle, onRefresh, compact, isExpanded }) {
  const [showAddFU, setShowAddFU] = useState(false);

  // Filter out soft-deleted manual tasks for display
  const visibleTasks = thread.tasks.filter(t => !t.deletedAt);
  const openCount = visibleTasks.filter(t => !t.doneAt).length;

  // Compact row - richer single-line with check-all for thread
  if (compact && !isExpanded) {
    const firstTask = visibleTasks.find(t => !t.doneAt);
    const allDone = visibleTasks.length > 0 && visibleTasks.every(t => t.doneAt);
    return (
      <div className={`thread ${thread.tag}`} data-open="false" style={{marginBottom:4}}>
        <div className="thread-head" style={{gap:8,padding:'8px 12px'}}>
          {/* Per-thread check button */}
          <button
            title={allDone ? 'All done' : 'Mark all tasks in this thread done'}
            onClick={e => { e.stopPropagation(); visibleTasks.filter(t=>!t.doneAt).forEach(t => onCheck(thread.id, t.id)); }}
            style={{width:20,height:20,borderRadius:'50%',border:`2px solid ${allDone?'#16a34a':'#cbd5e1'}`,background:allDone?'#16a34a':'transparent',cursor:'pointer',flexShrink:0,display:'flex',alignItems:'center',justifyContent:'center',color:'#fff',fontSize:11}}
          >{allDone ? '✓' : ''}</button>
          <Badge kind={thread.tag}>{thread.tag}</Badge>
          <div className="thread-title" style={{flex:1,cursor:'pointer'}} onClick={() => onToggle(thread.id)}>{stripMd(thread.title)}</div>
          {firstTask && <span style={{color:'var(--ink-3)',fontSize:12,maxWidth:220,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{firstTask.action}</span>}
          <span style={{color:'var(--ink-4)',fontFamily:'var(--mono)',fontSize:11,flexShrink:0}}>{openCount} open</span>
          <button className="btn tiny ghost" onClick={() => onToggle(thread.id)} style={{flexShrink:0}}>▾ Expand</button>
        </div>
      </div>
    );
  }

  return (
    <div className={`thread ${thread.tag}`} data-open={isExpanded}>
      <div className="thread-head" onClick={() => onToggle(thread.id)}>
        <I.chev className="thread-chev"/>
        <div className="thread-title">{stripMd(thread.title)}</div>
        <Badge kind={thread.tag}>{thread.tag}</Badge>
        <span className="thread-meta">{visibleTasks.length} {visibleTasks.length === 1 ? 'task' : 'tasks'}</span>
      </div>
      {isExpanded && (
        <div className="thread-body">
          {visibleTasks.map(t => (
            <TaskRow key={t.id} task={t} thread={thread}
              onCheck={() => onCheck(thread.id, t.id)}
              onOpen={onOpenTask}
              onRefresh={onRefresh} />
          ))}
          <div className="thread-foot">
            <button className="btn ghost sm" onClick={e => { e.stopPropagation(); setShowAddFU(true); }}>
              <I.plus/> Add follow-up task
            </button>
            <button className="btn ghost sm" onClick={() => onToggle(thread.id)}>Collapse</button>
          </div>
        </div>
      )}
      {showAddFU && (
        <AddFollowUpModal
          threadId={thread.id}
          threadTitle={stripMd(thread.title)}
          onClose={() => setShowAddFU(false)}
          onAdd={onRefresh}
        />
      )}
    </div>
  );
}

// ---- To-do list ----
function TodoView({ threads, onCheck, onOpenTask, banner, onBanner, onRefresh,
                    dismissedFollowUps, standaloneManualTasks, onCheckStandalone }) {
  const [search, setSearch] = useState('');
  const [compact, setCompact] = useState(false);
  const [confirmPending, setConfirmPending] = useState(null); // { kind, ...payload }

  // Inline add-task form state
  const [showAddTask, setShowAddTask] = useState(false);
  const [newTaskSubject, setNewTaskSubject] = useState('');
  const [newTaskNotes, setNewTaskNotes] = useState('');
  const [addingTask, setAddingTask] = useState(false);

  // Local expand state (not persisted) - urgent/dispute expanded by default; others collapsed if >5 threads
  const [expandedIds, setExpandedIds] = useState(() => {
    const ids = new Set();
    if (threads.length <= 5) {
      threads.forEach(t => ids.add(t.id));
    } else {
      threads.filter(t => ['urgent', 'dispute'].includes(t.tag)).forEach(t => ids.add(t.id));
    }
    return ids;
  });

  // When new threads arrive after a scan, auto-expand urgent/dispute ones
  const prevThreadIds = useRef(new Set(threads.map(t => t.id)));
  useEffect(() => {
    const newThreads = threads.filter(t => !prevThreadIds.current.has(t.id));
    if (newThreads.length > 0) {
      setExpandedIds(prev => {
        const next = new Set(prev);
        newThreads.forEach(t => {
          if (['urgent', 'dispute'].includes(t.tag)) next.add(t.id);
        });
        return next;
      });
    }
    prevThreadIds.current = new Set(threads.map(t => t.id));
  }, [threads]);

  const handleToggle = (threadId) => {
    setExpandedIds(prev => {
      const next = new Set(prev);
      if (next.has(threadId)) next.delete(threadId); else next.add(threadId);
      return next;
    });
  };

  const handleCollapseAll = () => setExpandedIds(new Set());
  const handleExpandAll  = () => setExpandedIds(new Set(threads.map(t => t.id)));

  const order = ['dispute','urgent','reply','action','tender','fyi'];
  const sorted = [...threads].sort((a,b) => order.indexOf(a.tag) - order.indexOf(b.tag));

  // Always filter out done + deleted tasks from main list
  const filtered = sorted.map(t => ({
    ...t,
    tasks: t.tasks.filter(x => {
      if (x.deletedAt) return false;
      if (x.doneAt) return false;
      if (search.trim()) {
        return x.subject?.toLowerCase().includes(search.toLowerCase()) ||
               x.from?.toLowerCase().includes(search.toLowerCase()) ||
               x.action?.toLowerCase().includes(search.toLowerCase());
      }
      return true;
    }),
  })).filter(t => t.tasks.length > 0);

  // Standalone tasks visible in MY TASKS (not deleted, not done)
  const visibleStandalone = (standaloneManualTasks || []).filter(t => !t.deletedAt && !t.doneAt);

  const sections = [
    { key:'urgent', label:'Urgent · disputes, claims & unread', tags:['dispute','urgent'] },
    { key:'reply',  label:'Action / Reply needed',              tags:['reply','action'] },
    { key:'tender', label:'Tenders',                            tags:['tender'] },
    { key:'fyi',    label:'FYI',                                tags:['fyi'] },
  ];

  const urgentCount = threads.reduce((n,t) =>
    n + (['urgent','dispute'].includes(t.tag) ? t.tasks.filter(x=>!x.doneAt && !x.deletedAt).length : 0), 0);
  const openCount = threads.reduce((n,t) => n + t.tasks.filter(x=>!x.doneAt && !x.deletedAt).length, 0);

  return (
    <div className="main-inner">
      <div className="page-head">
        <div>
          <h1 className="page-title">
            Today · {new Date().toLocaleDateString('en-AU', { weekday:'long', day:'numeric', month:'long' })}
          </h1>
          <p className="page-sub">
            {urgentCount} urgent · {openCount} open · unchecked items roll over at midnight AEDT
          </p>
        </div>
        <div style={{display:'flex', gap:6, alignItems:'center'}}>
          <div style={{position:'relative'}}>
            <I.search style={{position:'absolute', left:8, top:'50%', transform:'translateY(-50%)', color:'var(--ink-4)', pointerEvents:'none'}}/>
            <input type="text" placeholder="Search tasks..." value={search} onChange={e=>setSearch(e.target.value)}
              style={{paddingLeft:28, width:180}} />
          </div>
        </div>
      </div>

      {/* Toolbar: Collapse All / Expand All / Compact / Add task */}
      <div style={{display:'flex', gap:6, alignItems:'center', marginBottom:12, flexWrap:'wrap'}}>
        <button className="btn ghost sm" onClick={handleCollapseAll}>Collapse All</button>
        <button className="btn ghost sm" onClick={handleExpandAll}>Expand All</button>
        <button className={`btn sm ${compact ? 'primary' : 'ghost'}`}
          onClick={() => setCompact(c => !c)}>
          {compact ? '▣ Compact on' : '▤ Compact'}
        </button>
        {compact && (
          <span style={{color:'var(--ink-3)',fontSize:11,fontStyle:'italic'}}>Click ○ on a thread header to check all its tasks</span>
        )}
        <button className="btn ghost sm" onClick={() => setShowAddTask(s => !s)}
          style={{color:'#92400e', borderColor:'#f59e0b'}}>
          <I.plus/> Add task
        </button>
        <span style={{color:'var(--ink-4)', fontSize:11, fontFamily:'var(--mono)', marginLeft:4}}>
          {filtered.length} threads · {openCount} open
        </span>
      </div>

      {/* Inline add-task form */}
      {showAddTask && (
        <div style={{background:'#fffbeb', border:'1px solid #fde68a', borderRadius:8, padding:'14px 16px', marginBottom:14}}>
          <div style={{fontWeight:600, fontSize:13, marginBottom:10, color:'#92400e'}}>New task</div>
          <input type="text" value={newTaskSubject}
            onChange={e => setNewTaskSubject(e.target.value)}
            placeholder="Task title (required)"
            autoFocus
            style={{width:'100%', marginBottom:8}} />
          <textarea rows="2" value={newTaskNotes}
            onChange={e => setNewTaskNotes(e.target.value)}
            placeholder="Notes / description (optional)"
            style={{width:'100%', resize:'vertical', marginBottom:10}} />
          <div style={{display:'flex', gap:8}}>
            <button className="btn primary sm"
              disabled={addingTask || !newTaskSubject.trim()}
              onClick={async () => {
                setAddingTask(true);
                try {
                  await api.post('/api/task/standalone', { subject: newTaskSubject.trim(), action: newTaskNotes.trim() });
                  setShowAddTask(false);
                  setNewTaskSubject('');
                  setNewTaskNotes('');
                  onRefresh?.();
                } catch (err) {
                  alert('Failed: ' + err.message);
                } finally {
                  setAddingTask(false);
                }
              }}>
              {addingTask ? 'Adding...' : 'Add task'}
            </button>
            <button className="btn ghost sm" onClick={() => {
              setShowAddTask(false);
              setNewTaskSubject('');
              setNewTaskNotes('');
            }}>Cancel</button>
          </div>
        </div>
      )}

      {banner && (
        <div className="banner">
          <I.sparkle className="icon"/>
          <p>
            New email <strong>"{banner.subject}"</strong> matches existing thread <strong>{banner.threadTitle}</strong>. Add as a sub-task?
          </p>
          <button className="btn sm" onClick={() => onBanner('skip')}>No, skip</button>
          <button className="btn sm primary" onClick={() => onBanner('accept')}>Yes, add to thread</button>
        </div>
      )}

      {/* MY TASKS - standalone manual tasks */}
      {visibleStandalone.length > 0 && (
        <div style={{marginBottom:16}}>
          <div className="section-head">
            <span style={{color:'#92400e', fontWeight:700, letterSpacing:'0.04em'}}>MY TASKS</span>
            <span className="count">{visibleStandalone.length}</span>
            <span className="line"/>
          </div>
          <div style={{border:'1px solid #fde68a', borderRadius:8, overflow:'hidden', background:'#fffbeb', boxShadow:'0 1px 3px rgba(245,158,11,.1)'}}>
            {visibleStandalone.map(task => (
              <TaskRow key={task.id}
                task={task}
                thread={{ id: 'standalone', title: 'MY TASKS' }}
                onCheck={() => onCheckStandalone?.(task.id)}
                onOpen={() => {}}
                onRefresh={onRefresh}
              />
            ))}
          </div>
        </div>
      )}

      {filtered.length === 0 && visibleStandalone.length === 0 && (
        <div className="empty">
          {search ? `No tasks match "${search}"` : 'All caught up - no active tasks'}
        </div>
      )}

      {filtered.length === 0 && visibleStandalone.length > 0 && search && (
        <div className="empty">No email tasks match "{search}"</div>
      )}

      {sections.map(sec => {
        const items = filtered.filter(t => sec.tags.includes(t.tag));
        if (!items.length) return null;
        const open = items.reduce((n,t) => n + t.tasks.filter(x=>!x.doneAt && !x.deletedAt).length, 0);
        return (
          <div key={sec.key}>
            <div className="section-head">
              <span>{sec.label}</span>
              <span className="count">{open} open</span>
              <span className="line"/>
            </div>
            {items.map(t => (
              <Thread key={t.id} thread={t}
                onCheck={onCheck} onOpenTask={onOpenTask}
                onToggle={handleToggle} onRefresh={onRefresh}
                compact={compact} isExpanded={expandedIds.has(t.id)} />
            ))}
          </div>
        );
      })}



      {/* Confirm dialog for permanent deletions */}
      {confirmPending && (
        <ConfirmDialog
          title="Are you sure?"
          body={`This will permanently delete "${confirmPending.label}". This cannot be undone.`}
          confirmLabel="Yes, delete forever"
          danger
          onCancel={() => setConfirmPending(null)}
          onConfirm={async () => {
            try {
              if (confirmPending.kind === 'deleteManualTask') {
                await api.post('/api/task/manual/permanent-delete', { threadId: confirmPending.threadId, taskId: confirmPending.taskId });
              } else if (confirmPending.kind === 'deleteStandaloneTask') {
                await api.post('/api/task/standalone/permanent-delete', { id: confirmPending.id });
              } else if (confirmPending.kind === 'deleteFollowup') {
                await api.post('/api/followup/permanent-delete', { id: confirmPending.id });
              }
              onRefresh?.();
            } catch (err) {
              alert('Delete failed: ' + err.message);
            }
            setConfirmPending(null);
          }}
        />
      )}
    </div>
  );
}

// ---- Recently Deleted (standalone view) ----
function RecentlyDeletedView({ threads, standaloneManualTasks, dismissedFollowUps, onRefresh }) {
  const [confirmPending, setConfirmPending] = useState(null);

  const deletedItems = [];
  for (const th of (threads||[])) {
    for (const task of th.tasks) {
      if (task.isManual && task.deletedAt) deletedItems.push({ kind:'manualTask', task, thread: th });
    }
  }
  for (const task of (standaloneManualTasks||[])) {
    if (task.deletedAt) deletedItems.push({ kind:'standaloneTask', task });
  }
  for (const fu of (dismissedFollowUps||[])) {
    deletedItems.push({ kind:'followup', fu });
  }

  return (
    <div className="main-inner">
      <div className="page-head">
        <div>
          <h1 className="page-title">Recently Deleted</h1>
          <p className="page-sub">Deleted manual tasks and dismissed follow-ups. Restore or permanently delete.</p>
        </div>
      </div>
      {deletedItems.length === 0 && <div className="empty">Nothing here - recently deleted items will appear here.</div>}
      {deletedItems.length > 0 && (
        <div style={{background:'#fafaf9', border:'1px solid var(--line)', borderRadius:8, overflow:'hidden'}}>
          {deletedItems.map((item, idx) => {
            if (item.kind === 'manualTask') {
              const { task, thread } = item;
              return (
                <div key={task.id} style={{display:'flex',alignItems:'center',gap:10,padding:'12px 16px',borderBottom:'1px solid var(--line)'}}>
                  <div style={{flex:1}}>
                    <div style={{fontWeight:600,fontSize:13,color:'var(--ink-2)',textDecoration:'line-through'}}>{task.subject}</div>
                    <div style={{display:'flex',alignItems:'center',gap:6,marginTop:3}}><span style={{background:'#dbeafe',color:'#1d4ed8',fontSize:10,fontWeight:700,padding:'1px 6px',borderRadius:4,textTransform:'uppercase',letterSpacing:'.04em'}}>📋 To-do Task</span><span style={{fontSize:11,color:'var(--ink-4)'}}>Thread: {stripMd(thread.title)}</span></div>
                  </div>
                  <button className="btn sm ghost" onClick={async () => { try { await api.post('/api/task/manual/restore', { threadId: thread.id, taskId: task.id }); onRefresh?.(); } catch(e) { alert(e.message); } }}>Restore</button>
                  <button className="btn sm danger" onClick={() => setConfirmPending({ kind:'deleteManualTask', threadId: thread.id, taskId: task.id, label: task.subject })}><I.trash/></button>
                </div>
              );
            }
            if (item.kind === 'standaloneTask') {
              const { task } = item;
              return (
                <div key={task.id} style={{display:'flex',alignItems:'center',gap:10,padding:'12px 16px',borderBottom:'1px solid var(--line)'}}>
                  <div style={{flex:1}}>
                    <div style={{fontWeight:600,fontSize:13,color:'var(--ink-2)',textDecoration:'line-through'}}>{task.subject}</div>
                    <div style={{display:'flex',alignItems:'center',gap:6,marginTop:3}}><span style={{background:'#fef3c7',color:'#92400e',fontSize:10,fontWeight:700,padding:'1px 6px',borderRadius:4,textTransform:'uppercase',letterSpacing:'.04em'}}>✅ My Task</span></div>
                  </div>
                  <button className="btn sm ghost" onClick={async () => { try { await api.post('/api/task/standalone/restore', { id: task.id }); onRefresh?.(); } catch(e) { alert(e.message); } }}>Restore</button>
                  <button className="btn sm danger" onClick={() => setConfirmPending({ kind:'deleteStandaloneTask', id: task.id, label: task.subject })}><I.trash/></button>
                </div>
              );
            }
            if (item.kind === 'followup') {
              const { fu } = item;
              return (
                <div key={fu.id} style={{display:'flex',alignItems:'center',gap:10,padding:'12px 16px',borderBottom:'1px solid var(--line)'}}>
                  <div style={{flex:1}}>
                    <div style={{fontWeight:600,fontSize:13,color:'var(--ink-2)',textDecoration:'line-through'}}>{stripMd(fu.subject)}</div>
                    <div style={{display:'flex',alignItems:'center',gap:6,marginTop:3,flexWrap:'wrap'}}><span style={{background:'#fce7f3',color:'#9d174d',fontSize:10,fontWeight:700,padding:'1px 6px',borderRadius:4,textTransform:'uppercase',letterSpacing:'.04em'}}>⏰ Follow-up</span>{fu.autoDismissed && <span style={{background:'#dcfce7',color:'#166534',fontSize:10,fontWeight:700,padding:'1px 6px',borderRadius:4}}>✓ Auto-dismissed</span>}<span style={{fontSize:11,color:'var(--ink-4)'}}>{fu.autoDismissReason || ('to ' + (fu.to || 'unknown'))}</span></div>
                  </div>
                  <button className="btn sm ghost" onClick={async () => { try { await api.post('/api/followup/restore', { id: fu.id }); onRefresh?.(); } catch(e) { alert(e.message); } }}>Restore</button>
                  <button className="btn sm danger" onClick={() => setConfirmPending({ kind:'deleteFollowup', id: fu.id, label: fu.subject })}><I.trash/></button>
                </div>
              );
            }
            return null;
          })}
        </div>
      )}
      {confirmPending && (
        <ConfirmDialog title="Delete forever?" body={`"${confirmPending.label}" will be permanently removed.`}
          confirmLabel="Yes, delete forever" danger
          onCancel={() => setConfirmPending(null)}
          onConfirm={async () => {
            try {
              if (confirmPending.kind === 'deleteManualTask') await api.post('/api/task/manual/permanent-delete', { threadId: confirmPending.threadId, taskId: confirmPending.taskId });
              else if (confirmPending.kind === 'deleteStandaloneTask') await api.post('/api/task/standalone/permanent-delete', { id: confirmPending.id });
              else if (confirmPending.kind === 'deleteFollowup') await api.post('/api/followup/permanent-delete', { id: confirmPending.id });
              onRefresh?.();
            } catch(e) { alert(e.message); }
            setConfirmPending(null);
          }}
        />
      )}
    </div>
  );
}

// ---- Done list ----
function DoneView({ doneHistory, onUndo }) {
  const groups = doneHistory || {};
  const [active, setActive] = useState('all');
  const [undoing, setUndoing] = useState(null);
  const [removedIds, setRemovedIds] = useState(new Set());
  const [dateFrom, setDateFrom] = useState('');
  const [dateTo, setDateTo]   = useState('');
  const [search, setSearch]   = useState('');
  const allDayKeys = Object.keys(groups).sort().reverse();

  // Filter by date range + search
  const dayKeys = allDayKeys.filter(k => {
    if (dateFrom && k < dateFrom) return false;
    if (dateTo   && k > dateTo)   return false;
    if (search.trim()) {
      const q = search.toLowerCase();
      return (groups[k]||[]).some(it =>
        (it.subject||'').toLowerCase().includes(q) ||
        (it.from||'').toLowerCase().includes(q) ||
        (it.folder||'').toLowerCase().includes(q)
      );
    }
    return true;
  });

  const totalFiltered = dayKeys.reduce((n,k) => n + (groups[k]||[]).length, 0);

  const handleUndo = async (it) => {
    if (!it.taskId) return;
    setUndoing(it.id);
    // Optimistically hide from Done list immediately
    setRemovedIds(prev => new Set([...prev, it.id]));
    try {
      if (it.isStandalone) {
        await api.post('/api/task/standalone/undo', { id: it.taskId });
      } else {
        await fetch('/api/task/undo', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ threadId: it.threadId, taskId: it.taskId }) });
      }
      onUndo && onUndo();
    } catch(e) {
      console.error('Undo failed', e);
      // Rollback optimistic hide on error
      setRemovedIds(prev => { const s = new Set(prev); s.delete(it.id); return s; });
    }
    setUndoing(null);
  };

  return (
    <div className="main-inner">
      <div className="page-head">
        <div>
          <h1 className="page-title">Done</h1>
          <p className="page-sub">Completed tasks grouped by day. Each item shows the Outlook folder the email was moved to.</p>
        </div>
      </div>
      {/* Filters */}
      <div style={{display:'flex',gap:10,flexWrap:'wrap',alignItems:'flex-end',marginBottom:16,background:'var(--bg-2,#f5f4f0)',borderRadius:8,padding:'12px 14px'}}>
        <div>
          <div style={{fontSize:11,color:'var(--ink-4)',marginBottom:4,textTransform:'uppercase',letterSpacing:'.04em'}}>From</div>
          <input type="date" value={dateFrom} onChange={e => { setDateFrom(e.target.value); setActive('all'); }}
            style={{padding:'6px 10px',border:'1px solid var(--line)',borderRadius:6,fontSize:13,background:'#fff'}} />
        </div>
        <div>
          <div style={{fontSize:11,color:'var(--ink-4)',marginBottom:4,textTransform:'uppercase',letterSpacing:'.04em'}}>To</div>
          <input type="date" value={dateTo} onChange={e => { setDateTo(e.target.value); setActive('all'); }}
            style={{padding:'6px 10px',border:'1px solid var(--line)',borderRadius:6,fontSize:13,background:'#fff'}} />
        </div>
        <div style={{flex:1,minWidth:160}}>
          <div style={{fontSize:11,color:'var(--ink-4)',marginBottom:4,textTransform:'uppercase',letterSpacing:'.04em'}}>Search</div>
          <input type="text" placeholder="Subject, sender, folder..." value={search} onChange={e => { setSearch(e.target.value); setActive('all'); }}
            style={{width:'100%',padding:'6px 10px',border:'1px solid var(--line)',borderRadius:6,fontSize:13,background:'#fff'}} />
        </div>
        {(dateFrom||dateTo||search) && (
          <button className="btn ghost sm" onClick={() => { setDateFrom(''); setDateTo(''); setSearch(''); }} style={{alignSelf:'flex-end'}}>× Clear</button>
        )}
        <div style={{alignSelf:'flex-end',fontSize:12,color:'var(--ink-3)',fontFamily:'var(--mono)'}}>
          {totalFiltered} item{totalFiltered!==1?'s':''}
        </div>
      </div>

      {dayKeys.length === 0 && <div className="empty">{allDayKeys.length === 0 ? 'No completed tasks yet' : 'No results for this filter'}</div>}
      <div className="chips" style={{marginBottom:16,flexWrap:'wrap'}}>
        <button className="chip" aria-pressed={active==='all'} onClick={() => setActive('all')}>All</button>
        {dayKeys.map(k => (
          <button key={k} className="chip" aria-pressed={active===k} onClick={() => setActive(k)}>
            {k} · {(search ? (groups[k]||[]).filter(it => it.subject?.toLowerCase().includes(search.toLowerCase()) || it.from?.toLowerCase().includes(search.toLowerCase()) || it.folder?.toLowerCase().includes(search.toLowerCase())).length : (groups[k]||[]).length)}
          </button>
        ))}
      </div>
      {dayKeys.filter(k => active==='all' || active===k).map(day => {
        const dayItems = search.trim()
          ? (groups[day]||[]).filter(it => {
              if (removedIds.has(it.id)) return false;
              const q = search.toLowerCase();
              return (it.subject||'').toLowerCase().includes(q) || (it.from||'').toLowerCase().includes(q) || (it.folder||'').toLowerCase().includes(q);
            })
          : (groups[day]||[]).filter(it => !removedIds.has(it.id));
        if (!dayItems.length) return null;
        return (
        <div className="day-group" key={day}>
          <div className="day-head">
            <h3>{day}</h3>
            <span className="c">{dayItems.length} completed</span>
          </div>
          {dayItems.map(it => (
            <div className="done-item" key={it.id}>
              <Check checked={undoing !== it.id} onClick={() => handleUndo(it)}/>
              <div>
                <div className="t" style={{textDecoration: 'line-through', opacity:0.6}}>{it.subject}</div>
                <div className="m">from {it.from} · at {it.at}{it.disputeClosed ? ' · Dispute closed' : ''}</div>
              </div>
              <div style={{display:'flex', alignItems:'center', gap:8}}>
                <span className="folder-pill"><I.folder className="ic"/> {it.folder}</span>
                {(it.webLink || it.graphId) && <a href={outlookLink(it.webLink, it.graphId, it.messageId)} onClick={e=>{e.preventDefault();openInOutlook(it.webLink, it.graphId, it.messageId);}} target="_blank" rel="noreferrer" style={{color:'#2563eb',fontSize:12,fontWeight:600}}>Open ↗</a>}
              </div>
            </div>
          ))}
        </div>
        );
      })}
    </div>
  );
}


// ---- Follow-ups ----
function FollowupsView({ items, onRefresh }) {
  const [nudging, setNudging] = useState(null);
  const [nudgeText, setNudgeText] = useState({});
  const [search, setSearch] = useState('');
  const [bodyCache, setBodyCache] = useState({});
  const [expandedBody, setExpandedBody] = useState(null);
  const [confirmDismiss, setConfirmDismiss] = useState(null); // { fu }

  const toggleBody = async (fu) => {
    if (expandedBody === fu.id) { setExpandedBody(null); return; }
    setExpandedBody(fu.id);
    if (!bodyCache[fu.id] && fu.graphId) {
      try {
        const d = await api.get('/api/email/body?graphId=' + encodeURIComponent(fu.graphId));
        setBodyCache(c => ({...c, [fu.id]: d.body || '(no body)'}));
      } catch { setBodyCache(c => ({...c, [fu.id]: '(could not load body)'})); }
    }
  };

  const handleDeleteManual = async (fu) => {
    try {
      await api.post('/api/task/followup/delete', { threadId: fu.threadId, taskId: fu.taskId });
      onRefresh?.();
    } catch (err) {
      alert('Delete failed: ' + err.message);
    }
  };

  const handleDismiss = (fu) => {
    setConfirmDismiss({ fu });
  };

  const confirmDoDismiss = async (fu) => {
    try {
      await api.post('/api/followup/dismiss', { id: fu.id });
      onRefresh?.();
    } catch (err) {
      alert('Dismiss failed: ' + err.message);
    }
  };

  const getDraftNudge = async (item) => {
    setNudging(item.id);
    try {
      const { text } = await api.post('/api/ai/nudge', {
        subject: item.subject,
        recipientName: item.to,
        daysSince: item.sentDays,
      });
      setNudgeText(prev => ({ ...prev, [item.id]: text }));
    } catch (err) {
      alert('Could not generate nudge: ' + err.message);
    } finally {
      setNudging(null);
    }
  };

  return (
    <div className="main-inner">
      <div className="page-head">
        <div>
          <h1 className="page-title">Follow-ups</h1>
          <p className="page-sub">Emails you sent where no reply has landed within the threshold. Auto-clears when a reply arrives.</p>
        </div>
      </div>
      {/* Search bar */}
      <div style={{marginBottom:16,display:'flex',alignItems:'center',gap:10,background:'#fff',border:'1px solid #d1d5db',borderRadius:8,padding:'8px 14px',maxWidth:480,boxShadow:'0 1px 3px rgba(0,0,0,.06)'}}>
        <svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="#9ca3af" strokeWidth="1.5" strokeLinecap="round"><circle cx="6.5" cy="6.5" r="4.5"/><path d="M10.5 10.5l3 3"/></svg>
        <input type="text" placeholder="Search by subject, recipient, or keyword..." value={search} onChange={e => setSearch(e.target.value)}
          style={{flex:1,border:'none',outline:'none',fontSize:13.5,background:'transparent',color:'#1a1a18'}} />
        {search && <button onClick={() => setSearch('')} style={{border:'none',background:'none',cursor:'pointer',color:'#9ca3af',fontSize:16,lineHeight:1,padding:0}}>×</button>}
      </div>
      {confirmDismiss && (
        <ConfirmDialog
          title="Are you sure?"
          body="This will remove this follow-up from your list."
          confirmLabel="Yes, remove"
          danger
          onCancel={() => setConfirmDismiss(null)}
          onConfirm={async () => {
            await confirmDoDismiss(confirmDismiss.fu);
            setConfirmDismiss(null);
          }}
        />
      )}
      {items.length === 0 && <div className="empty">No pending follow-ups - inbox all clear</div>}
      {items.filter(fu => !search.trim() || fu.subject?.toLowerCase().includes(search.toLowerCase()) || fu.to?.toLowerCase().includes(search.toLowerCase())).map(fu => (
        <div className={`thread followup`} key={fu.id} data-open="true" style={{marginBottom:10}}>
          <div className="thread-head">
            <div style={{width:7, height:7, borderRadius:'50%', background: fu.urgency==='overdue' ? 'var(--urgent)' : 'oklch(55% 0.14 75)', marginLeft:6}}/>
            <div className="thread-title">{stripMd(fu.subject)}</div>
            <Badge kind={fu.urgency==='overdue' ? 'urgent' : 'reply'}>
              {fu.urgency==='overdue' ? `${fu.sentDays}d overdue` : 'Due today'}
            </Badge>
            <span className="thread-meta">sent {fu.sentDays}d ago</span>
            <button className="btn tiny ghost" title="Dismiss this follow-up" onClick={e => { e.stopPropagation(); handleDismiss(fu); }} style={{marginLeft:'auto',color:'var(--ink-3)'}}>Dismiss ×</button>
          </div>
          <div className="thread-body">
            <div className="task">
              <div/>
              <div className="task-body">
                <div className="task-title">Waiting on {fu.to}</div>
                <div className="task-action">{fu.action}</div>
                <div className="task-row">
                  <span>{fu.to} &lt;{fu.toEmail}&gt;</span>
                  <span className="sep">·</span>
                  {(fu.webLink || fu.graphId) && <><a href={outlookLink(fu.webLink, fu.graphId, fu.messageId)} onClick={e=>{e.preventDefault();openInOutlook(fu.webLink, fu.graphId, fu.messageId);}} target="_blank" rel="noreferrer" style={{color:'#2563eb',fontWeight:600,fontSize:12}}>📧 Open in Outlook</a><span className="sep">·</span></>}
                  <button className="btn tiny ghost" onClick={() => toggleBody(fu)}>
                    {expandedBody === fu.id ? '▼ Hide email' : '► View email'}
                  </button>
                  <button className="btn tiny ghost"
                    onClick={() => getDraftNudge(fu)}
                    disabled={nudging === fu.id}>
                    <I.sparkle/> {nudging === fu.id ? 'Drafting...' : 'Draft nudge'}
                  </button>
                  {fu.isManual && (
                    <button className="btn tiny ghost"
                      title="Remove this manual follow-up"
                      onClick={e => { e.stopPropagation(); handleDeleteManual(fu); }}
                      style={{color:'var(--urgent)'}}>
                      ×
                    </button>
                  )}
                </div>
                {nudgeText[fu.id] && (
                  <div className="reply-card" style={{marginTop:10}}>
                    <div className="rl-head">
                      <span className="rl-label">AI-drafted nudge</span>
                      <button className="btn tiny ghost" onClick={() => {
                        navigator.clipboard?.writeText(nudgeText[fu.id]);
                      }}>
                        <I.copy/> Copy
                      </button>
                    </div>
                    <pre>{nudgeText[fu.id]}</pre>
                  </div>
                )}
                {expandedBody === fu.id && (
                  <div style={{marginTop:10,background:'#f9f8f6',border:'1px solid #e8e7e3',borderRadius:6,padding:'10px 14px',fontSize:13,lineHeight:1.6,maxHeight:300,overflowY:'auto'}}>
                    {bodyCache[fu.id]
                      ? <div dangerouslySetInnerHTML={{__html: bodyCache[fu.id]}} />
                      : <span style={{color:'#888'}}>Loading...</span>
                    }
                  </div>
                )}
              </div>
            </div>
          </div>
        </div>
      ))}
    </div>
  );
}

// ---- Disputes ----
function DisputesView({ disputes, onAction, onRefresh }) {
  const sorted = [...disputes].sort((a,b) => {
    if (a.state==='resolved' && b.state!=='resolved') return 1;
    if (b.state==='resolved' && a.state!=='resolved') return -1;
    if (a.state==='escalated' && b.state!=='escalated') return -1;
    if (b.state==='escalated' && a.state!=='escalated') return 1;
    return a.priority - b.priority;
  });
  const active   = sorted.filter(d => d.state !== 'resolved');
  const resolved = sorted.filter(d => d.state === 'resolved');
  const [showAudit, setShowAudit]   = useState(null);
  const [confirm, setConfirm]       = useState(null);
  const [showNew, setShowNew]       = useState(false);
  const [newForm, setNewForm]       = useState({ title:'', party:'', amount:'', action:'' });
  const [busy, setBusy]             = useState(false);

  const stateLabel = { open:'Open', progress:'In progress', resolved:'Resolved', escalated:'Escalated' };
  const cycleState = async (d) => {
    const cycle = ['open','progress','escalated','resolved'];
    const next = cycle[(cycle.indexOf(d.state) + 1) % cycle.length];
    await onAction({ type:'state', id:d.id, next, title:d.title });
  };

  const createDispute = async () => {
    if (!newForm.title.trim()) return;
    setBusy(true);
    try {
      await api.post('/api/dispute/new', newForm);
      setShowNew(false);
      setNewForm({ title:'', party:'', amount:'', action:'' });
      onRefresh();
    } catch (err) { alert(err.message); }
    finally { setBusy(false); }
  };

  const Card = ({ d }) => (
    <div className={`dispute-card ${d.state}`}>
      <span className="drag" title="Drag to reorder"><I.drag/></span>
      <div className="dispute-body">
        <div className="d-title">{d.title}</div>
        <div className="d-meta">{d.party} · {d.amount} · opened {d.opened}</div>
        <div className="d-action">{d.action}</div>
      </div>
      <div className="dispute-actions">
        <button className={`state-pill ${d.state}`} onClick={() => cycleState(d)}>
          <span className="d"/>{stateLabel[d.state]}
        </button>
        <button className="btn tiny ghost" onClick={() => setShowAudit(d)}>Audit</button>
        {d.state !== 'resolved' && (
          <button className="btn tiny" onClick={() => setConfirm({ kind:'close', d })}>Close</button>
        )}
        <button className="btn tiny danger" onClick={() => setConfirm({ kind:'delete', d })}><I.trash/></button>
      </div>
    </div>
  );

  return (
    <div className="main-inner">
      <div className="page-head">
        <div>
          <h1 className="page-title">Disputes & Claims</h1>
          <p className="page-sub">Track formal disputes, SOPA claims, and payment disagreements. Emails classified as "dispute" or "claim" auto-appear here.</p>
        </div>
        <button className="btn primary" onClick={() => setShowNew(true)}><I.plus/> New dispute</button>
      </div>
      <div style={{
        background:'#fffbeb', border:'1px solid #fde68a', borderRadius:8,
        padding:'10px 14px', marginBottom:16, fontSize:12.5, color:'#92400e',
        display:'flex', alignItems:'center', gap:8,
      }}>
        <I.sparkle style={{color:'#f59e0b', flexShrink:0}}/>
        <span>Emails classified as <strong>dispute</strong> or <strong>claim</strong> by Claude AI are automatically added here. Use <strong>New dispute</strong> to manually track a disagreement.</span>
      </div>

      <div className="section-head"><span>Active</span><span className="count">{active.length}</span><span className="line"/></div>
      {active.length === 0 && <div className="empty" style={{padding:'20px 0'}}>No active disputes</div>}
      {active.map(d => <Card key={d.id} d={d}/>)}

      {resolved.length > 0 && (
        <>
          <div className="section-head"><span>Resolved</span><span className="count">{resolved.length}</span><span className="line"/></div>
          {resolved.map(d => <Card key={d.id} d={d}/>)}
        </>
      )}

      {/* New dispute modal */}
      {showNew && (
        <Modal onClose={() => setShowNew(false)}>
          <div className="modal-head"><h2>New dispute / claim</h2></div>
          <div className="modal-body">
            {[['Title','title'],['Party','party'],['Amount','amount'],['Notes','action']].map(([label, key]) => (
              <div className="modal-section" key={key}>
                <div className="label">{label}</div>
                <input type="text" value={newForm[key]} onChange={e => setNewForm(f=>({...f,[key]:e.target.value}))} />
              </div>
            ))}
          </div>
          <div className="modal-foot">
            <button className="btn ghost" onClick={() => setShowNew(false)}>Cancel</button>
            <button className="btn primary" onClick={createDispute} disabled={busy || !newForm.title.trim()}>
              {busy ? 'Creating...' : 'Create dispute'}
            </button>
          </div>
        </Modal>
      )}

      {/* Audit trail */}
      {showAudit && (
        <Modal onClose={() => setShowAudit(null)}>
          <div className="modal-head">
            <h2>Audit trail - {showAudit.title}</h2>
            <div className="from-line">Immutable once written · {showAudit.audit?.length || 0} events</div>
          </div>
          <div className="modal-body">
            {(showAudit.audit || []).map((a,i) => (
              <div key={i} style={{display:'grid', gridTemplateColumns:'150px 20px 1fr', gap:10, padding:'8px 0', borderBottom:'1px dashed var(--line)', fontSize:12.5}}>
                <span style={{fontFamily:'var(--mono)', color:'var(--ink-3)', fontSize:11}}>{new Date(a.t).toLocaleString('en-AU')}</span>
                <span style={{color: a.kind==='ai' ? 'var(--violet)' : a.kind==='user' ? 'var(--accent-ink)' : 'var(--ink-4)'}}>
                  {a.kind==='ai' ? '◆' : a.kind==='user' ? '●' : '○'}
                </span>
                <span>{a.text}</span>
              </div>
            ))}
          </div>
          <div className="modal-foot">
            <span style={{fontSize:11, color:'var(--ink-3)', fontFamily:'var(--mono)'}}>dispute.{showAudit.id}.audit</span>
            <button className="btn" onClick={() => setShowAudit(null)}>Close</button>
          </div>
        </Modal>
      )}

      {confirm?.kind === 'close' && (
        <ConfirmDialog
          title="Close this dispute?"
          body={`"${confirm.d.title}" will be marked Resolved and the email moved to "Disputes & Claims". An audit record will be kept.`}
          confirmLabel="Yes, close it"
          onCancel={() => setConfirm(null)}
          onConfirm={() => { onAction({type:'close', id:confirm.d.id, title:confirm.d.title}); setConfirm(null); }}
        />
      )}
      {confirm?.kind === 'delete' && (
        <ConfirmDialog
          title="Delete this dispute permanently?"
          body="This action cannot be undone. An audit record will be written before removal."
          confirmLabel="Yes, delete it" danger
          onCancel={() => setConfirm(null)}
          onConfirm={() => { onAction({type:'delete', id:confirm.d.id, title:confirm.d.title}); setConfirm(null); }}
        />
      )}
    </div>
  );
}

// ---- Folder rules ----
const FOLDER_OPTIONS = [
  '- stays in inbox -',
  'Disputes & Claims', 'Claims',
  'Tenders - VIC Gov',
  'Suppliers - Spray Seal',
  'Vendors - Estimating', 'Vendors - Kynection',
  'Internal - Payroll',
  'FYI',
  'Noise',
  'Read Later',
  'Archive',
];

function FolderRulesView({ folderRules, onRefresh }) {
  const rules = folderRules || [];
  const [local, setLocal] = useState(() => rules.map(r => ({...r})));
  const [saving, setSaving] = useState(false);
  const [saved, setSaved] = useState(false);

  const save = async () => {
    setSaving(true);
    try {
      await api.post('/api/settings/folders', { rules: local });
      setSaved(true);
      setTimeout(() => setSaved(false), 2000);
      onRefresh?.();
    } catch(e) { alert('Save failed: ' + e.message); }
    setSaving(false);
  };

  return (
    <div className="main-inner">
      <div className="page-head">
        <div>
          <h1 className="page-title">Folder rules</h1>
          <p className="page-sub">When a task is marked done, its email is moved in Outlook to the folder you set here. Missing folders are created automatically.</p>
        </div>
      </div>
      <div className="rules">
        <div className="row head">
          <div>Email category</div>
          <div/>
          <div>Outlook folder</div>
          <div>Moved</div>
        </div>
        {local.map((r, i) => (
          <div className="row" key={r.cat}>
            <div>{r.cat}</div>
            <div className="arrow">→</div>
            <div>
              <select value={r.folder} onChange={e => {
                const updated = [...local];
                updated[i] = {...r, folder: e.target.value};
                setLocal(updated);
              }} style={{padding:'5px 8px', border:'1px solid var(--line)', borderRadius:5, fontSize:13, background:'#fff', minWidth:200}}>
                {FOLDER_OPTIONS.map(opt => <option key={opt} value={opt}>{opt}</option>)}
              </select>
            </div>
            <div style={{fontFamily:'var(--mono)', fontSize:12, color:'var(--ink-3)'}}>{r.emails || '-'}</div>
          </div>
        ))}
      </div>
      <div style={{marginTop:16, display:'flex', gap:10, alignItems:'center'}}>
        <button className="btn primary" onClick={save} disabled={saving}>
          {saving ? 'Saving...' : saved ? '✓ Saved' : 'Save folder rules'}
        </button>
        <span style={{fontSize:12, color:'var(--ink-3)'}}>Changes apply to new emails marked done</span>
      </div>
    </div>
  );
}

// ---- Reply style ----
function ReplyStyleView({ employee, onSave, currentInterface, onInterfaceChange }) {
  const rs = employee?.replyStyle || {};
  const [tone,   setTone]   = useState(rs.tone    || 'professional');
  const [sign,   setSign]   = useState(rs.signOff || 'Kind regards');
  const [opts,   setOpts]   = useState(rs.options || 2);
  const [thresh, setThresh] = useState(2);
  const [saved, setSaved]   = useState(false);

  const tones = [
    { v:'professional', l:'Professional & concise' },
    { v:'warm',         l:'Warm & friendly' },
    { v:'formal',       l:'Formal & detailed' },
    { v:'direct',       l:'Direct & brief' },
  ];

  const save = async () => {
    await onSave({ tone, signOff: sign, options: opts, followUpThresholdDays: thresh });
    setSaved(true);
    setTimeout(() => setSaved(false), 2000);
  };

  const interfaces = [
    { id: 'general',    label: 'General',    icon: '📧', desc: 'General purpose email management' },
    { id: 'fleet',      label: 'Fleet',      icon: '🚛', desc: 'Plant maintenance & fleet correspondence' },
    { id: 'quoting',    label: 'Quoting',    icon: '📊', desc: 'Tender tracking, supplier quotes, estimating' },
    { id: 'accounting', label: 'Accounting', icon: '💰', desc: 'Coming soon', disabled: true },
  ];

  return (
    <div className="main-inner">
      <div className="page-head">
        <div>
          <h1 className="page-title">Settings</h1>
          <p className="page-sub">Manage your workspace and reply preferences.</p>
        </div>
      </div>

      <div className="form-card">
        <div className="form-row">
          <div><div className="k">Workspace</div><div className="k-sub">Switch your interface. Takes effect immediately.</div></div>
          <div style={{display:'flex', gap:8, flexWrap:'wrap'}}>
            {interfaces.map(i => (
              <button key={i.id}
                className="chip"
                aria-pressed={currentInterface === i.id}
                disabled={i.disabled}
                onClick={() => !i.disabled && currentInterface !== i.id && onInterfaceChange(i.id)}
                style={i.disabled ? {opacity:0.4, cursor:'not-allowed'} : {}}
                title={i.disabled ? 'Coming soon' : ''}
              >
                {i.icon} {i.label}{i.disabled ? ' ·  Soon' : ''}
              </button>
            ))}
          </div>
        </div>
      </div>

      <div className="form-card">
        <div className="form-row">
          <div><div className="k">Tone</div><div className="k-sub">Voice Claude uses when drafting reply options.</div></div>
          <div className="chips">
            {tones.map(t => <button className="chip" key={t.v} aria-pressed={tone===t.v} onClick={() => setTone(t.v)}>{t.l}</button>)}
          </div>
        </div>
        <div className="form-row">
          <div><div className="k">Reply options</div><div className="k-sub">Number of alternatives per email.</div></div>
          <div className="chips">
            {[1,2,3].map(n => <button className="chip" key={n} aria-pressed={opts===n} onClick={() => setOpts(n)}>{n}</button>)}
          </div>
        </div>
        <div className="form-row">
          <div><div className="k">Sign-off</div><div className="k-sub">Appended to every drafted reply. Use multiple lines for name, title, company.</div></div>
          <textarea value={sign} onChange={e => setSign(e.target.value)} rows={4}
            style={{width:'100%', padding:'8px 12px', border:'1px solid var(--line)', borderRadius:6, fontSize:13, lineHeight:1.6, fontFamily:'inherit', resize:'vertical', background:'#fff'}} />
        </div>
        <div className="form-row">
          <div><div className="k">Follow-up threshold</div><div className="k-sub">Business days before an email appears under Follow-ups.</div></div>
          <div className="chips">
            {[1,2,3,5].map(n => <button className="chip" key={n} aria-pressed={thresh===n} onClick={() => setThresh(n)}>{n} day{n>1?'s':''}</button>)}
          </div>
        </div>
      </div>

      <div className="form-card">
        <div style={{fontSize:11, letterSpacing:'0.08em', textTransform:'uppercase', color:'var(--ink-3)', marginBottom:10, fontWeight:500}}>Claude system prompt preview</div>
        <pre style={{margin:0, fontFamily:'var(--mono)', fontSize:11.5, color:'var(--ink-2)', lineHeight:1.6, whiteSpace:'pre-wrap'}}>
{`Employee: ${employee?.name || 'User'} · Role: ${employee?.role || ''}
Reply tone: ${tones.find(t=>t.v===tone)?.l.toLowerCase()}
Reply options: ${opts}
Sign-off: "${sign}"
Follow-up threshold: ${thresh} business day${thresh>1?'s':''} (disputes: ${Math.max(1,thresh-1)})`}
        </pre>
      </div>

      <div style={{display:'flex', gap:8, justifyContent:'flex-end'}}>
        <button className="btn primary" onClick={save}>{saved ? '✓ Saved' : 'Save preferences'}</button>
      </div>
    </div>
  );
}

// ---- Mailbox View (Tenders Inbox / direct browse) ----
function MailboxView({ mailbox }) {
  const [messages,  setMessages]  = useState(null);
  const [loading,   setLoading]   = useState(true);
  const [error,     setError]     = useState(null);
  const [selected,  setSelected]  = useState(null);
  const [body,      setBody]      = useState(null);
  const [loadBody,  setLoadBody]  = useState(false);
  const [skip,      setSkip]      = useState(0);
  const PAGE = 50;

  const load = useCallback(async (skp = 0) => {
    setLoading(true);
    setError(null);
    try {
      const q = new URLSearchParams({ mailbox, top: PAGE, skip: skp });
      const data = await api.get('/api/mailbox/messages?' + q);
      setMessages(data.messages || []);
      setSkip(skp);
    } catch (e) {
      setError(e.message);
    } finally {
      setLoading(false);
    }
  }, [mailbox]);

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

  const openMessage = async (msg) => {
    if (selected?.id === msg.id) { setSelected(null); setBody(null); return; }
    setSelected(msg);
    setBody(null);
    setLoadBody(true);
    try {
      const q = new URLSearchParams({ mailbox });
      const data = await api.get(`/api/mailbox/message/${encodeURIComponent(msg.id)}?${q}`);
      setBody(data.body || '');
    } catch (e) {
      setBody('(Could not load email body)');
    } finally {
      setLoadBody(false);
    }
  };

  const isShared = mailbox === 'shared:tenders';

  return (
    <div className="main-inner">
      <div className="page-head">
        <div>
          <h1 className="page-title">
            {isShared ? '📋 Tenders Inbox' : '📥 My Inbox'}
            {isShared && (
              <span style={{
                display:'inline-block', marginLeft:10, fontSize:11, fontWeight:700,
                background:'#d97706', color:'#fff', padding:'2px 8px', borderRadius:4,
                verticalAlign:'middle', letterSpacing:'0.04em',
              }}>SHARED MAILBOX</span>
            )}
          </h1>
          <p className="page-sub">
            {isShared
              ? 'tenders@eliteroads.com.au - app-level access via Microsoft Graph'
              : 'Your personal inbox - most recent 50 messages'}
          </p>
        </div>
        <button className="btn ghost sm" onClick={() => load(skip)} disabled={loading}>
          {loading ? <I.spin/> : <I.refresh/>} Refresh
        </button>
      </div>

      {error && (
        <div style={{background:'#fef2f2',border:'1px solid #fecaca',borderRadius:8,padding:'12px 16px',marginBottom:16,color:'#dc2626',fontSize:13}}>
          <strong>Error:</strong> {error}
          {isShared && error.includes('403') && (
            <div style={{marginTop:6,fontSize:12,color:'#991b1b'}}>
              The app may not have Mail.Read application permission granted in Azure AD. An admin needs to grant consent.
            </div>
          )}
        </div>
      )}

      {loading && !messages && (
        <div style={{display:'flex',alignItems:'center',gap:8,color:'var(--ink-3)',padding:'24px 0',fontSize:13}}>
          <I.spin/> Loading messages...
        </div>
      )}

      {messages && messages.length === 0 && !loading && (
        <div className="empty">No messages in this inbox</div>
      )}

      {messages && messages.length > 0 && (
        <div style={{border:'1px solid var(--line)', borderRadius:8, overflow:'hidden'}}>
          {messages.map((msg, idx) => {
            const isOpen = selected?.id === msg.id;
            const fromName = msg.from?.emailAddress?.name || msg.from?.emailAddress?.address || 'Unknown';
            const fromAddr = msg.from?.emailAddress?.address || '';
            const date = msg.receivedDateTime ? new Date(msg.receivedDateTime).toLocaleString('en-AU', {day:'numeric',month:'short',hour:'2-digit',minute:'2-digit'}) : '';

            return (
              <div key={msg.id}>
                <div
                  onClick={() => openMessage(msg)}
                  style={{
                    display:'grid', gridTemplateColumns:'1fr auto',
                    gap:8, padding:'11px 16px',
                    borderBottom: idx < messages.length - 1 ? '1px solid var(--line)' : 'none',
                    cursor:'pointer',
                    background: isOpen ? 'var(--bg-2)' : (!msg.isRead ? '#f0f9ff' : 'var(--bg)'),
                    transition:'background .1s',
                  }}
                  onMouseEnter={e => { if (!isOpen) e.currentTarget.style.background = 'var(--bg-2)'; }}
                  onMouseLeave={e => { if (!isOpen) e.currentTarget.style.background = !msg.isRead ? '#f0f9ff' : 'var(--bg)'; }}
                >
                  <div style={{minWidth:0}}>
                    <div style={{display:'flex', alignItems:'center', gap:8, marginBottom:3}}>
                      {!msg.isRead && (
                        <span style={{width:6,height:6,borderRadius:'50%',background:'#2563eb',flexShrink:0,display:'inline-block'}}/>
                      )}
                      <span style={{fontWeight: msg.isRead ? 400 : 700, fontSize:13, color:'var(--ink)', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap'}}>
                        {msg.subject || '(no subject)'}
                      </span>
                      {msg.hasAttachments && <span style={{fontSize:11,color:'var(--ink-3)',flexShrink:0}}>📎</span>}
                    </div>
                    <div style={{fontSize:11.5, color:'var(--ink-3)', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap'}}>
                      From: {fromName} {fromAddr && fromName !== fromAddr ? `<${fromAddr}>` : ''}
                    </div>
                    {!isOpen && (
                      <div style={{fontSize:11.5, color:'var(--ink-4)', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', marginTop:2}}>
                        {msg.bodyPreview || ''}
                      </div>
                    )}
                  </div>
                  <div style={{flexShrink:0, textAlign:'right', fontSize:11, color:'var(--ink-3)', fontFamily:'var(--mono)', marginTop:2}}>
                    {date}
                  </div>
                </div>
                {isOpen && (
                  <div style={{padding:'16px 18px', background:'#fafaf9', borderBottom: idx < messages.length - 1 ? '1px solid var(--line)' : 'none'}}>
                    <div style={{fontSize:11, color:'var(--ink-3)', marginBottom:10, fontFamily:'var(--mono)'}}>
                      From: {fromName} &lt;{fromAddr}&gt; · {date}
                    </div>
                    {loadBody
                      ? <div style={{color:'var(--ink-3)', fontSize:13}}>Loading...</div>
                      : <div
                          style={{fontSize:13, lineHeight:1.65, maxHeight:400, overflowY:'auto',
                            background:'#fff', border:'1px solid var(--line)', borderRadius:6,
                            padding:'12px 16px', whiteSpace:'pre-wrap'}}
                          dangerouslySetInnerHTML={{__html: body || msg.bodyPreview || '(no preview)'}}
                        />
                    }
                  </div>
                )}
              </div>
            );
          })}
        </div>
      )}

      {messages && messages.length === PAGE && (
        <div style={{marginTop:12, display:'flex', gap:8}}>
          {skip > 0 && (
            <button className="btn ghost sm" onClick={() => load(Math.max(0, skip - PAGE))}>← Prev {PAGE}</button>
          )}
          <button className="btn ghost sm" onClick={() => load(skip + PAGE)}>Next {PAGE} →</button>
        </div>
      )}
    </div>
  );
}

// ---- Tender View - 3-panel quoting interface ----
function TenderView({ onCreateOpportunity }) {
  // Expose bust-refresh to window so Refresh All button can call it without remounting
  useEffect(() => {
    window._tenderBustRefresh = () => loadFolders(false, true);
    return () => { delete window._tenderBustRefresh; };
  });

  const [folders,         setFolders]         = useState([]);
  const [foldersLoading,  setFoldersLoading]  = useState(true);
  const [foldersError,    setFoldersError]    = useState(null);
  const [selectedFolder,  setSelectedFolder]  = useState(null);
  const [messages,        setMessages]        = useState([]);
  const [msgsLoading,     setMsgsLoading]     = useState(false);
  const [selectedMsg,     setSelectedMsg]     = useState(null);
  const [msgBody,         setMsgBody]         = useState(null);
  const [msgBodyLoading,  setMsgBodyLoading]  = useState(false);
  const [classification,  setClassification]  = useState(null);
  const [classifying,     setClassifying]     = useState(false);
  const [expandedFolders, setExpandedFolders] = useState({});
  const [moveTarget,      setMoveTarget]      = useState(null);
  const [moveDropOpen,    setMoveDropOpen]    = useState(false);
  const [folderSearch,    setFolderSearch]    = useState('');
  const [moving,          setMoving]          = useState(false);
  const [toastMsg,        setToastMsg]        = useState(null);
  const [newFolderFor,    setNewFolderFor]    = useState(null);
  const [newFolderName,   setNewFolderName]   = useState('');
  const [creatingFolder,  setCreatingFolder]  = useState(false);
  const [rightPanelOpen,    setRightPanelOpen]    = useState(true);
  const [scoringOpportunity, setScoringOpportunity] = useState(false);
  const [oppMsgIds, setOppMsgIds] = useState(() => window._oppMessageIds || new Set());

  // Keep oppMsgIds in sync when window._oppMessageIds changes (after OpportunitiesView loads)
  useEffect(() => {
    const interval = setInterval(() => {
      if (window._oppMessageIds) setOppMsgIds(new Set(window._oppMessageIds));
    }, 3000);
    return () => clearInterval(interval);
  }, []);

  const isInOpportunities = (msg) => {
    if (!msg) return false;
    return oppMsgIds.has(msg.id) || oppMsgIds.has(msg.internetMessageId);
  };
  const [detailTab,          setDetailTab]          = useState('email'); // 'email' | 'analysis' | 'actions'
  const [pendingJump,        setPendingJump]        = useState(null); // { conversationId, subject }
  // Folder context menu
  const [ctxMenu,         setCtxMenu]         = useState(null); // { folder, x, y }
  const [renameFolder,    setRenameFolder]    = useState(null); // folder being renamed
  const [renameName,      setRenameName]      = useState('');
  const [movingFolder,    setMovingFolder]    = useState(null); // folder being moved
  const [moveFolderTo,    setMoveFolderTo]    = useState(null); // target parent
  const [folderWorking,   setFolderWorking]   = useState(false);
  // Thread grouping
  const [expandedThreads,   setExpandedThreads]   = useState({}); // conversationId → bool
  const [threadMessages,    setThreadMessages]     = useState({}); // conversationId → full msg array (incl. sent)
  const [threadLoading,     setThreadLoading]      = useState({}); // conversationId → bool
  const [prevMsgIds,        setPrevMsgIds]         = useState(new Set()); // track known message IDs for 🔔 new reply detection

  const PORTAL_COLORS = {
    'VENDOR PANEL':        '#2563eb',
    'ePROCURE':            '#16a34a',
    'FELIX':               '#ea580c',
    'AUSTRALIAN TENDERS':  '#7c3aed',
    'CONSOLIDATED TENDERS':'#d97706',
    'TENDERS VIC':         '#0891b2',
    'TENDER SEARCH':       '#6b7280',
  };

  function portalColor(portal) { return PORTAL_COLORS[portal] || '#6b7280'; }

  function detectPortal(from, subject) {
    const f = (from || '').toLowerCase(), s = (subject || '').toLowerCase();
    if (f.includes('vendorpanel') || s.includes('vendor panel') || s.includes('vendorpanel')) return 'VENDOR PANEL';
    if (f.includes('eprocure') || s.includes('eprocure')) return 'ePROCURE';
    if (f.includes('felix') || s.includes('felix')) return 'FELIX';
    if (f.includes('australiantenders') || f.includes('tenders.net.au')) return 'AUSTRALIAN TENDERS';
    if (f.includes('consolidatedtenders')) return 'CONSOLIDATED TENDERS';
    if (f.includes('tendersvic') || f.includes('vic.gov.au')) return 'TENDERS VIC';
    return 'TENDER SEARCH';
  }

  function flattenFolders(list) {
    const out = [];
    function walk(folders, depth, parentFid, parentName, ancestorNames) {
      for (const f of (folders || [])) {
        const path = [...(ancestorNames || []), f.displayName];
        out.push({ ...f, depth, parentFid, parentName, pathLabel: path.join(' › ') });
        if ((f.childFolders || []).length > 0) {
          walk(f.childFolders, depth + 1, f.id, f.displayName, path);
        }
      }
    }
    walk(list, 0, null, null, []);
    return out;
  }

  // Group flat message array into threads using conversationId
  // Falls back to normalised subject if conversationId is missing
  function groupIntoThreads(msgs) {
    const map = new Map(); // key → { key, messages[] }
    for (const msg of msgs) {
      // Use conversationId as primary key; fall back to normalised subject
      const key = msg.conversationId ||
        (msg.subject || '').replace(/^((Re|RE|Fwd?|FWD?):\s*)+/g, '').toLowerCase().trim().replace(/\s+/g, ' ');
      if (!map.has(key)) map.set(key, { key, messages: [] });
      map.get(key).messages.push(msg);
    }
    // Sort messages within each thread oldest-first, threads newest-first
    const threads = [...map.values()].map(t => ({
      ...t,
      messages: [...t.messages].sort((a, b) => new Date(a.receivedDateTime) - new Date(b.receivedDateTime)),
    }));
    threads.sort((a, b) => {
      const latestA = Math.max(...a.messages.map(m => new Date(m.receivedDateTime)));
      const latestB = Math.max(...b.messages.map(m => new Date(m.receivedDateTime)));
      return latestB - latestA;
    });
    return threads;
  }

  function getDaysUntil(dateStr) {
    if (!dateStr) return null;
    return Math.round((new Date(dateStr) - new Date()) / 86400000);
  }

  function showToast(msg) {
    setToastMsg(msg);
    setTimeout(() => setToastMsg(null), 3500);
  }

  // Load folders - called on mount and after move/create to keep tree in sync
  const loadFolders = useCallback(async (selectInbox = false, bustCache = false) => {
    setFoldersLoading(true);
    setFoldersError(null);
    try {
      const data = await api.get('/api/tenders/folders' + (bustCache ? '?bust=1' : ''));
      const f = data.folders || [];
      setFolders(f);
      // Auto-expand folders that contain 'TENDER' or 'PORTAL' in their name
      const defaults = {};
      const expand = (list) => {
        for (const folder of list) {
          const n = folder.displayName?.toUpperCase() || '';
          if (n.includes('TENDER') || n.includes('PORTAL')) defaults[folder.id] = true;
          if ((folder.childFolders || []).length > 0) expand(folder.childFolders);
        }
      };
      expand(f);
      setExpandedFolders(prev => ({ ...prev, ...defaults }));
      if (selectInbox) {
        const inbox = f.find(x => x.displayName?.toLowerCase() === 'inbox');
        if (inbox) { setSelectedFolder(inbox); loadMsgs(inbox.id); }
      }
    } catch (e) {
      setFoldersError(e.message);
    } finally {
      setFoldersLoading(false);
    }
  }, [loadMsgs]);

  useEffect(() => {
    const jump = window._pendingConversationJump;
    if (jump) {
      delete window._pendingConversationJump;
      setPendingJump(jump);
    }
    loadFolders(true);
  }, []);

  const loadMsgs = useCallback(async (folderId) => {
    setMsgsLoading(true);
    setSelectedMsg(null); setMsgBody(null); setClassification(null); setMoveTarget(null);
    setThreadMessages({}); // clear stale cross-folder cache on folder switch
    try {
      const folderName = encodeURIComponent(selectedFolder?.displayName || folderId);
      const d = await api.get(`/api/tenders/folder-messages?folderId=${encodeURIComponent(folderId)}&top=100&folderName=${folderName}`);
      const incoming = d.messages || [];
      // Track new replies: any message whose ID wasn't in the previous load
      setPrevMsgIds(prev => {
        const newIds = new Set(incoming.map(m => m.id));
        // Mark threads that gained new messages since last load
        if (prev.size > 0) {
          const newArrivals = incoming.filter(m => !prev.has(m.id));
          if (newArrivals.length > 0) {
            // Auto-expand threads with new arrivals
            setExpandedThreads(et => {
              const next = { ...et };
              newArrivals.forEach(m => { if (m.conversationId) next[m.conversationId] = true; });
              return next;
            });
          }
        }
        return newIds;
      });
      setMessages(incoming);
      // Pre-fetch cross-folder conversations for all unique conversationIds
      // This ensures sent replies show up immediately without waiting for a click
      const convIds = [...new Set(incoming.map(m => m.conversationId).filter(Boolean))];
      // Fire off conversation loads in background (non-blocking)
      convIds.forEach(cid => {
        setTimeout(() => loadConversation(cid), 100);
      });
    } catch (e) {
      setMessages([]);
    } finally {
      setMsgsLoading(false);
    }
  }, [loadConversation]);

  // Load full cross-folder conversation (inbox + sent + all subfolders)
  const loadConversation = useCallback(async (conversationId, force = false) => {
    if (!conversationId) return;
    // Skip if already loaded with 2+ messages (don't re-fetch unnecessarily)
    // But always load if not yet loaded, or if forced, or if we only have 1 msg (may be stale/partial)
    const existing = threadMessages[conversationId];
    if (existing && existing.length >= 2 && !force) return;
    setThreadLoading(prev => ({ ...prev, [conversationId]: true }));
    try {
      const d = await api.get(`/api/tenders/conversation/${encodeURIComponent(conversationId)}`);
      const msgs = d.messages || [];
      if (msgs.length > 0) {
        setThreadMessages(prev => ({ ...prev, [conversationId]: msgs }));
      }
    } catch (e) {
      console.warn('loadConversation failed:', e.message);
      // Non-fatal - fall back to folder-only messages
    } finally {
      setThreadLoading(prev => ({ ...prev, [conversationId]: false }));
    }
  }, [threadMessages]);

  // When messages load and there's a pending jump, auto-select the matching thread
  useEffect(() => {
    if (!pendingJump || messages.length === 0) return;
    const { conversationId } = pendingJump;
    const threadMsgs = messages.filter(m => m.conversationId === conversationId);
    if (threadMsgs.length > 0) {
      const latest = [...threadMsgs].sort((a,b) => new Date(b.receivedDateTime) - new Date(a.receivedDateTime))[0];
      selectMessage(latest);
      setExpandedThreads(prev => ({ ...prev, [conversationId]: true }));
      loadConversation(conversationId, true);
      setPendingJump(null);
    }
    // If not found in current folder, the user needs to navigate to the right folder
    // We still clear the jump to avoid infinite loop
    else if (!msgsLoading) {
      showToast('⚠️ Email may be in a different folder - check Follow Up folders');
      setPendingJump(null);
    }
  }, [messages, pendingJump, msgsLoading]);

  const selectFolder = useCallback((folder) => {
    setSelectedFolder(folder);
    loadMsgs(folder.id);
  }, [loadMsgs]);

  const selectMessage = async (msg) => {
    if (selectedMsg?.id === msg.id) return;
    setSelectedMsg(msg);
    setMsgBody(null); setMsgBodyLoading(true); setClassification(null); setMoveTarget(null);
    try {
      const d = await api.get(`/api/mailbox/message/${encodeURIComponent(msg.id)}?mailbox=shared:tenders`);
      const html = d.body || '';
      setMsgBody(html);
      // AI classify in background
      setClassifying(true);
      const bodyText = html.replace(/<[^>]+>/g, ' ').slice(0, 3000);
      api.post('/api/tenders/ai-classify', {
        messageId: msg.id,
        subject: msg.subject,
        body: bodyText,
        from: msg.from?.emailAddress?.address || '',
      }).then(result => {
        // Resolve folder IDs from the tree
        const all = flattenFolders(folders);
        const portalFolder = all.find(f =>
          f.displayName?.toUpperCase() === result.suggestedFolder?.toUpperCase()
        );
        const subfolderMatch = portalFolder
          ? all.find(f => f.parentFid === portalFolder.id && f.displayName === result.subFolder)
          : null;
        const enriched = {
          ...result,
          suggestedFolderId: subfolderMatch?.id || portalFolder?.id || null,
        };
        setClassification(enriched);
        if (subfolderMatch) setMoveTarget(subfolderMatch);
        else if (portalFolder) setMoveTarget(portalFolder);
      }).catch(() => {}).finally(() => setClassifying(false));
    } catch (e) {
      setMsgBody('(Could not load email body)');
    } finally {
      setMsgBodyLoading(false);
    }
  };

  const moveMessage = async (targetFolder) => {
    if (!selectedMsg || !targetFolder || moving) return;
    setMoving(true);
    try {
      await api.post('/api/tenders/move', {
        messageId:        selectedMsg.id,
        targetFolderId:   targetFolder.id,
        targetFolderName: targetFolder.pathLabel || targetFolder.displayName,
        subject:          selectedMsg.subject || '(no subject)',
        from:             selectedMsg.from?.emailAddress?.address || '',
        fromFolderName:   selectedFolder?.displayName || null,
      });
      showToast(`Moved → ${targetFolder.pathLabel || targetFolder.displayName}`);
      setMessages(prev => prev.filter(m => m.id !== selectedMsg.id));
      setSelectedMsg(null); setMsgBody(null); setClassification(null); setMoveDropOpen(false);
      // Refresh folder tree to reflect updated unread counts
      loadFolders(false);
    } catch (e) {
      showToast('Move failed: ' + e.message);
    } finally {
      setMoving(false);
    }
  };

  const moveToSubfolder = async (subfolderName) => {
    if (!selectedMsg || !classification) return;
    const all = flattenFolders(folders);
    const portal = classification.sourcePortal || 'TENDER SEARCH';
    const portalFolder = all.find(f => f.displayName?.toUpperCase() === portal.toUpperCase());
    if (!portalFolder) { showToast(`Folder "${portal}" not found`); return; }
    const sub = all.find(f => f.parentFid === portalFolder.id && f.displayName === subfolderName);
    if (!sub) { showToast(`"${subfolderName}" subfolder not found under ${portal}`); return; }
    await moveMessage(sub);
  };

  const createFolder = async () => {
    if (!newFolderName.trim() || !newFolderFor || creatingFolder) return;
    setCreatingFolder(true);
    try {
      await api.post('/api/tenders/create-folder', { parentFolderId: newFolderFor, name: newFolderName.trim() });
      await loadFolders(false);
      showToast(`Folder "${newFolderName}" created`);
      setNewFolderFor(null); setNewFolderName('');
    } catch (e) {
      showToast('Create failed: ' + e.message);
    } finally {
      setCreatingFolder(false);
    }
  };

  // ---- Folder management actions ----
  const doRenameFolder = async () => {
    if (!renameFolder || !renameName.trim() || folderWorking) return;
    setFolderWorking(true);
    try {
      await api.patch(`/api/tenders/folder/${encodeURIComponent(renameFolder.id)}`, { name: renameName.trim() });
      showToast(`Renamed → "${renameName.trim()}"`);
      setRenameFolder(null); setRenameName('');
      await loadFolders(false);
    } catch (e) { showToast('Rename failed: ' + e.message); }
    finally { setFolderWorking(false); }
  };

  const doDeleteFolder = async (folder) => {
    if (!folder || folderWorking) return;
    setFolderWorking(true);
    try {
      await api.delete(`/api/tenders/folder/${encodeURIComponent(folder.id)}`);
      showToast(`"${folder.displayName}" moved to Deleted Items`);
      if (selectedFolder?.id === folder.id) { setSelectedFolder(null); setMessages([]); }
      await loadFolders(false);
    } catch (e) { showToast('Delete failed: ' + e.message); }
    finally { setFolderWorking(false); }
  };

  const doMoveFolder = async () => {
    if (!movingFolder || !moveFolderTo || folderWorking) return;
    setFolderWorking(true);
    try {
      await api.post(`/api/tenders/folder/${encodeURIComponent(movingFolder.id)}/move`, { destinationId: moveFolderTo.id });
      showToast(`"${movingFolder.displayName}" moved under "${moveFolderTo.displayName}"`);
      setMovingFolder(null); setMoveFolderTo(null);
      await loadFolders(false);
    } catch (e) { showToast('Move failed: ' + e.message); }
    finally { setFolderWorking(false); }
  };

  // Close context menu on outside click
  React.useEffect(() => {
    if (!ctxMenu) return;
    const handler = () => setCtxMenu(null);
    window.addEventListener('click', handler);
    return () => window.removeEventListener('click', handler);
  }, [ctxMenu]);

  // Folder tree node (recursive)
  function FolderNode({ folder, depth }) {
    const isSelected = selectedFolder?.id === folder.id;
    const hasChildren = (folder.childFolders || []).length > 0;
    const isExpanded = !!expandedFolders[folder.id];
    const [hov, setHov] = useState(false);

    return (
      <div>
        <div
          style={{
            display:'flex', alignItems:'center', gap:4,
            padding:`5px ${8 + depth * 14}px 5px ${8 + depth * 14}px`,
            cursor:'pointer', borderRadius:4, margin:'1px 6px',
            background: isSelected ? 'rgba(255,255,255,0.14)' : hov ? 'rgba(255,255,255,0.06)' : 'transparent',
          }}
          onMouseEnter={() => setHov(true)}
          onMouseLeave={() => setHov(false)}
          onClick={() => {
            if (hasChildren) setExpandedFolders(prev => ({ ...prev, [folder.id]: !prev[folder.id] }));
            selectFolder(folder);
          }}
          onContextMenu={e => {
            e.preventDefault(); e.stopPropagation();
            setCtxMenu({ folder, x: e.clientX, y: e.clientY });
          }}
        >
          <span style={{ width:10, flexShrink:0, fontSize:8, color:'rgba(255,255,255,0.4)' }}>
            {hasChildren ? (isExpanded ? '▼' : '▶') : ''}
          </span>
          <span style={{
            flex:1, fontSize:12, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap',
            color: isSelected ? '#fff' : 'rgba(255,255,255,0.72)',
            fontWeight: isSelected ? 600 : 400,
          }}>
            {renameFolder?.id === folder.id
              ? <input
                  autoFocus
                  value={renameName}
                  onChange={e => setRenameName(e.target.value)}
                  onKeyDown={e => { if(e.key==='Enter') doRenameFolder(); if(e.key==='Escape'){setRenameFolder(null);setRenameName('');} }}
                  onClick={e => e.stopPropagation()}
                  style={{ all:'unset', width:'100%', background:'rgba(255,255,255,0.15)', borderRadius:3,
                    padding:'1px 4px', color:'#fff', fontSize:12, outline:'1px solid rgba(255,255,255,0.4)' }}
                />
              : folder.displayName
            }
          </span>
          {folder.unreadItemCount > 0 && renameFolder?.id !== folder.id && (
            <span style={{
              fontSize:9.5, fontWeight:700, padding:'1px 5px', borderRadius:10, flexShrink:0,
              background: isSelected ? 'rgba(255,255,255,0.25)' : 'rgba(255,255,255,0.15)',
              color: isSelected ? '#fff' : 'rgba(255,255,255,0.7)',
            }}>{folder.unreadItemCount}</span>
          )}
          {hov && renameFolder?.id !== folder.id && (
            <button
              onClick={e => { e.stopPropagation(); setCtxMenu({ folder, x: e.currentTarget.getBoundingClientRect().right, y: e.currentTarget.getBoundingClientRect().bottom }); }}
              style={{ all:'unset', cursor:'pointer', fontSize:11, color:'rgba(255,255,255,0.45)', padding:'0 3px', lineHeight:1 }}
              title="Folder options"
            >⋯</button>
          )}
        </div>
        {hasChildren && isExpanded && (
          <div>
            {(folder.childFolders || []).map(cf => (
              <FolderNode key={cf.id} folder={cf} depth={depth + 1} />
            ))}
          </div>
        )}
      </div>
    );
  }

  function DueDateBadge({ dueDate }) {
    const days = getDaysUntil(dueDate);
    if (days === null) return null;
    const color = days < 0 ? '#7f1d1d' : days <= 1 ? '#dc2626' : days <= 5 ? '#ea580c' : '#6b7280';
    const label = days < 0 ? 'Overdue' : days === 0 ? 'Due today' : `Due in ${days}d`;
    return (
      <span style={{ fontSize:10, fontWeight:700, padding:'2px 6px', borderRadius:4, background:color+'22', color, border:`1px solid ${color}44` }}>
        ⏰ {label}
      </span>
    );
  }

  const allFlat = flattenFolders(folders);

  // ---- Context menu for folder operations ----
  const FolderCtxMenu = ctxMenu ? (() => {
    const f = ctxMenu.folder;
    const isSystemFolder = ['inbox','drafts','sent items','deleted items','junk email','outbox','archive','conversation history'].includes(f.displayName?.toLowerCase());
    return (
      <div
        onClick={e => e.stopPropagation()}
        style={{
          position:'fixed', left: Math.min(ctxMenu.x, window.innerWidth - 180), top: Math.min(ctxMenu.y, window.innerHeight - 200),
          width:168, background:'#1e1e1c', border:'1px solid rgba(255,255,255,0.15)',
          borderRadius:8, boxShadow:'0 6px 24px rgba(0,0,0,0.5)', zIndex:1000, overflow:'hidden',
          fontSize:12.5,
        }}
      >
        <div style={{ padding:'7px 12px 5px', fontSize:10, fontWeight:700, color:'rgba(255,255,255,0.3)', textTransform:'uppercase', letterSpacing:'0.07em', borderBottom:'1px solid rgba(255,255,255,0.08)' }}>
          {f.displayName.length > 20 ? f.displayName.slice(0,18)+'...' : f.displayName}
        </div>
        {[{
          label:'📂 New subfolder', action: () => { setNewFolderFor(f.id); setNewFolderName(''); setCtxMenu(null); }
        }, !isSystemFolder && {
          label:'✏️ Rename', action: () => { setRenameFolder(f); setRenameName(f.displayName); setCtxMenu(null); }
        }, {
          label:'📁 Move to...', action: () => { setMovingFolder(f); setMoveFolderTo(null); setCtxMenu(null); }
        }, !isSystemFolder && {
          label:'🗑️ Delete', action: () => {
            if (window.confirm(`Delete "${f.displayName}"? It will move to Deleted Items in Outlook and can be recovered.`)) {
              setCtxMenu(null);
              doDeleteFolder(f);
            }
          }, danger: true
        }].filter(Boolean).map((item, i) => (
          <button key={i}
            onClick={() => item.action()}
            style={{
              all:'unset', display:'block', width:'100%', boxSizing:'border-box',
              padding:'8px 14px', cursor:'pointer', color: item.danger ? '#f87171' : 'rgba(255,255,255,0.82)',
              borderBottom:'1px solid rgba(255,255,255,0.06)',
            }}
            onMouseEnter={e => e.currentTarget.style.background='rgba(255,255,255,0.08)'}
            onMouseLeave={e => e.currentTarget.style.background='transparent'}
          >{item.label}</button>
        ))}
      </div>
    );
  })() : null;

  // ---- Move folder modal ----
  const MoveFolderModal = movingFolder ? (
    <div style={{
      position:'fixed', inset:0, background:'rgba(0,0,0,0.55)', zIndex:900,
      display:'flex', alignItems:'center', justifyContent:'center',
    }} onClick={() => { setMovingFolder(null); setMoveFolderTo(null); }}>
      <div style={{
        background:'#fff', borderRadius:12, padding:'22px 24px', width:340, maxHeight:'70vh',
        boxShadow:'0 8px 32px rgba(0,0,0,0.25)', overflow:'hidden', display:'flex', flexDirection:'column',
      }} onClick={e => e.stopPropagation()}>
        <div style={{ fontWeight:700, fontSize:14, marginBottom:4 }}>Move "{movingFolder.displayName}"</div>
        <div style={{ fontSize:12, color:'#6b7280', marginBottom:12 }}>Choose a destination folder:</div>
        <div style={{ flex:1, overflowY:'auto', border:'1px solid #e5e7eb', borderRadius:6, marginBottom:14 }}>
          {allFlat
            .filter(f => f.id !== movingFolder.id && f.parentFid !== movingFolder.id)
            .map(f => (
              <button key={f.id}
                onClick={() => setMoveFolderTo(f)}
                style={{
                  all:'unset', display:'block', width:'100%', boxSizing:'border-box',
                  padding:`7px ${10 + (f.depth||0)*14}px`, fontSize:12, cursor:'pointer',
                  background: moveFolderTo?.id === f.id ? '#eff6ff' : 'transparent',
                  color: moveFolderTo?.id === f.id ? '#1d4ed8' : '#374151',
                  fontWeight: moveFolderTo?.id === f.id ? 600 : 400,
                  borderBottom:'1px solid #f3f4f6',
                }}
                onMouseEnter={e => { if(moveFolderTo?.id!==f.id) e.currentTarget.style.background='#f9fafb'; }}
                onMouseLeave={e => { if(moveFolderTo?.id!==f.id) e.currentTarget.style.background='transparent'; }}
              >
                {f.depth > 0 && <span style={{ opacity:0.3, marginRight:4 }}>{' '.repeat(f.depth*2)}└</span>}
                {f.displayName}
              </button>
            ))
          }
        </div>
        <div style={{ display:'flex', gap:8 }}>
          <button className="btn ghost sm" style={{ flex:1 }} onClick={() => { setMovingFolder(null); setMoveFolderTo(null); }}>Cancel</button>
          <button className="btn primary sm" style={{ flex:1 }}
            disabled={!moveFolderTo || folderWorking}
            onClick={doMoveFolder}
          >{folderWorking ? 'Moving...' : 'Move here'}</button>
        </div>
      </div>
    </div>
  ) : null;

  return (
    <div style={{ display:'flex', margin:'-20px -28px -40px', height:'calc(100vh - 52px)', overflow:'hidden' }}>

      {/* LEFT: Folder tree */}
      <div style={{
        width:240, flexShrink:0, display:'flex', flexDirection:'column',
        background:'#1a1a18', borderRight:'1px solid rgba(255,255,255,0.07)',
        height:'100%', overflow:'hidden',
      }}>
        <div style={{ padding:'11px 12px 9px', borderBottom:'1px solid rgba(255,255,255,0.08)', flexShrink:0, display:'flex', alignItems:'center', justifyContent:'space-between' }}>
          <div>
            <div style={{ fontSize:10.5, fontWeight:700, color:'rgba(255,255,255,0.45)', textTransform:'uppercase', letterSpacing:'0.08em' }}>Tender Folders</div>
            <div style={{ fontSize:9.5, color:'rgba(255,255,255,0.28)', marginTop:1, fontFamily:'var(--mono)' }}>tenders@eliteroads.com.au</div>
          </div>
          <button
            onClick={() => loadFolders(false)}
            disabled={foldersLoading}
            title="Sync folders from Outlook"
            style={{ all:'unset', cursor:'pointer', padding:'4px 6px', borderRadius:4, color:'rgba(255,255,255,0.35)', fontSize:13, lineHeight:1,
              background:'rgba(255,255,255,0.05)', border:'1px solid rgba(255,255,255,0.1)' }}
          >{foldersLoading ? '↻' : '↻'}</button>
        </div>

        <div style={{ flex:1, overflowY:'auto', paddingTop:6 }}>
          {foldersLoading && <div style={{ padding:'14px 16px', color:'rgba(255,255,255,0.35)', fontSize:12 }}>Loading folders...</div>}
          {foldersError && <div style={{ padding:'12px 14px', color:'#f87171', fontSize:11 }}>Error: {foldersError}</div>}
          {folders.map(f => <FolderNode key={f.id} folder={f} depth={0} />)}
        </div>

        {/* New folder input */}
        {newFolderFor && (
          <div style={{ padding:'10px 12px', borderTop:'1px solid rgba(255,255,255,0.08)', background:'rgba(255,255,255,0.03)', flexShrink:0 }}>
            <div style={{ fontSize:10.5, color:'rgba(255,255,255,0.45)', marginBottom:5 }}>New subfolder name:</div>
            <input
              autoFocus
              value={newFolderName}
              onChange={e => setNewFolderName(e.target.value)}
              onKeyDown={e => { if (e.key==='Enter') createFolder(); if (e.key==='Escape') setNewFolderFor(null); }}
              style={{
                width:'100%', boxSizing:'border-box', background:'rgba(255,255,255,0.1)',
                border:'1px solid rgba(255,255,255,0.2)', borderRadius:4,
                color:'#fff', padding:'5px 8px', fontSize:12, outline:'none',
              }}
              placeholder="Folder name..."
            />
            <div style={{ display:'flex', gap:5, marginTop:6 }}>
              <button
                onClick={createFolder}
                disabled={!newFolderName.trim() || creatingFolder}
                style={{ flex:1, background:'#3b82f6', color:'#fff', border:'none', borderRadius:4, padding:'4px 0', fontSize:11, fontWeight:600, cursor:'pointer' }}
              >{creatingFolder ? 'Creating...' : 'Create'}</button>
              <button
                onClick={() => setNewFolderFor(null)}
                style={{ background:'transparent', color:'rgba(255,255,255,0.45)', border:'1px solid rgba(255,255,255,0.18)', borderRadius:4, padding:'4px 7px', fontSize:11, cursor:'pointer' }}
              >✕</button>
            </div>
          </div>
        )}
      </div>

      {/* MIDDLE: Message list */}
      <div style={{ width:340, flexShrink:0, display:'flex', flexDirection:'column', borderRight:'1px solid var(--line)', height:'100%', overflow:'hidden', background:'var(--bg)' }}>
        {/* Header */}
        {/* Header */}
        <div style={{ padding:'11px 14px 10px', borderBottom:'1px solid var(--line)', flexShrink:0, display:'flex', alignItems:'center', justifyContent:'space-between' }}>
          <div>
            <div style={{ fontSize:12.5, fontWeight:700, color:'var(--ink)' }}>{selectedFolder?.displayName || 'Select a folder'}</div>
            {messages.length > 0 && (() => {
              const threads = groupIntoThreads(messages);
              return <div style={{ fontSize:10.5, color:'var(--ink-3)', marginTop:1 }}>
                {threads.length} thread{threads.length!==1?'s':''} &middot; {messages.length} email{messages.length!==1?'s':''}
              </div>;
            })()}
          </div>
          <button className="btn ghost sm" onClick={() => selectedFolder && loadMsgs(selectedFolder.id)} disabled={msgsLoading}>
            {msgsLoading ? <I.spin/> : <I.refresh/>}
          </button>
        </div>

        <div style={{ flex:1, overflowY:'auto' }}>
          {msgsLoading && (
            <div style={{ padding:'20px 16px', color:'var(--ink-3)', fontSize:12, display:'flex', alignItems:'center', gap:8 }}>
              <I.spin/> Loading...
            </div>
          )}
          {!msgsLoading && messages.length === 0 && (
            <div style={{ padding:'32px 16px', textAlign:'center', color:'var(--ink-4)', fontSize:13 }}>No emails in this folder</div>
          )}

          {/* Threaded message list - threads sorted newest-first */}
          {!msgsLoading && groupIntoThreads(messages).map(thread => {
            // Use cross-folder messages if already loaded (includes sent)
            const fullThread  = threadMessages[thread.key];
            const allMsgs     = fullThread || thread.messages;
            const latest      = allMsgs[allMsgs.length - 1];
            const isMulti     = allMsgs.length > 1 || (thread.messages.length > 1);
            const isExpanded  = !!expandedThreads[thread.key];
            const hasUnread   = thread.messages.some(m => !m.isRead);
            const hasSelMsg   = allMsgs.some(m => m.id === selectedMsg?.id);
            const portal      = detectPortal(thread.messages[0]?.from?.emailAddress?.address || '', thread.messages[0]?.subject || '');
            const latestDate  = latest?.receivedDateTime
              ? new Date(latest.receivedDateTime).toLocaleDateString('en-AU', { day:'numeric', month:'short' })
              : '';
            // All unique senders (including sent)
            const senderNames = [...new Set(allMsgs.map(m => {
              const addr = m.from?.emailAddress?.address || '';
              const isSentByUs = addr.toLowerCase().includes('eliteroads');
              return isSentByUs ? 'Elite Roads' : (m.from?.emailAddress?.name || addr || 'Unknown');
            }))];
            const hasAttach   = allMsgs.some(m => m.hasAttachments);
            const totalCount  = fullThread ? fullThread.length : thread.messages.length;
            // Clean subject (strip Re:/Fwd:)
            const cleanSubject = (thread.messages[0]?.subject || '(no subject)').replace(/^((Re|RE|Fwd?|FWD?):\s*)+/g, '');

            return (
              <div key={thread.key} style={{ borderBottom:'1px solid var(--line)' }}>

                {/* Thread header row */}
                <div
                  onClick={() => {
                    if (isMulti) {
                      const opening = !expandedThreads[thread.key];
                      setExpandedThreads(prev => ({ ...prev, [thread.key]: opening }));
                      if (opening && thread.key) loadConversation(thread.key);
                      if (!opening && hasSelMsg) { setSelectedMsg(null); setMsgBody(null); setClassification(null); }
                      // Always open the latest email in the thread when clicking the header
                      const latestMsg = allMsgs[allMsgs.length - 1] || thread.messages[thread.messages.length - 1];
                      if (latestMsg) selectMessage(latestMsg);
                    } else {
                      if (thread.key) loadConversation(thread.key);
                      selectMessage(thread.messages[0]);
                    }
                  }}
                  style={{
                    padding:'9px 14px', cursor:'pointer',
                    background: hasSelMsg && !isMulti ? '#eff6ff' : hasSelMsg ? '#f8faff' : hasUnread ? '#f0f9ff' : 'var(--bg)',
                    borderLeft: hasSelMsg ? '3px solid #2563eb' : hasUnread ? '3px solid #93c5fd' : '3px solid transparent',
                  }}
                >
                  <div style={{ display:'flex', alignItems:'flex-start', gap:5, marginBottom:3 }}>
                    {/* Unread dot or expand arrow */}
                    <span style={{ width:14, flexShrink:0, marginTop:3, display:'flex', alignItems:'center', justifyContent:'center' }}>
                      {isMulti
                        ? <span style={{ fontSize:8, color:'var(--ink-4)' }}>{isExpanded ? '▼' : '▶'}</span>
                        : hasUnread ? <span style={{ width:6, height:6, borderRadius:'50%', background:'#2563eb', display:'block' }}/> : null
                      }
                    </span>
                    <div style={{ flex:1, minWidth:0 }}>
                      <div style={{ fontSize:12.5, fontWeight:hasUnread?700:500, color:'var(--ink)', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
                        {cleanSubject}
                      </div>
                      <div style={{ fontSize:11, color:'var(--ink-3)', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', marginTop:1 }}>
                        {senderNames.slice(0,3).join(', ')}{senderNames.length > 3 ? ` +${senderNames.length-3}` : ''}
                      </div>
                    </div>
                    <div style={{ display:'flex', flexDirection:'column', alignItems:'flex-end', gap:3, flexShrink:0 }}>
                      <div style={{ fontSize:10, color:'var(--ink-4)', fontFamily:'var(--mono)' }}>{latestDate}</div>
                      {isMulti && (
                        <span style={{ fontSize:9.5, fontWeight:700, padding:'1px 5px', borderRadius:10, background:'#e5e7eb', color:'#374151' }}>
                          {totalCount}
                        </span>
                      )}
                    </div>
                  </div>

                  {/* Tags row */}
                  <div style={{ display:'flex', flexWrap:'wrap', gap:4, paddingLeft:19 }}>
                    <span style={{
                      fontSize:9.5, fontWeight:700, padding:'1px 6px', borderRadius:10,
                      background: portalColor(portal)+'20', color: portalColor(portal),
                      border:`1px solid ${portalColor(portal)}44`, letterSpacing:'0.03em',
                    }}>{portal}</span>
                    {hasAttach && <span style={{ fontSize:9.5, fontWeight:700, padding:'2px 7px', borderRadius:10, background:'#ea580c', color:'#fff', border:'1px solid #c2410c' }}>📎 ATTACH</span>}
                    {/* Opportunity signal tag */}
                    {(() => {
                      const SCOPE_KW = ['asphalt','resurfacing','profiling','cold milling','pavement','bitumen','spray seal','line marking','road reconstruction','milling','paving','surfacing','resealing','patching','road works','roadworks','carriageway'];
                      const text = allMsgs.map(m => (m.subject||'') + ' ' + (m.bodyPreview||'')).join(' ').toLowerCase();
                      const matches = SCOPE_KW.filter(kw => text.includes(kw));
                      if (matches.length >= 2) return (
                        <span style={{ fontSize:9.5, fontWeight:700, padding:'2px 7px', borderRadius:10,
                          background:'#dcfce7', color:'#166534', border:'1px solid #bbf7d0' }}>
                          🔥 Strong Opportunity
                        </span>
                      );
                      if (matches.length === 1) return (
                        <span style={{ fontSize:9.5, fontWeight:700, padding:'2px 7px', borderRadius:10,
                          background:'#fef9c3', color:'#854d0e', border:'1px solid #fde68a' }}>
                          👍 Possible Opportunity
                        </span>
                      );
                      return null;
                    })()}
                    {/* In Opportunities badge */}
                    {allMsgs.some(m => oppMsgIds.has(m.id) || oppMsgIds.has(m.internetMessageId)) && (
                      <span style={{ fontSize:9.5, fontWeight:700, padding:'2px 7px', borderRadius:10,
                        background:'#ede9fe', color:'#5b21b6', border:'1px solid #c4b5fd' }}>
                        🎯 In Opportunities
                      </span>
                    )}
                  </div>

                  {/* Latest snippet (single-email threads) */}
                  {!isMulti && latest.bodyPreview && (
                    <div style={{ fontSize:10.5, color:'var(--ink-4)', marginTop:3, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', paddingLeft:19 }}>
                      {latest.bodyPreview.slice(0, 90)}
                    </div>
                  )}
                </div>

                {/* Expanded: full conversation (inbox + sent + all folders) */}
                {isMulti && isExpanded && (() => {
                  // Use cross-folder conversation if loaded, else fall back to folder-only
                  // Sort newest-first so latest email is at the top
                  const rawMsgs = threadMessages[thread.key] || thread.messages;
                  const fullMsgs = [...rawMsgs].sort((a, b) => new Date(b.receivedDateTime) - new Date(a.receivedDateTime));
                  const isLoadingConv = !!threadLoading[thread.key];
                  return (
                    <div style={{ background:'var(--bg-2)', borderTop:'1px solid var(--line)' }}>
                      {isLoadingConv && (
                        <div style={{ padding:'8px 28px', fontSize:11, color:'var(--ink-4)', display:'flex', alignItems:'center', gap:6 }}>
                          <I.spin/> Loading full thread...
                        </div>
                      )}
                      {fullMsgs.map((msg, idx) => {
                        const isSel     = selectedMsg?.id === msg.id;
                        const fromAddr  = msg.from?.emailAddress?.address || '';
                        const isSent    = fromAddr.toLowerCase().includes('eliteroads') || fromAddr.toLowerCase() === 'tenders@eliteroads.com.au';
                        const fromName  = msg.from?.emailAddress?.name || fromAddr || 'Unknown';
                        const dateStr   = msg.receivedDateTime
                          ? new Date(msg.receivedDateTime).toLocaleString('en-AU', { day:'numeric', month:'short', hour:'2-digit', minute:'2-digit' })
                          : '';
                        const isNewest  = idx === 0; // newest-first sort, so index 0 is latest
                        return (
                          <div
                            key={msg.id}
                            onClick={e => { e.stopPropagation(); selectMessage(msg); }}
                            style={{
                              padding:'7px 14px 7px 28px', cursor:'pointer',
                              borderBottom: idx < fullMsgs.length-1 ? '1px solid var(--line)' : 'none',
                              background: isSel ? '#eff6ff' : isSent ? '#f0fdf4' : !msg.isRead ? '#f0f9ff' : 'transparent',
                              borderLeft: isSel ? '3px solid #2563eb' : isSent ? '3px solid #16a34a' : '3px solid transparent',
                            }}
                          >
                            <div style={{ display:'flex', alignItems:'center', gap:6 }}>
                              {!isSent && !msg.isRead && <span style={{ width:5, height:5, borderRadius:'50%', background:'#2563eb', flexShrink:0 }}/>}
                              <div style={{ flex:1, minWidth:0 }}>
                                <div style={{ display:'flex', alignItems:'center', gap:5 }}>
                                  <span style={{ fontSize:11.5, fontWeight: isSent ? 600 : msg.isRead?400:600, color: isSent ? '#15803d' : 'var(--ink)', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', flex:1 }}>
                                    {fromName}
                                  </span>
                                  {isSent && <span style={{ fontSize:9, fontWeight:700, padding:'1px 5px', borderRadius:8, background:'#dcfce7', color:'#166534', flexShrink:0 }}>SENT</span>}
                                  {isNewest && !isSent && <span style={{ fontSize:9, fontWeight:700, padding:'1px 5px', borderRadius:8, background:'#dbeafe', color:'#1d4ed8', flexShrink:0 }}>LATEST</span>}
                                  <span style={{ fontSize:10, color:'var(--ink-4)', fontFamily:'var(--mono)', flexShrink:0 }}>{dateStr}</span>
                                </div>
                                {msg.bodyPreview && (
                                  <div style={{ fontSize:10.5, color: isSent ? '#4ade80' : 'var(--ink-4)', opacity: isSent ? 0.8 : 1, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', marginTop:1 }}>
                                    {msg.bodyPreview.slice(0, 80)}
                                  </div>
                                )}
                              </div>
                              {msg.hasAttachments && <span style={{ fontSize:11 }}>📎</span>}
                            </div>
                          </div>
                        );
                      })}
                    </div>
                  );
                })()}
              </div>
            );
          })}
        </div>
      </div>

      {/* RIGHT: Email detail + AI panel + action bar */}
      <div style={{ flex:1, display:'flex', flexDirection:'column', height:'100%', overflow:'hidden', background:'var(--bg)' }}>
        {!selectedMsg ? (
          <div style={{ flex:1, display:'flex', alignItems:'center', justifyContent:'center', color:'var(--ink-4)', fontSize:13 }}>
            <div style={{ textAlign:'center' }}>
              <div style={{ fontSize:36, marginBottom:10 }}>📋</div>
              <div style={{ fontWeight:600, marginBottom:4 }}>Select a tender to review</div>
              <div style={{ fontSize:11, color:'var(--ink-4)' }}>AI scores and summarises automatically when you open an email</div>
            </div>
          </div>
        ) : (
          <>
            {/* ===== EMAIL HEADER ===== */}
            <div style={{ padding:'12px 16px 10px', borderBottom:'1px solid var(--line)', flexShrink:0, background:'var(--bg)' }}>
              {/* Top row: subject + Open in Outlook */}
              <div style={{ display:'flex', alignItems:'flex-start', gap:8, marginBottom:6 }}>
                <div style={{ flex:1, minWidth:0 }}>
                  <div style={{ fontSize:13.5, fontWeight:700, color:'var(--ink)', lineHeight:1.35, marginBottom:3 }}>
                    {(selectedMsg.subject || '(no subject)').replace(/^((Re|RE|Fwd?|FWD?):\s*)+/g,'')}
                  </div>
                  <div style={{ display:'flex', flexWrap:'wrap', gap:8, fontSize:11, color:'var(--ink-3)' }}>
                    <span>From: <strong style={{ color:'var(--ink-2)' }}>{selectedMsg.from?.emailAddress?.name || selectedMsg.from?.emailAddress?.address}</strong></span>
                    <span style={{ color:'var(--ink-4)', fontFamily:'var(--mono)' }}>
                      {selectedMsg.receivedDateTime ? new Date(selectedMsg.receivedDateTime).toLocaleString('en-AU',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}) : ''}
                    </span>
                  </div>
                </div>
                {/* Open in Outlook button */}
                {(selectedMsg.webLink || selectedMsg.id) && (
                  <button
                    onClick={() => openInOutlook(selectedMsg.webLink, selectedMsg.id, selectedMsg.internetMessageId, 'shared:tenders')}
                    style={{ all:'unset', cursor:'pointer', display:'inline-flex', alignItems:'center', gap:5,
                      padding:'5px 10px', borderRadius:6, border:'1px solid #bfdbfe',
                      background:'#eff6ff', color:'#1d4ed8', fontSize:11, fontWeight:700, flexShrink:0, whiteSpace:'nowrap' }}
                    title="Open this email in Outlook"
                  >
                    <span style={{ fontSize:13 }}>📧</span> Open in Outlook
                  </button>
                )}
              </div>

              {/* Indicator pills row */}
              <div style={{ display:'flex', flexWrap:'wrap', gap:5, marginTop:6 }}>
                {/* In Opportunities badge */}
                {isInOpportunities(selectedMsg) && (
                  <span style={{ fontSize:10, fontWeight:700, padding:'3px 9px', borderRadius:10,
                    background:'#ede9fe', color:'#5b21b6', border:'1px solid #c4b5fd',
                    display:'inline-flex', alignItems:'center', gap:4 }}>
                    🎯 In Opportunities
                  </span>
                )}
                {/* Thread indicator */}
                {(() => {
                  const thread = groupIntoThreads(messages).find(t => t.messages.some(m => m.id === selectedMsg.id));
                  const fullMsgs = (thread && threadMessages[thread.key]) || thread?.messages || [];
                  const total = fullMsgs.length || 1;
                  if (total > 1) return (
                    <span style={{ fontSize:10, fontWeight:700, padding:'2px 8px', borderRadius:8, background:'#eff6ff', color:'#1d4ed8', border:'1px solid #bfdbfe' }}>
                      🗨 {total} emails in thread
                    </span>
                  );
                  return null;
                })()}
                {/* Attachment indicator */}
                {selectedMsg.hasAttachments && (
                  <span style={{ fontSize:10, fontWeight:700, padding:'2px 8px', borderRadius:8, background:'#fff7ed', color:'#c2410c', border:'1px solid #fed7aa' }}>
                    📎 Has attachments
                  </span>
                )}
                {/* AI score pill - shows once classified */}
                {classification && (
                  <span style={{ fontSize:10, fontWeight:700, padding:'2px 8px', borderRadius:8,
                    background: classification.isKeywordMatch ? '#dcfce7' : '#f3f4f6',
                    color: classification.isKeywordMatch ? '#166534' : '#6b7280',
                    border: `1px solid ${classification.isKeywordMatch ? '#bbf7d0' : '#e5e7eb'}` }}>
                    {classification.isKeywordMatch ? '✅ Scope keywords matched' : '⚠️ No scope keywords'}
                  </span>
                )}
                {classification?.confidence >= 0.7 && (
                  <span style={{ fontSize:10, fontWeight:700, padding:'2px 8px', borderRadius:8, background:'#dbeafe', color:'#1e40af', border:'1px solid #bfdbfe' }}>
                    🤖 {Math.round(classification.confidence*100)}% AI confidence
                  </span>
                )}
                {classification?.dueDate && (() => {
                  const days = Math.round((new Date(classification.dueDate) - new Date()) / 86400000);
                  const color = days < 7 ? '#dc2626' : days < 14 ? '#d97706' : '#16a34a';
                  return (
                    <span style={{ fontSize:10, fontWeight:700, padding:'2px 8px', borderRadius:8, background:color+'18', color, border:`1px solid ${color}44` }}>
                      ⏰ {days < 0 ? 'OVERDUE' : days === 0 ? 'Due TODAY' : `Due in ${days}d`}
                    </span>
                  );
                })()}
                {classification?.tenderValue && (
                  <span style={{ fontSize:10, fontWeight:700, padding:'2px 8px', borderRadius:8, background:'#fef3c7', color:'#92400e', border:'1px solid #fde68a' }}>
                    💰 {classification.tenderValue}
                  </span>
                )}
                {classifying && !classification && (
                  <span style={{ fontSize:10, color:'var(--ink-4)', display:'flex', alignItems:'center', gap:4 }}>
                    <I.spin/> AI analysing...
                  </span>
                )}
              </div>

              {/* Detail tabs */}
              <div style={{ display:'flex', gap:0, marginTop:10, borderBottom:'1px solid var(--line)', marginLeft:-16, marginRight:-16, paddingLeft:16 }}>
                {[['email','📧 Email'],['analysis','🤖 AI Analysis'],['actions','⚡ Actions']].map(([k,l]) => (
                  <button key={k} onClick={() => setDetailTab(k)}
                    style={{ all:'unset', cursor:'pointer', padding:'6px 14px', fontSize:11.5, fontWeight: detailTab===k ? 700 : 400,
                      color: detailTab===k ? 'var(--ink)' : 'var(--ink-3)',
                      borderBottom: detailTab===k ? '2px solid #2563eb' : '2px solid transparent',
                      marginBottom:-1,
                    }}>{l}
                    {k==='analysis' && classifying && !classification && <I.spin style={{marginLeft:4}}/>}
                    {k==='analysis' && classification && (
                      <span style={{ marginLeft:5, fontSize:9.5, fontWeight:700, padding:'1px 5px', borderRadius:8,
                        background: classification.isKeywordMatch ? '#dcfce7' : '#f3f4f6',
                        color: classification.isKeywordMatch ? '#166534' : '#9ca3af' }}>
                        {classification.isKeywordMatch ? 'MATCH' : 'WEAK'}
                      </span>
                    )}
                  </button>
                ))}
              </div>
            </div>

            {/* ===== TAB CONTENT ===== */}
            <div style={{ flex:1, overflowY:'auto', padding: detailTab === 'email' ? '0' : '14px 18px' }}>

              {/* EMAIL TAB */}
              {detailTab === 'email' && (
                <div style={{ height:'100%', display:'flex', flexDirection:'column' }}>
                  {msgBodyLoading
                    ? <div style={{ padding:'20px', color:'var(--ink-3)', fontSize:13, display:'flex', alignItems:'center', gap:8 }}><I.spin/> Loading email...</div>
                    : msgBody && msgBody !== '(Could not load email body)' && msgBody.trim().startsWith('<')
                      ? <iframe srcDoc={msgBody} sandbox="allow-same-origin allow-popups" title="Email body"
                          style={{ flex:1, width:'100%', border:'none', background:'#fff', minHeight:300 }} />
                      : <div style={{ flex:1, overflowY:'auto', padding:'14px 18px' }}>
                          {msgBody && msgBody !== '(Could not load email body)'
                            ? <pre style={{whiteSpace:'pre-wrap',fontFamily:'inherit',margin:0,fontSize:13,lineHeight:1.65}}>{msgBody}</pre>
                            : <div style={{color:'#92400e',background:'#fef3c7',border:'1px solid #fde68a',borderRadius:6,padding:'12px 14px',fontSize:12}}>
                                ⚠️ Could not load email body -
                                <button onClick={() => openInOutlook(selectedMsg.webLink, selectedMsg.id, selectedMsg.internetMessageId, 'shared:tenders')}
                                  style={{all:'unset',cursor:'pointer',color:'#2563eb',fontWeight:600,marginLeft:4}}>Open in Outlook ↗</button>
                              </div>}
                        </div>}
                  {classification?.isExternalFollowup && (classification?.externalLinks||[]).length > 0 && (
                    <div style={{ padding:'10px 16px', borderTop:'1px solid var(--line)', background:'#f0f7ff', flexShrink:0 }}>
                      <div style={{ fontSize:10.5, fontWeight:700, color:'#1e40af', marginBottom:6 }}>🔗 Tender Portal Links - open to request documents:</div>
                      <div style={{ display:'flex', flexWrap:'wrap', gap:6 }}>
                        {classification.externalLinks.map((link, i) => (
                          <a key={i} href={link} target="_blank" rel="noopener noreferrer"
                            style={{ fontSize:11.5, padding:'5px 12px', borderRadius:6, textDecoration:'none',
                              background:'#2563eb', color:'#fff', fontWeight:600 }}>Open Portal {i+1} ↗</a>
                        ))}
                      </div>
                    </div>
                  )}
                </div>
              )}

              {/* AI ANALYSIS TAB */}
              {detailTab === 'analysis' && (
                <div>
                  {classifying && !classification && (
                    <div style={{ color:'var(--ink-3)', fontSize:13, display:'flex', alignItems:'center', gap:8, padding:'20px 0' }}><I.spin/> AI analysing this email...</div>
                  )}
                  {!classification && !classifying && (
                    <div style={{ color:'var(--ink-4)', fontSize:13, textAlign:'center', padding:'30px 0' }}>Open an email to see AI analysis</div>
                  )}
                  {classification && (
                    <div>
                      {/* ---- SUMMARY - dominant element ---- */}
                      <div style={{ background:'#f0f7ff', border:'1px solid #bfdbfe', borderRadius:8, padding:'14px 16px', marginBottom:12 }}>
                        <div style={{ fontSize:10.5, fontWeight:700, color:'#1e40af', textTransform:'uppercase', letterSpacing:'0.07em', marginBottom:6 }}>
                          🤖 AI Summary
                        </div>
                        <div style={{ fontSize:13, color:'#1e293b', lineHeight:1.65 }}>
                          {classification.reasoning || <span style={{color:'var(--ink-4)',fontStyle:'italic'}}>No summary available - rescan this email to generate one.</span>}
                        </div>
                      </div>

                      {/* ---- OPPORTUNITY SIGNAL ---- */}
                      <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:12, flexWrap:'wrap' }}>
                        {classification.isKeywordMatch ? (
                          <span style={{ fontSize:11, fontWeight:700, padding:'4px 10px', borderRadius:8,
                            background:'#dcfce7', color:'#166534', border:'1px solid #bbf7d0' }}>
                            🔥 Strong Opportunity - scope keywords matched
                          </span>
                        ) : (
                          <span style={{ fontSize:11, fontWeight:600, padding:'4px 10px', borderRadius:8,
                            background:'#f3f4f6', color:'#6b7280', border:'1px solid #e5e7eb' }}>
                            ⚠️ Weak signal - no scope keywords found
                          </span>
                        )}
                        <span style={{ fontSize:11, fontWeight:600, padding:'4px 10px', borderRadius:8,
                          background:'#eff6ff', color:'#1d4ed8', border:'1px solid #bfdbfe' }}>
                          {Math.round((classification.confidence||0)*100)}% AI confidence
                        </span>
                      </div>

                      {/* ---- KEYWORDS ---- */}
                      {classification.isKeywordMatch && (classification.keywordsMatched||[]).length > 0 && (
                        <div style={{ marginBottom:12 }}>
                          <div style={{ fontSize:10, color:'#6b7280', fontWeight:600, textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:5 }}>Keywords Matched</div>
                          <div style={{ display:'flex', flexWrap:'wrap', gap:4 }}>
                            {classification.keywordsMatched.map(kw => (
                              <span key={kw} style={{ fontSize:11, padding:'3px 8px', borderRadius:8, background:'#fee2e2', color:'#991b1b', border:'1px solid #fecaca', fontWeight:600 }}>{kw}</span>
                            ))}
                          </div>
                        </div>
                      )}

                      {/* ---- KEY DETAILS ---- */}
                      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:10, marginBottom:12 }}>
                        <div>
                          <div style={{ fontSize:10, color:'#6b7280', fontWeight:600, textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:3 }}>Suggested Folder</div>
                          <div style={{ fontSize:12, color:'#1e40af', fontWeight:700 }}>
                            {classification.suggestedFolder}{classification.subFolder && <span style={{ color:'#3b82f6' }}> → {classification.subFolder}</span>}
                          </div>
                        </div>
                        <div>
                          <div style={{ fontSize:10, color:'#6b7280', fontWeight:600, textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:3 }}>Source Portal</div>
                          <span style={{ fontSize:11, fontWeight:700, padding:'2px 8px', borderRadius:8,
                            background:portalColor(classification.sourcePortal)+'20', color:portalColor(classification.sourcePortal),
                            border:`1px solid ${portalColor(classification.sourcePortal)}44` }}>{classification.sourcePortal}</span>
                        </div>
                        {classification.tenderValue && (
                          <div>
                            <div style={{ fontSize:10, color:'#6b7280', fontWeight:600, textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:3 }}>💰 Tender Value</div>
                            <span style={{ fontSize:12, fontWeight:700, color:'#92400e', background:'#fef3c7', padding:'2px 8px', borderRadius:4 }}>{classification.tenderValue}</span>
                          </div>
                        )}
                        {classification.dueDate && (
                          <div>
                            <div style={{ fontSize:10, color:'#6b7280', fontWeight:600, textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:3 }}>⏰ Closing Date</div>
                            <div style={{ display:'flex', alignItems:'center', gap:6, flexWrap:'wrap' }}>
                              <span style={{ fontSize:12, fontWeight:600, color:'var(--ink)' }}>
                                {new Date(classification.dueDate).toLocaleDateString('en-AU',{day:'numeric',month:'short',year:'numeric'})}
                              </span>
                              <DueDateBadge dueDate={classification.dueDate} />
                            </div>
                          </div>
                        )}
                      </div>

                      {/* ---- PORTAL LINKS ---- */}
                      {classification.isExternalFollowup && (classification.externalLinks||[]).length > 0 && (
                        <div style={{ marginBottom:8 }}>
                          <div style={{ fontSize:10, color:'#6b7280', fontWeight:600, textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:6 }}>🔗 Portal Links - open to request documents</div>
                          <div style={{ display:'flex', flexWrap:'wrap', gap:6 }}>
                            {classification.externalLinks.slice(0,3).map((link, i) => (
                              <a key={i} href={link} target="_blank" rel="noopener noreferrer"
                                style={{ fontSize:12, padding:'6px 12px', borderRadius:6, textDecoration:'none',
                                  background:'#2563eb', color:'#fff', fontWeight:600 }}>Open Portal {i+1} ↗</a>
                            ))}
                          </div>
                        </div>
                      )}
                    </div>
                  )}
                </div>
              )}

              {/* ACTIONS TAB */}
              {detailTab === 'actions' && (
                <div style={{ padding:'4px 0' }}>
                  <div style={{ fontSize:11.5, color:'var(--ink-4)', marginBottom:14 }}>All folder and email actions. Changes reflect in Outlook immediately.</div>
                </div>
              )}

            </div> {/* end tab content */}

            {/* ===== FIXED ACTION BAR (always visible) ===== */}
            <div style={{ padding:'10px 16px', borderTop:'2px solid var(--line)', background:'#f8f7f4', flexShrink:0 }}>
              {/* Move row */}
              <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:8, flexWrap:'wrap' }}>
                <span style={{ fontSize:11.5, fontWeight:600, color:'var(--ink-2)', flexShrink:0 }}>Move to:</span>

                {/* Folder picker — searchable */}
                <div style={{ position:'relative' }}>
                  <button
                    onClick={() => { setMoveDropOpen(o => !o); }}
                    style={{
                      all:'unset', display:'flex', alignItems:'center', gap:6, padding:'5px 10px',
                      border:'1px solid var(--line)', borderRadius:6, cursor:'pointer', fontSize:12,
                      background:'var(--bg)', color:'var(--ink)', fontWeight:500,
                    }}
                  >
                    <span style={{ maxWidth:180, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
                      {moveTarget ? moveTarget.displayName : 'Select folder…'}
                    </span>
                    <span style={{ fontSize:9, color:'var(--ink-3)' }}>▼</span>
                  </button>

                  {moveDropOpen && (() => {
                    const filtered = folderSearch.trim()
                      ? allFlat.filter(f => f.displayName?.toLowerCase().includes(folderSearch.toLowerCase()))
                      : allFlat;
                    return (
                    <div style={{
                      position:'absolute', bottom:'100%', left:0, marginBottom:4,
                      minWidth:250, maxHeight:320, display:'flex', flexDirection:'column',
                      background:'#fff', border:'1px solid var(--line)', borderRadius:8,
                      boxShadow:'0 4px 20px rgba(0,0,0,.13)', zIndex:50,
                    }}>
                      {/* Search box */}
                      <div style={{ padding:'8px 10px', borderBottom:'1px solid var(--line)', flexShrink:0 }}>
                        <input
                          autoFocus
                          placeholder="Search folders…"
                          value={folderSearch}
                          onChange={e => setFolderSearch(e.target.value)}
                          style={{ width:'100%', boxSizing:'border-box', padding:'5px 8px', border:'1px solid #d1d5db', borderRadius:5, fontSize:12 }}
                        />
                      </div>
                      {/* Folder list */}
                      <div style={{ overflowY:'auto', flex:1 }}>
                        {filtered.map(f => (
                          <button key={f.id}
                            onClick={() => { setMoveTarget(f); setMoveDropOpen(false); setFolderSearch(''); }}
                            style={{
                              all:'unset', display:'block', width:'100%', boxSizing:'border-box',
                              padding:`6px ${10 + (f.depth||0) * 14}px`, fontSize:11.5, cursor:'pointer',
                              color: f.depth === 0 ? 'var(--ink-2)' : 'var(--ink)',
                              fontWeight: f.depth === 0 ? 700 : f.depth === 1 ? 500 : 400,
                              background: moveTarget?.id === f.id ? 'var(--bg-2)' : 'transparent',
                              borderBottom: f.depth === 0 && !folderSearch ? '1px solid var(--line)' : 'none',
                            }}
                            onMouseEnter={e => e.currentTarget.style.background='var(--bg-3)'}
                            onMouseLeave={e => e.currentTarget.style.background = moveTarget?.id === f.id ? 'var(--bg-2)' : 'transparent'}
                          >
                            {f.depth > 0 && !folderSearch && <span style={{ opacity:0.35, marginRight:4 }}>{f.depth === 1 ? '└' : '  └'}</span>}
                            {f.displayName}
                          </button>
                        ))}
                        {filtered.length === 0 && (
                          <div style={{ padding:'12px', fontSize:12, color:'var(--ink-4)', textAlign:'center' }}>No folders match</div>
                        )}
                      </div>
                      {/* New folder button */}
                      <button
                        onClick={() => { setMoveDropOpen(false); setNewFolderFor(selectedFolder?.id || (allFlat[0]?.id || null)); setNewFolderName(''); }}
                        style={{ all:'unset', display:'block', width:'100%', boxSizing:'border-box', padding:'8px 12px', fontSize:12, cursor:'pointer', color:'#2563eb', fontWeight:600, borderTop:'1px solid var(--line)', flexShrink:0 }}
                      >+ New folder…</button>
                    </div>);
                  })()}
                </div>

                <button
                  className="btn primary sm"
                  onClick={() => moveTarget && moveMessage(moveTarget)}
                  disabled={!moveTarget || moving}
                  style={{ flexShrink:0 }}
                >
                  {moving && <I.spin/>} {moving ? 'Moving…' : 'Move'}
                </button>
              </div>

              {/* Quick actions */}
              <div style={{ display:'flex', gap:7, flexWrap:'wrap', marginBottom:7 }}>
                {/* Add to Opportunities - primary action */}
                <button
                  className="btn primary sm"
                  style={{
                    background: isInOpportunities(selectedMsg) ? '#16a34a' : '#7c3aed',
                    borderColor: isInOpportunities(selectedMsg) ? '#16a34a' : '#7c3aed',
                    fontWeight:700,
                    opacity: isInOpportunities(selectedMsg) ? 0.85 : 1,
                  }}
                  disabled={moving || !selectedMsg || scoringOpportunity || isInOpportunities(selectedMsg)}
                  title={isInOpportunities(selectedMsg) ? 'Already in Opportunities — view in Opportunities tab' : 'Score and add to Opportunities pipeline'}
                  onClick={async () => {
                    if (!selectedMsg || isInOpportunities(selectedMsg)) return;
                    setScoringOpportunity(true);
                    showToast('🤖 AI scoring this tender...');
                    try {
                      const bodyText = msgBody ? msgBody.replace(/<[^>]+>/g,' ').replace(/\s+/g,' ').slice(0,3000) : (selectedMsg.bodyPreview||'');
                      const score = await api.post('/api/tenders/score-opportunity', {
                        messageId:     selectedMsg.id,
                        subject:       selectedMsg.subject,
                        body:          bodyText,
                        from:          selectedMsg.from?.emailAddress?.address || '',
                        conversationId: selectedMsg.conversationId,
                        receivedAt:    selectedMsg.receivedDateTime,
                        hasAttachments: selectedMsg.hasAttachments,
                      });
                      // Build opportunity record
                      await api.post('/api/opportunities', {
                        conversationId:  selectedMsg.conversationId,
                        messageId:       selectedMsg.id,        // Graph message ID
                        internetMsgId:   selectedMsg.internetMessageId || null,
                        webLink:         selectedMsg.webLink || null,
                        subject:         selectedMsg.subject,
                        from:            selectedMsg.from?.emailAddress?.name || selectedMsg.from?.emailAddress?.address,
                        fromEmail:       selectedMsg.from?.emailAddress?.address,
                        receivedAt:      selectedMsg.receivedDateTime,
                        fitScore:        score.fitScore,
                        scoreBreakdown:  score.scoreBreakdown,
                        recommendation:  score.recommendation,
                        summary:         score.summary,
                        riskFlags:       score.riskFlags || [],
                        client:          score.extracted?.client,
                        location:        score.extracted?.location,
                        scope:           score.extracted?.scope,
                        tenderValue:     score.extracted?.tenderValue || classification?.tenderValue,
                        dueDate:         score.extracted?.dueDate || classification?.dueDate,
                        contactName:     score.extracted?.contactName,
                        contactEmail:    score.extracted?.contactEmail,
                        tenderRef:       score.extracted?.tenderRef,
                        portalSource:    classification?.sourcePortal || '',
                        tasks:           score.suggestedTasks || [],
                        status:          'new',
                      });
                      showToast(`🎯 Added! Score: ${score.fitScore}/100 - ${score.recommendation}`);
                      // Immediately mark as in-opportunities in local state
                      const newIds = new Set(oppMsgIds);
                      newIds.add(selectedMsg.id);
                      if (selectedMsg.internetMessageId) newIds.add(selectedMsg.internetMessageId);
                      setOppMsgIds(newIds);
                      window._oppMessageIds = newIds;
                      if (onCreateOpportunity) onCreateOpportunity();
                    } catch(e) {
                      showToast('Error: ' + e.message);
                    } finally {
                      setScoringOpportunity(false);
                    }
                  }}
                >{scoringOpportunity ? '🤖 Scoring...' : isInOpportunities(selectedMsg) ? '✅ In Opportunities' : '🎯 Add to Opportunities'}</button>
              </div>
              <div style={{ display:'flex', gap:7, flexWrap:'wrap' }}>
                <button
                  className="btn ghost sm"
                  style={{ color:'#16a34a', borderColor:'#bbf7d0', fontWeight:600 }}
                  onClick={async () => {
                    await moveToSubfolder('Follow Up');
                    // Also add to Follow-ups tab
                    try {
                      await api.post('/api/followup/add', {
                        subject: selectedMsg.subject || '(no subject)',
                        graphId: selectedMsg.id,
                        from: selectedMsg.from?.emailAddress?.name || '',
                        fromEmail: selectedMsg.from?.emailAddress?.address || '',
                        threadId: selectedMsg.conversationId || selectedMsg.id,
                        taskId: selectedMsg.id,
                        webLink: selectedMsg.webLink || null,
                        mailbox: 'shared:tenders',
                      });
                      showToast('🔔 Added to Follow-ups tab');
                    } catch(e) { /* non-critical */ }
                  }}
                  disabled={moving || !classification}
                  title="Move to [portal] → Follow Up and add to Follow-ups tab"
                >✓ Follow Up</button>
                <button
                  className="btn ghost sm"
                  style={{ color:'#6b7280', fontWeight:600 }}
                  onClick={() => moveToSubfolder('Not Tendering')}
                  disabled={moving || !classification}
                  title="Move to [portal] → Not Tendering"
                >✕ Not Tendering</button>
                {classification?.isExternalFollowup && (
                  <span style={{
                    display:'inline-flex', alignItems:'center', gap:4, padding:'3px 8px',
                    fontSize:11.5, color:'#1d4ed8', background:'#dbeafe', borderRadius:6,
                    border:'1px solid #bfdbfe', fontWeight:600,
                  }}>🔗 External Followup</span>
                )}
                {classification?.isKeywordMatch && (
                  <span style={{
                    display:'inline-flex', alignItems:'center', gap:4, padding:'3px 8px',
                    fontSize:11.5, color:'#991b1b', background:'#fee2e2', borderRadius:6,
                    border:'1px solid #fecaca', fontWeight:600,
                  }}>🚩 Keywords Match</span>
                )}
              </div>
            </div>
          </>
        )}
      </div>

      {/* Toast */}
      {toastMsg && (
        <div style={{
          position:'fixed', bottom:24, right:24, zIndex:200,
          background:'#1a1a18', color:'#fff', padding:'10px 18px',
          borderRadius:8, fontSize:13, fontWeight:500,
          boxShadow:'0 4px 20px rgba(0,0,0,.25)',
        }}>{toastMsg}</div>
      )}

      {/* Folder context menu (right-click / ⋯) */}
      {FolderCtxMenu}

      {/* Move folder modal */}
      {MoveFolderModal}
    </div>
  );
}

// ---- My To-Do List ----
function TodosView({ todos, threads, onRefresh }) {
  const [showAdd, setShowAdd]     = useState(false);
  const [title, setTitle]         = useState('');
  const [notes, setNotes]         = useState('');
  const [linkedIds, setLinkedIds] = useState([]);
  const [adding, setAdding]       = useState(false);
  const [editId, setEditId]       = useState(null);
  const [editTitle, setEditTitle] = useState('');
  const [editNotes, setEditNotes] = useState('');
  const [editLinked, setEditLinked] = useState([]);
  const [filter, setFilter]       = useState('open');
  const [confirmDel, setConfirmDel] = useState(null);

  const threadOptions = (threads || []).map(t => ({
    id: t.id,
    label: stripMd(t.title || t.id),
  }));

  const statusColors = {
    open:       { bg:'#f0fdf4', color:'#16a34a', border:'#bbf7d0', label:'Open' },
    inprogress: { bg:'#eff6ff', color:'#2563eb', border:'#bfdbfe', label:'In Progress' },
    done:       { bg:'#f9fafb', color:'#6b7280', border:'#e5e7eb', label:'Done' },
  };

  const visible = (todos || []).filter(t => {
    if (filter === 'open')       return t.status !== 'done';
    if (filter === 'done')       return t.status === 'done';
    if (filter === 'inprogress') return t.status === 'inprogress';
    return true;
  });

  const handleAdd = async () => {
    if (!title.trim()) return;
    setAdding(true);
    try {
      await api.post('/api/todos', { title: title.trim(), notes: notes.trim(), linkedThreadIds: linkedIds });
      setTitle(''); setNotes(''); setLinkedIds([]); setShowAdd(false);
      onRefresh?.();
    } catch (err) { alert('Failed: ' + err.message); }
    setAdding(false);
  };

  const handleStatusCycle = async (todo) => {
    const cycle = ['open', 'inprogress', 'done'];
    const next = cycle[(cycle.indexOf(todo.status) + 1) % cycle.length];
    try {
      await api.patch(`/api/todos/${todo.id}`, { status: next });
      onRefresh?.();
    } catch (err) { alert('Failed: ' + err.message); }
  };

  const handleSaveEdit = async (todo) => {
    try {
      await api.patch(`/api/todos/${todo.id}`, { title: editTitle, notes: editNotes, linkedThreadIds: editLinked });
      setEditId(null);
      onRefresh?.();
    } catch (err) { alert('Failed: ' + err.message); }
  };

  const handleDelete = async (id) => {
    try {
      await api.delete(`/api/todos/${id}`);
      setConfirmDel(null);
      onRefresh?.();
    } catch (err) { alert('Failed: ' + err.message); }
  };

  const openCount = (todos || []).filter(t => t.status !== 'done').length;

  return (
    <div className="main-inner">
      <div className="page-head">
        <div>
          <h1 className="page-title">My To-Do List</h1>
          <p className="page-sub">{openCount} open · manually track tasks and link email threads</p>
        </div>
        <button className="btn primary" onClick={() => setShowAdd(s => !s)}><I.plus/> New task</button>
      </div>

      {/* Filter tabs */}
      <div style={{display:'flex', gap:6, marginBottom:16}}>
        {[['open','Open'],['inprogress','In Progress'],['done','Done'],['all','All']].map(([v,l]) => (
          <button key={v} className="chip" aria-pressed={filter===v} onClick={() => setFilter(v)}>{l}</button>
        ))}
      </div>

      {/* Add form */}
      {showAdd && (
        <div style={{background:'#f0fdf4', border:'1px solid #bbf7d0', borderRadius:8, padding:'16px 18px', marginBottom:16}}>
          <div style={{fontWeight:700, fontSize:13, marginBottom:12, color:'#166534'}}>New task</div>
          <input type="text" value={title} onChange={e => setTitle(e.target.value)}
            placeholder="Task title (required)" autoFocus style={{width:'100%', marginBottom:8}}
            onKeyDown={e => e.key==='Enter' && handleAdd()} />
          <textarea rows="2" value={notes} onChange={e => setNotes(e.target.value)}
            placeholder="Notes (optional)" style={{width:'100%', resize:'vertical', marginBottom:10}} />
          {threadOptions.length > 0 && (
            <div style={{marginBottom:10}}>
              <div style={{fontSize:11, color:'#166534', marginBottom:4, fontWeight:600}}>Link to email threads (optional)</div>
              <div style={{display:'flex', flexWrap:'wrap', gap:6}}>
                {threadOptions.slice(0,10).map(t => (
                  <button key={t.id}
                    onClick={() => setLinkedIds(prev => prev.includes(t.id) ? prev.filter(x=>x!==t.id) : [...prev, t.id])}
                    style={{
                      fontSize:11, padding:'3px 9px', borderRadius:4, border:'1px solid', cursor:'pointer', fontWeight:600,
                      background: linkedIds.includes(t.id) ? '#16a34a' : '#fff',
                      color: linkedIds.includes(t.id) ? '#fff' : '#374151',
                      borderColor: linkedIds.includes(t.id) ? '#16a34a' : '#d1d5db',
                    }}
                  >{t.label.slice(0,40)}</button>
                ))}
              </div>
            </div>
          )}
          <div style={{display:'flex', gap:8}}>
            <button className="btn primary sm" onClick={handleAdd} disabled={adding || !title.trim()}>
              {adding ? 'Adding...' : 'Add task'}
            </button>
            <button className="btn ghost sm" onClick={() => { setShowAdd(false); setTitle(''); setNotes(''); setLinkedIds([]); }}>Cancel</button>
          </div>
        </div>
      )}

      {visible.length === 0 && (
        <div className="empty">{filter==='done' ? 'No completed tasks yet' : 'No open tasks - add one above'}</div>
      )}

      {visible.map(todo => {
        const sc = statusColors[todo.status] || statusColors.open;
        const isEditing = editId === todo.id;
        const linked = (todo.linkedThreadIds || []).map(id => threadOptions.find(t => t.id === id)).filter(Boolean);
        return (
          <div key={todo.id} style={{background:'#fff', border:'1px solid var(--line)', borderRadius:8, padding:'13px 16px', marginBottom:10}}>
            {isEditing ? (
              <div>
                <input type="text" value={editTitle} onChange={e => setEditTitle(e.target.value)}
                  style={{width:'100%', marginBottom:8}} autoFocus />
                <textarea rows="2" value={editNotes} onChange={e => setEditNotes(e.target.value)}
                  style={{width:'100%', resize:'vertical', marginBottom:8}} placeholder="Notes" />
                {threadOptions.length > 0 && (
                  <div style={{display:'flex', flexWrap:'wrap', gap:5, marginBottom:10}}>
                    {threadOptions.slice(0,10).map(t => (
                      <button key={t.id}
                        onClick={() => setEditLinked(prev => prev.includes(t.id) ? prev.filter(x=>x!==t.id) : [...prev, t.id])}
                        style={{
                          fontSize:11, padding:'3px 8px', borderRadius:4, border:'1px solid', cursor:'pointer',
                          background: editLinked.includes(t.id) ? '#16a34a' : '#fff',
                          color: editLinked.includes(t.id) ? '#fff' : '#374151',
                          borderColor: editLinked.includes(t.id) ? '#16a34a' : '#d1d5db',
                        }}
                      >{t.label.slice(0,36)}</button>
                    ))}
                  </div>
                )}
                <div style={{display:'flex', gap:6}}>
                  <button className="btn tiny primary" onClick={() => handleSaveEdit(todo)}>Save</button>
                  <button className="btn tiny ghost" onClick={() => setEditId(null)}>Cancel</button>
                </div>
              </div>
            ) : (
              <div style={{display:'flex', alignItems:'flex-start', gap:12}}>
                <div style={{flex:1, minWidth:0}}>
                  <div style={{display:'flex', alignItems:'center', gap:8, flexWrap:'wrap', marginBottom:4}}>
                    <span style={{fontWeight:700, fontSize:13.5, color:'var(--ink)', textDecoration: todo.status==='done' ? 'line-through' : 'none', opacity: todo.status==='done' ? 0.5 : 1}}>
                      {todo.title}
                    </span>
                    <button
                      onClick={() => handleStatusCycle(todo)}
                      style={{fontSize:10, fontWeight:700, padding:'2px 8px', borderRadius:10, border:`1px solid ${sc.border}`, background:sc.bg, color:sc.color, cursor:'pointer'}}
                    >{sc.label}</button>
                  </div>
                  {todo.notes && <div style={{fontSize:12, color:'var(--ink-3)', marginBottom:4, lineHeight:1.5}}>{todo.notes}</div>}
                  {linked.length > 0 && (
                    <div style={{display:'flex', flexWrap:'wrap', gap:5, marginTop:4}}>
                      {linked.map(t => (
                        <span key={t.id} style={{fontSize:10.5, padding:'2px 8px', borderRadius:4, background:'#eff6ff', color:'#1d4ed8', border:'1px solid #bfdbfe'}}>🔗 {t.label.slice(0,36)}</span>
                      ))}
                    </div>
                  )}
                  <div style={{fontSize:10.5, color:'var(--ink-4)', marginTop:5, fontFamily:'var(--mono)'}}>{formatDate(todo.createdAt)}</div>
                </div>
                <div style={{display:'flex', gap:4, flexShrink:0}}>
                  <button className="btn tiny ghost" title="Edit" onClick={() => { setEditId(todo.id); setEditTitle(todo.title); setEditNotes(todo.notes||''); setEditLinked(todo.linkedThreadIds||[]); }}>✏️</button>
                  <button className="btn tiny ghost" title="Delete" onClick={() => setConfirmDel(todo.id)} style={{color:'var(--urgent)'}}>🗑️</button>
                </div>
              </div>
            )}
          </div>
        );
      })}

      {confirmDel && (
        <ConfirmDialog
          title="Delete task?"
          body="This cannot be undone."
          confirmLabel="Yes, delete"
          danger
          onCancel={() => setConfirmDel(null)}
          onConfirm={() => handleDelete(confirmDel)}
        />
      )}
    </div>
  );
}

// ============================================================
// Opportunities View - Tender Pipeline
// ============================================================
function OpportunitiesView() {
  const [opps,          setOpps]          = useState([]);
  const [loading,       setLoading]       = useState(true);
  const [selected,      setSelected]      = useState(null); // selected opportunity ID
  const [tab,           setTab]           = useState('pipeline'); // 'pipeline' | 'workspace'
  const [statusFilter,  setStatusFilter]  = useState('check'); // 'check' | 'tendering' | 'won' | 'lost' | 'all'
  const [toast,         setToast]         = useState(null);
  const [dateFilter,    setDateFilter]    = useState('all'); // 'all' | 'overdue' | '7' | '14' | '30'
  const [priorityFilter, setPriorityFilter] = useState('all'); // 'all' | '1' | '2' | '3' | '4' | '5'
  const [searchFilter,  setSearchFilter]  = useState('');
  const [linkModal,     setLinkModal]     = useState(false);
  const [linkQuoteNo,   setLinkQuoteNo]   = useState('');
  const [linkQuoteName, setLinkQuoteName] = useState('');
  const [savingLink,    setSavingLink]    = useState(false);
  const [taskWorking,   setTaskWorking]   = useState(false);
  // New task form
  const [addingTask,    setAddingTask]    = useState(false);
  const [editingDueDate, setEditingDueDate] = useState(false);
  const [dueDateInput,   setDueDateInput]  = useState('');
  const [newTaskTitle,  setNewTaskTitle]  = useState('');
  const [newTaskCat,    setNewTaskCat]    = useState('admin');
  const [newTaskAssign, setNewTaskAssign] = useState('');
  const [editingTask,   setEditingTask]   = useState(null); // taskId being edited
  const [editTaskTitle, setEditTaskTitle] = useState('');
  const [editTaskAssign,setEditTaskAssign]= useState('');
  const [taskTab,       setTaskTab]       = useState('active'); // 'active' | 'deleted'

  function showToast(msg) {
    setToast(msg);
    setTimeout(() => setToast(null), 3500);
  }

  async function loadOpps() {
    setLoading(true);
    try {
      const d = await api.get('/api/opportunities');
      const list = d.opportunities || [];
      setOpps(list);
      // Publish messageId set to window so TenderView can check without prop drilling
      window._oppMessageIds = new Set(
        list.flatMap(o => [o.messageId, o.internetMsgId].filter(Boolean))
      );
    } catch(e) { console.error(e); }
    finally { setLoading(false); }
  }

  useEffect(() => { loadOpps(); }, []);

  const selectedOpp = opps.find(o => o.id === selected);

  async function updateOpp(id, updates) {
    const updated = await api.patch(`/api/opportunities/${id}`, updates);
    setOpps(prev => prev.map(o => o.id === id ? updated.opportunity : o));
    if (selected === id) setSelected(id); // keep selected
    return updated.opportunity;
  }

  async function dismissOpp(opp) {
    if (!confirm(`Dismiss "${opp.subject?.slice(0,60)}"?\nThis will permanently remove it from Opportunities.`)) return;
    try {
      await api.delete(`/api/opportunities/${opp.id}`);
      setOpps(prev => prev.filter(o => o.id !== opp.id));
      if (selected === opp.id) setSelected(null);
      showToast('Opportunity dismissed');
    } catch(e) { showToast('Error: ' + e.message); }
  }

  async function handleDecision(opp, decision) {
    if (decision === 'go') {
      // Check BenchMark linkage - show link modal
      setSelected(opp.id);
      setLinkModal(true);
      setLinkQuoteNo('');
      setLinkQuoteName('');
    } else if (decision === 'pass') {
      await updateOpp(opp.id, { status: 'passed', userDecision: 'pass', decidedAt: new Date().toISOString() });
      showToast(`"${opp.subject?.slice(0,40)}" marked as Passed`);
    }
  }

  async function confirmGo() {
    if (!selectedOpp) return;
    setSavingLink(true);
    try {
      const updates = {
        status: 'tendering',
        userDecision: 'go',
        decidedAt: new Date().toISOString(),
        benchmarkLinked: !!(linkQuoteNo.trim() || linkQuoteName.trim()),
        benchmarkQuoteNo: linkQuoteNo.trim() || null,
        benchmarkQuoteName: linkQuoteName.trim() || null,
        benchmarkLinkedAt: (linkQuoteNo.trim() || linkQuoteName.trim()) ? new Date().toISOString() : null,
      };
      await updateOpp(selectedOpp.id, updates);
      setLinkModal(false);
      setTab('workspace');
      showToast(`✅ Moved to Tendering${updates.benchmarkLinked ? ' - BenchMark linked' : ' - BenchMark link pending'}`);
    } finally { setSavingLink(false); }
  }

  async function toggleTask(oppId, taskId, done) {
    if (taskWorking) return;
    setTaskWorking(true);
    try {
      const updated = await api.patch(`/api/opportunities/${oppId}/task/${taskId}`, { done });
      setOpps(prev => prev.map(o => o.id === oppId ? updated.opportunity : o));
      // Log to timeline
      const opp = opps.find(o => o.id === oppId);
      const task = (opp?.tasks||[]).find(t => t.id === taskId);
      if (task) {
        const entry = `${new Date().toISOString()} - Task ${done ? 'completed' : 'reopened'}: "${task.title}"`;
        await updateOpp(oppId, { timeline: [...(opp.timeline||[]), entry] });
      }
    } catch(e) { showToast('Error: ' + e.message); }
    finally { setTaskWorking(false); }
  }

  async function addTask(oppId) {
    if (!newTaskTitle.trim() || taskWorking) return;
    setTaskWorking(true);
    try {
      const opp = opps.find(o => o.id === oppId);
      const newTask = {
        id: 'task-' + Date.now(),
        title: newTaskTitle.trim(),
        category: newTaskCat,
        assignee: newTaskAssign.trim(),
        done: false, doneAt: null, notes: '',
      };
      const newTasks = [...(opp.tasks||[]), newTask];
      const entry = `${new Date().toISOString()} - Task added: "${newTask.title}"`;
      const updated = await updateOpp(oppId, {
        tasks: newTasks,
        timeline: [...(opp.timeline||[]), entry],
      });
      setNewTaskTitle(''); setNewTaskAssign(''); setAddingTask(false);
      showToast('✅ Task added');
    } catch(e) { showToast('Error: ' + e.message); }
    finally { setTaskWorking(false); }
  }

  async function deleteTask(oppId, taskId) {
    if (taskWorking) return;
    setTaskWorking(true);
    try {
      const opp = opps.find(o => o.id === oppId);
      const task = (opp?.tasks||[]).find(t => t.id === taskId);
      // Soft delete - mark deletedAt instead of removing
      const newTasks = (opp.tasks||[]).map(t =>
        t.id === taskId ? { ...t, deletedAt: new Date().toISOString() } : t
      );
      const entry = `${new Date().toISOString()} - Task deleted: "${task?.title||'(task)'}"`;
      await updateOpp(oppId, { tasks: newTasks, timeline: [...(opp.timeline||[]), entry] });
      showToast('Task moved to Deleted');
    } catch(e) { showToast('Error: ' + e.message); }
    finally { setTaskWorking(false); }
  }

  async function restoreTask(oppId, taskId) {
    if (taskWorking) return;
    setTaskWorking(true);
    try {
      const opp = opps.find(o => o.id === oppId);
      const task = (opp?.tasks||[]).find(t => t.id === taskId);
      const newTasks = (opp.tasks||[]).map(t =>
        t.id === taskId ? { ...t, deletedAt: null } : t
      );
      const entry = `${new Date().toISOString()} - Task restored: "${task?.title||'(task)'}"`;
      await updateOpp(oppId, { tasks: newTasks, timeline: [...(opp.timeline||[]), entry] });
      showToast('Task restored');
    } catch(e) { showToast('Error: ' + e.message); }
    finally { setTaskWorking(false); }
  }

  async function saveEditTask(oppId, taskId) {
    if (!editTaskTitle.trim() || taskWorking) return;
    setTaskWorking(true);
    try {
      const opp = opps.find(o => o.id === oppId);
      const newTasks = (opp.tasks||[]).map(t =>
        t.id === taskId ? { ...t, title: editTaskTitle.trim(), assignee: editTaskAssign.trim() } : t
      );
      const entry = `${new Date().toISOString()} - Task edited: "${editTaskTitle.trim()}"`;
      await updateOpp(oppId, { tasks: newTasks, timeline: [...(opp.timeline||[]), entry] });
      setEditingTask(null);
      showToast('Task updated');
    } catch(e) { showToast('Error: ' + e.message); }
    finally { setTaskWorking(false); }
  }

  async function savePriority(id, priority) {
    await updateOpp(id, { priority: parseInt(priority) });
  }

  // Score colour
  function scoreColor(score) {
    if (score >= 75) return '#16a34a';
    if (score >= 50) return '#d97706';
    return '#dc2626';
  }
  function scoreLabel(score) {
    if (score >= 75) return '🔥 HOT';
    if (score >= 50) return '👍 GOOD';
    return '⚠️ WEAK';
  }
  function recoBadge(rec) {
    if (rec === 'GO')     return { bg:'#dcfce7', color:'#166534', label:'✅ GO' };
    if (rec === 'REVIEW') return { bg:'#fef9c3', color:'#854d0e', label:'👀 REVIEW' };
    return                       { bg:'#fee2e2', color:'#991b1b', label:'❌ PASS' };
  }
  function statusBadge(status) {
    const map = {
      new:       { bg:'#dbeafe', color:'#1e40af', label:'NEW' },
      review:    { bg:'#fef3c7', color:'#92400e', label:'REVIEW' },
      tendering: { bg:'#d1fae5', color:'#065f46', label:'TENDERING' },
      won:       { bg:'#dcfce7', color:'#166534', label:'WON 🎉' },
      lost:      { bg:'#fee2e2', color:'#991b1b', label:'LOST' },
      passed:    { bg:'#f3f4f6', color:'#6b7280', label:'PASSED' },
    };
    return map[status] || map.new;
  }

  // Filter opps
  const filtered = opps.filter(o => {
    // Status filter
    if (statusFilter === 'check')     { if (!['new','review'].includes(o.status)) return false; }
    else if (statusFilter === 'tendering') { if (o.status !== 'tendering') return false; }
    else if (statusFilter === 'won')       { if (o.status !== 'won') return false; }
    else if (statusFilter === 'lost')      { if (!['lost','passed'].includes(o.status)) return false; }
    // Date/closing filter
    if (dateFilter !== 'all' && o.dueDate) {
      const days = Math.round((new Date(o.dueDate) - new Date()) / 86400000);
      if (dateFilter === 'overdue' && days >= 0) return false;
      if (dateFilter === '7'  && (days < 0 || days > 7))  return false;
      if (dateFilter === '14' && (days < 0 || days > 14)) return false;
      if (dateFilter === '30' && (days < 0 || days > 30)) return false;
    } else if (dateFilter !== 'all' && !o.dueDate) {
      return false; // hide if no due date when filtering by date
    }
    // Priority filter
    if (priorityFilter !== 'all') {
      if (String(o.priority || 3) !== priorityFilter) return false;
    }
    // Search filter
    if (searchFilter.trim()) {
      const q = searchFilter.toLowerCase();
      if (!(o.subject||'').toLowerCase().includes(q) &&
          !(o.client||'').toLowerCase().includes(q) &&
          !(o.location||'').toLowerCase().includes(q)) return false;
    }
    return true;
  }).sort((a,b) => {
    // Sort: by due date (soonest first) if date filter active, else priority asc + fitScore desc
    if (dateFilter !== 'all') {
      const da = a.dueDate ? new Date(a.dueDate) : new Date('9999');
      const db = b.dueDate ? new Date(b.dueDate) : new Date('9999');
      return da - db;
    }
    if (a.priority !== b.priority) return a.priority - b.priority;
    return (b.fitScore||0) - (a.fitScore||0);
  });

  const workspaceOpp = selectedOpp || filtered[0] || null;

  if (loading) return (
    <div style={{ display:'flex', alignItems:'center', justifyContent:'center', height:300, color:'var(--ink-3)', fontSize:13 }}>
      <I.spin/> &nbsp;Loading opportunities...
    </div>
  );

  return (
    <div style={{ display:'flex', height:'calc(100vh - 52px)', margin:'-20px -28px -40px', overflow:'hidden' }}>

      {/* LEFT: Pipeline list */}
      <div style={{ width:380, flexShrink:0, display:'flex', flexDirection:'column', borderRight:'1px solid var(--line)', height:'100%', overflow:'hidden', background:'var(--bg)' }}>

        {/* Header */}
        <div style={{ padding:'13px 16px 10px', borderBottom:'1px solid var(--line)', flexShrink:0 }}>
          <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:8 }}>
            <div style={{ fontSize:13, fontWeight:700, color:'var(--ink)' }}>🎯 Opportunities</div>
            <span style={{ fontSize:11, color:'var(--ink-4)' }}>{filtered.length} shown</span>
          </div>
          {/* Pipeline stage filter - 2-row layout to avoid cramping */}
          <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr 1fr 1fr', gap:3, marginBottom:6 }}>
            {[
              ['check',     '🔍 Check', ['new','review']],
              ['tendering', '📄 Tender', ['tendering']],
              ['won',       '🎉 Won',    ['won']],
              ['lost',      '❌ Lost',  ['lost','passed']],
              ['all',       'All',     null],
            ].map(([k, l, statuses]) => {
              const count = statuses ? opps.filter(o => statuses.includes(o.status)).length : opps.length;
              return (
                <button key={k} onClick={() => setStatusFilter(k)}
                  style={{ all:'unset', cursor:'pointer', padding:'5px 4px', borderRadius:8,
                    fontSize:10.5, fontWeight:600, textAlign:'center',
                    background: statusFilter===k ? '#1a1a18' : '#f0ede8',
                    color: statusFilter===k ? '#fff' : 'var(--ink-3)',
                  }}>
                  <span style={{ display:'block' }}>{l}</span>
                  {count > 0 && <span style={{ fontSize:9, opacity: statusFilter===k ? 0.7 : 0.55 }}>{count}</span>}
                </button>
              );
            })}
          </div>

          {/* Date / closing filter */}
          <div style={{ display:'flex', gap:3, flexWrap:'wrap', alignItems:'center' }}>
            <span style={{ fontSize:10, fontWeight:600, color:'var(--ink-4)', flexShrink:0 }}>⏰</span>
            {[['all','Any'],['overdue','OD'],['7','≤7d'],['14','≤14d'],['30','≤30d']].map(([k,l]) => (
              <button key={k} onClick={() => setDateFilter(k)}
                style={{ all:'unset', cursor:'pointer', padding:'3px 8px', borderRadius:8, fontSize:10.5, fontWeight:600,
                  background: dateFilter===k ? (k==='overdue'?'#dc2626':'#1d4ed8') : '#f0ede8',
                  color: dateFilter===k ? '#fff' : 'var(--ink-3)',
                }}>{l}</button>
            ))}
          </div>

          {/* Priority filter */}
          <div style={{ display:'flex', gap:3, flexWrap:'wrap', alignItems:'center' }}>
            <span style={{ fontSize:10, fontWeight:600, color:'var(--ink-4)', flexShrink:0 }}>⚡</span>
            <button onClick={() => setPriorityFilter('all')}
              style={{ all:'unset', cursor:'pointer', padding:'3px 8px', borderRadius:8, fontSize:10.5, fontWeight:600,
                background: priorityFilter==='all' ? '#1d4ed8' : '#f0ede8',
                color: priorityFilter==='all' ? '#fff' : 'var(--ink-3)' }}>Any</button>
            {[['1','🔴 P1'],['2','🟠 P2'],['3','🟡 P3'],['4','🔵 P4'],['5','⚪ P5']].map(([k,l]) => {
              const count = opps.filter(o => String(o.priority||3) === k).length;
              return (
                <button key={k} onClick={() => setPriorityFilter(k)}
                  style={{ all:'unset', cursor:'pointer', padding:'3px 8px', borderRadius:8, fontSize:10.5, fontWeight:600,
                    background: priorityFilter===k ? '#7c3aed' : '#f0ede8',
                    color: priorityFilter===k ? '#fff' : 'var(--ink-3)',
                    opacity: count === 0 ? 0.45 : 1,
                  }}>{l}{count > 0 && <span style={{ fontSize:9, marginLeft:2, opacity:0.7 }}>{count}</span>}</button>
              );
            })}
          </div>

          {/* Search */}
          <input
            value={searchFilter} onChange={e => setSearchFilter(e.target.value)}
            placeholder="Search subject, client, location..."
            style={{ marginTop:7, width:'100%', boxSizing:'border-box', border:'1px solid var(--line)',
              borderRadius:6, padding:'5px 9px', fontSize:11.5, outline:'none', color:'var(--ink)' }}
          />
        </div>

        <div style={{ flex:1, overflowY:'auto' }}>
          {filtered.length === 0 && (
            <div style={{ padding:'40px 20px', textAlign:'center', color:'var(--ink-4)', fontSize:13 }}>
              <div style={{ fontSize:32, marginBottom:10 }}>🎯</div>
              <div style={{ fontWeight:600, marginBottom:4 }}>No opportunities yet</div>
              <div style={{ fontSize:12 }}>Open a tender email and click "Add to Opportunities"</div>
            </div>
          )}
          {filtered.map(opp => {
            const isSel  = selected === opp.id;
            const sb     = statusBadge(opp.status);
            const rb     = recoBadge(opp.recommendation);
            const sc     = scoreColor(opp.fitScore || 0);
            const tasks  = opp.tasks || [];
            const done   = tasks.filter(t => t.done).length;
            const daysLeft = opp.dueDate ? Math.round((new Date(opp.dueDate) - new Date()) / 86400000) : null;
            return (
              <div key={opp.id}
                onClick={() => { setSelected(opp.id); setTab('workspace'); }}
                style={{
                  padding:'12px 16px', borderBottom:'1px solid var(--line)', cursor:'pointer',
                  background: isSel ? '#f0f9ff' : 'var(--bg)',
                  borderLeft: isSel ? '3px solid #2563eb' : '3px solid transparent',
                }}
              >
                {/* Top row: score + status */}
                <div style={{ display:'flex', alignItems:'center', gap:6, marginBottom:5 }}>
                  <div style={{ fontSize:18, fontWeight:800, color:sc, minWidth:32 }}>{opp.fitScore||'?'}</div>
                  <div style={{ flex:1, minWidth:0 }}>
                    <div style={{ fontSize:12.5, fontWeight:700, color:'var(--ink)', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
                      {(opp.subject || '(no subject)').replace(/^((Re|RE|Fwd?|FWD?):\s*)+/g,'')}
                    </div>
                    <div style={{ fontSize:11, color:'var(--ink-3)', marginTop:1 }}>{opp.client || opp.from || ''}</div>
                  </div>
                  <div style={{ display:'flex', flexDirection:'column', alignItems:'flex-end', gap:3, flexShrink:0 }}>
                    <span style={{ fontSize:9.5, fontWeight:700, padding:'2px 7px', borderRadius:8, background:sb.bg, color:sb.color }}>{sb.label}</span>
                    <span style={{ fontSize:9.5, fontWeight:700, padding:'2px 7px', borderRadius:8, background:rb.bg, color:rb.color }}>{rb.label}</span>
                  </div>
                </div>

                {/* Score bar */}
                <div style={{ height:4, background:'#f0ede8', borderRadius:2, marginBottom:6, overflow:'hidden' }}>
                  <div style={{ height:'100%', width:`${opp.fitScore||0}%`, background:sc, borderRadius:2, transition:'width 0.3s' }}/>
                </div>

                {/* Meta row */}
                <div style={{ display:'flex', flexWrap:'wrap', gap:5, fontSize:10, color:'var(--ink-4)' }}>
                  {opp.tenderValue && <span>💰 {opp.tenderValue}</span>}
                  {daysLeft !== null ? (
                    <span style={{ color: daysLeft < 7 ? '#dc2626' : daysLeft < 14 ? '#d97706' : 'var(--ink-4)', fontWeight: daysLeft < 14 ? 700 : 400 }}>
                      ⏰ {daysLeft < 0 ? 'Overdue' : daysLeft === 0 ? 'Due today' : `${daysLeft}d left`}
                    </span>
                  ) : (
                    <span style={{ color:'#f59e0b', fontWeight:700 }}>⚠️ No due date</span>
                  )}
                  {tasks.length > 0 && <span>✅ {done}/{tasks.length} tasks</span>}
                  {!opp.benchmarkLinked && opp.status === 'tendering' && (
                    <span style={{ color:'#dc2626', fontWeight:700 }}>⚠️ BM unlinked</span>
                  )}
                  {opp.benchmarkLinked && <span style={{ color:'#16a34a' }}>🔗 {opp.benchmarkQuoteNo || opp.benchmarkQuoteName}</span>}
                </div>

                {/* Email thread link + priority */}
                {isSel && (
                  <div style={{ marginTop:7, display:'flex', flexDirection:'column', gap:6 }}>
                    {/* Thread link */}
                    {opp.conversationId && (
                      <div style={{ display:'flex', alignItems:'center', gap:6 }}>
                        <span style={{ fontSize:10, color:'var(--ink-4)', flexShrink:0 }}>📧 Email thread:</span>
                        <button
                          onClick={e => { e.stopPropagation();
                            openInOutlook(opp.webLink, opp.messageId, opp.internetMsgId, 'shared:tenders');
                          }}
                          style={{ all:'unset', cursor:'pointer', fontSize:11, color:'#2563eb', fontWeight:600,
                            overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', maxWidth:200,
                            textDecoration:'underline' }}
                          title="Open in Outlook"
                        >
                          {(opp.subject||'(no subject)').replace(/^((Re|RE|Fwd?|FWD?):\s*)+/g,'').slice(0,35)}... ↗
                        </button>
                      </div>
                    )}
                  </div>
                )}

                {/* Priority selector */}
                {isSel && (
                  <div style={{ display:'flex', alignItems:'center', gap:6, marginTop:4 }}>
                    <span style={{ fontSize:10, color:'var(--ink-4)' }}>Priority:</span>
                    {[1,2,3,4,5].map(p => (
                      <button key={p}
                        onClick={e => { e.stopPropagation(); savePriority(opp.id, p); }}
                        style={{ all:'unset', cursor:'pointer', width:20, height:20, borderRadius:4, fontSize:10.5, fontWeight:700, textAlign:'center',
                          background: opp.priority===p ? '#1a1a18' : '#f0ede8',
                          color: opp.priority===p ? '#fff' : 'var(--ink-3)',
                        }}>{p}</button>
                    ))}
                  </div>
                )}
              </div>
            );
          })}
        </div>
      </div>

      {/* RIGHT: Workspace / detail */}
      <div style={{ flex:1, display:'flex', flexDirection:'column', height:'100%', overflow:'hidden' }}>
        {!workspaceOpp ? (
          <div style={{ flex:1, display:'flex', alignItems:'center', justifyContent:'center', color:'var(--ink-4)', fontSize:13 }}>
            <div style={{ textAlign:'center' }}>
              <div style={{ fontSize:36, marginBottom:10 }}>🎯</div>
              <div>Select an opportunity to view its workspace</div>
            </div>
          </div>
        ) : (
          <>
            {/* Workspace header */}
            <div style={{ padding:'13px 20px 10px', borderBottom:'1px solid var(--line)', flexShrink:0, background:'var(--bg)' }}>
              <div style={{ display:'flex', alignItems:'flex-start', gap:10, marginBottom:8 }}>
                <div style={{ flex:1, minWidth:0 }}>
                  <div style={{ fontSize:14, fontWeight:700, color:'var(--ink)', lineHeight:1.35 }}>
                    {(workspaceOpp.subject||'').replace(/^((Re|RE|Fwd?|FWD?):\s*)+/g,'')}
                  </div>
                  <div style={{ fontSize:11.5, color:'var(--ink-3)', marginTop:3 }}>
                    {workspaceOpp.client || workspaceOpp.from} · {workspaceOpp.location || 'Location TBC'} · {workspaceOpp.tenderValue || 'Value TBC'}
                    {(workspaceOpp.webLink || workspaceOpp.conversationId) && (
                      <>
                        ·
                        <button
                          onClick={() => openInOutlook(workspaceOpp.webLink, workspaceOpp.messageId, workspaceOpp.internetMsgId, 'shared:tenders')}
                          style={{ all:'unset', cursor:'pointer', color:'#2563eb', fontWeight:600, fontSize:11.5, marginLeft:2 }}
                          title="Open in Outlook"
                        >📧 Open in Outlook ↗</button>
                      </>
                    )}
                  </div>
                </div>
                {/* Due date + score circle */}
                <div style={{ display:'flex', flexDirection:'column', alignItems:'flex-end', gap:6, flexShrink:0 }}>
                  {/* Due date - editable */}
                  <div style={{ textAlign:'right' }}>
                    <div style={{ fontSize:9, fontWeight:700, color:'var(--ink-4)', textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:3 }}>Closing Date</div>
                    {editingDueDate ? (
                      <div style={{ display:'flex', flexDirection:'column', alignItems:'flex-end', gap:4 }}>
                        <input
                          autoFocus
                          type="date"
                          value={dueDateInput}
                          onChange={e => setDueDateInput(e.target.value)}
                          onKeyDown={e => {
                            if (e.key === 'Enter' && dueDateInput) {
                              updateOpp(workspaceOpp.id, {
                                dueDate: dueDateInput,
                                timeline: [...(workspaceOpp.timeline||[]), `${new Date().toISOString()} - Closing date set to ${dueDateInput}`]
                              });
                              setEditingDueDate(false);
                            }
                            if (e.key === 'Escape') { setDueDateInput(workspaceOpp.dueDate||''); setEditingDueDate(false); }
                          }}
                          style={{ border:'1px solid #bfdbfe', borderRadius:6, padding:'4px 8px', fontSize:12,
                            outline:'none', background:'#f0f7ff', color:'var(--ink)', textAlign:'right' }}
                        />
                        <div style={{ display:'flex', gap:4 }}>
                          <button className="btn primary sm" style={{ fontSize:10.5, padding:'2px 8px' }}
                            disabled={!dueDateInput}
                            onClick={e => { e.stopPropagation();
                              if (!dueDateInput) return; // never save empty
                              updateOpp(workspaceOpp.id, {
                                dueDate: dueDateInput,
                                timeline: [...(workspaceOpp.timeline||[]), `${new Date().toISOString()} - Closing date set to ${dueDateInput}`]
                              });
                              setEditingDueDate(false);
                            }}>Save</button>
                          <button className="btn ghost sm" style={{ fontSize:10.5, padding:'2px 8px' }}
                            onClick={e => { e.stopPropagation();
                              setDueDateInput(workspaceOpp.dueDate || ''); // restore original
                              setEditingDueDate(false);
                            }}>Cancel</button>
                          {workspaceOpp.dueDate && (
                            <button className="btn ghost sm" style={{ fontSize:10.5, padding:'2px 8px', color:'#dc2626' }}
                              onClick={e => { e.stopPropagation();
                                if (!window.confirm('Clear the closing date?')) return;
                                updateOpp(workspaceOpp.id, { dueDate: null, timeline:[...(workspaceOpp.timeline||[]),`${new Date().toISOString()} - Closing date cleared`] });
                                setEditingDueDate(false);
                              }}>Clear</button>
                          )}
                        </div>
                      </div>
                    ) : (
                      <div
                        onClick={() => { setEditingDueDate(true); setDueDateInput(workspaceOpp.dueDate || ''); }}
                        title="Click to edit closing date"
                        style={{ cursor:'pointer' }}
                      >
                        {workspaceOpp.dueDate ? (() => {
                          const days = Math.round((new Date(workspaceOpp.dueDate) - new Date()) / 86400000);
                          const color = days < 0 ? '#dc2626' : days <= 7 ? '#dc2626' : days <= 14 ? '#d97706' : '#16a34a';
                          const label = days < 0 ? 'OVERDUE' : days === 0 ? 'Due TODAY' : `Due in ${days}d`;
                          return (
                            <>
                              <div style={{ fontSize:12, fontWeight:700, color }}>
                                {new Date(workspaceOpp.dueDate).toLocaleDateString('en-AU',{day:'numeric',month:'short',year:'numeric'})}
                              </div>
                              <div style={{ fontSize:10.5, fontWeight:700, padding:'2px 7px', borderRadius:6,
                                background:color+'18', color, border:`1px solid ${color}44`, marginTop:2, display:'inline-block' }}>
                                {label}
                              </div>
                            </>
                          );
                        })() : (
                          <div style={{ fontSize:11, color:'#2563eb', fontStyle:'italic', textDecoration:'underline' }}>+ Set date</div>
                        )}
                        <div style={{ fontSize:9, color:'var(--ink-4)', marginTop:2 }}>click to edit</div>
                      </div>
                    )}
                  </div>
                  {/* Score circle */}
                  <div style={{ width:46, height:46, borderRadius:'50%', display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center',
                    background: scoreColor(workspaceOpp.fitScore||0)+'18', border:`2px solid ${scoreColor(workspaceOpp.fitScore||0)}` }}>
                    <div style={{ fontSize:15, fontWeight:800, color:scoreColor(workspaceOpp.fitScore||0), lineHeight:1 }}>{workspaceOpp.fitScore||'?'}</div>
                    <div style={{ fontSize:7, color:scoreColor(workspaceOpp.fitScore||0), fontWeight:700 }}>FIT</div>
                  </div>
                </div>
              </div>

              {/* Action buttons - full pipeline with undo at every stage */}
              <div style={{ display:'flex', gap:7, flexWrap:'wrap', alignItems:'center' }}>
                {/* Pipeline breadcrumb */}
                <div style={{ display:'flex', alignItems:'center', gap:4, fontSize:10.5, color:'var(--ink-4)', marginRight:4, flexShrink:0 }}>
                  {['new/review','→','tendering','→','won/lost'].map((s,i) => {
                    const isActive = (s==='new/review' && ['new','review'].includes(workspaceOpp.status))
                      || (s==='tendering' && workspaceOpp.status==='tendering')
                      || (s==='won/lost' && ['won','lost','passed'].includes(workspaceOpp.status));
                    return <span key={i} style={{ fontWeight: isActive?700:400, color: isActive?'#1d4ed8':'var(--ink-4)' }}>{s}</span>;
                  })}
                </div>

                {(workspaceOpp.status === 'new' || workspaceOpp.status === 'review') && (
                  <>
                    <button className="btn primary sm"
                      onClick={() => handleDecision(workspaceOpp, 'go')}
                      style={{ background:'#16a34a', borderColor:'#16a34a' }}
                    >✅ GO - Start Tendering</button>
                    <button className="btn ghost sm"
                      onClick={() => handleDecision(workspaceOpp, 'pass')}
                      style={{ color:'#dc2626', borderColor:'#fca5a5' }}
                    >❌ Pass</button>
                    <button className="btn ghost sm"
                      onClick={() => dismissOpp(workspaceOpp)}
                      style={{ color:'#9ca3af', borderColor:'#e5e7eb', fontSize:11 }}
                      title="Permanently remove from Opportunities"
                    >🗑 Dismiss</button>
                    <button className="btn ghost sm"
                      onClick={() => updateOpp(workspaceOpp.id, { status: workspaceOpp.status === 'review' ? 'new' : 'review' })}
                      style={{ color: workspaceOpp.status === 'review' ? '#854d0e' : 'var(--ink-3)',
                        borderColor: workspaceOpp.status === 'review' ? '#fde68a' : undefined,
                        background: workspaceOpp.status === 'review' ? '#fef9c3' : undefined }}
                    >{workspaceOpp.status === 'review' ? '👀 In Review - click to clear' : '👀 Mark for Review'}</button>
                  </>
                )}

                {workspaceOpp.status === 'tendering' && (
                  <>
                    <button className="btn primary sm"
                      onClick={() => updateOpp(workspaceOpp.id, { status:'won', timeline:[...(workspaceOpp.timeline||[]),`${new Date().toISOString()} - Marked as Won`] }).then(() => showToast('🎉 Won!'))}
                      style={{ background:'#16a34a', borderColor:'#16a34a' }}
                    >🎉 Mark Won</button>
                    <button className="btn ghost sm"
                      onClick={() => updateOpp(workspaceOpp.id, { status:'lost', timeline:[...(workspaceOpp.timeline||[]),`${new Date().toISOString()} - Marked as Lost`] }).then(() => showToast('Marked as Lost'))}
                      style={{ color:'#6b7280' }}
                    >❌ Mark Lost</button>
                    {/* Undo back to To Check */}
                    <button className="btn ghost sm"
                      onClick={() => updateOpp(workspaceOpp.id, { status:'new', userDecision:null, timeline:[...(workspaceOpp.timeline||[]),`${new Date().toISOString()} - Moved back to To Check`] }).then(() => showToast('Moved back to To Check'))}
                      style={{ color:'#6b7280', fontSize:11 }}
                    >↩ Undo - back to To Check</button>
                    {!workspaceOpp.benchmarkLinked && (
                      <button className="btn ghost sm"
                        onClick={() => { setLinkModal(true); setLinkQuoteNo(workspaceOpp.benchmarkQuoteNo||''); setLinkQuoteName(workspaceOpp.benchmarkQuoteName||''); }}
                        style={{ color:'#d97706', borderColor:'#fde68a', fontWeight:700 }}
                      >⚠️ Link BenchMark Quote</button>
                    )}
                    {workspaceOpp.benchmarkLinked && (
                      <button className="btn ghost sm"
                        onClick={() => { setLinkModal(true); setLinkQuoteNo(workspaceOpp.benchmarkQuoteNo||''); setLinkQuoteName(workspaceOpp.benchmarkQuoteName||''); }}
                        style={{ color:'#16a34a' }}
                      >🔗 BM: {workspaceOpp.benchmarkQuoteNo || workspaceOpp.benchmarkQuoteName}</button>
                    )}
                  </>
                )}

                {/* Won - show undo */}
                {workspaceOpp.status === 'won' && (
                  <>
                    <span style={{ fontSize:12, fontWeight:700, padding:'4px 12px', borderRadius:8, background:'#dcfce7', color:'#166534', border:'1px solid #bbf7d0' }}>🎉 WON</span>
                    <button className="btn ghost sm"
                      onClick={() => updateOpp(workspaceOpp.id, { status:'tendering', timeline:[...(workspaceOpp.timeline||[]),`${new Date().toISOString()} - Won status undone - back to Tendering`] }).then(() => showToast('Undone - back to Tendering'))}
                      style={{ color:'#6b7280', fontSize:11 }}
                    >↩ Undo Won</button>
                  </>
                )}

                {/* Lost - show undo */}
                {workspaceOpp.status === 'lost' && (
                  <>
                    <span style={{ fontSize:12, fontWeight:700, padding:'4px 12px', borderRadius:8, background:'#fee2e2', color:'#991b1b', border:'1px solid #fecaca' }}>❌ LOST</span>
                    <button className="btn ghost sm"
                      onClick={() => updateOpp(workspaceOpp.id, { status:'tendering', timeline:[...(workspaceOpp.timeline||[]),`${new Date().toISOString()} - Lost status undone - back to Tendering`] }).then(() => showToast('Undone - back to Tendering'))}
                      style={{ color:'#6b7280', fontSize:11 }}
                    >↩ Undo Lost</button>
                  </>
                )}

                {/* Passed - show undo + dismiss */}
                {workspaceOpp.status === 'passed' && (
                  <>
                    <span style={{ fontSize:12, fontWeight:700, padding:'4px 12px', borderRadius:8, background:'#f3f4f6', color:'#6b7280', border:'1px solid #e5e7eb' }}>PASSED</span>
                    <button className="btn ghost sm"
                      onClick={() => updateOpp(workspaceOpp.id, { status:'new', userDecision:null, timeline:[...(workspaceOpp.timeline||[]),`${new Date().toISOString()} - Pass undone - back to To Check`] }).then(() => showToast('Undone - back to To Check'))}
                      style={{ color:'#2563eb', fontSize:11, fontWeight:700 }}
                    >↩ Undo Pass - back to To Check</button>
                    <button className="btn ghost sm"
                      onClick={() => dismissOpp(workspaceOpp)}
                      style={{ color:'#9ca3af', borderColor:'#e5e7eb', fontSize:11 }}
                      title="Permanently remove from Opportunities"
                    >🗑 Dismiss</button>
                  </>
                )}
              </div>
            </div>

            {/* Workspace tabs */}
            <div style={{ display:'flex', borderBottom:'1px solid var(--line)', flexShrink:0, background:'var(--bg)' }}>
              {[['summary','📊 Score & Summary'],['tasks','✅ Tasks'],['timeline','📅 Timeline']].map(([k,l]) => (
                <button key={k} onClick={() => setTab(k)}
                  style={{ all:'unset', cursor:'pointer', padding:'8px 16px', fontSize:12, fontWeight:tab===k?700:400,
                    color: tab===k ? 'var(--ink)' : 'var(--ink-3)',
                    borderBottom: tab===k ? '2px solid #2563eb' : '2px solid transparent',
                  }}>{l}</button>
              ))}
            </div>

            <div style={{ flex:1, overflowY:'auto', padding:'16px 20px' }}>

              {/* SUMMARY TAB */}
              {tab === 'summary' && (
                <div>
                  {/* AI Summary */}
                  {workspaceOpp.summary && (
                    <div style={{ background:'#f0f9ff', border:'1px solid #bfdbfe', borderRadius:8, padding:'12px 14px', marginBottom:16 }}>
                      <div style={{ fontSize:10.5, fontWeight:700, color:'#1e40af', marginBottom:5, textTransform:'uppercase', letterSpacing:'0.06em' }}>🤖 AI Assessment</div>
                      <div style={{ fontSize:13, color:'#1e3a5f', lineHeight:1.55 }}>{workspaceOpp.summary}</div>
                    </div>
                  )}

                  {/* Score breakdown */}
                  <div style={{ marginBottom:16 }}>
                    <div style={{ fontSize:11, fontWeight:700, color:'var(--ink-2)', marginBottom:8, textTransform:'uppercase', letterSpacing:'0.06em' }}>Score Breakdown</div>
                    {Object.entries(workspaceOpp.scoreBreakdown || {}).map(([k,v]) => {
                      const maxes = { scopeMatch:40, location:20, valueRange:15, clientType:15, leadTime:10 };
                      const max   = maxes[k] || 10;
                      const pct   = Math.round((v/max)*100);
                      const labels = { scopeMatch:'Scope Match', location:'Location', valueRange:'Value Range', clientType:'Client Type', leadTime:'Lead Time' };
                      return (
                        <div key={k} style={{ marginBottom:7 }}>
                          <div style={{ display:'flex', justifyContent:'space-between', fontSize:11.5, marginBottom:2 }}>
                            <span style={{ color:'var(--ink-2)' }}>{labels[k]||k}</span>
                            <span style={{ fontFamily:'var(--mono)', fontWeight:700, color:scoreColor(pct) }}>{v}/{max}</span>
                          </div>
                          <div style={{ height:5, background:'#f0ede8', borderRadius:3, overflow:'hidden' }}>
                            <div style={{ height:'100%', width:`${pct}%`, background:scoreColor(pct), borderRadius:3 }}/>
                          </div>
                        </div>
                      );
                    })}
                  </div>

                  {/* Risk flags */}
                  {(workspaceOpp.riskFlags||[]).length > 0 && (
                    <div style={{ marginBottom:16 }}>
                      <div style={{ fontSize:11, fontWeight:700, color:'var(--ink-2)', marginBottom:6, textTransform:'uppercase', letterSpacing:'0.06em' }}>Risk Flags</div>
                      {workspaceOpp.riskFlags.map((f,i) => (
                        <div key={i} style={{ display:'flex', alignItems:'center', gap:6, marginBottom:4, fontSize:12, color:'#92400e' }}>
                          <span>⚠️</span><span>{f}</span>
                        </div>
                      ))}
                    </div>
                  )}

                  {/* Extracted details */}
                  <div style={{ marginBottom:16 }}>
                    <div style={{ fontSize:11, fontWeight:700, color:'var(--ink-2)', marginBottom:8, textTransform:'uppercase', letterSpacing:'0.06em' }}>Tender Details</div>
                    <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:'6px 16px' }}>
                      {[
                        ['Client',    workspaceOpp.client],
                        ['Location',  workspaceOpp.location],
                        ['Scope',     workspaceOpp.scope],
                        ['Value',     workspaceOpp.tenderValue],
                        ['Due Date',  workspaceOpp.dueDate],
                        ['Ref',       workspaceOpp.tenderRef],
                        ['Contact',   workspaceOpp.contactName],
                        ['Portal',    workspaceOpp.portalSource],
                      ].filter(([,v]) => v).map(([k,v]) => (
                        <div key={k}>
                          <div style={{ fontSize:10, color:'var(--ink-4)', fontWeight:600, textTransform:'uppercase', letterSpacing:'0.05em' }}>{k}</div>
                          <div style={{ fontSize:12, color:'var(--ink)', marginTop:1 }}>{v}</div>
                        </div>
                      ))}
                    </div>
                  </div>

                  {/* BenchMark status */}
                  <div style={{ background: workspaceOpp.benchmarkLinked ? '#f0fdf4' : '#fefce8',
                    border:`1px solid ${workspaceOpp.benchmarkLinked ? '#bbf7d0' : '#fde68a'}`,
                    borderRadius:8, padding:'10px 14px' }}>
                    <div style={{ fontSize:11, fontWeight:700, marginBottom:4,
                      color: workspaceOpp.benchmarkLinked ? '#166534' : '#854d0e' }}>
                      🔗 BenchMark Estimating
                    </div>
                    {workspaceOpp.benchmarkLinked ? (
                      <div style={{ fontSize:12 }}>
                        Linked: <strong>{workspaceOpp.benchmarkQuoteNo || workspaceOpp.benchmarkQuoteName}</strong>
                        {workspaceOpp.benchmarkQuoteNo && workspaceOpp.benchmarkQuoteName && ` - ${workspaceOpp.benchmarkQuoteName}`}
                      </div>
                    ) : (
                      <div style={{ fontSize:12, color:'#92400e' }}>
                        ⚠️ Not yet linked to a BenchMark quote. Michael (Senior Estimator) creates this - ask him for the quote number once the estimate is underway.
                        <button className="btn ghost sm" style={{ marginTop:7, display:'block', color:'#d97706', borderColor:'#fde68a', fontWeight:700 }}
                          onClick={() => { setLinkModal(true); setLinkQuoteNo(''); setLinkQuoteName(''); }}>
                          + Link Quote Now
                        </button>
                      </div>
                    )}
                  </div>
                </div>
              )}

              {/* TASKS TAB */}
              {tab === 'tasks' && (
                <div>
                  {/* Sub-tabs: Active | Deleted */}
                  <div style={{ display:'flex', gap:0, marginBottom:12, borderBottom:'1px solid var(--line)' }}>
                    {[['active', 'Tasks', (workspaceOpp.tasks||[]).filter(t=>!t.deletedAt).length],
                      ['deleted', 'Deleted', (workspaceOpp.tasks||[]).filter(t=>!!t.deletedAt).length]
                    ].map(([k,l,c]) => (
                      <button key={k} onClick={() => setTaskTab(k)}
                        style={{ all:'unset', cursor:'pointer', padding:'5px 14px', fontSize:12, fontWeight: taskTab===k?700:400,
                          color: taskTab===k ? 'var(--ink)' : 'var(--ink-4)',
                          borderBottom: taskTab===k ? '2px solid #2563eb' : '2px solid transparent',
                          marginBottom:-1 }}
                      >{l}{c > 0 && <span style={{ marginLeft:4, fontSize:10, opacity:0.6 }}>{c}</span>}</button>
                    ))}
                    {taskTab === 'active' && (
                      <button className="btn ghost sm"
                        onClick={() => setAddingTask(a => !a)}
                        style={{ marginLeft:'auto', fontSize:11, fontWeight:700, color:'#2563eb', borderColor:'#bfdbfe', marginBottom:4 }}
                      >{addingTask ? '✕ Cancel' : '+ Add Task'}</button>
                    )}
                  </div>

                  {/* Progress bar */}
                  {taskTab === 'active' && (() => {
                    const total = (workspaceOpp.tasks||[]).filter(t=>!t.deletedAt).length;
                    const done = (workspaceOpp.tasks||[]).filter(t=>!t.deletedAt && t.done).length;
                    return total > 0 ? (
                      <div style={{ marginBottom:12 }}>
                        <div style={{ display:'flex', justifyContent:'space-between', fontSize:11, color:'var(--ink-4)', marginBottom:4 }}>
                          <span>{done} of {total} complete</span>
                          <span>{Math.round(done/total*100)}%</span>
                        </div>
                        <div style={{ height:4, background:'#f0ede8', borderRadius:2, overflow:'hidden' }}>
                          <div style={{ height:'100%', width:`${Math.round(done/total*100)}%`, background:'#16a34a', borderRadius:2, transition:'width 0.3s' }}/>
                        </div>
                      </div>
                    ) : null;
                  })()}

                  {/* Add task form */}
                  {addingTask && (
                    <div style={{ background:'#f8faff', border:'1px solid #bfdbfe', borderRadius:8, padding:'12px 14px', marginBottom:14 }}>
                      <div style={{ fontSize:11, fontWeight:700, color:'#1d4ed8', marginBottom:8 }}>New Task</div>
                      <input
                        autoFocus
                        value={newTaskTitle}
                        onChange={e => setNewTaskTitle(e.target.value)}
                        onKeyDown={e => { if(e.key==='Enter') addTask(workspaceOpp.id); if(e.key==='Escape') setAddingTask(false); }}
                        placeholder="Task title..."
                        style={{ width:'100%', boxSizing:'border-box', border:'1px solid #bfdbfe', borderRadius:6, padding:'7px 10px', fontSize:13, marginBottom:8, outline:'none' }}
                      />
                      <div style={{ display:'flex', gap:8, marginBottom:8 }}>
                        <select value={newTaskCat} onChange={e => setNewTaskCat(e.target.value)}
                          style={{ flex:1, border:'1px solid #e5e7eb', borderRadius:6, padding:'6px 8px', fontSize:12 }}>
                          <option value="estimating">📊 Estimating</option>
                          <option value="supplier">📦 Suppliers</option>
                          <option value="compliance">📝 Compliance</option>
                          <option value="admin">⚙️ Admin</option>
                        </select>
                        <input
                          value={newTaskAssign}
                          onChange={e => setNewTaskAssign(e.target.value)}
                          placeholder="Assignee (optional)"
                          style={{ flex:1, border:'1px solid #e5e7eb', borderRadius:6, padding:'6px 10px', fontSize:12, outline:'none' }}
                        />
                      </div>
                      <div style={{ display:'flex', gap:8 }}>
                        <button className="btn primary sm" style={{ flex:1 }}
                          disabled={!newTaskTitle.trim() || taskWorking}
                          onClick={() => addTask(workspaceOpp.id)}
                        >{taskWorking ? 'Adding...' : 'Add Task'}</button>
                        <button className="btn ghost sm" onClick={() => setAddingTask(false)}>Cancel</button>
                      </div>
                    </div>
                  )}

                  {/* ACTIVE TASKS */}
                  {taskTab === 'active' && (
                    <>
                      {(workspaceOpp.tasks||[]).filter(t=>!t.deletedAt).length === 0 && !addingTask && (
                        <div style={{ color:'var(--ink-4)', fontSize:13, padding:'20px 0', textAlign:'center' }}>No tasks yet. Click + Add Task to create one.</div>
                      )}
                      {['estimating','supplier','compliance','admin'].map(cat => {
                        const catTasks = (workspaceOpp.tasks||[]).filter(t => !t.deletedAt && (t.category||'admin') === cat);
                        if (!catTasks.length) return null;
                        const catLabels = { estimating:'📊 Estimating', supplier:'📦 Suppliers', compliance:'📝 Compliance', admin:'⚙️ Admin' };
                        return (
                          <div key={cat} style={{ marginBottom:16 }}>
                            <div style={{ fontSize:10.5, fontWeight:700, color:'var(--ink-3)', textTransform:'uppercase', letterSpacing:'0.07em', marginBottom:6 }}>
                              {catLabels[cat]||cat}
                            </div>
                            {catTasks.map(task => (
                              <div key={task.id} style={{ borderBottom:'1px solid var(--line)', opacity: task.done ? 0.6 : 1 }}>
                                {editingTask === task.id ? (
                                  /* Inline edit form */
                                  <div style={{ padding:'8px 0', display:'flex', flexDirection:'column', gap:6 }}>
                                    <input autoFocus value={editTaskTitle} onChange={e=>setEditTaskTitle(e.target.value)}
                                      onKeyDown={e=>{ if(e.key==='Enter') saveEditTask(workspaceOpp.id,task.id); if(e.key==='Escape'){setEditingTask(null);} }}
                                      style={{ border:'1px solid #bfdbfe', borderRadius:6, padding:'5px 9px', fontSize:13, outline:'none', background:'#f0f7ff' }}
                                    />
                                    <input value={editTaskAssign} onChange={e=>setEditTaskAssign(e.target.value)}
                                      placeholder="Assignee"
                                      style={{ border:'1px solid #e5e7eb', borderRadius:6, padding:'4px 9px', fontSize:12, outline:'none' }}
                                    />
                                    <div style={{ display:'flex', gap:6 }}>
                                      <button className="btn primary sm" style={{ flex:1 }}
                                        disabled={!editTaskTitle.trim()}
                                        onClick={() => saveEditTask(workspaceOpp.id, task.id)}>Save</button>
                                      <button className="btn ghost sm" onClick={() => setEditingTask(null)}>Cancel</button>
                                    </div>
                                  </div>
                                ) : (
                                  <div style={{ display:'flex', alignItems:'flex-start', gap:8, padding:'8px 0' }}>
                                    <input type="checkbox" checked={!!task.done}
                                      onChange={e => toggleTask(workspaceOpp.id, task.id, e.target.checked)}
                                      style={{ marginTop:2, flexShrink:0, cursor:'pointer', width:14, height:14 }}
                                    />
                                    <div style={{ flex:1, minWidth:0 }}>
                                      <div style={{ fontSize:12.5, color:'var(--ink)', textDecoration: task.done ? 'line-through' : 'none' }}>
                                        {task.title}
                                      </div>
                                      {task.assignee && <div style={{ fontSize:10.5, color:'var(--ink-4)', marginTop:1 }}>👤 {task.assignee}</div>}
                                      {task.doneAt && (
                                        <div style={{ fontSize:10, color:'#16a34a', marginTop:1 }}>
                                          ✓ Done {new Date(task.doneAt).toLocaleString('en-AU',{day:'numeric',month:'short',hour:'2-digit',minute:'2-digit'})}
                                        </div>
                                      )}
                                    </div>
                                    {/* Edit button */}
                                    <button
                                      onClick={() => { setEditingTask(task.id); setEditTaskTitle(task.title); setEditTaskAssign(task.assignee||''); }}
                                      style={{ all:'unset', cursor:'pointer', fontSize:12, color:'#d1d5db', padding:'0 3px', lineHeight:1, flexShrink:0 }}
                                      onMouseEnter={e=>e.currentTarget.style.color='#2563eb'}
                                      onMouseLeave={e=>e.currentTarget.style.color='#d1d5db'}
                                      title="Edit task"
                                    >✏️</button>
                                    {/* Delete button */}
                                    <button
                                      onClick={() => deleteTask(workspaceOpp.id, task.id)}
                                      style={{ all:'unset', cursor:'pointer', fontSize:13, color:'#d1d5db', padding:'0 3px', lineHeight:1, flexShrink:0 }}
                                      onMouseEnter={e=>e.currentTarget.style.color='#dc2626'}
                                      onMouseLeave={e=>e.currentTarget.style.color='#d1d5db'}
                                      title="Delete task"
                                    >✕</button>
                                  </div>
                                )}
                              </div>
                            ))}
                          </div>
                        );
                      })}
                    </>
                  )}

                  {/* DELETED TASKS */}
                  {taskTab === 'deleted' && (
                    <>
                      {(workspaceOpp.tasks||[]).filter(t=>!!t.deletedAt).length === 0 ? (
                        <div style={{ color:'var(--ink-4)', fontSize:13, padding:'20px 0', textAlign:'center' }}>No deleted tasks.</div>
                      ) : (
                        (workspaceOpp.tasks||[]).filter(t=>!!t.deletedAt).map(task => (
                          <div key={task.id} style={{ display:'flex', alignItems:'center', gap:8, padding:'8px 0',
                            borderBottom:'1px solid var(--line)', opacity:0.6 }}>
                            <div style={{ flex:1, minWidth:0 }}>
                              <div style={{ fontSize:12.5, color:'var(--ink)', textDecoration:'line-through' }}>{task.title}</div>
                              {task.assignee && <div style={{ fontSize:10.5, color:'var(--ink-4)' }}>👤 {task.assignee}</div>}
                              <div style={{ fontSize:10, color:'var(--ink-4)', marginTop:1 }}>
                                Deleted {new Date(task.deletedAt).toLocaleString('en-AU',{day:'numeric',month:'short',hour:'2-digit',minute:'2-digit'})}
                              </div>
                            </div>
                            <button className="btn ghost sm" style={{ fontSize:11, color:'#16a34a', borderColor:'#bbf7d0', flexShrink:0 }}
                              onClick={() => restoreTask(workspaceOpp.id, task.id)}>Restore</button>
                          </div>
                        ))
                      )}
                    </>
                  )}
                </div>
              )}

              {/* TIMELINE TAB */}
              {tab === 'timeline' && (
                <div>
                  {(workspaceOpp.timeline||[]).length === 0 && (
                    <div style={{ color:'var(--ink-4)', fontSize:13 }}>No timeline entries yet.</div>
                  )}
                  {[...(workspaceOpp.timeline||[])].reverse().map((entry, i) => {
                    const parts = entry.split(' - ');
                    const ts = parts[0] ? new Date(parts[0]).toLocaleString('en-AU',{day:'numeric',month:'short',hour:'2-digit',minute:'2-digit'}) : parts[0];
                    const msg = parts.slice(1).join(' - ');
                    return (
                      <div key={i} style={{ display:'flex', gap:10, marginBottom:10 }}>
                        <div style={{ width:6, flexShrink:0, marginTop:5 }}>
                          <div style={{ width:6, height:6, borderRadius:'50%', background:'#2563eb' }}/>
                        </div>
                        <div>
                          <div style={{ fontSize:11, color:'var(--ink-4)', fontFamily:'var(--mono)' }}>{ts}</div>
                          <div style={{ fontSize:12.5, color:'var(--ink)', marginTop:1 }}>{msg}</div>
                        </div>
                      </div>
                    );
                  })}
                </div>
              )}

            </div>
          </>
        )}
      </div>

      {/* BenchMark Link Modal */}
      {linkModal && (
        <div style={{ position:'fixed', inset:0, background:'rgba(0,0,0,0.5)', zIndex:900, display:'flex', alignItems:'center', justifyContent:'center' }}
          onClick={() => setLinkModal(false)}>
          <div style={{ background:'#fff', borderRadius:12, padding:'24px', width:400, boxShadow:'0 8px 32px rgba(0,0,0,0.2)' }}
            onClick={e => e.stopPropagation()}>
            <div style={{ fontSize:15, fontWeight:700, marginBottom:4 }}>🔗 Link BenchMark Quote</div>
            <div style={{ fontSize:12.5, color:'#6b7280', marginBottom:16, lineHeight:1.5 }}>
              Michael (Senior Estimator) creates the BenchMark quote. Enter the quote number or name once he has set it up. You can skip this now and link it later.
            </div>
            <div style={{ marginBottom:10 }}>
              <label style={{ fontSize:11.5, fontWeight:600, display:'block', marginBottom:4 }}>Quote Number <span style={{color:'#9ca3af',fontWeight:400}}>(e.g. Q-2026-142)</span></label>
              <input value={linkQuoteNo} onChange={e=>setLinkQuoteNo(e.target.value)}
                style={{ width:'100%', boxSizing:'border-box', border:'1px solid #e5e7eb', borderRadius:6, padding:'7px 10px', fontSize:13 }}
                placeholder="Q-XXXX" />
            </div>
            <div style={{ marginBottom:18 }}>
              <label style={{ fontSize:11.5, fontWeight:600, display:'block', marginBottom:4 }}>Quote Name <span style={{color:'#9ca3af',fontWeight:400}}>(optional)</span></label>
              <input value={linkQuoteName} onChange={e=>setLinkQuoteName(e.target.value)}
                style={{ width:'100%', boxSizing:'border-box', border:'1px solid #e5e7eb', borderRadius:6, padding:'7px 10px', fontSize:13 }}
                placeholder="e.g. VICT Terminal Expansion" />
            </div>
            <div style={{ display:'flex', gap:8 }}>
              <button className="btn ghost sm" style={{ flex:1 }} onClick={() => setLinkModal(false)}>Cancel</button>
              <button className="btn ghost sm" style={{ flex:1, color:'#6b7280' }}
                onClick={() => {
                  // Skip linking - proceed to GO without BM link
                  if (workspaceOpp?.status !== 'tendering') confirmGo();
                  else setLinkModal(false);
                }}
              >Skip for now ⚠️</button>
              <button className="btn primary sm" style={{ flex:1 }}
                disabled={!linkQuoteNo.trim() && !linkQuoteName.trim() || savingLink}
                onClick={workspaceOpp?.status !== 'tendering' ? confirmGo : async () => {
                  setSavingLink(true);
                  try {
                    await updateOpp(workspaceOpp.id, {
                      benchmarkLinked: true,
                      benchmarkQuoteNo: linkQuoteNo.trim()||null,
                      benchmarkQuoteName: linkQuoteName.trim()||null,
                      benchmarkLinkedAt: new Date().toISOString(),
                    });
                    setLinkModal(false);
                    showToast('🔗 BenchMark quote linked');
                  } finally { setSavingLink(false); }
                }}
              >{savingLink ? 'Saving...' : 'Link & Confirm'}</button>
            </div>
          </div>
        </div>
      )}

      {/* Toast */}
      {toast && (
        <div style={{ position:'fixed', bottom:24, right:24, zIndex:999, background:'#1a1a18', color:'#fff',
          padding:'10px 18px', borderRadius:8, fontSize:13, fontWeight:500, boxShadow:'0 4px 20px rgba(0,0,0,.25)' }}>
          {toast}
        </div>
      )}
    </div>
  );
}

// ============================================================
// Move Log View - full audit trail of all email folder moves
// ============================================================
function MoveLogView() {
  const [log,      setLog]      = useState([]);
  const [loading,  setLoading]  = useState(true);
  const [filter,   setFilter]   = useState('all'); // 'all' | 'portal' | 'outlook'
  const [search,   setSearch]   = useState('');

  async function loadLog() {
    setLoading(true);
    try {
      const d = await api.get('/api/tenders/move-log?limit=500');
      setLog(d.log || []);
    } catch(e) { console.error(e); }
    finally { setLoading(false); }
  }

  useEffect(() => { loadLog(); }, []);

  const filtered = log.filter(entry => {
    if (filter === 'portal' && entry.source !== 'portal') return false;
    if (filter === 'outlook' && entry.source !== 'outlook') return false;
    if (search) {
      const q = search.toLowerCase();
      return (entry.subject||'').toLowerCase().includes(q) ||
             (entry.from||'').toLowerCase().includes(q) ||
             (entry.toFolder||'').toLowerCase().includes(q) ||
             (entry.fromFolder||'').toLowerCase().includes(q);
    }
    return true;
  });

  function sourceBadge(source) {
    if (source === 'portal') return { bg:'#dbeafe', color:'#1d4ed8', label:'🖥 Portal' };
    return { bg:'#f3e8ff', color:'#6b21a8', label:'📧 Outlook' };
  }

  return (
    <div style={{ maxWidth:900 }}>
      {/* Header */}
      <div style={{ display:'flex', alignItems:'center', gap:12, marginBottom:20, flexWrap:'wrap' }}>
        <div style={{ flex:1, minWidth:0 }}>
          <div style={{ fontSize:16, fontWeight:700, color:'var(--ink)', marginBottom:3 }}>📂 Email Move Log</div>
          <div style={{ fontSize:12, color:'var(--ink-3)' }}>
            Every folder move for tenders@eliteroads.com.au - whether done in this portal or directly in Outlook.
          </div>
        </div>
        <button className="btn ghost sm" onClick={loadLog} disabled={loading}>
          {loading ? <I.spin/> : <I.refresh/>} Refresh
        </button>
      </div>

      {/* Filters */}
      <div style={{ display:'flex', gap:8, marginBottom:14, flexWrap:'wrap', alignItems:'center' }}>
        <div style={{ display:'flex', gap:4 }}>
          {[['all','All'],['portal','🖥 Portal'],['outlook','📧 Outlook']].map(([k,l]) => (
            <button key={k} onClick={() => setFilter(k)}
              style={{ all:'unset', cursor:'pointer', padding:'4px 12px', borderRadius:12, fontSize:11.5, fontWeight:600,
                background: filter===k ? '#1a1a18' : '#f0ede8', color: filter===k ? '#fff' : 'var(--ink-3)' }}>{l}</button>
          ))}
        </div>
        <input
          value={search} onChange={e => setSearch(e.target.value)}
          placeholder="Search subject, folder, sender..."
          style={{ flex:1, minWidth:180, border:'1px solid var(--line)', borderRadius:6, padding:'5px 10px', fontSize:12, outline:'none' }}
        />
        <span style={{ fontSize:11.5, color:'var(--ink-4)', flexShrink:0 }}>{filtered.length} entries</span>
      </div>

      {loading && (
        <div style={{ color:'var(--ink-3)', fontSize:13, display:'flex', alignItems:'center', gap:8, padding:'20px 0' }}>
          <I.spin/> Loading move log...
        </div>
      )}

      {!loading && filtered.length === 0 && (
        <div style={{ textAlign:'center', padding:'48px 0', color:'var(--ink-4)' }}>
          <div style={{ fontSize:28, marginBottom:10 }}>📂</div>
          <div style={{ fontWeight:600, marginBottom:4 }}>No moves recorded yet</div>
          <div style={{ fontSize:12 }}>Moves will appear here as soon as emails are filed - from the portal or Outlook.</div>
        </div>
      )}

      {/* Log table */}
      {!loading && filtered.length > 0 && (
        <div style={{ border:'1px solid var(--line)', borderRadius:8, overflow:'hidden' }}>
          {/* Table header */}
          <div style={{ display:'grid', gridTemplateColumns:'140px 1fr 130px 130px 90px 100px', gap:0,
            background:'#f8f7f4', borderBottom:'1px solid var(--line)', padding:'7px 14px',
            fontSize:10, fontWeight:700, color:'var(--ink-3)', textTransform:'uppercase', letterSpacing:'0.06em' }}>
            <span>Time</span>
            <span>Email</span>
            <span>From Folder</span>
            <span>To Folder</span>
            <span>Source</span>
            <span>By</span>
          </div>
          {filtered.map((entry, i) => {
            const sb = sourceBadge(entry.source);
            const dt = entry.movedAt ? new Date(entry.movedAt).toLocaleString('en-AU',{
              day:'numeric', month:'short', hour:'2-digit', minute:'2-digit'
            }) : '-';
            return (
              <div key={entry.id || i}
                style={{ display:'grid', gridTemplateColumns:'140px 1fr 130px 130px 90px 100px', gap:0,
                  padding:'9px 14px', borderBottom: i < filtered.length-1 ? '1px solid var(--line)' : 'none',
                  background: entry.source === 'outlook' ? '#fdf8ff' : 'var(--bg)',
                  alignItems:'start',
                }}
              >
                <div style={{ fontSize:11, color:'var(--ink-4)', fontFamily:'var(--mono)', paddingTop:1 }}>{dt}</div>
                <div style={{ minWidth:0, paddingRight:12 }}>
                  <div style={{ fontSize:12.5, fontWeight:600, color:'var(--ink)', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
                    {entry.subject}
                  </div>
                  {entry.from && <div style={{ fontSize:10.5, color:'var(--ink-4)', marginTop:1 }}>{entry.from}</div>}
                </div>
                <div style={{ fontSize:11.5, color:'var(--ink-3)', paddingRight:8 }}>
                  {entry.fromFolder
                    ? <span>{entry.fromFolder}</span>
                    : <span style={{color:'var(--ink-4)',fontStyle:'italic'}}>unknown</span>}
                </div>
                <div style={{ fontSize:11.5, fontWeight:600, color:'var(--ink)', paddingRight:8 }}>
                  → {entry.toFolder}
                </div>
                <div>
                  <span style={{ fontSize:10, fontWeight:700, padding:'2px 7px', borderRadius:8,
                    background:sb.bg, color:sb.color }}>{sb.label}</span>
                </div>
                <div style={{ fontSize:11.5, color:'var(--ink-3)' }}>{entry.movedBy || '-'}</div>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

Object.assign(window, {
  TaskModal, TaskRow, Thread, TodoView, DoneView, RecentlyDeletedView,
  FollowupsView, DisputesView, FolderRulesView, ReplyStyleView,
  AddFollowUpModal, MailboxView, TenderView, TodosView, OpportunitiesView, MoveLogView,
});
