// Mail — Zeranda's "Caught" coverage layer.
// NOT an inbox. The job: prove nothing slipped, so you never open Gmail to double-check.
//
// COST MODEL (important): the bulk of mail is sorted by cheap deterministic RULES
// (sender + keyword match) — no AI. Only the small "needs a look" pile gets a closer,
// model-assisted read. So volume stays cheap; AI is spent only where it earns its keep.
//
// SCOPE: we reconcile a TIME WINDOW (today / yesterday / last 7 / last 30 days), not the
// whole mailbox — bounded work = fast. Lists are paginated so nothing renders in bulk.
// Raw email is fetched live on demand, never stored — matching the privacy promise.

const MAIL_TODAY = '2026-06-24';
function daysAgo(iso) { return Math.round((new Date(MAIL_TODAY) - new Date(iso)) / 86400000); }
function relTime(m) {
  const n = daysAgo(m.dateISO);
  if (n <= 0) return m.when || 'Today';
  if (n === 1) return 'Yesterday';
  if (n < 7) return n + ' days ago';
  return new Date(m.dateISO).toLocaleDateString('en-US', { month:'short', day:'numeric' });
}

// What kind of record an email became / could become.
const MAIL_KIND = {
  bill:         { icon:'dollar-sign',  tint:'gold',   verb:'Bill',         to:'bills' },
  package:      { icon:'package',      tint:'teal',   verb:'Package',      to:'packages' },
  event:        { icon:'calendar',     tint:'purple', verb:'Event',        to:'calendar' },
  task:         { icon:'check-square', tint:'blue',   verb:'Task',         to:'tasks' },
  receipt:      { icon:'receipt',      tint:'gold',   verb:'Receipt',      to:'bills' },
  subscription: { icon:'credit-card',  tint:'green',  verb:'Subscription', to:'subs' },
  statement:    { icon:'landmark',     tint:'blue',   verb:'Statement',    to:'banking' },
};

