// Contact detail slideout — full history + deals + activity timeline
function ContactDetail({ contact, deals, reps, showrooms, contacts, tasks, onClose, openDeal, onNewDeal, onEditContact, onSetReminder, onReassignContact, onMergeContacts, onVerifiedUpdate, siteVisits, onSaveSiteVisit, currentUser, isLeadership }) {
  const [editing, setEditing] = React.useState(false);
  const [draft, setDraft] = React.useState(null);
  const [verifyModal, setVerifyModal] = React.useState(null);
  const [siteVisit, setSiteVisit] = React.useState(null); // {visit, mode}
  const [reminderText, setReminderText] = React.useState(contact?.reminder?.text || '');
  const [reminderDate, setReminderDate] = React.useState(contact?.reminder?.date || '');
  const [reminderType, setReminderType] = React.useState(contact?.reminder?.type || 'followup_call');
  const [reminderTime, setReminderTime] = React.useState(contact?.reminder?.time || '09:00');
  const [reminderLead, setReminderLead] = React.useState(contact?.reminder?.leadMinutes != null ? contact.reminder.leadMinutes : 15);
  const [reassignOpen, setReassignOpen] = React.useState(false);
  const [mergeOpen, setMergeOpen] = React.useState(false);
  const potentialMatches = React.useMemo(() => contact ? findPotentialMatches(contact, contacts || []) : [], [contact, contacts]);
  React.useEffect(() => {
    setDraft(contact);
    setReminderText(contact?.reminder?.text || '');
    setReminderDate(contact?.reminder?.date || '');
    setReminderType(contact?.reminder?.type || 'followup_call');
    setReminderTime(contact?.reminder?.time || '09:00');
    setReminderLead(contact?.reminder?.leadMinutes != null ? contact.reminder.leadMinutes : 15);
  }, [contact?.id]);

  if (!contact) return null;
  const lm = lifecycleMeta(contact.lifecycle);
  // Find all the contact's deals
  const myDeals = deals.filter(d => contact.dealIds.includes(d.id));
  const sold = myDeals.filter(d => d.stage === 'sold');
  const active = myDeals.filter(d => d.stage !== 'sold' && d.stage !== 'lost');
  const lost = myDeals.filter(d => d.stage === 'lost');
  const home = showrooms.find(s => s.id === contact.homeShowroom);

  // Find the *current* active deal (if any) — that's "where they sit right now"
  const currentDeal = active[0] || sold[0] || lost[0] || null;

  // Build unified timeline: contact entries + deal activities, sorted by date
  const timeline = [];
  for (const e of (contact.entries || [])) {
    timeline.push({
      type: 'contact_entry',
      date: e.date,
      title: `Contact recorded — ${e.outcome.replace('_', ' ')}`,
      sub: `${e.means || ''}${e.means && e.source ? ' · ' : ''}${e.source || ''}${e.salesPerson ? ` · by ${e.salesPerson}` : ''}`,
      body: e.comments,
      icon: e.outcome === 'qualifies' ? 'check' : e.outcome === 'no_sale' ? 'close' : 'tag',
      tone: e.outcome === 'qualifies' ? 'green' : e.outcome === 'no_sale' ? 'red' : 'blue',
    });
  }
  for (const d of myDeals) {
    timeline.push({
      type: 'deal',
      date: d.created || d.designApptDate,
      title: `Quote ${d.quoteNumber || d.id} created — ${d.product || 'job'}`,
      sub: `${fmt.money(d.value, true)} · ${d.repName}`,
      stage: d.stage,
      dealRef: d,
      icon: 'tag',
    });
    for (const act of (d.activities || []).slice(0, 6)) {
      timeline.push({
        type: 'activity',
        date: act.date,
        title: act.label,
        sub: `${d.quoteNumber || d.id} · ${d.product}`,
        actType: act.type,
        icon: act.type === 'call' ? 'phone' : act.type === 'email' ? 'mail' : act.type === 'meeting' ? 'users' : 'tag',
      });
    }
  }
  // Audit trail entries — captured wherever the contact is touched (edits, reminders, reassignments, calendar appts)
  for (const a of (contact.audit || [])) {
    const subParts = [];
    if (a.by) subParts.push('by ' + a.by);
    if (a.store) subParts.push('at ' + a.store);
    if (a.method) subParts.push(a.method);
    if (a.ts) subParts.push(a.ts);
    timeline.push({
      type: 'audit',
      date: a.date,
      title: a.label,
      sub: subParts.join(' · '),
      actType: 'note',
      tone: a.field ? 'blue' : undefined,
      icon: 'edit',
    });
  }
  timeline.sort((a, b) => (b.date || '').localeCompare(a.date || ''));

  return (
    <div>
      <div className="detail-overlay" onClick={onClose}/>
      {verifyModal && (
        <VerifiedUpdateModal
          pending={verifyModal}
          contact={contact}
          contacts={contacts}
          showrooms={showrooms}
          currentUser={currentUser}
          onCancel={() => setVerifyModal(null)}
          onConfirm={(meta) => {
            onVerifiedUpdate(contact.id, verifyModal.draft, meta);
            setVerifyModal(null);
            setEditing(false);
          }}
        />
      )}
      {siteVisit && (
        <SiteVisitForm
          contact={contact}
          currentUser={currentUser}
          existing={siteVisit.visit}
          mode={siteVisit.mode}
          onClose={() => setSiteVisit(null)}
          onSave={(v) => { onSaveSiteVisit(v); setSiteVisit(null); }}
        />
      )}
      <div className="detail-panel" style={{ width: 620 }}>
        <div className="detail-hd">
          <div className="detail-row1">
            <span className="detail-id mono">{contact.id && contact.id.length > 12 ? '#' + contact.id.slice(0, 8).toUpperCase() : contact.id}</span>
            <span style={{ color: 'var(--ink-3)', fontSize: 11 }}>·</span>
            <LifecyclePill stage={contact.lifecycle}/>
            <button className="detail-close" style={{ marginLeft: 'auto' }} onClick={onClose}>
              <Icon name="close" size={16}/>
            </button>
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
            <Avatar name={contact.name} sz="lg"/>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div className="detail-customer">{contact.name}</div>
              <div style={{ fontSize: 13, color: 'var(--ink-3)' }}>
                {contact.suburb || ''}{contact.suburb && home ? ' · ' : ''}{home ? home.name + ' showroom' : ''}
              </div>
            </div>
            {contact.ltv > 0 && (
              <div style={{ textAlign: 'right' }}>
                <div style={{ fontSize: 10.5, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.06em' }}>Lifetime value</div>
                <div className="num" style={{ fontSize: 18, fontWeight: 600, color: 'var(--won)', letterSpacing: '-0.02em' }}>{fmt.money(contact.ltv, true)}</div>
              </div>
            )}
          </div>
          <div style={{ display: 'flex', gap: 6, marginTop: 4 }}>
            {contact.phone && <a className="btn sm primary" href={`tel:${contact.phone.replace(/\s+/g, '')}`}><Icon name="phone" size={12}/> Call</a>}
            {contact.email && <a className="btn sm" href={`mailto:${contact.email}`}><Icon name="mail" size={12}/> Email</a>}
            <button className="btn sm accent" onClick={() => onNewDeal && onNewDeal(contact)}>
              <Icon name="plus" size={12}/> New deal
            </button>
            <button
              className="btn sm"
              style={{ marginLeft: 'auto' }}
              onClick={() => { document.getElementById('reminder-section-' + contact.id)?.scrollIntoView({ behavior: 'smooth', block: 'center' }); document.getElementById('reminder-text-' + contact.id)?.focus(); }}
            >
              <Icon name="calendar" size={12}/> Reminder
            </button>
            <button
              className="btn sm"
              onClick={() => setSiteVisit({ visit: null, mode: 'staff' })}
              title="Arrange an on-site visit — WHS safety checklist"
            >
              <Icon name="home" size={12}/> On-site visit
            </button>
            {potentialMatches.length > 0 && (isLeadership || currentUser.role === 'manager') && (
              <button
                className="btn sm"
                style={{ background: 'var(--warn-soft)', color: 'oklch(0.45 0.13 60)', borderColor: 'oklch(0.85 0.08 70)' }}
                onClick={() => setMergeOpen(true)}
                title={`${potentialMatches.length} possible duplicate${potentialMatches.length > 1 ? 's' : ''}`}
              >
                <Icon name="users" size={12}/> Merge ({potentialMatches.length})
              </button>
            )}
          </div>
        </div>

        <div className="detail-body">
          <div className="detail-section">
            <h4>Where they currently sit</h4>
            <ContactJourney contact={contact} myDeals={myDeals} currentDeal={currentDeal} openDeal={openDeal} onSetLifecycle={(lc) => onEditContact(contact.id, { lifecycle: lc })}/>
          </div>

          <div className="detail-section">
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
              <h4 style={{ margin: 0 }}>Identity</h4>
              {!editing ? (
                <button className="btn sm" onClick={() => { setDraft({ ...contact }); setEditing(true); }}>
                  <Icon name="edit" size={11}/> Edit
                </button>
              ) : (
                <div style={{ display: 'flex', gap: 4 }}>
                  <button className="btn sm" onClick={() => { setDraft(contact); setEditing(false); }}>Cancel</button>
                  <button className="btn sm primary" onClick={() => {
                    const phoneChanged = (draft.phone || '') !== (contact.phone || '');
                    const emailChanged = (draft.email || '') !== (contact.email || '');
                    if (phoneChanged || emailChanged) { setVerifyModal({ draft: { ...draft }, phoneChanged, emailChanged }); }
                    else { onEditContact(contact.id, draft); setEditing(false); }
                  }}>
                    <Icon name="check" size={11}/> Save
                  </button>
                </div>
              )}
            </div>
            <div className="detail-grid">
              <div className="detail-field">
                <span className="lbl">Name</span>
                {editing
                  ? <input value={draft.name || ''} onChange={(e) => setDraft({...draft, name: e.target.value})}/>
                  : <div className="val">{contact.name}</div>}
              </div>
              <div className="detail-field">
                <span className="lbl">Phone</span>
                {editing
                  ? <input className="mono" value={draft.phone || ''} onChange={(e) => setDraft({...draft, phone: e.target.value})}/>
                  : <div className="val mono" style={{ fontSize: 12 }}>{contact.phone || '—'}</div>}
              </div>
              <div className="detail-field">
                <span className="lbl">Email</span>
                {editing
                  ? <input className="mono" type="email" value={draft.email || ''} onChange={(e) => setDraft({...draft, email: e.target.value})}/>
                  : <div className="val mono" style={{ fontSize: 12 }}>{contact.email || '—'}</div>}
              </div>
              <div className="detail-field">
                <span className="lbl">Suburb</span>
                {editing
                  ? <input value={draft.suburb || ''} onChange={(e) => setDraft({...draft, suburb: e.target.value})}/>
                  : <div className="val">{contact.suburb || '—'}</div>}
              </div>
              <div className="detail-field">
                <span className="lbl">Owner designer</span>
                {editing && isLeadership
                  ? <select value={draft.owner || ''} onChange={(e) => setDraft({...draft, owner: e.target.value})}>
                      <option value="">—</option>
                      {reps.filter(r => r.works.includes(draft.homeShowroom)).map(r => <option key={r.id} value={r.name}>{r.name}</option>)}
                    </select>
                  : (
                    <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                      <span className="val" style={{ flex: 1 }}>{contact.owner || <em style={{ color: 'var(--ink-3)', fontStyle: 'normal' }}>— unassigned —</em>}</span>
                      {isLeadership && !editing && (
                        <button
                          className="btn sm"
                          onClick={() => setReassignOpen(true)}
                          title="Reassign this contact to another designer"
                          style={{ fontSize: 11, padding: '3px 8px' }}
                        >
                          <Icon name="users" size={11}/> Reassign
                        </button>
                      )}
                    </div>
                  )}
              </div>
              <div className="detail-field">
                <span className="lbl">Home showroom</span>
                {editing && isLeadership
                  ? <select value={draft.homeShowroom} onChange={(e) => setDraft({...draft, homeShowroom: e.target.value})}>
                      {showrooms.map(s => <option key={s.id} value={s.id}>{s.name} ({s.state})</option>)}
                    </select>
                  : <div className="val">{home ? home.name : '—'}</div>}
              </div>
              <div className="detail-field" style={{ gridColumn: '1 / -1' }}>
                <span className="lbl">Stage</span>
                {editing ? (
                  <select value={draft.lifecycle || 'new_lead'} onChange={(e) => setDraft({...draft, lifecycle: e.target.value})}>
                    <option value="new_lead">New Lead</option>
                    <option value="qualified">Qualified</option>
                    <option value="in_pipeline">Quoted / In Pipeline</option>
                    <option value="customer_active">Active Customer</option>
                    <option value="past_customer">Past Customer</option>
                    <option value="lost_quote">Lost Quote</option>
                    <option value="dormant_new_lead">Dormant — Lead</option>
                    <option value="dormant_past_customer">Dormant — Past Customer</option>
                  </select>
                ) : (
                  <div className="val"><LifecyclePill stage={contact.lifecycle}/></div>
                )}
              </div>
              <div className="detail-field">
                <span className="lbl">First contact</span>
                <div className="val">{contact.firstContact ? fmt.shortDate(contact.firstContact) : '—'}</div>
              </div>
              <div className="detail-field">
                <span className="lbl">Last touched</span>
                <div className="val">{contact.lastContact ? fmt.shortDate(contact.lastContact) : '—'}</div>
              </div>
            </div>
          </div>

          <div className="detail-section">
            <h4>How they came to us</h4>
            <div className="detail-grid">
              <div className="detail-field">
                <span className="lbl">First reached via</span>
                <div className="val">
                  <span className="pill">
                    <span className="dot" style={{ background: 'var(--info)' }}></span>
                    {contact.primaryMeans || '—'}
                  </span>
                </div>
              </div>
              <div className="detail-field">
                <span className="lbl">Heard about us through</span>
                <div className="val">
                  <span className="pill">
                    <span className="dot" style={{ background: 'var(--accent)' }}></span>
                    {contact.primarySource || '—'}
                  </span>
                </div>
              </div>
            </div>
          </div>

          {myDeals.length > 0 && (
            <div className="detail-section">
              <h4>Quotes & jobs ({myDeals.length})</h4>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
                {myDeals.map(d => (
                  <div
                    key={d.id}
                    onClick={() => openDeal(d)}
                    style={{
                      padding: '10px 12px',
                      border: '1px solid var(--border)',
                      borderRadius: 8,
                      cursor: 'pointer',
                      display: 'flex',
                      alignItems: 'center',
                      gap: 12,
                    }}
                  >
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                        <span style={{ fontWeight: 500 }}>{d.product || 'Quote'}</span>
                        <span className="mono" style={{ fontSize: 11, color: 'var(--ink-3)' }}>{d.quoteNumber || d.id}</span>
                      </div>
                      <div style={{ fontSize: 11.5, color: 'var(--ink-3)', marginTop: 2 }}>
                        {d.created ? fmt.shortDate(d.created) : ''} · {d.repName}
                      </div>
                    </div>
                    <span className="num" style={{ fontWeight: 600, fontVariantNumeric: 'tabular-nums' }}>
                      {fmt.money(d.value, true)}
                    </span>
                    <StagePill stage={d.stage} stages={[{ id: d.stage, label: d.statusRaw || d.stage }]}/>
                  </div>
                ))}
              </div>
              <div style={{ marginTop: 10, padding: '8px 12px', background: 'var(--surface-2)', borderRadius: 6, fontSize: 11.5, color: 'var(--ink-3)', display: 'flex', gap: 14 }}>
                <span>Total quoted: <strong className="num" style={{ color: 'var(--ink) ' }}>{fmt.money(contact.totalQuoted || 0, true)}</strong></span>
                <span>Won: <strong className="num" style={{ color: 'var(--won)' }}>{sold.length}</strong></span>
                <span>Active: <strong className="num">{active.length}</strong></span>
                <span>Lost: <strong className="num" style={{ color: 'var(--lost)' }}>{lost.length}</strong></span>
              </div>
            </div>
          )}

          <div className="detail-section">
            <h4>Audit trail &amp; activity ({timeline.length})</h4>
            {timeline.length === 0 ? (
              <div className="empty">No activity recorded yet.</div>
            ) : (
              <div className="activity-list">
                {timeline.slice(0, 30).map((t, i) => (
                  <div className="activity-item" key={i} style={{ alignItems: 'flex-start' }}>
                    <span className={`activity-dot ${t.actType || ''}`} style={{
                      background: t.tone === 'green' ? 'var(--won)' :
                                 t.tone === 'red' ? 'var(--lost)' :
                                 t.tone === 'blue' ? 'var(--info)' : 'var(--ink-3)',
                    }}></span>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{ fontSize: 12.5, fontWeight: 500 }}>{t.title}</div>
                      {t.sub && <div className="activity-meta">{t.sub}</div>}
                      {t.body && <div style={{ fontSize: 11.5, color: 'var(--ink-2)', marginTop: 3, fontStyle: 'italic' }}>{t.body.slice(0, 220)}{t.body.length > 220 ? '…' : ''}</div>}
                    </div>
                    <div className="activity-meta" style={{ whiteSpace: 'nowrap' }}>
                      {t.date ? fmt.shortDate(t.date) : ''}
                    </div>
                  </div>
                ))}
                {timeline.length > 30 && (
                  <div style={{ padding: '6px 0', fontSize: 11, color: 'var(--ink-3)', textAlign: 'center' }}>
                    + {timeline.length - 30} more entries
                  </div>
                )}
              </div>
            )}
          </div>

          {(contact.transferLog && contact.transferLog.length > 0) && (
            <div className="detail-section">
              <h4>Ownership history</h4>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                {contact.transferLog.slice().reverse().map((t, i) => (
                  <div key={i} style={{
                    display: 'flex',
                    alignItems: 'center',
                    gap: 10,
                    padding: '8px 12px',
                    border: '1px solid var(--border)',
                    borderRadius: 6,
                    background: 'var(--surface-2)',
                    fontSize: 12,
                  }}>
                    <Icon name="arrow-right" size={12} color="var(--ink-3)"/>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{ fontWeight: 500 }}>
                        {t.from ? <span>{t.from}</span> : <em style={{ fontStyle: 'normal', color: 'var(--ink-3)' }}>Unassigned</em>}
                        <span style={{ color: 'var(--ink-3)' }}> → </span>
                        {t.to ? <span>{t.to}</span> : <em style={{ fontStyle: 'normal', color: 'var(--ink-3)' }}>Unassigned</em>}
                      </div>
                      <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 1 }}>
                        by {t.changedBy} · {fmt.shortDate(t.date)}
                        {t.reason ? ` · “${t.reason}”` : ''}
                      </div>
                    </div>
                  </div>
                ))}
              </div>
            </div>
          )}

          <div className="detail-section section-card">
            <h4>Customer rating &amp; tags</h4>
            <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 10 }}>
              <span style={{ fontSize: 11.5, color: 'var(--ink-3)' }}>Designer rating</span>
              <div style={{ display: 'flex', gap: 2 }}>
                {[1, 2, 3, 4, 5].map(n => (
                  <button
                    key={n}
                    onClick={() => onEditContact(contact.id, { rating: contact.rating === n ? 0 : n })}
                    style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 18, color: (contact.rating || 0) >= n ? 'oklch(0.65 0.16 70)' : 'var(--ink-4)', padding: 0, lineHeight: 1 }}
                    title={n === 1 ? 'Difficult' : n === 2 ? 'Demanding' : n === 3 ? 'Neutral' : n === 4 ? 'Pleasant' : 'Excellent'}
                  >{(contact.rating || 0) >= n ? '★' : '☆'}</button>
                ))}
              </div>
              {contact.rating > 0 && (
                <span style={{ fontSize: 11, color: 'var(--ink-3)' }}>
                  {contact.rating === 1 ? 'Difficult' : contact.rating === 2 ? 'Demanding' : contact.rating === 3 ? 'Neutral' : contact.rating === 4 ? 'Pleasant' : 'Excellent'}
                </span>
              )}
            </div>
            <textarea
              placeholder="Notes for other designers · e.g. 'Loves matte finishes, hates being upsold', 'Decision-maker is the wife', 'Pay on completion only'"
              defaultValue={contact.customerNotes || ''}
              onBlur={(e) => onEditContact(contact.id, { customerNotes: e.target.value })}
              rows="2"
              style={{
                width: '100%', padding: '8px 10px', border: '1px solid var(--border)', borderRadius: 4,
                fontFamily: 'inherit', fontSize: 12.5, resize: 'vertical', outline: 'none',
                background: 'var(--surface-2)',
              }}
            />
          </div>

          <div className="detail-section">
            <h4>Site visits &amp; WHS checklists</h4>
            {(!siteVisits || siteVisits.length === 0) ? (
              <div style={{ fontSize: 12, color: 'var(--ink-3)', display: 'flex', alignItems: 'center', gap: 8, padding: '8px 0' }}>
                <span>No site-visit checklists yet.</span>
                <button className="btn sm" onClick={() => setSiteVisit({ visit: null, mode: 'staff' })}><Icon name="home" size={11}/> Arrange on-site visit</button>
              </div>
            ) : (
              <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                {siteVisits.map(v => {
                  const STc = { draft: 'var(--ink-3)', sent: 'var(--warn)', signed: 'var(--info)', locked: 'var(--won)' }[v.status] || 'var(--ink-3)';
                  const STl = { draft: 'Draft', sent: 'Awaiting customer signature', signed: 'Signed — pending finalise', locked: 'Finalised & locked' }[v.status] || v.status;
                  return (
                    <div key={v.id} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '9px 12px', border: '1px solid var(--border)', borderRadius: 6, background: 'var(--surface-2)' }}>
                      <Icon name={v.status === 'locked' ? 'lock' : 'check'} size={14} color={STc}/>
                      <div style={{ flex: 1, minWidth: 0 }}>
                        <div style={{ fontSize: 12.5, fontWeight: 500 }}>{v.title || 'Site-visit checklist'}</div>
                        <div style={{ fontSize: 11, color: 'var(--ink-3)' }}><span style={{ color: STc, fontWeight: 600 }}>{STl}</span>{v.jobNumber ? ' · ' + v.jobNumber : ''} · {fmt.shortDate((v.createdAt || '').slice(0, 10))}</div>
                      </div>
                      {v.status === 'sent' && (
                        <button className="btn sm primary" onClick={() => setSiteVisit({ visit: v, mode: 'customer' })} title="Open the customer signing view (simulates the link emailed to the customer)"><Icon name="mail" size={11}/> Customer signing</button>
                      )}
                      <button className="btn sm" onClick={() => setSiteVisit({ visit: v, mode: 'staff' })}>{v.status === 'locked' ? 'View' : v.status === 'signed' ? 'Finalise' : 'Open'}</button>
                    </div>
                  );
                })}
                <button className="btn sm" style={{ alignSelf: 'flex-start', marginTop: 2 }} onClick={() => setSiteVisit({ visit: null, mode: 'staff' })}><Icon name="home" size={11}/> New on-site visit</button>
              </div>
            )}
          </div>

          <div className="detail-section" id={`reminder-section-${contact.id}`}>
            <h4>Follow-up reminder</h4>
            {contact.reminder && (
              <div style={{ background: 'var(--warn-soft)', border: '1px solid oklch(0.85 0.08 70)', borderRadius: 6, padding: '8px 12px', marginBottom: 8, display: 'flex', alignItems: 'center', gap: 8, fontSize: 12 }}>
                <Icon name="calendar" size={13} color="oklch(0.5 0.13 60)"/>
                <div style={{ flex: 1 }}>
                  <div style={{ fontWeight: 500 }}>{window.apptTypeLabel ? window.apptTypeLabel(contact.reminder.type) : 'Reminder'} · {fmt.shortDate(contact.reminder.date)}{contact.reminder.time ? ' at ' + contact.reminder.time : ''}</div>
                  <div style={{ fontSize: 11, color: 'var(--ink-3)' }}>by {contact.reminder.setBy} · alerts {window.reminderLeadLabel ? window.reminderLeadLabel(contact.reminder.leadMinutes != null ? contact.reminder.leadMinutes : 15) : '15 minutes before'}</div>
                  {contact.reminder.text && <div style={{ marginTop: 4, fontStyle: 'italic', color: 'var(--ink-2)' }}>“{contact.reminder.text}”</div>}
                </div>
                <button className="btn sm" onClick={() => onSetReminder(contact.id, null, null)}>
                  <Icon name="close" size={11}/>
                </button>
              </div>
            )}
            <div className="quick-remind-row">
              <span className="lbl" style={{ marginRight: 2 }}>Remind in:</span>
              {(window.quickReminderPresets ? window.quickReminderPresets() : []).map(p => {
                const presetDate = window.quickReminderDate(p.days);
                const active = reminderDate === presetDate;
                return (
                  <button
                    key={p.id}
                    className={`filter-chip ${active ? 'active' : ''}`}
                    onClick={() => setReminderDate(presetDate)}
                  >{p.label}</button>
                );
              })}
            </div>
            <div className="detail-grid" style={{ marginBottom: 8 }}>
              <div className="detail-field">
                <span className="lbl">Reminder type</span>
                <select value={reminderType} onChange={(e) => setReminderType(e.target.value)}>
                  {(window.apptTypes ? window.apptTypes() : [{ id: 'followup_call', label: 'Follow-up call' }]).map(t => (
                    <option key={t.id} value={t.id}>{t.label}</option>
                  ))}
                </select>
              </div>
              <div className="detail-field">
                <span className="lbl">Owner</span>
                <div className="val" style={{ padding: '7px 0', fontSize: 13 }}>{contact.owner || '—'}</div>
              </div>
              <div className="detail-field">
                <span className="lbl">Date</span>
                <input type="date" value={reminderDate} min={(() => { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; })()} onChange={(e) => setReminderDate(e.target.value)}/>
              </div>
              <div className="detail-field">
                <span className="lbl">Time</span>
                <input type="time" value={reminderTime} onChange={(e) => setReminderTime(e.target.value)}/>
              </div>
              <div className="detail-field" style={{ gridColumn: '1 / -1' }}>
                <span className="lbl">Remind me before <span style={{ fontSize: 10, color: 'var(--ink-3)' }}>· when the pop-up appears</span></span>
                <select value={reminderLead} onChange={(e) => setReminderLead(Number(e.target.value))}>
                  {(window.REMINDER_LEADS || [{ v: 15, label: '15 minutes before' }]).map(o => <option key={o.v} value={o.v}>{o.label}</option>)}
                </select>
              </div>
            </div>
            <ReminderAvailability
              date={reminderDate}
              time={reminderTime}
              ownerName={contact.owner}
              reps={reps}
              deals={deals}
              tasks={tasks}
              contacts={contacts}
              excludeContactId={contact.id}
            />
            <textarea
              id={`reminder-text-${contact.id}`}
              placeholder="Note for this reminder… e.g. 'Bring revised stone options'"
              rows="2"
              value={reminderText}
              onChange={(e) => setReminderText(e.target.value)}
              style={{
                width: '100%',
                padding: '10px 12px',
                border: '1px solid var(--border)',
                borderRadius: 6,
                fontFamily: 'inherit',
                fontSize: 12.5,
                resize: 'vertical',
                outline: 'none',
                background: 'var(--surface-2)',
                marginTop: 8,
              }}
            />
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 8 }}>
              <span style={{ fontSize: 11, color: 'var(--ink-3)', display: 'flex', alignItems: 'center', gap: 5 }}>
                <Icon name="calendar" size={12}/> Saving adds this to {contact.owner ? contact.owner + "'s" : 'the'} calendar
              </span>
              <button
                className="btn sm primary"
                style={{ marginLeft: 'auto' }}
                disabled={!reminderDate}
                onClick={() => onSetReminder(contact.id, reminderText, reminderDate, reminderType, reminderTime, reminderLead)}
              >
                <Icon name="check" size={11}/> Save &amp; add to calendar
              </button>
            </div>
          </div>
        </div>
      </div>
      {reassignOpen && (
        <ContactReassignModal
          contact={contact}
          reps={reps}
          showrooms={showrooms}
          currentUser={currentUser}
          onClose={() => setReassignOpen(false)}
          onConfirm={(newOwnerName, reason) => onReassignContact(contact.id, newOwnerName, reason)}
        />
      )}
      {mergeOpen && (
        <MergeModal
          source={contact}
          candidates={potentialMatches}
          onClose={() => setMergeOpen(false)}
          onConfirm={(targetId) => { onMergeContacts(contact.id, targetId); onClose(); }}
        />
      )}
    </div>
  );
}