// Sample mail across a 30-day window, tied to the connected inboxes so source markers light up.
// status: 'handled' (rule → record) | 'skipped' (rule → nothing to do) | 'needs' (rules unsure → closer look)
// needs items carry an `intent`: 'convert' (suggest a record → confirm) or 'read' (just read & acknowledge)
const MAIL = [
  // ===== TODAY — the small pile that needed a closer look + same-day auto-sorted =====
  { id:'m1', status:'needs', intent:'convert', confidence:58, mailbox:'alex.carter@gmail.com', when:'40m ago', dateISO:'2026-06-24',
    from:'Precision Auto Care', sender:'billing@precisionautocare.com', subject:'Service invoice — your June visit',
    snippet:'Thank you for visiting. Please find your service summary attached…',
    reason:'Looks like a service invoice, but the total was split across the PDF — the rules couldn’t pin the amount, so I took a closer look.',
    suggestion:{ kind:'bill', label:'Bill · Precision Auto Care', detail:'≈ $569.63 · due date unclear' },
    attachments:[{ id:'m1a1', name:'invoice-5521.pdf', size:184320 }, { id:'m1a2', name:'service-photos.jpg', size:521000 }],
    body:'Dear Alex,\n\nThank you for servicing your vehicle with Precision Auto Care. Your invoice (#5521) is attached. Charges include engine oil change, front brake pads, air filter, labor and applicable sales tax.\n\nPlease clear the outstanding balance at your earliest convenience.\n\nRegards,\nService desk, Precision Auto Care' },
  { id:'m2', status:'needs', intent:'convert', confidence:64, mailbox:'alex@acme.co', when:'2h ago', dateISO:'2026-06-24',
    from:'Acme PMO', sender:'pmo@acme.co', subject:'Hold: project kickoff — time TBC',
    snippet:'Pencilling in a kickoff for the new engagement. Proposing Tue, time to be confirmed…',
    reason:'Reads like a meeting invite, but there’s no confirmed time yet — I didn’t want to put a wrong slot on your calendar.',
    suggestion:{ kind:'event', label:'Event · Project kickoff', detail:'Tue, Jun 9 · time to confirm' },
    body:'Hi Alex,\n\nWe’d like to kick off the new engagement next week — likely Tuesday. The exact time is still being confirmed with the wider team; I’ll lock it in shortly.\n\nWanted to give you a heads-up so you can keep the afternoon loosely free.\n\nThanks,\nAcme PMO' },
  { id:'m3', status:'needs', intent:'read', confidence:71, mailbox:'alex.carter@gmail.com', when:'5h ago', dateISO:'2026-06-24',
    from:'Chase', sender:'no-reply@alerts.chase.com', subject:'A sign-in was blocked on your account',
    snippet:'We blocked a sign-in attempt from a new device. If this was you, no action is needed…',
    reason:'From your bank and might want a quick look — but there’s nothing to file, so I left it for you to confirm.',
    body:'Dear Customer,\n\nWe blocked a sign-in attempt to your online banking from a new device (Chrome · Windows) at 11:42 PM PT.\n\nIf this was you, you can safely ignore this message. If it wasn’t, please review your recent activity.\n\nWe never ask for your password or one-time codes over email.\n\n— Chase Security' },
  { id:'h_t1', status:'handled', mailbox:'alex.carter@gmail.com', when:'1h ago', dateISO:'2026-06-24', from:'Geico', sender:'noreply@geico.com',
    subject:'Your auto policy payment is due', snippet:'Your premium of $142.00 is due Jun 9…', outcome:{ kind:'bill', detail:'$142.00 · due Jun 9' }, match:'sender “geico.com” + “payment is due”',
    body:'Hi Alex,\n\nThis is a reminder that your auto insurance premium of $142.00 is due on June 9.\n\n— Geico' },
  { id:'h_t2', status:'handled', mailbox:'alex.carter@gmail.com', when:'3h ago', dateISO:'2026-06-24', from:'Amazon', sender:'ship-confirm@amazon.com',
    subject:'Your package is out for delivery', snippet:'AirPods Pro (2nd gen) — arriving today by 9 PM…', outcome:{ kind:'package', detail:'AirPods Pro · by 9 PM' }, match:'sender “amazon.com” + “out for delivery”',
    body:'Hello Alex,\n\nYour AirPods Pro (2nd gen) is out for delivery and should arrive today by 9 PM.\n\n— Amazon' },
  { id:'s_t1', status:'skipped', mailbox:'alex.c.projects@gmail.com', when:'2h ago', dateISO:'2026-06-24', from:'The Hustle', sender:'news@thehustle.co',
    subject:'The 5-minute morning brief', snippet:'Today: markets, a new AI launch, and a chart you’ll like…', skipReason:'Newsletter', match:'bulk-sender + “unsubscribe” footer',
    body:'Good morning! Here’s your daily 5-minute brief…' },
  { id:'s_t2', status:'skipped', mailbox:'alex.carter@gmail.com', when:'4h ago', dateISO:'2026-06-24', from:'LinkedIn', sender:'notifications@linkedin.com',
    subject:'You appeared in 9 searches this week', snippet:'See who’s been looking at your profile…', skipReason:'Social notification', match:'known notification sender',
    body:'Your profile appeared in 9 searches this week.' },
  { id:'s_t3', status:'skipped', mailbox:'alex.carter@gmail.com', when:'20m ago', dateISO:'2026-06-24', from:'Spotify', sender:'no-reply@spotify.com',
    subject:'Your weekly mix is ready 🎵', snippet:'30 fresh songs picked for you this week…', skipReason:'Promotion', match:'promotional sender',
    body:'Your Discover Weekly is ready — 30 new songs.' },

  // ===== YESTERDAY =====
  { id:'h_y1', status:'handled', mailbox:'alex.c.projects@gmail.com', dateISO:'2026-06-23', from:'Maple Grove Properties', sender:'leasing@maplegrove.com',
    subject:'Lease renewal — please call to confirm', snippet:'We need your decision before the June 15 deadline…', outcome:{ kind:'task', detail:'Reply to landlord · due Jun 5' }, match:'“action needed” + known contact',
    body:'Dear Alex,\n\nYour lease is up for renewal. Please confirm before June 15 to lock your rate.\n\n— Karen, Maple Grove' },
  { id:'h_y2', status:'handled', mailbox:'alex.carter@gmail.com', dateISO:'2026-06-23', from:'Adobe', sender:'billing@adobe.com',
    subject:'Your Creative Cloud plan renews soon', snippet:'$59.99 renews automatically on Jun 24…', outcome:{ kind:'subscription', detail:'Creative Cloud · $59.99 / mo' }, match:'sender “adobe.com” + “renews”', attachments:[{ id:'hy2a', name:'adobe-receipt-may.pdf', size:96000 }],
    body:'Hi Alex,\n\nYour Creative Cloud plan renews on June 24 for $59.99. No action needed.\n\n— Adobe' },
  { id:'h_y3', status:'handled', mailbox:'alex.carter@gmail.com', dateISO:'2026-06-23', from:'Bright Smile Dental', sender:'appointments@brightsmile.com',
    subject:'Your cleaning appointment is confirmed', snippet:'Tue, Jun 2 at 4:00 PM. Bring your insurance card…', outcome:{ kind:'event', detail:'Dentist · Jun 2, 4 PM' }, match:'“appointment is confirmed” + date',
    body:'Hi Alex,\n\nThis confirms your cleaning on Tuesday, June 2 at 4:00 PM.\n\n— Bright Smile Dental' },
  { id:'h_y4', status:'handled', mailbox:'alex.carter@gmail.com', dateISO:'2026-06-23', from:'Whole Foods', sender:'receipts@wholefoods.com',
    subject:'Your receipt from today’s visit', snippet:'Total $86.40 · In-store groceries…', outcome:{ kind:'receipt', detail:'$86.40 · groceries' }, match:'sender “wholefoods.com” + “receipt”',
    body:'Thanks for shopping! Your total was $86.40.\n\n— Whole Foods Market' },
  { id:'s_y1', status:'skipped', mailbox:'alex@acme.co', dateISO:'2026-06-23', from:'Sarah Chen', sender:'sarah.chen@acme.co',
    subject:'Re: QBR agenda + deck', snippet:'Adding you as a presenter — slide 6 is yours…', skipReason:'Work thread — nothing to track', match:'reply in human thread, no record signals',
    body:'Adding you as a presenter — slide 6 is yours. Shout if you want to swap.' },
  { id:'s_y2', status:'skipped', mailbox:'alex.c.projects@gmail.com', dateISO:'2026-06-23', from:'Medium Daily', sender:'noreply@medium.com',
    subject:'Stories for you', snippet:'Based on what you read: 6 picks on design and AI…', skipReason:'Newsletter', match:'bulk-sender + “unsubscribe” footer',
    body:'Here are today’s reading picks for you.' },

  // ===== EARLIER THIS WEEK (within 7 days) =====
  { id:'h_w1', status:'handled', mailbox:'a.carter@globex.io', dateISO:'2026-06-22', from:'United Airlines', sender:'noreply@united.com',
    subject:'Your itinerary — UA 738 to San Francisco', snippet:'Departs Fri, Jun 12 · check in 24h before…', outcome:{ kind:'event', detail:'Flight to SFO · Jun 12' }, match:'airline sender + “itinerary”',
    body:'Your flight UA 738 to San Francisco departs Friday, June 12.\n\n— United Airlines' },
  { id:'s_w1', status:'skipped', mailbox:'alex.carter@gmail.com', dateISO:'2026-06-22', from:'Jake Miller', sender:'jake.miller@gmail.com',
    subject:'Trip planning — June long weekend', snippet:'Found two cabins near Tahoe, sending links. Which one?…', skipReason:'Personal — no record needed', match:'personal contact, conversational',
    body:'Found two cabins near Tahoe — sending links. Which one do you prefer?' },
  { id:'h_w2', status:'handled', mailbox:'alex.c.projects@gmail.com', dateISO:'2026-06-21', from:'American Express', sender:'statements@americanexpress.com',
    subject:'Your May 2026 credit card statement', snippet:'Amount due $1,842.00 · due Jun 18…', outcome:{ kind:'statement', detail:'Amex Card · May 2026' }, match:'bank sender + “statement”', attachments:[{ id:'hw2a', name:'Amex-Card-May-2026.pdf', size:198656 }, { id:'hw2b', name:'Rewards-Summary.pdf', size:81920 }],
    body:'Your May credit card statement is ready. Amount due $1,842.00 by Jun 18.\n\n— American Express' },
  { id:'h_w3', status:'handled', mailbox:'alex.carter@gmail.com', dateISO:'2026-06-21', from:'Netflix', sender:'info@netflix.com',
    subject:'Your receipt from Netflix', snippet:'Premium plan · $22.99…', outcome:{ kind:'subscription', detail:'Netflix · $22.99 / mo' }, match:'sender “netflix.com” + “receipt”',
    body:'Thanks for being a member. You were charged $22.99 for Premium.\n\n— Netflix' },
  { id:'s_w2', status:'skipped', mailbox:'alex@acme.co', dateISO:'2026-06-20', from:'David Park', sender:'david.park@acme.co',
    subject:'Re: Staging deploy', snippet:'Rolled back, root-causing now…', skipReason:'Work thread — nothing to track', match:'reply in human thread, no record signals',
    body:'Rolled back the staging deploy, root-causing now. Will update.' },
  { id:'h_w4', status:'handled', mailbox:'alex.carter@gmail.com', dateISO:'2026-06-20', from:'Uber', sender:'receipts@uber.com',
    subject:'Your Tuesday evening trip with Uber', snippet:'Trip · Airport → Home · $31.80…', outcome:{ kind:'receipt', detail:'$31.80 · Airport → Home' }, match:'sender “uber.com” + “trip”',
    body:'Thanks for riding. Your trip total was $31.80.\n\n— Uber' },
  { id:'h_w5', status:'handled', mailbox:'alex.carter@gmail.com', dateISO:'2026-06-19', from:'Experian', sender:'reports@experian.com',
    subject:'Your annual credit report is ready', snippet:'View your complimentary report…', outcome:{ kind:'statement', detail:'Experian · credit report' }, match:'known sender + “report ready”',
    body:'Your annual credit report is ready to view.\n\n— Experian' },
  { id:'s_w3', status:'skipped', mailbox:'alex.c.projects@gmail.com', dateISO:'2026-06-19', from:'Quora Digest', sender:'digest@quora.com',
    subject:'Top answers for you this week', snippet:'5 answers picked from topics you follow…', skipReason:'Newsletter', match:'bulk-sender + “unsubscribe” footer',
    body:'Top answers for you this week.' },
  { id:'h_w6', status:'handled', mailbox:'alex.carter@gmail.com', dateISO:'2026-06-18', from:'Fidelity', sender:'no-reply@fidelity.com',
    subject:'Your May portfolio statement', snippet:'Portfolio value $61,294…', outcome:{ kind:'statement', detail:'Fidelity · May 2026' }, match:'broker sender + “statement”', attachments:[{ id:'hw6a', name:'Fidelity-Holdings-May-2026.pdf', size:401408 }, { id:'hw6b', name:'PnL-Statement-May.csv', size:24576 }],
    body:'Your May holdings statement is attached. Portfolio value $61,294.\n\n— Fidelity' },
  { id:'h_w7', status:'handled', mailbox:'alex.carter@gmail.com', dateISO:'2026-06-18', from:'Amazon', sender:'ship-confirm@amazon.com',
    subject:'Your order has shipped — Nike Pegasus 41', snippet:'Arriving Wed, Jun 4 via UPS…', outcome:{ kind:'package', detail:'Nike Pegasus 41 · Jun 4' }, match:'sender “amazon.com” + “has shipped”',
    body:'Your Nike Pegasus 41 has shipped and arrives Wednesday, June 4.\n\n— Amazon' },

  // ===== EARLIER THIS MONTH (8–30 days) =====
  { id:'h_m1', status:'handled', mailbox:'alex.carter@gmail.com', dateISO:'2026-06-17', from:'Apple', sender:'no_reply@apple.com',
    subject:'Your refund from Apple', snippet:'iCloud+ 2TB · −$9.99…', outcome:{ kind:'receipt', detail:'Refund · −$9.99' }, match:'sender “apple.com” + “refund”',
    body:'A refund of $9.99 for iCloud+ 2TB has been issued.\n\n— Apple' },
  { id:'s_m1', status:'skipped', mailbox:'alex@acme.co', dateISO:'2026-06-16', from:'Glassdoor', sender:'noreply@glassdoor.com',
    subject:'New jobs matching your profile', snippet:'8 roles you might like…', skipReason:'Promotion', match:'promotional sender',
    body:'8 new roles match your profile.' },
  { id:'h_m2', status:'handled', mailbox:'alex@acme.co', dateISO:'2026-06-15', from:'Bank of America', sender:'alerts@bankofamerica.com',
    subject:'Your May 2026 checking account statement', snippet:'Closing balance $8,630…', outcome:{ kind:'statement', detail:'Bank of America · May 2026' }, match:'bank sender + “statement”',
    body:'Your May statement is ready. Closing balance $8,630.\n\n— Bank of America' },
  { id:'h_m3', status:'handled', mailbox:'alex@acme.co', dateISO:'2026-06-15', from:'IRS', sender:'noreply@irs.gov',
    subject:'Q2 estimated tax reminder', snippet:'$540 estimated income tax due Jun 15…', outcome:{ kind:'bill', detail:'$540 · due Jun 15' }, match:'sender “irs.gov” + “tax due”', attachments:[{ id:'hm3a', name:'IRS-Q2-voucher.pdf', size:64000 }],
    body:'This is a reminder that your Q2 estimated tax of $540 is due June 15.\n\n— IRS' },
  { id:'s_m2', status:'skipped', mailbox:'alex.carter@gmail.com', dateISO:'2026-06-12', from:'X', sender:'info@x.com',
    subject:'See what’s happening', snippet:'Trends and posts you may have missed…', skipReason:'Social notification', match:'known notification sender',
    body:'Here’s what you missed.' },
  { id:'h_m4', status:'handled', mailbox:'alex.carter@gmail.com', dateISO:'2026-06-10', from:'State Farm', sender:'noreply@statefarm.com',
    subject:'Your auto policy statement', snippet:'$96.40 · due Jun 22…', outcome:{ kind:'bill', detail:'$96.40 · due Jun 22' }, match:'insurer sender + “policy statement”',
    body:'Your auto policy amount of $96.40 is due June 22.\n\n— State Farm' },
  { id:'h_m5', status:'handled', mailbox:'alex.carter@gmail.com', dateISO:'2026-06-08', from:'OpenAI', sender:'billing@openai.com',
    subject:'Your ChatGPT receipt', snippet:'Plus plan · $20.00…', outcome:{ kind:'subscription', detail:'ChatGPT · $20.00 / mo' }, match:'sender “openai.com” + “receipt”',
    body:'You were charged $20.00 for ChatGPT Plus.\n\n— OpenAI' },
  { id:'s_m3', status:'skipped', mailbox:'alex.c.projects@gmail.com', dateISO:'2026-06-05', from:'Booking.com', sender:'noreply@booking.com',
    subject:'Deals for your next trip', snippet:'Save up to 30% this week…', skipReason:'Promotion', match:'promotional sender',
    body:'Save up to 30% on your next stay.' },
  { id:'h_m6', status:'handled', mailbox:'alex.carter@gmail.com', dateISO:'2026-06-03', from:'Pacific Gas & Electric', sender:'billing@pge.com',
    subject:'Your June statement is ready', snippet:'$63.20 · due Jun 18…', outcome:{ kind:'bill', detail:'$63.20 · due Jun 18' }, match:'utility sender + “statement”',
    body:'Your June statement of $63.20 is due June 18.\n\n— PG&E' },
  { id:'h_m7', status:'handled', mailbox:'alex.carter@gmail.com', dateISO:'2026-05-30', from:'Best Buy', sender:'noreply@bestbuy.com',
    subject:'Refund processed for your return', snippet:'Damaged item · −$34.99…', outcome:{ kind:'receipt', detail:'Refund · −$34.99' }, match:'sender “bestbuy.com” + “refund”',
    body:'Your refund of $34.99 has been processed.\n\n— Best Buy' },
  { id:'h_m8', status:'handled', mailbox:'alex.carter@gmail.com', dateISO:'2026-05-28', from:'Verizon', sender:'ebill@verizon.com',
    subject:'Your wireless bill is ready', snippet:'$89.12 · due soon…', outcome:{ kind:'bill', detail:'$89.12 · wireless' }, match:'telecom sender + “bill is ready”',
    body:'Your wireless bill of $89.12 is ready.\n\n— Verizon' },
  { id:'s_m4', status:'skipped', mailbox:'alex.c.projects@gmail.com', dateISO:'2026-05-26', from:'Coursera', sender:'no-reply@coursera.org',
    subject:'New courses this week', snippet:'Picks based on your interests…', skipReason:'Newsletter', match:'bulk-sender + “unsubscribe” footer',
    body:'New courses we think you’ll like.' },
];