// Verified update workflow — OTP / in-person verification, then a global conflict check,
// before a phone/email change is applied. Old value is preserved in the audit trail.
function VerifiedUpdateModal({ pending, contact, contacts, showrooms, currentUser, onCancel, onConfirm }) {
  const { draft, phoneChanged, emailChanged } = pending;
  const methodOptions = [];
  if (phoneChanged && draft.phone) methodOptions.push({ id: 'sms', label: 'OTP via SMS', to: draft.phone, methodLabel: 'OTP via SMS' });
  if (emailChanged && draft.email) methodOptions.push({ id: 'email', label: 'OTP via email', to: draft.email, methodLabel: 'OTP via email' });
  methodOptions.push({ id: 'inperson', label: 'In-person — confirmed by staff', to: null, methodLabel: 'In-person (staff confirmed)' });

  const [method, setMethod] = React.useState(methodOptions[0].id);
  const [code] = React.useState(() => String(Math.floor(100000 + Math.random() * 900000)));
  const [sent, setSent] = React.useState(false);
  const [entered, setEntered] = React.useState('');
  const [note, setNote] = React.useState('');
  const [verified, setVerified] = React.useState(false);

  const sel = methodOptions.find(m => m.id === method);
  const isOtp = method === 'sms' || method === 'email';

  // Global conflict check (runs after verification)
  const normPhone = (p) => (p || '').toString().replace(/\D/g, '').replace(/^61/, '0');
  const conflicts = React.useMemo(() => {
    if (!verified) return [];
    const out = [];
    if (phoneChanged && draft.phone) {
      const np = normPhone(draft.phone);
      (contacts || []).forEach(o => { if (o.id !== contact.id && np.length >= 8 && normPhone(o.phone) === np) out.push({ field: 'Phone number', value: draft.phone, other: o }); });
    }
    if (emailChanged && draft.email) {
      const ne = draft.email.toLowerCase().trim();
      (contacts || []).forEach(o => { if (o.id !== contact.id && ne && (o.email || '').toLowerCase().trim() === ne) out.push({ field: 'Email address', value: draft.email, other: o }); });
    }
    return out;
  }, [verified, contacts, draft, phoneChanged, emailChanged, contact.id]);

  const storeName = (id) => (showrooms || []).find(s => s.id === id)?.name || id;
  const verify = () => {
    if (isOtp) { if (entered.trim() === code) setVerified(true); }
    else { if (note.trim().length > 2) setVerified(true); }
  };

  const lbl = { fontSize: 11, color: 'var(--ink-3)', display: 'block', marginBottom: 4 };

  return (
    <React.Fragment>
      <div className="detail-overlay" style={{ zIndex: 60 }} onClick={onCancel}></div>
      <div className="invite-modal" style={{ zIndex: 61, width: 500 }}>
        <div style={{ padding: '16px 20px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', gap: 11 }}>
          <div style={{ width: 32, height: 32, borderRadius: 8, background: 'var(--accent-soft)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
            <Icon name="lock" size={16} color="var(--accent-ink)"/>
          </div>
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 15, fontWeight: 600 }}>Verify contact change</div>
            <div style={{ fontSize: 11.5, color: 'var(--ink-3)' }}>Identity fields require verification before saving</div>
          </div>
          <button onClick={onCancel} className="detail-close"><Icon name="close" size={16}/></button>
        </div>

        <div style={{ padding: '16px 20px', maxHeight: '64vh', overflowY: 'auto' }}>
          {/* Pending changes */}
          <div style={{ fontSize: 10.5, textTransform: 'uppercase', letterSpacing: '0.07em', color: 'var(--ink-3)', fontWeight: 600, marginBottom: 7 }}>Change requested</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginBottom: 18 }}>
            {phoneChanged && <ChangeRow field="Phone" from={contact.phone} to={draft.phone}/>}
            {emailChanged && <ChangeRow field="Email" from={contact.email} to={draft.email}/>}
          </div>

          {!verified ? (
            <React.Fragment>
              <div style={{ fontSize: 10.5, textTransform: 'uppercase', letterSpacing: '0.07em', color: 'var(--ink-3)', fontWeight: 600, marginBottom: 7 }}>Verification method</div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginBottom: 14 }}>
                {methodOptions.map(m => (
                  <button key={m.id} onClick={() => { setMethod(m.id); setSent(false); setEntered(''); }}
                    className={`reassign-row ${method === m.id ? 'on' : ''}`} style={{ textAlign: 'left' }}>
                    <span className={`radio ${method === m.id ? 'on' : ''}`}>{method === m.id && <span className="radio-dot"></span>}</span>
                    <div style={{ flex: 1 }}>
                      <div style={{ fontSize: 12.5, fontWeight: 500 }}>{m.label}</div>
                      {m.to && <div style={{ fontSize: 11, color: 'var(--ink-3)' }} className="mono">{m.to}</div>}
                    </div>
                  </button>
                ))}
              </div>

              {isOtp ? (
                <div>
                  {!sent ? (
                    <button className="btn" onClick={() => setSent(true)}><Icon name="mail" size={12}/> Send verification code to {sel.to}</button>
                  ) : (
                    <div>
                      <div style={{ background: 'var(--info-soft)', border: '1px solid color-mix(in oklch, var(--info) 30%, transparent)', borderRadius: 'var(--r-md)', padding: '8px 11px', fontSize: 11.5, color: 'oklch(0.4 0.09 240)', marginBottom: 10 }}>
                        <strong>Demo</strong> — a real system texts/emails this. Code sent to {sel.to}: <span className="mono" style={{ fontWeight: 700, letterSpacing: '0.1em' }}>{code}</span>
                      </div>
                      <label style={lbl}>Enter the 6-digit code the customer received</label>
                      <div style={{ display: 'flex', gap: 8 }}>
                        <input className="mono" value={entered} onChange={(e) => setEntered(e.target.value.replace(/\D/g, '').slice(0, 6))} placeholder="••••••" style={{ flex: 1, letterSpacing: '0.3em', textAlign: 'center', padding: '8px', border: '1px solid var(--border-strong)', borderRadius: 6, fontSize: 16 }}/>
                        <button className="btn primary" disabled={entered.length !== 6} style={entered.length !== 6 ? { opacity: 0.5 } : null} onClick={verify}>Verify</button>
                      </div>
                      {entered.length === 6 && entered !== code && <div style={{ fontSize: 11, color: 'var(--lost)', marginTop: 6 }}>Code doesn’t match — re-check with the customer.</div>}
                    </div>
                  )}
                </div>
              ) : (
                <div>
                  <label style={lbl}>Staff confirmation note (required) — how was identity confirmed?</label>
                  <textarea value={note} onChange={(e) => setNote(e.target.value)} rows="2" placeholder="e.g. Customer present in showroom, photo ID sighted"
                    style={{ width: '100%', padding: '8px 10px', border: '1px solid var(--border)', borderRadius: 6, fontFamily: 'inherit', fontSize: 12.5, resize: 'vertical', outline: 'none' }}/>
                  <button className="btn primary" style={{ marginTop: 8, opacity: note.trim().length > 2 ? 1 : 0.5 }} disabled={note.trim().length <= 2} onClick={verify}>
                    <Icon name="check" size={12}/> Confirm in-person verification
                  </button>
                </div>
              )}
            </React.Fragment>
          ) : conflicts.length > 0 ? (
            <div style={{ background: 'var(--lost-soft)', border: '1px solid color-mix(in oklch, var(--lost) 35%, transparent)', borderRadius: 'var(--r-md)', padding: '14px' }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
                <Icon name="alert" size={15} color="var(--lost)"/>
                <span style={{ fontWeight: 600, fontSize: 13, color: 'oklch(0.4 0.12 28)' }}>Update blocked — conflict found</span>
              </div>
              {conflicts.map((cf, i) => (
                <div key={i} style={{ fontSize: 12, color: 'var(--ink-2)', marginBottom: 6 }}>
                  This {cf.field.toLowerCase()} (<span className="mono">{cf.value}</span>) already exists on another record: <strong>{cf.other.name}</strong> · {storeName(cf.other.homeShowroom)}.
                </div>
              ))}
              <div style={{ fontSize: 11.5, color: 'oklch(0.4 0.12 28)', marginTop: 4, display: 'flex', alignItems: 'center', gap: 5 }}>
                <Icon name="lock" size={12}/> Flagged to Global Admin for review. The change was not applied.
              </div>
            </div>
          ) : (
            <div style={{ background: 'var(--won-soft)', border: '1px solid color-mix(in oklch, var(--won) 30%, transparent)', borderRadius: 'var(--r-md)', padding: '14px', display: 'flex', alignItems: 'center', gap: 10 }}>
              <Icon name="check" size={16} color="var(--won)"/>
              <div style={{ fontSize: 12.5, color: 'var(--ink-2)' }}>
                <strong>Verified — no conflicts found.</strong> The previous value will be archived in the audit trail.
              </div>
            </div>
          )}
        </div>

        <div style={{ padding: '13px 20px', borderTop: '1px solid var(--border)', display: 'flex', gap: 8 }}>
          <button className="btn" onClick={onCancel}>{verified && conflicts.length > 0 ? 'Close' : 'Cancel'}</button>
          {verified && conflicts.length === 0 && (
            <button className="btn primary" style={{ marginLeft: 'auto' }} onClick={() => onConfirm({ methodLabel: sel.methodLabel, note })}>
              <Icon name="check" size={12}/> Apply verified update
            </button>
          )}
        </div>
      </div>
    </React.Fragment>
  );
}

function ChangeRow({ field, from, to }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 11px', border: '1px solid var(--border)', borderRadius: 'var(--r-md)', background: 'var(--surface-2)', fontSize: 12 }}>
      <span style={{ fontWeight: 600, minWidth: 48 }}>{field}</span>
      <span className="mono" style={{ color: 'var(--ink-3)', textDecoration: 'line-through' }}>{from || '—'}</span>
      <Icon name="arrow-right" size={12} color="var(--ink-3)"/>
      <span className="mono" style={{ fontWeight: 600 }}>{to || '—'}</span>
    </div>
  );
}

window.ContactDetail = ContactDetail;

// Checks an owner's calendar (deal appts, quote presos, tasks, other contact reminders)
// for clashes around the chosen date/time, and shows what's already booked that day.
function ReminderAvailability({ date, time, ownerName, reps, deals, tasks, contacts, excludeContactId }) {
  if (!date) return null;
  const owner = (reps || []).find(r => r.name === ownerName);
  const sameDay = [];
  // Deal-derived appts for this owner on this day
  (deals || []).forEach(d => {
    if (owner && d.rep !== owner.id) return;
    if (d.designApptDate === date) sameDay.push({ t: '10:00', label: 'Design appt · ' + d.customer });
    if (d.quotePresentationDate === date) sameDay.push({ t: '14:00', label: 'Quote presentation · ' + d.customer });
    if (d.followUpReminder && d.followUpReminder.date === date) sameDay.push({ t: d.followUpReminder.time || '09:00', label: 'Follow up · ' + d.customer });
  });
  // Tasks
  (tasks || []).forEach(t => {
    if (owner && t.repId !== owner.id) return;
    if (t.date === date) sameDay.push({ t: t.time || '09:00', label: (t.title || 'Task') });
  });
  // Other contact reminders for this owner
  (contacts || []).forEach(c => {
    if (c.id === excludeContactId) return;
    if (c.owner !== ownerName) return;
    if (c.reminder && c.reminder.date === date) sameDay.push({ t: c.reminder.time || '09:00', label: (window.apptTypeLabel ? window.apptTypeLabel(c.reminder.type) : 'Reminder') + ' · ' + c.name });
  });
  sameDay.sort((a, b) => a.t.localeCompare(b.t));
  const clash = sameDay.find(e => e.t === time);

  return (
    <div style={{
      border: '1px solid var(--border)', borderRadius: 6, padding: '8px 12px',
      background: clash ? 'var(--lost-soft)' : (sameDay.length ? 'var(--surface-2)' : 'var(--won-soft)'),
      fontSize: 11.5,
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 6, fontWeight: 500, color: clash ? 'oklch(0.45 0.16 25)' : (sameDay.length ? 'var(--ink-2)' : 'oklch(0.4 0.12 155)') }}>
        <Icon name={clash ? 'alert' : 'check'} size={12}/>
        {clash
          ? `Clash at ${time} — ${clash.label}`
          : (sameDay.length
            ? `${ownerName || 'Owner'} has ${sameDay.length} item${sameDay.length > 1 ? 's' : ''} that day · ${time} is free`
            : `${ownerName || 'Owner'} is free on ${fmt.shortDate(date)}`)}
      </div>
      {sameDay.length > 0 && (
        <div style={{ marginTop: 6, display: 'flex', flexDirection: 'column', gap: 3 }}>
          {sameDay.map((e, i) => (
            <div key={i} style={{ display: 'flex', gap: 8, color: e.t === time ? 'oklch(0.45 0.16 25)' : 'var(--ink-3)' }}>
              <span className="num" style={{ minWidth: 42 }}>{e.t}</span>
              <span>{e.label}</span>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}
window.ReminderAvailability = ReminderAvailability;

function ContactReassignModal({ contact, reps, showrooms, currentUser, onClose, onConfirm }) {
  // Reps that work at the contact's home showroom
  const eligibleReps = reps.filter(r => r.works.includes(contact.homeShowroom));
  const [selectedName, setSelectedName] = React.useState('');
  const [reason, setReason] = React.useState('');
  const [notify, setNotify] = React.useState(true);

  return (
    <div>
      <div className="detail-overlay" onClick={onClose} style={{ zIndex: 52 }}/>
      <div className="invite-modal" style={{ zIndex: 53, width: 540 }}>
        <div style={{ padding: '18px 22px 14px', borderBottom: '1px solid var(--border)' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <div>
              <div style={{ fontSize: 18, fontWeight: 600, letterSpacing: '-0.02em' }}>Reassign contact</div>
              <div style={{ fontSize: 12, color: 'var(--ink-3)', marginTop: 4 }}>
                Transfer <strong>{contact.name}</strong> from <strong>{contact.owner || 'Unassigned'}</strong> to another designer.
              </div>
            </div>
            <button className="detail-close" style={{ marginLeft: 'auto' }} onClick={onClose}>
              <Icon name="close" size={16}/>
            </button>
          </div>
        </div>
        <div style={{ padding: '18px 22px', maxHeight: '60vh', overflowY: 'auto' }}>
          <div className="detail-section">
            <h4>Assign to</h4>
            <div className="reassign-list">
              {eligibleReps.map(r => (
                <div
                  key={r.id}
                  className={`reassign-row ${selectedName === r.name ? 'on' : ''}`}
                  onClick={() => setSelectedName(r.name)}
                >
                  <span className={`radio ${selectedName === r.name ? 'on' : ''}`}>
                    {selectedName === r.name && <span className="radio-dot"></span>}
                  </span>
                  <Avatar name={r.name} sz="sm"/>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontWeight: 500, fontSize: 13 }}>
                      {r.name}
                      {contact.owner === r.name && <span className="chip-soft" style={{ marginLeft: 6, fontSize: 9.5, padding: '0 5px' }}>current owner</span>}
                    </div>
                    <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 1 }}>
                      Works at: {r.works.map(sid => showrooms.find(s => s.id === sid)?.name).join(', ')}
                    </div>
                  </div>
                </div>
              ))}
            </div>
          </div>
          <div className="detail-section">
            <h4>Reason for reassignment (optional)</h4>
            <textarea
              placeholder="e.g. 'Original owner on leave', 'Customer prefers a different designer', 'Workload balance'"
              value={reason}
              onChange={(e) => setReason(e.target.value)}
              rows="3"
              style={{
                width: '100%',
                padding: '10px 12px',
                border: '1px solid var(--border)',
                borderRadius: 6,
                fontFamily: 'inherit',
                fontSize: 12.5,
                resize: 'vertical',
                outline: 'none',
              }}
            />
            <label style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 10, fontSize: 12, color: 'var(--ink-2)', cursor: 'pointer' }}>
              <input type="checkbox" checked={notify} onChange={(e) => setNotify(e.target.checked)} style={{ margin: 0 }}/>
              Notify the new owner by email
            </label>
          </div>
        </div>
        <div style={{ padding: '14px 22px', borderTop: '1px solid var(--border)', background: 'var(--surface-2)', display: 'flex', gap: 8 }}>
          <button className="btn" onClick={onClose}>Cancel</button>
          <button
            className="btn primary"
            disabled={!selectedName || selectedName === contact.owner}
            style={{ marginLeft: 'auto', opacity: (!selectedName || selectedName === contact.owner) ? 0.5 : 1 }}
            onClick={() => { onConfirm(selectedName, reason); onClose(); }}
          >
            <Icon name="check" size={12}/> Confirm reassignment
          </button>
        </div>
      </div>
    </div>
  );
}

window.ContactReassignModal = ContactReassignModal;

// Customer journey visualization — shows exactly where the contact currently sits
function ContactJourney({ contact, myDeals, currentDeal, openDeal, onSetLifecycle }) {
  // Compute which steps are achieved
  const lc = contact.lifecycle;
  const baseStage = lc.startsWith('dormant_') ? lc.replace('dormant_', '') : lc;
  const isDormant = lc.startsWith('dormant_');
  const isLost = baseStage === 'lost_quote' || baseStage === 'no_sale';

  // Determine position on linear journey
  const positions = {
    new_contact:     1,
    new_lead:        2,
    qualified:       3,
    in_pipeline:     4,
    customer_active: 5,
    past_customer:   5,
    lost_quote:      4,
    no_sale:         2,
  };
  const pos = positions[baseStage] || 1;

  // Lifecycle value to set when clicking each step
  const stepLifecycle = { 1: 'new_lead', 2: 'new_lead', 3: 'qualified', 4: 'in_pipeline', 5: 'past_customer' };

  const steps = [
    { id: 1, label: 'First Contact', sub: 'recorded' },
    { id: 2, label: 'New Lead', sub: 'engaged' },
    { id: 3, label: 'Qualified', sub: 'appt booked' },
    { id: 4, label: 'Quoted', sub: 'in pipeline' },
    { id: 5, label: 'Customer', sub: 'sold' },
  ];

  // Current step description
  let positionLabel = '';
  let positionSub = '';
  let positionTone = 'accent';
  if (baseStage === 'new_contact') {
    positionLabel = 'New contact — no follow-up yet';
    positionSub = 'Capture next step on contact card';
  } else if (baseStage === 'new_lead') {
    positionLabel = 'Lead — engaged, not ready';
    positionSub = 'Needs nurturing · check in regularly';
  } else if (baseStage === 'no_sale') {
    positionLabel = 'Did Not Qualify';
    positionSub = 'Marked as not the right client';
    positionTone = 'lost';
  } else if (baseStage === 'qualified') {
    positionLabel = 'Qualified — appointment booked';
    positionSub = 'Waiting for design appointment to happen';
  } else if (baseStage === 'in_pipeline' && currentDeal) {
    positionLabel = `${currentDeal.statusRaw || currentDeal.stage} — Quote ${currentDeal.quoteNumber || currentDeal.id}`;
    positionSub = `${fmt.money(currentDeal.value, true)} · ${currentDeal.product} · ${currentDeal.repName}`;
    positionTone = currentDeal.stage === 'on_hold' ? 'warn' : 'accent';
  } else if (baseStage === 'customer_active') {
    positionLabel = 'Active Customer — new deal in pipeline';
    positionSub = `${myDeals.filter(d => d.stage === 'sold').length} previous sale${myDeals.filter(d => d.stage === 'sold').length > 1 ? 's' : ''} · current deal: ${currentDeal?.id || ''}`;
    positionTone = 'green';
  } else if (baseStage === 'past_customer') {
    positionLabel = 'Past Customer';
    positionSub = `${myDeals.filter(d => d.stage === 'sold').length} sale${myDeals.filter(d => d.stage === 'sold').length > 1 ? 's' : ''} · ${fmt.money(contact.ltv, true)} lifetime · re-engage for next job`;
    positionTone = 'green';
  } else if (baseStage === 'lost_quote') {
    positionLabel = 'Lost — quote not won';
    positionSub = currentDeal ? `Quote ${currentDeal.quoteNumber || currentDeal.id} · ${fmt.money(currentDeal.value, true)} · ${currentDeal.product}` : '';
    positionTone = 'lost';
  }

  // Days since last activity context
  const days = contact.daysSinceContact || 0;
  let idleLabel = '';
  if (days < 30) idleLabel = `Last touched ${days} day${days !== 1 ? 's' : ''} ago`;
  else if (days < 365) idleLabel = `Last touched ${Math.floor(days / 30)} month${Math.floor(days / 30) !== 1 ? 's' : ''} ago`;
  else idleLabel = `Last touched ${Math.floor(days / 365)} year${Math.floor(days / 365) !== 1 ? 's' : ''} ago`;

  const toneColor = positionTone === 'green' ? 'var(--won)' :
                    positionTone === 'lost' ? 'var(--lost)' :
                    positionTone === 'warn' ? 'oklch(0.55 0.13 60)' :
                    'var(--accent)';

  return (
    <div>
      {/* Step indicator */}
      <div className="journey">
        {steps.map((s, i) => {
          const achieved = s.id < pos;
          const current = s.id === pos && !isLost;
          const lostBranch = isLost && s.id >= pos;
          const canClick = onSetLifecycle && !current;
          return (
            <React.Fragment key={s.id}>
              <div
                className={`j-step ${achieved ? 'achieved' : ''} ${current ? 'current' : ''} ${lostBranch ? 'lost-branch' : ''}`}
                style={canClick ? { cursor: 'pointer' } : {}}
                title={canClick ? `Move to ${s.label}` : undefined}
                onClick={canClick ? () => onSetLifecycle(stepLifecycle[s.id]) : undefined}
              >
                <div className="j-dot">
                  {achieved ? <Icon name="check" size={10} color="white"/> :
                   current ? <span style={{ width: 8, height: 8, background: 'white', borderRadius: 4 }}></span> :
                   lostBranch ? <Icon name="close" size={10} color="white"/> :
                   <span className="j-dot-inner"></span>}
                </div>
                <div className="j-label">{s.label}</div>
                <div className="j-sub">{current ? 'current' : canClick ? 'click to set' : s.sub}</div>
              </div>
              {i < steps.length - 1 && (
                <div className={`j-line ${achieved ? 'achieved' : ''} ${isLost && s.id >= pos - 1 ? 'lost-branch' : ''}`}></div>
              )}
            </React.Fragment>
          );
        })}
      </div>

      {/* Current position summary */}
      <div className="journey-summary" style={{
        marginTop: 14,
        padding: '14px 16px',
        background: `color-mix(in oklch, ${toneColor} 6%, var(--surface))`,
        border: `1px solid color-mix(in oklch, ${toneColor} 20%, transparent)`,
        borderRadius: 8,
      }}>
        <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 10 }}>
          <div>
            <div style={{ fontSize: 10.5, fontWeight: 500, textTransform: 'uppercase', letterSpacing: '0.08em', color: toneColor, marginBottom: 2 }}>
              {isDormant ? 'Dormant · ' : ''}Current position
            </div>
            <div style={{ fontSize: 14, fontWeight: 600, color: 'var(--ink)' }}>{positionLabel}</div>
            {positionSub && <div style={{ fontSize: 12, color: 'var(--ink-2)', marginTop: 2 }}>{positionSub}</div>}
          </div>
          {currentDeal && (
            <button
              className="btn sm"
              onClick={() => openDeal(currentDeal)}
              style={{ flexShrink: 0, whiteSpace: 'nowrap' }}
            >
              Open deal <Icon name="arrow-right" size={11}/>
            </button>
          )}
        </div>
        <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 8, display: 'flex', alignItems: 'center', gap: 6 }}>
          <Icon name="clock" size={11}/>
          {idleLabel}
          {isDormant && <span style={{ marginLeft: 'auto', color: 'var(--lost)', fontWeight: 500 }}>· cold for {Math.floor(days / 365)}+ year{Math.floor(days / 365) !== 1 ? 's' : ''} — re-engage</span>}
        </div>
      </div>
    </div>
  );
}

window.ContactJourney = ContactJourney;