function senderTint(name) {
  let h = 0; for (const c of (name || '?')) h = (h * 31 + c.charCodeAt(0)) >>> 0;
  return ['teal','purple','blue','gold','pink','green'][h % 6];
}

// Time windows — bounded scope keeps each reconcile cheap & fast.
const MAIL_WINDOWS = [
  { id:'today',     label:'Today',        phrase:'today',             test:n => n === 0 },
  { id:'yesterday', label:'Yesterday',    phrase:'yesterday',         test:n => n === 1 },
  { id:'7d',        label:'Last 7 days',  phrase:'the last 7 days',   test:n => n >= 0 && n <= 6 },
  { id:'30d',       label:'Last 30 days', phrase:'the last 30 days',  test:n => n >= 0 && n <= 29 },
];

// ---- One mail row -------------------------------------------------------
function MailRow({ m, onOpen, resolved, onConfirm, onSeen }) {
  const tint = senderTint(m.from);
  const isNeeds = m.status === 'needs' && !resolved;
  const sugK = m.suggestion && MAIL_KIND[m.suggestion.kind];
  const outK = m.outcome && MAIL_KIND[m.outcome.kind];

  return (
    <div className={`mailrow ${isNeeds ? 'needs' : ''} ${m.intent === 'read' ? 'intent-read' : ''}`} onClick={() => onOpen(m)}>
      {isNeeds && <span className="mailrow-rail" />}
      <span className="mail-av" style={{ background: tintBg(tint), color: tintFg(tint) }}>{(m.from || '?').trim()[0].toUpperCase()}</span>
      <div className="mail-main">
        <div className="mail-line1">
          <span className="mail-from">{m.from}</span>
          <MailboxBadge addr={m.mailbox} variant="mini" />
          {m.attachments && m.attachments.length > 0 && <span className="mail-att" title={`${m.attachments.length} attachment${m.attachments.length === 1 ? '' : 's'}`}><Icon name="paperclip" size={11} /> {m.attachments.length}</span>}
          <span className="mail-when">{relTime(m)}</span>
        </div>
        <div className="mail-subj">{m.subject}</div>
        <div className="mail-snip">{m.snippet}</div>

        {isNeeds ? (
          <div className="mail-reason"><Icon name="sparkles" size={13} color="var(--gold-700)" /><span>{m.reason}</span></div>
        ) : resolved ? (
          <div className="mail-status done"><Icon name="check" size={13} color="var(--green-600)" />{resolved.label}</div>
        ) : m.status === 'handled' ? (
          <div className="mail-status">
            <span className="out-chip" style={{ background: tintBg(outK.tint), color: tintFg(outK.tint) }}>
              <Icon name={outK.icon} size={12} color={tintFg(outK.tint)} />{outK.verb}
            </span>
            <span className="out-detail">{m.outcome.detail}</span>
          </div>
        ) : (
          <div className="mail-status skip"><Icon name="minus-circle" size={13} color="var(--fg-4)" />{m.skipReason}</div>
        )}
      </div>

      {isNeeds && (
        <div className="mail-acts" onClick={e => e.stopPropagation()}>
          {m.intent === 'convert' ? (
            <React.Fragment>
              <div className="mail-suggest">
                <span className="sug-chip" style={{ background: tintBg(sugK.tint), color: tintFg(sugK.tint) }}>
                  <Icon name={sugK.icon} size={12} color={tintFg(sugK.tint)} />{sugK.verb}
                </span>
                <span className="sug-detail">{m.suggestion.detail}</span>
              </div>
              <div className="mail-btns">
                <button className="mbtn primary" onClick={() => onConfirm(m)}><Icon name="check" size={14} /> Confirm {sugK.verb.toLowerCase()}</button>
                <button className="mbtn" onClick={() => onOpen(m)}>Review</button>
              </div>
            </React.Fragment>
          ) : (
            <div className="mail-btns">
              <button className="mbtn primary" onClick={() => onOpen(m)}><Icon name="mail-open" size={14} /> Read</button>
              <button className="mbtn" onClick={() => onSeen(m)}><Icon name="check" size={14} /> Mark seen</button>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// ---- Reading drawer: Zeranda's read first, raw email on demand ----------
function MailDrawer({ m, resolved, onClose, onConfirm, onSeen, onConvert, go }) {
  const [raw, setRaw] = useState(false);
  useEffect(() => { setRaw(false); }, [m && m.id]);
  if (!m) return null;
  const tint = senderTint(m.from);
  const sugK = m.suggestion && MAIL_KIND[m.suggestion.kind];
  const outK = m.outcome && MAIL_KIND[m.outcome.kind];
  const needsReview = m.status === 'needs' && !resolved;

  return (
    <React.Fragment>
      <div className="scrim" onClick={onClose} />
      <div className="drawer zd-scroll">
        <div className="dhead">
          <span className="mail-av lg" style={{ background: tintBg(tint), color: tintFg(tint) }}>{(m.from || '?').trim()[0].toUpperCase()}</span>
          <div style={{ flex:1, minWidth:0 }}>
            <div style={{ fontFamily:'var(--font-display)', fontWeight:700, fontSize:17, whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis' }}>{m.from}</div>
            <div style={{ fontSize:12.5, color:'var(--fg-3)' }} className="zd-mono">{m.sender}</div>
          </div>
          <button className="iconbtn" onClick={onClose}><Icon name="x" size={18} /></button>
        </div>

        <div className="dbody">
          <div className="field"><div className="k">Subject</div><div className="v" style={{ fontWeight:600 }}>{m.subject}</div></div>
          <div className="drow">
            <div className="field" style={{ flex:1 }}><div className="k">Inbox</div><div className="v"><MailboxBadge addr={m.mailbox} variant="tag" /></div></div>
            <div className="field" style={{ flex:1 }}><div className="k">Received</div><div className="v">{relTime(m)}</div></div>
          </div>

          {/* How Zeranda sorted this — rules for the bulk, a closer look only when unsure */}
          <div className={`zread ${needsReview ? 'review' : 'rule'}`}>
            <div className="zread-h">
              <Icon name={needsReview ? 'sparkles' : 'zap'} size={15} color={needsReview ? 'var(--gold-700)' : 'var(--teal-500)'} />
              {needsReview ? 'Needed a closer look' : 'Sorted automatically'}
            </div>
            {needsReview ? (
              <React.Fragment>
                <p className="zread-p">{m.reason}</p>
                {m.intent === 'convert' ? (
                  <div className="zread-suggest">
                    <span className="sug-chip lg" style={{ background: tintBg(sugK.tint), color: tintFg(sugK.tint) }}>
                      <Icon name={sugK.icon} size={13} color={tintFg(sugK.tint)} />{m.suggestion.label}
                    </span>
                    <span className="sug-detail">{m.suggestion.detail}</span>
                  </div>
                ) : (
                  <div className="zread-readnote"><Icon name="eye" size={14} color="var(--fg-3)" /> Nothing to file — just worth your eyes.</div>
                )}
                <div className="zread-conf">
                  <span>Confidence</span>
                  <div className="track" style={{ flex:1 }}><div className="fill" style={{ width:`${m.confidence}%` }} /></div>
                  <b>{m.confidence}%</b>
                </div>
              </React.Fragment>
            ) : resolved ? (
              <div className="zread-done"><Icon name="check-circle-2" size={16} color="var(--green-600)" /> {resolved.label}</div>
            ) : m.status === 'handled' ? (
              <React.Fragment>
                <p className="zread-p">Matched by a rule — no AI needed — so it was filed straight away.</p>
                <div className="zread-suggest">
                  <span className="sug-chip lg" style={{ background: tintBg(outK.tint), color: tintFg(outK.tint) }}>
                    <Icon name={outK.icon} size={13} color={tintFg(outK.tint)} />{outK.verb}
                  </span>
                  <span className="sug-detail">{m.outcome.detail}</span>
                </div>
                {m.match && <div className="zread-rule"><Icon name="filter" size={12} color="var(--fg-3)" /> Rule: {m.match}</div>}
              </React.Fragment>
            ) : (
              <React.Fragment>
                <p className="zread-p">Filtered as <b>{m.skipReason.toLowerCase()}</b> — by rule, no AI needed. It’s here so “skipped” never means “missed.”</p>
                {m.match && <div className="zread-rule"><Icon name="filter" size={12} color="var(--fg-3)" /> Rule: {m.match}</div>}
              </React.Fragment>
            )}
          </div>

          {m.attachments && m.attachments.length > 0 && (
            <div className="field"><div className="k">Attachments · {m.attachments.length}</div><AttachmentList items={m.attachments} /></div>
          )}

          {/* Raw email — fetched live, never stored */}
          <div className="rawmail">
            <button className="raw-toggle" onClick={() => setRaw(v => !v)}>
              <Icon name={raw ? 'chevron-down' : 'chevron-right'} size={16} />
              {raw ? 'Hide original email' : 'View original email'}
            </button>
            {raw && (
              <div className="raw-body">
                <div className="raw-live"><Icon name="shield-check" size={13} color="var(--teal-500)" /> Fetched live just now · not stored by Zeranda</div>
                <div className="raw-headers">
                  <div><span>From</span> {m.from} <span className="zd-mono">&lt;{m.sender}&gt;</span></div>
                  <div><span>To</span> <span className="zd-mono">{m.mailbox}</span></div>
                  <div><span>Subject</span> {m.subject}</div>
                </div>
                <pre className="raw-text">{m.body}</pre>
              </div>
            )}
          </div>
        </div>

        <div className="drawer-foot" style={{ flexWrap:'wrap' }}>
          {needsReview ? (
            m.intent === 'convert' ? (
              <React.Fragment>
                <Btn variant="primary" icon="check" style={{ flex:1, justifyContent:'center' }} onClick={() => onConfirm(m)}>Confirm {sugK.verb.toLowerCase()}</Btn>
                <Btn variant="secondary" icon="x" onClick={() => onSeen(m)}>Not needed</Btn>
              </React.Fragment>
            ) : (
              <React.Fragment>
                <Btn variant="primary" icon="check" style={{ flex:1, justifyContent:'center' }} onClick={() => onSeen(m)}>Got it — mark seen</Btn>
                <Btn variant="secondary" icon="plus" onClick={() => onConvert(m)}>Make a record</Btn>
              </React.Fragment>
            )
          ) : m.status === 'handled' ? (
            <Btn variant="primary" icon="arrow-right" style={{ width:'100%', justifyContent:'center' }} onClick={() => { go(outK.to); onClose(); }}>View the {outK.verb.toLowerCase()}</Btn>
          ) : (
            <Btn variant="secondary" icon="plus" style={{ width:'100%', justifyContent:'center' }} onClick={() => onConvert(m)}>Actually — make a record from this</Btn>
          )}
        </div>
      </div>
    </React.Fragment>
  );
}

// ---- The screen ---------------------------------------------------------
const MAIL_PAGE = 8;
const NEEDS_TREND = [3, 1, 2, 0, 1, 2, 3];   // “needed you” per day across the last 7 days
function MailCoverage({ go }) {
  const perm = usePermissions();
  const accounts = perm.accounts();
  const [win, setWin] = useState('7d');
  const [lens, setLens] = useState('needs');
  const [acct, setAcct] = useState('all');
  const [q, setQ] = useState('');
  const [attOnly, setAttOnly] = useState(false);
  const [page, setPage] = useState(1);
  const [sel, setSel] = useState(null);
  const [resolved, setResolved] = useState({});   // id -> { as, label }
  const [rulesOpen, setRulesOpen] = useState(false);
  const [recapOpen, setRecapOpen] = useState(false);
  const [undo, setUndo] = useState(null);          // { ids, label }
  const [recordFor, setRecordFor] = useState(null); // mail being turned into a record

  const winMeta = MAIL_WINDOWS.find(w => w.id === win) || MAIL_WINDOWS[2];
  const acctEmail = acct === 'all' ? null : (perm.account(acct) || {}).email;
  const inScope = m => winMeta.test(daysAgo(m.dateISO)) && (!acctEmail || m.mailbox === acctEmail);

  // Window-scoped tallies for the coverage statement
  const scoped = MAIL.filter(inScope);
  const needsLeft = scoped.filter(m => m.status === 'needs' && !resolved[m.id]);
  const needsTotal = scoped.filter(m => m.status === 'needs').length;
  const handledN = scoped.filter(m => m.status === 'handled').length;
  const skippedN = scoped.filter(m => m.status === 'skipped').length;
  const autoN = handledN + skippedN;            // sorted by rules — no AI
  const totalScanned = scoped.length;

  // Catch-me-up recap — built from the same counts, in Zeranda’s voice
  const kindCount = k => scoped.filter(m => m.status === 'handled' && m.outcome && m.outcome.kind === k).length;
  const recapBits = [
    [kindCount('bill'), 'bill'], [kindCount('package'), 'package'], [kindCount('event'), 'event'],
    [kindCount('statement'), 'statement'], [kindCount('subscription') + kindCount('receipt'), 'receipt'],
  ].filter(([n]) => n > 0).map(([n, w]) => `${n} ${w}${n === 1 ? '' : 's'}`);
  const recap = `Across ${winMeta.phrase} I sorted ${autoN} of ${totalScanned} emails by rules${recapBits.length ? ' — ' + recapBits.slice(0, 4).join(', ') + ' filed automatically' : ''}. ${needsLeft.length === 0 ? 'Nothing is waiting on you, and nothing was missed.' : `${needsLeft.length} didn’t match a rule and ${needsLeft.length === 1 ? 'needs' : 'need'} a quick look — everything else is done.`}`;

  // resolve one or many; remember it so it can be undone
  const applyResolve = (items, makeEntry, label) => {
    if (!items.length) return;
    setResolved(r => { const n = { ...r }; items.forEach(m => { n[m.id] = makeEntry(m); }); return n; });
    setUndo({ ids: items.map(m => m.id), label });
    setSel(null);
  };
  const onConfirm = m => applyResolve([m], x => ({ as:'converted', label:`${MAIL_KIND[x.suggestion.kind].verb} created — now tracked` }), `${MAIL_KIND[m.suggestion.kind].verb} created`);
  const onConvert = (m, kind) => applyResolve([m], () => ({ as:'converted', label: kind ? `${MAIL_KIND[kind].verb} created — now tracked` : 'Record created from this email' }), kind ? `${MAIL_KIND[kind].verb} created` : 'Record created');
  const saveRecord = (m, kind) => { setRecordFor(null); onConvert(m, kind); };
  const onSeen = m => applyResolve([m], () => ({ as:'seen', label:'Marked as seen — nothing to track' }), 'Marked as seen');
  const bulkConfirm = () => { const items = needsLeft.filter(m => m.intent === 'convert'); applyResolve(items, m => ({ as:'converted', label:`${MAIL_KIND[m.suggestion.kind].verb} created — now tracked` }), `Confirmed ${items.length} suggested record${items.length === 1 ? '' : 's'}`); };
  const bulkSeen = () => { const items = needsLeft.filter(m => m.intent === 'read'); applyResolve(items, () => ({ as:'seen', label:'Marked as seen' }), `Marked ${items.length} as seen`); };
  const doUndo = () => { if (!undo) return; setResolved(r => { const n = { ...r }; undo.ids.forEach(id => delete n[id]); return n; }); setUndo(null); };

  const lensPred = m => lens === 'needs' ? (m.status === 'needs' && !resolved[m.id])
    : lens === 'handled' ? m.status === 'handled'
    : lens === 'skipped' ? m.status === 'skipped' : true;

  // per-account counts for the filter bar (current lens + window)
  const counts = { all: MAIL.filter(m => winMeta.test(daysAgo(m.dateISO)) && lensPred(m)).length };
  accounts.forEach(a => { counts[a.id] = MAIL.filter(m => winMeta.test(daysAgo(m.dateISO)) && lensPred(m) && m.mailbox === a.email).length; });

  let list = scoped.filter(lensPred);
  if (q) { const s = q.toLowerCase(); list = list.filter(m => (m.from + m.subject + m.snippet).toLowerCase().includes(s)); }
  if (attOnly) list = list.filter(m => m.attachments && m.attachments.length);
  list = [...list].sort((a, b) => (a.dateISO < b.dateISO ? 1 : a.dateISO > b.dateISO ? -1 : 0));   // newest first

  // pagination — never render the whole window at once
  useEffect(() => { setPage(1); }, [win, lens, acct, q, attOnly]);
  const pageCount = Math.ceil(list.length / MAIL_PAGE) || 1;
  const pg = Math.min(page, pageCount);
  const paged = list.slice((pg - 1) * MAIL_PAGE, pg * MAIL_PAGE);

  const allClear = needsLeft.length === 0;
  const LENSES = [
    { id:'needs',   label:'Needs a look', n:needsLeft.length, icon:'inbox' },
    { id:'handled', label:'Handled',      n:handledN, icon:'check-circle-2' },
    { id:'skipped', label:'Skipped',      n:skippedN, icon:'minus-circle' },
    { id:'all',     label:'All mail',     n:totalScanned, icon:'mails' },
  ];

  return (
    <div className="screen-inner">
      {/* ---- Coverage statement (the thing that replaces opening Gmail) ---- */}
      <div className={`coverage rise ${allClear ? 'clear' : 'attn'}`}>
        <span className="cov-ic"><Icon name={allClear ? 'shield-check' : 'mail-question'} size={26} color={allClear ? 'var(--green-600)' : 'var(--gold-700)'} /></span>
        <div className="cov-body">
          <div className="cov-h">{allClear ? 'You’re all caught up' : `${needsLeft.length} email${needsLeft.length === 1 ? '' : 's'} need a look`}</div>
          <div className="cov-s">
            {allClear
              ? `Every email from ${winMeta.phrase} is accounted for — nothing slipped.`
              : `Everything else from ${winMeta.phrase} is handled. Clear ${needsLeft.length === 1 ? 'this one' : 'these'} and you’re done — no need to open Gmail.`}
          </div>
          <div className="cov-stats">
            <span className="cov-stat"><b>{totalScanned}</b> reviewed</span>
            <span className="cov-dot" />
            <span className="cov-stat ok"><b>{handledN}</b> handled</span>
            <span className="cov-dot" />
            <span className="cov-stat muted"><b>{skippedN}</b> skipped</span>
            <span className="cov-dot" />
            <span className={`cov-stat ${needsLeft.length ? 'warn' : 'ok'}`}><b>{needsLeft.length}</b> need you</span>
          </div>
          <div className="cov-method"><Icon name="zap" size={13} color="var(--teal-500)" /> <b>{autoN}</b> sorted by rules · only <b>{needsLeft.length}</b> needed a closer look</div>
          <button className="cov-recap-btn" onClick={() => setRecapOpen(v => !v)}><Icon name={recapOpen ? 'chevron-down' : 'sparkles'} size={13} color="var(--gold-700)" /> {recapOpen ? 'Hide recap' : 'Catch me up'}</button>
          {recapOpen && <p className="cov-recap">{recap}</p>}
        </div>
        <div className="cov-trust"><Icon name="eye-off" size={13} color="var(--fg-3)" /> Email read live · no content stored</div>
      </div>

      {/* ---- Time window — bounded scope keeps each check fast ---- */}
      <div className="winbar rise">
        <div className="winbar-l"><Icon name="calendar-range" size={16} color="var(--fg-3)" /> Reconciling</div>
        <div className="seg winseg">
          {MAIL_WINDOWS.map(w => (
            <button key={w.id} className={win === w.id ? 'on' : ''} onClick={() => setWin(w.id)}>{w.label}</button>
          ))}
        </div>
        <div className="winbar-r">
          <button className="winbtn" onClick={() => setRulesOpen(true)}><Icon name="sliders-horizontal" size={14} /> Sorting rules</button>
          <span className="winbar-hint">{totalScanned} email{totalScanned === 1 ? '' : 's'} in range</span>
        </div>
      </div>

      {/* ---- Coverage health — trust numbers + 7-day trend ---- */}
      <CoverageHealth scanned={totalScanned} autoSorted={autoN} neededYou={needsTotal} trend={NEEDS_TREND} />

      <div className="card rise" style={{ overflow:'hidden' }}>
        <div className="card-pad" style={{ paddingBottom:0 }}>
          <div className="lens-tabs">
            {LENSES.map(l => (
              <button key={l.id} className={`lens-tab ${lens === l.id ? 'on' : ''} ${l.id === 'needs' && l.n ? 'has-needs' : ''}`} onClick={() => setLens(l.id)}>
                <Icon name={l.icon} size={15} />{l.label}<span className="lens-n">{l.n}</span>
              </button>
            ))}
          </div>

          <div className="filterbar" style={{ marginTop:14 }}>
            <div className="search" style={{ flex:'1 1 220px' }}><Icon name="search" size={17} /><input placeholder="Search sender or subject…" value={q} onChange={e => setQ(e.target.value)} />{q && <button className="x-clear" onClick={() => setQ('')}><Icon name="x" size={14} /></button>}</div>
            <button className={`att-toggle ${attOnly ? 'on' : ''}`} onClick={() => setAttOnly(v => !v)} title="Only emails with attachments"><Icon name="paperclip" size={15} /> Has attachment</button>
          </div>
          <div style={{ margin:'12px 0 4px' }}>
            <AccountFilterBar accounts={accounts} value={acct} onChange={setAcct} counts={counts} />
          </div>
        </div>

        <div className="mail-list">
          {lens === 'needs' && needsLeft.length > 0 && (
            <div className="bulkbar">
              <div className="bb-l"><Icon name="layers" size={15} color="var(--gold-700)" /> <b>{needsLeft.length}</b> to clear</div>
              <div className="bb-acts">
                {needsLeft.some(m => m.intent === 'convert') && <button className="mbtn primary" onClick={bulkConfirm}><Icon name="check-check" size={14} /> Confirm {needsLeft.filter(m => m.intent === 'convert').length} suggested</button>}
                {needsLeft.some(m => m.intent === 'read') && <button className="mbtn" onClick={bulkSeen}><Icon name="check" size={14} /> Mark {needsLeft.filter(m => m.intent === 'read').length} read</button>}
              </div>
            </div>
          )}
          {lens === 'needs' && !q && acct === 'all' && allClear && (
            <div className="mail-empty">
              <span className="me-ic"><Icon name="check-circle-2" size={30} color="var(--green-600)" /></span>
              <div className="me-h">Nothing needs you {winMeta.id === 'today' ? 'today' : 'in this range'}</div>
              <div className="me-s">Every email from {winMeta.phrase} has been handled or read. You can trust the count — this is the all-clear you’d go to Gmail for.</div>
              <button className="viewall" onClick={() => setLens('handled')}>See what Zeranda handled <Icon name="arrow-right" size={14} /></button>
            </div>
          )}
          {paged.map(m => (
            <MailRow key={m.id} m={m} resolved={resolved[m.id]} onOpen={setSel} onConfirm={onConfirm} onSeen={onSeen} />
          ))}
          {list.length === 0 && !(lens === 'needs' && allClear && acct === 'all' && !q) && (
            <div className="mail-empty"><span className="me-ic"><Icon name="search-x" size={26} color="var(--fg-4)" /></span><div className="me-h">No mail here</div><div className="me-s">Nothing matches this view in {winMeta.phrase}.</div></div>
          )}
        </div>

        <Pager page={pg} pageCount={pageCount} total={list.length} pageSize={MAIL_PAGE} onPage={setPage} noun="emails" />
      </div>

      {sel && <MailDrawer m={sel} resolved={resolved[sel.id]} onClose={() => setSel(null)}
        onConfirm={onConfirm} onSeen={onSeen} onConvert={setRecordFor} go={go} />}
      {recordFor && <RecordModal m={recordFor} onClose={() => setRecordFor(null)} onSave={saveRecord} />}
      {rulesOpen && <RulesPanel onClose={() => setRulesOpen(false)} />}
      {undo && <UndoToast label={undo.label} onUndo={doUndo} onClose={() => setUndo(null)} />}
    </div>
  );
}
window.MailCoverage = MailCoverage;
