// Clinician persona — desktop layout for the in-clinic team.
// Screens: today, customer record, capture, walk-in modal.

const ClNav = [
  { id: 'today',     label: 'Today',     icon: 'calendar' },
  { id: 'customers', label: 'Customers', icon: 'users' },
  { id: 'enquiries', label: 'Enquiries', icon: 'mail' },
  { id: 'capture',   label: 'Capture',   icon: 'camera' },
  { id: 'inbox',     label: 'Inbox',     icon: 'mail' },
  { id: 'timeoff',   label: 'Time off',  icon: 'sparkle' },
];

function ClinicianShell({ route, setRoute, tweaks, runtimePhotos, setRuntimePhotos, runtimeNotes, setRuntimeNotes }) {
  // walkIn is now { open: bool, mode: 'walkin' | 'future' } so we can differentiate
  // the two top-bar buttons (same modal, different defaults).
  const [walkIn, setWalkIn] = React.useState({ open: false, mode: 'walkin' });
  const [newCust, setNewCust] = React.useState(false);
  const [activeCustomer, setActiveCustomer] = React.useState('sophie');
  const [cashUp, setCashUp] = React.useState(null);
  const [notifs, setNotifs] = React.useState(false);

  const unreadNotifs = NOTIFICATIONS.length;
  const handleNotifAction = (n) => {
    if (n.link?.customerId) {
      setActiveCustomer(n.link.customerId);
      setRoute({ screen: 'record', customerId: n.link.customerId });
    }
  };

  const view = (() => {
    if (route.screen === 'record') {
      return <ClRecord customerId={route.customerId || activeCustomer} setRoute={setRoute} tweaks={tweaks} runtimePhotos={runtimePhotos} setRuntimePhotos={setRuntimePhotos} runtimeNotes={runtimeNotes} setRuntimeNotes={setRuntimeNotes}/>;
    }
    if (route.screen === 'capture') {
      return <ClCapture customerId={route.customerId || activeCustomer} setRoute={setRoute} runtimePhotos={runtimePhotos} setRuntimePhotos={setRuntimePhotos}/>;
    }
    if (route.screen === 'customers') {
      return <ClCustomerList setActive={(id) => { setActiveCustomer(id); setRoute({ screen: 'record', customerId: id }); }} onNew={() => setNewCust(true)}/>;
    }
    if (route.screen === 'inbox') {
      return <ClInbox/>;
    }
    if (route.screen === 'enquiries') {
      return <EnquiriesInbox onOpenLead={(id) => setRoute({ screen: 'lead', leadId: id })}/>;
    }
    if (route.screen === 'lead') {
      return <LeadDetail leadId={route.leadId} onBack={() => setRoute({ screen: 'enquiries' })} onOpenCompose={() => {}}/>;
    }
    if (route.screen === 'timeoff') {
      return <ClTimeOff clinicianId="grace"/>;
    }
    return <ClToday
      setRoute={setRoute}
      setActive={setActiveCustomer}
      onWalkIn={() => setWalkIn({ open: true, mode: 'walkin' })}
      onNewBooking={() => setWalkIn({ open: true, mode: 'future' })}
      onNewCustomer={() => setNewCust(true)}
      onCheckOut={(a) => setCashUp(a)}
      tweaks={tweaks}
    />;
  })();

  const navId =
    route.screen === 'record' ? 'customers' :
    route.screen === 'lead'   ? 'enquiries' :
    route.screen;

  return (
    <div className="clinician-shell">
      <aside className="cl-side">
        <div className="cl-brand"><span className="dot"/>Demo Clinic · specialist</div>
        <div className="cl-nav">
          {ClNav.map(n => (
            <button key={n.id} className={navId === n.id ? 'active' : ''}
              onClick={() => setRoute({ screen: n.id })}>
              <Icon name={n.icon} size={16}/><span className="nav-label">{n.label}</span>
            </button>
          ))}
        </div>
        <div className="cl-side-foot">
          <span className="avatar" style={{ background: 'var(--blush-deep)', color: '#fff' }}>GH</span>
          <div className="who"><b>Grace Hollis</b><span>Aesthetic nurse</span></div>
        </div>
      </aside>

      <main className="cl-main">
        <div className="cl-toolbar">
          <NotificationBell count={unreadNotifs} onOpen={() => setNotifs(true)}/>
        </div>
        {view}
      </main>

      {walkIn.open && <WalkInModal mode={walkIn.mode} onClose={() => setWalkIn({ open: false, mode: 'walkin' })}/>}
      {newCust && <NewCustomerModal onClose={() => setNewCust(false)}/>}
      {cashUp && <CashUpModal appt={cashUp} onClose={() => setCashUp(null)} onRebook={() => { setCashUp(null); setWalkIn({ open: true, mode: 'future' }); }}/>}
      {notifs && <NotificationPanel onClose={() => setNotifs(false)} onAction={handleNotifAction}/>}
    </div>
  );
}

// ── List view of today's schedule (extracted so we can use local state for
//    check-in updates without re-rendering the parent's calendar). ──────
function ListSchedule({ today, nowIdx, openRecord, onCheckOut }) {
  const [appts, setAppts] = React.useState(today);
  React.useEffect(() => { setAppts(today); }, [today]);
  const updateAppt = (next) => setAppts(prev => prev.map(a => a.id === next.id ? next : a));
  return (
    <section className="panel">
      <div className="panel-head"><h3>Today's schedule</h3><span className="meta">{appts.length} appointments</span></div>
      {appts.map((a, i) => {
        const c = customerById(a.customerId);
        const cl = clinicianById(a.clinicianId);
        const t = treatmentById(a.treatmentId);
        const fl = flagInfo(c.flag);
        const status = a.status || 'confirmed';
        return (
          <div key={a.id} className={'appt-row' + (i === nowIdx ? ' now' : '') + (status === 'completed' ? ' done' : '')}
            onClick={() => openRecord(c.id)}>
            <div className="appt-time">{a.time}<small>{a.mins} min</small></div>
            <div className="appt-meta">
              <div className="who">
                <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', minWidth: 0 }}>{c.name}</span>
                {fl && <span className={'tag ' + fl.tone}>{fl.label}</span>}
              </div>
              <div className="what">
                <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{t.name}</span>
                <span className="dot"/>
                <span>{cl.name.split(' ')[0]}</span>
                {a.room && <><span className="dot"/><span>{a.room}</span></>}
              </div>
            </div>
            <div style={{ display: 'flex', gap: 6, alignItems: 'center', flexShrink: 0, flexWrap: 'wrap', justifyContent: 'flex-end' }} onClick={(e) => e.stopPropagation()}>
              <CheckInButton appt={a} onUpdate={updateAppt}/>
              <button className="app-btn small icon-only" onClick={() => onCheckOut(a)} title="Cash-up"><Icon name="pound" size={12}/></button>
            </div>
          </div>
        );
      })}
    </section>
  );
}

// ── Today screen — calendar-by-default, with list toggle ───────
// (§1: calendar diary view, §2: pending bookings, §12: dashboard, §11: reminders)
function ClToday({ setRoute, setActive, onWalkIn, onNewBooking, onNewCustomer, onCheckOut, tweaks, viewerName = 'Grace' }) {
  const [diaryDate, setDiaryDate] = React.useState(todayISO);
  const today = APPOINTMENTS.filter(a => a.date === diaryDate && a.status !== 'cancelled');
  const [view, setView] = React.useState('calendar'); // 'calendar' | 'list'
  const [showForm, setShowForm] = React.useState(null);
  const nowIdx = 2;

  const openRecord = (cid) => { setActive(cid); setRoute({ screen: 'record', customerId: cid }); };

  const stats = [
    { lbl: diaryDate === todayISO ? 'Today' : 'Booked', val: today.length, sub: today.filter(a => a.status === 'confirmed').length + ' confirmed · ' + today.filter(a => a.status === 'pending').length + ' pending' },
    { lbl: 'Pending approval', val: PENDING_BOOKINGS.length, sub: 'online bookings to review' },
    { lbl: 'Studios in use',   val: '3 / 3', sub: 'all rooms booked AM' },
  ];

  return (
    <>
      <div className="cl-head">
        {/* min-width sized to the longest date so the action buttons never
            reflow when the heading text changes length between days */}
        <div style={{ minWidth: 'min(430px, 60vw)' }}>
          <span className="eyebrow">{diaryDate === todayISO ? new Date(diaryDate + 'T12:00:00').toLocaleDateString('en-GB', { weekday: 'long', day: 'numeric', month: 'long' }) : 'The diary'}</span>
          <h1 style={{ whiteSpace: 'nowrap' }}>{diaryDate === todayISO ? 'Good morning, ' + viewerName : fmtDate(diaryDate)}<DemoNote title="The diary" text="Drag a booking to move it (tap and hold on a phone), click a gap to book, and click a booking for check-in, payment and messages. Each staff member has their own login and their own view of this — you decide who sees and does what."/></h1>
        </div>
        <div className="actions">
          <div className="view-toggle">
            <button onClick={() => setDiaryDate(shiftDay(diaryDate, -1))} aria-label="Previous day"><Icon name="chevL" size={12}/></button>
            <button style={{ minWidth: 58 }} className={diaryDate === todayISO ? 'active' : ''} onClick={() => setDiaryDate(todayISO)}>Today</button>
            <button onClick={() => setDiaryDate(shiftDay(diaryDate, 1))} aria-label="Next day"><Icon name="chevR" size={12}/></button>
          </div>
          <div className="view-toggle">
            <button className={view === 'calendar' ? 'active' : ''} onClick={() => setView('calendar')}><Icon name="calendar" size={12}/>Calendar</button>
            <button className={view === 'list' ? 'active' : ''} onClick={() => setView('list')}><Icon name="list" size={12}/>List</button>
          </div>
          <button className="app-btn" onClick={onNewCustomer}><Icon name="user" size={14}/>New customer</button>
          <button className="app-btn" onClick={onWalkIn}><Icon name="plus" size={14}/>Walk-in</button>
          <button className="app-btn primary" onClick={onNewBooking || onWalkIn}><Icon name="calendar" size={14}/>New booking</button>
        </div>
      </div>

      <div className="stat-row">
        {stats.map(s => (
          <div key={s.lbl} className="stat">
            <div className="lbl">{s.lbl}</div>
            <div className="val">{s.val}</div>
            <div className="delta">{s.sub}</div>
          </div>
        ))}
      </div>

      {view === 'calendar' ? (
        <DiaryView key={diaryDate} date={diaryDate} appointments={today} onOpenRecord={openRecord} onCheckOut={onCheckOut} defaultClinician="grace"/>
      ) : (
        <ListSchedule today={today} nowIdx={nowIdx} openRecord={openRecord} onCheckOut={onCheckOut}/>
      )}

      {/* Pending bookings always visible — Emma's #1 dashboard ask (§2 + §12) */}
      <div style={{ marginTop: 24 }}>
        <PendingBookingsPanel onOpenForm={(cid) => setShowForm(cid)}/>
      </div>

      {/* Tasks (§14) — shared daily running list, replaces the old static reminders panel */}
      <div style={{ marginTop: 24 }}>
        <TasksPanel currentClinicianId="grace" onOpenRecord={openRecord}/>
      </div>

      <div className="cl-grid" style={{ marginTop: 24 }}>
        <WaitlistPanel/>

        <div className="panel">
          <div className="panel-head"><h3>System reminders</h3><span className="meta">Auto-generated</span></div>
          <div style={{ fontSize: 13, color: 'var(--fg-2)', display: 'flex', flexDirection: 'column', gap: 12 }}>
            <div style={{ display: 'flex', gap: 10, alignItems: 'flex-start' }}>
              <Icon name="bell" size={14}/><div>Hannah W's <b>2-week post-CO2 photos</b> due — capture today.</div>
            </div>
            <div style={{ display: 'flex', gap: 10, alignItems: 'flex-start' }}>
              <Icon name="info" size={14}/><div>Lucy B's consultation needs <b>consent form</b> signed before treatment.</div>
            </div>
            <div style={{ display: 'flex', gap: 10, alignItems: 'flex-start' }}>
              <Icon name="lock" size={14}/><div>Patch test for Sophie R clears tomorrow — book microneedling.</div>
            </div>
            <div style={{ display: 'flex', gap: 10, alignItems: 'flex-start' }}>
              <Icon name="mail" size={14}/><div>Amelia C — no contact in 13 months. Send re-engagement email?</div>
            </div>
          </div>
        </div>
      </div>

      {showForm && <MedicalFormModal customerId={showForm} onClose={() => setShowForm(null)}/>}
    </>
  );
}

// ── Time off — clinician's leave + shifts (§1.7) ─────────────
function ClTimeOff({ clinicianId }) {
  const [request, setRequest] = React.useState(false);
  const [pending, setPending] = React.useState([]); // submitted via modal
  const me = clinicianById(clinicianId);
  const myLeave = LEAVE.filter(lv => lv.clinicianId === clinicianId).concat(pending);
  const dayNames = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
  const myShifts = STAFF_SHIFTS[clinicianId] || [];

  return (
    <>
      <div className="cl-head">
        <div><span className="eyebrow">My availability</span><h1>Time off & shifts</h1></div>
        <div className="actions">
          <button className="app-btn primary" onClick={() => setRequest(true)}><Icon name="plus" size={14}/>Request leave</button>
        </div>
      </div>

      <div className="cl-grid">
        <section className="panel">
          <div className="panel-head"><h3>Approved leave</h3><span className="meta">Blocked from booking</span></div>
          {myLeave.length === 0 ? (
            <div className="empty"><Icon name="calendar" size={28}/><div>No leave on the books.</div></div>
          ) : myLeave.map(lv => (
            <div key={lv.id} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '14px 0', borderBottom: '1px solid var(--line)' }}>
              <div>
                <div style={{ font: '500 14px/1.2 var(--font-body)', color: 'var(--fg-1)' }}>{lv.kind}{lv.note && ' · ' + lv.note}</div>
                <div style={{ fontSize: 12, color: 'var(--fg-3)', marginTop: 4 }}>
                  {fmtDate(lv.from)} {lv.from !== lv.to ? '— ' + fmtDate(lv.to) : ''}
                </div>
              </div>
              <span className="tag ok"><Icon name="check" size={10}/>approved</span>
            </div>
          ))}
        </section>

        <aside className="panel">
          <div className="panel-head"><h3>My weekly shifts</h3><span className="meta">Set by clinic</span></div>
          {dayNames.map((d, i) => (
            <div key={d} style={{ display: 'flex', justifyContent: 'space-between', padding: '10px 0', borderBottom: i === 6 ? 0 : '1px solid var(--line)', fontSize: 13 }}>
              <span style={{ color: 'var(--fg-1)' }}>{d}</span>
              <span style={{ color: myShifts[i] ? 'var(--fg-2)' : 'var(--fg-3)', fontVariantNumeric: 'tabular-nums' }}>{myShifts[i] || 'Off'}</span>
            </div>
          ))}
          <div style={{ marginTop: 12, fontSize: 11, color: 'var(--fg-3)', lineHeight: 1.5 }}>
            Need a permanent shift change? <a href="#">Message {clinicianById('emma').name.split(' ')[0]}</a>.
          </div>
        </aside>
      </div>

      {request && <RequestLeaveModal clinicianId={clinicianId} onClose={() => setRequest(false)}
        onRequest={(r) => setPending(prev => [...prev, { id: 'lv-new-' + Date.now(), clinicianId, ...r }])}/>}
    </>
  );
}

// ── Customer list ────────────────────────────────────────────
// §3 — alphabetical, search by name/phone/email, filter by flag,
// phone+email shown under the name (OVATU benchmark).
function ClCustomerList({ setActive, onNew }) {
  const [q, setQ] = React.useState('');
  const [filter, setFilter] = React.useState('all'); // all / highCheckIn / highCancel / inactive / followUp
  const counts = {
    all:          CUSTOMERS.length,
    highCheckIn:  CUSTOMERS.filter(c => c.flag === 'highCheckIn').length,
    highCancel:   CUSTOMERS.filter(c => c.flag === 'highCancel').length,
    inactive:     CUSTOMERS.filter(c => c.flag === 'inactive').length,
    followUp:     CUSTOMERS.filter(c => c.flag === 'followUp').length,
  };

  // Search across name + phone (digits) + email
  const qLower = q.trim().toLowerCase();
  const qDigits = qLower.replace(/\D/g, '');
  const matches = (c) => {
    if (!qLower) return true;
    if (c.name.toLowerCase().includes(qLower)) return true;
    if (c.email.toLowerCase().includes(qLower)) return true;
    if (qDigits && c.phone.replace(/\D/g, '').includes(qDigits)) return true;
    return false;
  };

  const filtered = CUSTOMERS
    .filter(matches)
    .filter(c => filter === 'all' ? true : c.flag === filter)
    .slice()
    .sort((a, b) => a.name.localeCompare(b.name)); // §3.1 alphabetical

  // Group by first letter of last name (mimics Emma's screenshots — A, B, C…)
  const groups = filtered.reduce((acc, c) => {
    const lastInitial = (c.name.split(' ').pop() || c.name)[0].toUpperCase();
    (acc[lastInitial] = acc[lastInitial] || []).push(c);
    return acc;
  }, {});
  const letters = Object.keys(groups).sort();

  const filterBtn = (id, label) => (
    <button className={filter === id ? 'active' : ''} onClick={() => setFilter(id)}>
      {label}{counts[id] !== undefined && <span style={{ marginLeft: 6, color: filter === id ? 'rgba(247,242,238,0.55)' : 'var(--fg-3)' }}>{counts[id]}</span>}
    </button>
  );

  return (
    <>
      <div className="cl-head">
        <div><span className="eyebrow">Records · {filtered.length} of {CUSTOMERS.length}</span><h1>Customers</h1></div>
        <div className="actions">
          <button className="app-btn"><Icon name="upload" size={14}/>Export</button>
          <button className="app-btn primary" onClick={onNew}><Icon name="plus" size={14}/>New customer</button>
        </div>
      </div>

      <div className="cust-search-bar">
        <Icon name="search" size={16}/>
        <input value={q} onChange={e => setQ(e.target.value)}
          placeholder="Search by name, phone or email…"/>
        {q && <button className="app-btn ghost icon-only" onClick={() => setQ('')}><Icon name="close" size={14}/></button>}
      </div>

      <div className="cust-filter-row">
        {filterBtn('all',         'All')}
        {filterBtn('highCheckIn', 'High check-in')}
        {filterBtn('followUp',    'Follow-up')}
        {filterBtn('highCancel',  'High cancellation')}
        {filterBtn('inactive',    'Inactive')}
      </div>

      <div className="panel" style={{ padding: 0 }}>
        {filtered.length === 0 ? (
          <div className="empty"><Icon name="search" size={28}/><div>No customers match that.</div></div>
        ) : letters.map(L => (
          <React.Fragment key={L}>
            <div className="cust-letter-head">{L}</div>
            {groups[L].map(c => {
              const fi = flagInfo(c.flag);
              const lastAppt = LAST_COMPLETED_VISIT[c.id];
              return (
                <div key={c.id} className="cust-list-row" onClick={() => setActive(c.id)}>
                  <span className="avatar sm" style={{ background: 'var(--blush-soft)' }}>{c.initials}</span>
                  <div className="cl-body">
                    <b>{c.name}</b>
                    <div className="cl-contact">
                      <span><Icon name="mail" size={12}/>{c.email}</span>
                      <span><Icon name="phone" size={12}/>{c.phone}</span>
                    </div>
                  </div>
                  <div className="cl-flag">{fi && <span className={'tag ' + fi.tone}>{fi.label}</span>}</div>
                  <div className="cl-meta">{lastAppt ? 'Last · ' + fmtDate(lastAppt.date) : 'New'}</div>
                  {(c.account < 0 || c.flag === 'highCancel' || c.flag === 'followUp') ? (
                    <Icon name="info" size={16} className="cl-warn"/>
                  ) : (
                    <Icon name="chevR" size={14}/>
                  )}
                </div>
              );
            })}
          </React.Fragment>
        ))}
      </div>
    </>
  );
}

// ── Customer record ──────────────────────────────────────────
function ClRecord({ customerId, setRoute, tweaks, runtimePhotos, setRuntimePhotos, runtimeNotes, setRuntimeNotes }) {
  const c = customerById(customerId);
  const [tab, setTab] = React.useState('notes');
  const [bulkUpload, setBulkUpload] = React.useState(false);
  const [showForm, setShowForm] = React.useState(false);
  const [compose, setCompose] = React.useState(null); // null | { channel, formsToAttach?, defaultTemplate? }
  const [newNote, setNewNote] = React.useState(false);
  const [editingNote, setEditingNote] = React.useState(null); // note object being edited
  const [showCompare, setShowCompare] = React.useState(false);
  const [showPhone, setShowPhone] = React.useState(false);
  const [annotatePhoto, setAnnotatePhoto] = React.useState(null);
  // Runtime (this-session) notes shown above the seeded ones. Notes are
  // editable for 24h after creation; after that the system locks them and
  // any edit attempt is blocked. Edits append to `editHistory`.
  const runtimeNoteList = (runtimeNotes && runtimeNotes[c.id]) || [];
  const seededNotes = NOTES[c.id] || [];
  const notes = [...runtimeNoteList, ...seededNotes];
  const runtime = (runtimePhotos && runtimePhotos[c.id]) || [];
  const seeded  = PHOTOS[c.id] || [];
  const photos  = [...runtime, ...seeded];
  const apps = APPOINTMENTS.filter(a => a.customerId === c.id).sort((a,b) => b.date.localeCompare(a.date));
  const msgCount = (COMM_LOG[c.id] || []).length;
  const unreadCount = (COMM_LOG[c.id] || []).filter(m => m.direction === 'in' && !m.read).length;
  const fl = flagInfo(c.flag);

  return (
    <>
      <button className="app-btn ghost small" style={{ marginBottom: 12 }} onClick={() => setRoute({ screen: 'today' })}>
        <Icon name="back" size={14}/>Back
      </button>

      {/* Quick alerts/flags banner — Emma's "deposits, treatments, costs, important account notes" (§5.2) */}
      {(c.alert || c.account < 0 || fl) && (
        <div className="amend-alert" style={{ marginBottom: 12 }}>
          <Icon name="info" size={14}/>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 12, alignItems: 'center', flex: 1 }}>
            {fl && <span className={'tag ' + fl.tone}>{fl.label}</span>}
            {c.deposit > 0 && <span className="tag blush">Deposit on file · {fmtMoney(c.deposit)}</span>}
            {c.account < 0 && <span className="tag warn">Owes {fmtMoney(Math.abs(c.account))}</span>}
            {c.alert && <span style={{ fontSize: 12, color: 'var(--fg-2)' }}>{c.alert}</span>}
          </div>
        </div>
      )}

      {/* Upcoming treatments (§5.2b) + Forms gate (§4) side by side */}
      <div className="record-prelude">
        <UpcomingTreatments customerId={c.id}/>
        <FormsGate customerId={c.id} onSend={(missing) => setCompose({
          channel: 'email',
          formsToAttach: (missing || []).map(m => m.name),
          defaultTemplate: 'consent-email',
        })}/>
      </div>

      <div className="record-head">
        <span className="avatar lg" style={{ background: 'var(--blush)' }}>{c.initials}</span>
        <div>
          <h2>{c.name}</h2>
          <div className="meta">
            <span><Icon name="phone" size={12}/>{c.phone}</span>
            <span><Icon name="mail" size={12}/>{c.email}</span>
            <span><Icon name="user" size={12}/>{c.dob}</span>
            <span className="tag blush">Customer since {c.joined}</span>
          </div>
        </div>
        <div style={{ display: 'flex', gap: 8 }}>
          <button className="app-btn" onClick={() => setRoute({ screen: 'capture', customerId: c.id })}>
            <Icon name="camera" size={14}/>Capture
          </button>
          <button className="app-btn primary" onClick={() => setNewNote(true)}><Icon name="plus" size={14}/>New note</button>
        </div>
      </div>

      <div className="record-tabs">
        {[
          { id: 'overview',     label: 'About' },
          { id: 'notes',        label: 'Notes' },
          { id: 'photos',       label: 'Photos' },
          { id: 'appointments', label: 'Visits' },
          { id: 'messages',     label: 'Messages', badge: unreadCount },
          { id: 'consent',      label: 'Forms' },
        ].map(t => (
          <button key={t.id} className={tab === t.id ? 'active' : ''} onClick={() => setTab(t.id)}>
            {t.label}
            {t.badge > 0 && <span className="tab-badge">{t.badge}</span>}
          </button>
        ))}
      </div>

      {tab === 'overview' && (
        <div className="cl-2col">
          <div className="panel">
            <div className="panel-head"><h3>About</h3></div>
            <div className="kv-grid">
              <div className="kv"><div className="lbl">Allergies</div><div className="val">{c.allergies}</div></div>
              <div className="kv"><div className="lbl">Medications</div><div className="val">{c.meds}</div></div>
              <div className="kv"><div className="lbl">Concerns</div><div className="val">{c.concerns.join(', ')}</div></div>
              <div className="kv"><div className="lbl">Skin type (Fitz)</div><div className="val">III</div></div>
              <div className="kv"><div className="lbl">Consent</div><div className="val">Up to date · 2025-04</div></div>
              <div className="kv"><div className="lbl">Patch test</div><div className="val">Tretinoin · clears 03 May</div></div>
            </div>
          </div>
          <div className="panel">
            <div className="panel-head"><h3>Treatment plan</h3><span className="meta">Active</span></div>
            <div style={{ fontSize: 14, color: 'var(--fg-2)', lineHeight: 1.55 }}>
              6-month plan agreed Apr 2025. Course of <b>3× microneedling</b> at 4-week intervals, followed by HydraFacial maintenance every 6 weeks. Home routine: vitamin C AM, retinoid PM (graduated).
            </div>
            <div className="divider" style={{ margin: '20px 0' }}/>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13 }}><span>Microneedling · 3 of 3</span><span className="tag ok">complete</span></div>
              <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13 }}><span>HydraFacial maintenance</span><span className="tag blush">ongoing</span></div>
              <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13 }}><span>Tretinoin home use</span><span className="tag">awaiting patch</span></div>
            </div>
          </div>
        </div>
      )}

      {tab === 'notes' && (() => {
        // Q14 — notes are editable for 24h after creation, then lock.
        // Runtime notes carry `createdAt` (ms epoch); seeded notes have only
        // a date string and are always shown as locked (their creation is
        // always >24h in the past).
        const EDIT_WINDOW_MS = 24 * 60 * 60 * 1000;
        const noteIsEditable = (n) => n.createdAt && (Date.now() - n.createdAt < EDIT_WINDOW_MS);
        const editLockHint = (n) => {
          if (noteIsEditable(n)) {
            const minsLeft = Math.max(0, Math.round((EDIT_WINDOW_MS - (Date.now() - n.createdAt)) / 60000));
            const h = Math.floor(minsLeft / 60);
            const m = minsLeft % 60;
            return 'Editable · ' + (h ? h + 'h ' : '') + m + 'm left';
          }
          return 'Locked · 24h edit window passed';
        };
        const editHistoryDetail = (n) => {
          if (!n.editHistory || n.editHistory.length === 0) return null;
          return (
            <details className="note-edit-history">
              <summary>Edited {n.editHistory.length} time{n.editHistory.length === 1 ? '' : 's'}</summary>
              <ul>
                {n.editHistory.map((h, i) => (
                  <li key={i}>
                    <span className="when">{new Date(h.when).toLocaleString('en-GB', { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' })}</span>
                    {' · '}<b>{h.editor}</b>
                    {h.changes ? <> {' · '}{h.changes}</> : null}
                  </li>
                ))}
              </ul>
            </details>
          );
        };
        return tweaks.noteLayout === 'accordion' ? (
          <div className="notes-accordion">
            {notes.map((n, i) => (
              <details key={n.id} className="note-acc" open={i === 0}>
                <summary>
                  <div className="left">
                    <span className="when">{n.kind} · {n.when}</span>
                    <h4>{n.title}</h4>
                  </div>
                  <Icon name="chevR" size={16}/>
                </summary>
                <div className="acc-body">
                  <p style={{ margin: '12px 0 0' }}>{n.body}</p>
                  <div className="author">— {n.author}</div>
                  <div className="note-edit-row">
                    <span className={'tag ' + (noteIsEditable(n) ? 'ok' : 'ghost')}>{editLockHint(n)}</span>
                    {noteIsEditable(n) && (
                      <button className="app-btn ghost small" onClick={(e) => { e.stopPropagation(); setEditingNote(n); }}>
                        <Icon name="edit" size={12}/>Edit
                      </button>
                    )}
                  </div>
                  {editHistoryDetail(n)}
                </div>
              </details>
            ))}
          </div>
        ) : (
          <div className="notes-timeline">
            {notes.map(n => (
              <div key={n.id} className="note-item">
                <div className="when">
                  <span>{n.when}</span>
                  <span className="tag">{n.kind}</span>
                  <span className={'tag ' + (noteIsEditable(n) ? 'ok' : 'ghost')} style={{ marginLeft: 'auto' }}>{editLockHint(n)}</span>
                </div>
                <h4>{n.title}</h4>
                <div className="body">{n.body}</div>
                <div className="author">— {n.author}</div>
                {noteIsEditable(n) && (
                  <div className="note-edit-row">
                    <button className="app-btn ghost small" onClick={() => setEditingNote(n)}>
                      <Icon name="edit" size={12}/>Edit note
                    </button>
                  </div>
                )}
                {editHistoryDetail(n)}
              </div>
            ))}
            {notes.length === 0 && <div className="empty"><Icon name="file" size={28}/><div>No notes yet.</div></div>}
          </div>
        );
      })()}

      {tab === 'photos' && (
        <>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16, gap: 12, flexWrap: 'wrap' }}>
            <div style={{ display: 'flex', gap: 6 }}>
              <span className="tag ghost">All</span>
              <span className="tag ghost">Front</span>
              <span className="tag ghost">Profile</span>
            </div>
            <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
              <button className="app-btn" onClick={() => setBulkUpload(true)}><Icon name="upload" size={14}/>Bulk upload</button>
              <button className="app-btn"
                disabled={!c.marketingConsent}
                title={c.marketingConsent ? '' : 'Photo & marketing consent not signed — see Consent tab'}
                onClick={() => setShowPhone(true)}>
                <Icon name="phone" size={14}/>Send to phone
              </button>
              <button className="app-btn"
                disabled={photos.length < 2}
                title={photos.length < 2 ? 'Need at least two photos to compare' : ''}
                onClick={() => setShowCompare(true)}>
                <Icon name="photo" size={14}/>Compare
              </button>
              <button className="app-btn primary" onClick={() => setRoute({ screen: 'capture', customerId: c.id })}>
                <Icon name="camera" size={14}/>Capture session
              </button>
            </div>
          </div>
          {photos.length >= 2 && (
            <div className="compare" style={{ marginBottom: 24 }}>
              <div className="col">
                <div className="lbl"><span>Before</span><span>{photos[photos.length - 1].when || (photos[photos.length - 1].ts ? new Date(photos[photos.length - 1].ts).toLocaleDateString() : '')}</span></div>
                {photos[photos.length - 1].dataUrl
                  ? <img src={photos[photos.length - 1].dataUrl} className="compare-img" alt=""/>
                  : <div className={'ph ' + (photos[photos.length - 1].tone || 'ph-portrait')}/>}
              </div>
              <div className="col">
                <div className="lbl"><span>After</span><span>{photos[0].when || (photos[0].ts ? new Date(photos[0].ts).toLocaleDateString() : '')}</span></div>
                {photos[0].dataUrl
                  ? <img src={photos[0].dataUrl} className="compare-img" alt=""/>
                  : <div className={'ph ' + (photos[0].tone || 'ph-portrait')}/>}
              </div>
            </div>
          )}
          <div className="photo-grid">
            {photos.map(p => {
              const isRuntime = p.id?.startsWith('rt-');
              const when = isRuntime ? new Date(p.ts).toLocaleString('en-GB', { day:'numeric', month:'short', hour:'2-digit', minute:'2-digit' }) : p.when;
              const label = isRuntime ? p.pose : p.label;
              return (
                <button key={p.id}
                  className={'photo-card ' + (isRuntime ? '' : (p.tone + ' ph'))}
                  style={{ cursor: 'pointer', appearance: 'none', border: 0, padding: 0, font: 'inherit', textAlign: 'left', background: 'transparent' }}
                  onClick={() => setAnnotatePhoto(p)}>
                  {isRuntime && p.dataUrl && <img src={p.dataUrl} alt={p.pose} className="photo-card-img"/>}
                  {isRuntime && !p.dataUrl && <div className="photo-card-fallback ph ph-portrait"><span>Camera unavailable</span></div>}
                  {isRuntime && p.pins?.length > 0 && (
                    <div className="photo-pin-count">{p.pins.length} pin{p.pins.length === 1 ? '' : 's'}</div>
                  )}
                  <div className="label"><span>{label}</span><span className="when">{when}</span></div>
                </button>
              );
            })}
          </div>
          {photos.length === 0 && <div className="empty"><Icon name="photo" size={28}/><div>No photos on file.</div></div>}
        </>
      )}

      {tab === 'appointments' && (
        <div className="panel" style={{ padding: 16 }}>
          <div className="tbl-scroll">
            <table className="tbl">
              <thead><tr><th>Date</th><th>Time</th><th>Treatment</th><th>Specialist</th><th>Status</th></tr></thead>
              <tbody>
                {apps.map(a => {
                  const t = treatmentById(a.treatmentId);
                  const cl = clinicianById(a.clinicianId);
                  return (
                    <tr key={a.id}>
                      <td>{fmtDate(a.date)}</td>
                      <td>{a.time}</td>
                      <td>{t.name}</td>
                      <td>{cl.name}</td>
                      <td><span className={'tag ' + (a.status === 'pending' ? 'warn' : 'ok')}>{a.status}</span></td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        </div>
      )}

      {tab === 'messages' && (
        <CommHistory customerId={c.id} onCompose={(channel) => setCompose({ channel })}/>
      )}

      {tab === 'consent' && (
        <>
        <div style={{ fontSize: 12, color: 'var(--fg-3)', marginBottom: 10 }}>
          Signed on screen, stored on the record, chased automatically when missing.
          <DemoNote title="Forms, your way" text="Medical history, consents and per-treatment questionnaires are all digital and fully customisable — your questions, your wording. Clients sign before they arrive; anything unsigned is chased without anyone having to remember."/>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 12 }}>
          {[
            { id: 'micro',     name: 'Microneedling consent', when: '2025-04-18', state: 'ok',   clickable: false },
            { id: 'photo',     name: 'Photography & data',    when: '2025-04-18', state: 'ok',   clickable: false },
            { id: 'tret',      name: 'Tretinoin home use',    when: 'Pending signature', state: 'warn', clickable: false },
            { id: 'medical',   name: 'Medical history',       when: '2025-04-18', state: 'ok',   clickable: true },
          ].map(d => (
            <div key={d.name} className="panel" style={{ padding: 20, cursor: d.clickable ? 'pointer' : 'default' }}
              onClick={() => d.clickable && setShowForm(true)}>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 }}>
                <div>
                  <div style={{ fontSize: 15, fontWeight: 500, color: 'var(--fg-1)' }}>{d.name}</div>
                  <div style={{ fontSize: 12, color: 'var(--fg-3)', marginTop: 4 }}>{d.when}</div>
                  {d.clickable && (
                    <div style={{ fontSize: 11, color: 'var(--blush-deep)', marginTop: 8, letterSpacing: '0.04em', textTransform: 'uppercase', display: 'flex', gap: 4, alignItems: 'center' }}>
                      Open form <Icon name="arrow" size={11}/>
                    </div>
                  )}
                </div>
                <span className={'tag ' + (d.state === 'ok' ? 'ok' : 'warn')}>{d.state === 'ok' ? 'signed' : 'awaiting'}</span>
              </div>
            </div>
          ))}
        </div>
        </>
      )}

      {bulkUpload && <BulkUploadModal customerId={c.id} onClose={() => setBulkUpload(false)}/>}
      {showForm && <MedicalFormModal customerId={c.id} onClose={() => setShowForm(false)}/>}
      {compose && <SendMessageModal
        customerId={c.id}
        defaultChannel={compose.channel}
        defaultTemplate={compose.defaultTemplate}
        formsToAttach={compose.formsToAttach || []}
        onClose={() => setCompose(null)}/>}
      {newNote && <NewNoteModal customerId={c.id} setRuntimeNotes={setRuntimeNotes} onClose={() => setNewNote(false)}/>}
      {editingNote && <EditNoteModal note={editingNote} setRuntimeNotes={setRuntimeNotes} onClose={() => setEditingNote(null)}/>}
      {showCompare && <ComparePhotosModal customerId={c.id} photos={photos} onClose={() => setShowCompare(false)}/>}
      {showPhone && <PhoneHandoffModal customerId={c.id} onClose={() => setShowPhone(false)}/>}
      {annotatePhoto && <PhotoAnnotateModal
        photo={annotatePhoto}
        onSave={(pins) => {
          setRuntimePhotos(prev => {
            const list = (prev[c.id] || []).map(rp => rp.id === annotatePhoto.id ? { ...rp, pins } : rp);
            return { ...prev, [c.id]: list };
          });
        }}
        onClose={() => setAnnotatePhoto(null)}/>}
    </>
  );
}

// ── New note modal ──────────────────────────────────────────
// Emma can either type the note, or hit Dictate to record her voice.
// For the prototype: real device microphone capture (MediaRecorder API,
// works in iOS Safari 14.3+). Transcription + AI structuring is mocked
// because there's no backend yet — when the backend ships, we swap the
// mock for a real Whisper + LLM call. The recording, the permission
// flow, and the integration with the 24h edit window are all real.
function NewNoteModal({ customerId, setRuntimeNotes, onClose }) {
  const c = customerById(customerId);
  const [mode, setMode] = React.useState('type'); // 'type' | 'dictate'
  const [kind, setKind] = React.useState('Treatment');
  const [title, setTitle] = React.useState('');
  const [body, setBody] = React.useState('');
  const [saving, setSaving] = React.useState(false);
  const [saved, setSaved] = React.useState(false);

  // Recording state
  const [recState, setRecState] = React.useState('idle'); // 'idle' | 'recording' | 'processing' | 'error'
  const [recError, setRecError] = React.useState(null);
  const [recSeconds, setRecSeconds] = React.useState(0);
  const recorderRef = React.useRef(null);
  const streamRef = React.useRef(null);
  const tickRef = React.useRef(null);

  // Cleanup on unmount: stop any live mic stream and any timers.
  React.useEffect(() => () => {
    if (tickRef.current) clearInterval(tickRef.current);
    if (recorderRef.current && recorderRef.current.state !== 'inactive') {
      try { recorderRef.current.stop(); } catch (e) { /* ignore */ }
    }
    if (streamRef.current) streamRef.current.getTracks().forEach(t => t.stop());
  }, []);

  const startRecording = async () => {
    setRecError(null);
    if (!navigator.mediaDevices?.getUserMedia || typeof MediaRecorder === 'undefined') {
      setRecError('NotSupported');
      setRecState('error');
      return;
    }
    try {
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      streamRef.current = stream;
      const recorder = new MediaRecorder(stream);
      recorderRef.current = recorder;
      recorder.start();
      setRecState('recording');
      setRecSeconds(0);
      tickRef.current = setInterval(() => setRecSeconds(s => s + 1), 1000);
    } catch (err) {
      setRecError(err.name || 'MicError');
      setRecState('error');
    }
  };

  const stopRecording = () => {
    if (tickRef.current) { clearInterval(tickRef.current); tickRef.current = null; }
    if (recorderRef.current && recorderRef.current.state !== 'inactive') {
      try { recorderRef.current.stop(); } catch (e) { /* ignore */ }
    }
    if (streamRef.current) {
      streamRef.current.getTracks().forEach(t => t.stop());
      streamRef.current = null;
    }
    setRecState('processing');
    // Mock the transcription + AI summarisation pipeline. In production this
    // would POST the recorded audio Blob to /api/transcribe → Whisper, then
    // /api/structure → LLM to produce {kind, title, body}.
    setTimeout(() => {
      const mock = MOCK_DICTATIONS[Math.floor(Math.random() * MOCK_DICTATIONS.length)];
      setKind(mock.kind);
      setTitle(mock.title);
      setBody(mock.body);
      setRecState('idle');
      setMode('type'); // drop into review mode so Emma can edit before saving
    }, 1400);
  };

  const cancelRecording = () => {
    if (tickRef.current) { clearInterval(tickRef.current); tickRef.current = null; }
    if (recorderRef.current && recorderRef.current.state !== 'inactive') {
      try { recorderRef.current.stop(); } catch (e) { /* ignore */ }
    }
    if (streamRef.current) {
      streamRef.current.getTracks().forEach(t => t.stop());
      streamRef.current = null;
    }
    setRecState('idle');
    setRecSeconds(0);
  };

  const fmtTimer = (s) => {
    const m = Math.floor(s / 60);
    const sec = s % 60;
    return m + ':' + (sec < 10 ? '0' + sec : sec);
  };

  const save = () => {
    setSaving(true);
    setTimeout(() => {
      const now = Date.now();
      const entry = {
        id: 'rt-note-' + now,
        kind,
        title: title || '(no title)',
        body,
        author: 'Grace Hollis',
        when: new Date(now).toLocaleString('en-GB', { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' }),
        createdAt: now,
        editHistory: [],
      };
      setRuntimeNotes && setRuntimeNotes(prev => {
        const list = prev[customerId] || [];
        return { ...prev, [customerId]: [entry, ...list] };
      });
      setSaving(false); setSaved(true);
    }, 500);
  };

  const errorMessage = (code) => {
    if (code === 'NotAllowedError') return 'Microphone permission denied. Allow it in your browser settings and try again.';
    if (code === 'NotFoundError')   return 'No microphone found on this device.';
    if (code === 'NotSupported')    return 'Voice recording isn\'t supported in this browser.';
    return 'Microphone unavailable. Type your note instead.';
  };

  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal" style={{ width: 'min(560px, 94vw)' }} onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div><span className="eyebrow">New note · {c.name}</span><h2>{saved ? 'Note saved' : 'Add to record'}</h2></div>
          <button className="app-btn ghost icon-only" onClick={onClose}><Icon name="close" size={16}/></button>
        </div>
        {saved ? (
          <>
            <div className="confirm-mark"><Icon name="check" size={32}/></div>
            <p style={{ textAlign: 'center', fontSize: 13, color: 'var(--fg-3)', margin: '0 0 20px' }}>{kind} note added to {c.name}'s record.</p>
            <div style={{ display: 'flex', justifyContent: 'center' }}><button className="app-btn primary" onClick={onClose}>Done</button></div>
          </>
        ) : (
          <>
            <div className="dictate-mode-pill">
              <button className={mode === 'type' ? 'active' : ''} onClick={() => { if (recState !== 'recording') setMode('type'); }}>
                <Icon name="edit" size={12}/>Type
              </button>
              <button className={mode === 'dictate' ? 'active' : ''} onClick={() => setMode('dictate')}>
                <Icon name="phone" size={12}/>Dictate
              </button>
            </div>

            {mode === 'dictate' && (
              <div className="dictate-stage">
                {recState === 'idle' && (
                  <>
                    <button className="dictate-orb" onClick={startRecording} aria-label="Start recording">
                      <span className="dictate-orb-glyph">●</span>
                    </button>
                    <div className="dictate-hint">Tap to start · talk through the consultation, treatment plan, or follow-up</div>
                    <div className="dictate-tip">Mention the client's name, treatment, what you observed, and what's next. The system will structure it into a note for you to review.</div>
                  </>
                )}
                {recState === 'recording' && (
                  <>
                    <button className="dictate-orb recording" onClick={stopRecording} aria-label="Stop recording">
                      <span className="dictate-orb-glyph">■</span>
                    </button>
                    <div className="dictate-timer">{fmtTimer(recSeconds)}</div>
                    <div className="dictate-bars">
                      {[0,1,2,3,4,5,6,7,8].map(i => <span key={i} className="dictate-bar" style={{ animationDelay: (i * 0.08) + 's' }}/>)}
                    </div>
                    <div className="dictate-hint">Recording · tap the square to stop</div>
                    <button className="app-btn ghost small" onClick={cancelRecording}>Cancel</button>
                  </>
                )}
                {recState === 'processing' && (
                  <>
                    <div className="dictate-orb processing">
                      <span className="dictate-orb-glyph">…</span>
                    </div>
                    <div className="dictate-hint">Transcribing and structuring your note…</div>
                    <div className="dictate-tip">Listening to the recording, finding the key points, and drafting a treatment-style note.</div>
                  </>
                )}
                {recState === 'error' && (
                  <>
                    <div className="dictate-orb error">
                      <Icon name="info" size={28}/>
                    </div>
                    <div className="dictate-hint">{errorMessage(recError)}</div>
                    <button className="app-btn" onClick={() => { setRecState('idle'); setRecError(null); }}>Try again</button>
                  </>
                )}
                <div className="dictate-foot">
                  <Icon name="info" size={11}/>
                  <span>Demo mode · real Whisper transcription will run server-side once the backend is live. Audio captured here doesn't leave the prototype.</span>
                </div>
              </div>
            )}

            {mode === 'type' && (
              <>
                <div className="field">
                  <label>Type</label>
                  <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
                    {['Treatment', 'Consultation', 'Plan', 'Follow-up', 'Aftercare'].map(k => (
                      <button key={k} className={'pose-chip' + (kind === k ? ' captured' : '')} onClick={() => setKind(k)}>{k}</button>
                    ))}
                  </div>
                </div>
                <div className="field"><label>Title</label><input className="stripe-input" autoFocus placeholder="e.g. HydraFacial · session 6" value={title} onChange={e => setTitle(e.target.value)}/></div>
                <div className="field">
                  <label>Note</label>
                  <textarea className="stripe-input" rows="6" placeholder="Settings, observations, plan, aftercare advised…" value={body} onChange={e => setBody(e.target.value)} style={{ fontFamily: 'inherit', resize: 'vertical' }}/>
                </div>
                <div style={{ padding: 12, background: 'var(--cream)', borderRadius: 4, fontSize: 12, color: 'var(--fg-3)', display: 'flex', gap: 10, alignItems: 'center' }}>
                  <Icon name="info" size={12}/>
                  <span>Saved with timestamp + your name · editable for 24 hours, then locks with full edit history.</span>
                </div>
                <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 }}>
                  <button className="app-btn ghost" onClick={onClose}>Cancel</button>
                  <button className="app-btn primary" disabled={!title || !body || saving} onClick={save}>{saving ? 'Saving…' : 'Save note'}<Icon name="check" size={14}/></button>
                </div>
              </>
            )}
          </>
        )}
      </div>
    </div>
  );
}

// Mock dictation results — what the AI pipeline would produce in production.
// Picked at random so each demo recording feels different.
const MOCK_DICTATIONS = [
  {
    kind: 'Consultation',
    title: 'HIFU consultation · suitability assessment',
    body: 'Reviewed jaw and lower-face concerns. Skin laxity mild–moderate, good muscle tone. HIFU is suitable — recommended single session focusing on jawline and submental area. Discussed sensation, downtime, expected timeline (visible from 6 weeks, peak 12).\n\nAdvised:\n• Stop retinoids 5 days before\n• Hydration in week before\n• Realistic expectations — supportive treatment, not surgical replacement\n\nClient happy to proceed. Quoted £495 with 50% deposit. Booked for 4-week slot with the doctor.',
  },
  {
    kind: 'Treatment',
    title: 'Microneedling · session 2 of 3',
    body: 'Second session in the 3-treatment course. Skin tolerance excellent, mild transient erythema settled within 6 minutes. Targeted forehead and cheeks, depth 1.0mm escalating to 1.5mm on cheekbones. Britenol booster applied for residual pigmentation.\n\nNo bruising, no broken skin. Aftercare given verbally and emailed:\n• SPF50 daily, no exceptions for 7 days\n• No exfoliants 5 days\n• Gentle cleanser only 48h\n• Avoid heat / sauna 48h\n\nNext session in 4 weeks. Considering adding a HydraFacial 2 weeks post-final session for hydration support.',
  },
  {
    kind: 'Follow-up',
    title: 'CO2 day-7 check-in',
    body: 'Week-one follow-up post CO2 resurfacing. Healing as expected — peeling complete by day 5, mild residual erythema fading well. No infection signs, no hyperpigmentation concerns. Client compliant with aftercare and SPF.\n\nAdvised:\n• Continue gentle cleansing only for another week\n• SPF50 minimum daily for 12 weeks\n• Resume retinoid at week 4 if comfortable\n• Book 6-week review for results photography\n\nVery happy with early results — visible improvement in fine lines around eyes and lip border.',
  },
  {
    kind: 'Plan',
    title: 'Treatment plan · pigmentation course',
    body: 'Following assessment, recommended 12-week pigmentation programme:\n\n1. Two HydraFacial sessions (2 weeks apart) for prep and surface clearance\n2. Three microneedling with Britenol booster sessions, 4 weeks apart\n3. Daily home routine: vitamin C AM, retinoid PM (graduated), SPF50 always\n4. 12-week review with comparison photography\n\nTotal investment: £1,180 across the course. Recommended monthly payment plan to spread cost. Course discount applied — 10% off versus session-by-session.\n\nClient to confirm in next 48 hours.',
  },
];

// ── Edit note modal — only available within the 24h window (Q14) ───
// Captures a `before` snapshot, applies the change, appends to editHistory.
function EditNoteModal({ note, setRuntimeNotes, onClose }) {
  const [title, setTitle] = React.useState(note.title || '');
  const [body, setBody] = React.useState(note.body || '');
  const [saving, setSaving] = React.useState(false);
  const [saved, setSaved] = React.useState(false);

  const save = () => {
    setSaving(true);
    setTimeout(() => {
      const before = { title: note.title, body: note.body };
      const changes = [];
      if (before.title !== title) changes.push('title');
      if (before.body !== body) changes.push('body');
      const editEntry = {
        when: Date.now(),
        editor: 'Grace Hollis',
        changes: changes.length ? 'edited ' + changes.join(' + ') : 'no change',
      };
      // The runtimeNotes state lives at App level keyed by customerId.
      // We don't get the customerId directly — derive it from the note id
      // pattern, or have the caller include it. Easier: scan the map and
      // patch wherever this note id is found.
      setRuntimeNotes && setRuntimeNotes(prev => {
        const next = { ...prev };
        for (const cid of Object.keys(next)) {
          const list = next[cid];
          if (list.some(n => n.id === note.id)) {
            next[cid] = list.map(n => n.id === note.id
              ? { ...n, title, body, editHistory: [...(n.editHistory || []), editEntry] }
              : n);
            break;
          }
        }
        return next;
      });
      setSaving(false); setSaved(true);
    }, 500);
  };

  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal" style={{ width: 'min(560px, 94vw)' }} onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div><span className="eyebrow">Edit note · {note.kind}</span><h2>{saved ? 'Note updated' : 'Edit note'}</h2></div>
          <button className="app-btn ghost icon-only" onClick={onClose}><Icon name="close" size={16}/></button>
        </div>
        {saved ? (
          <>
            <div className="confirm-mark"><Icon name="check" size={32}/></div>
            <p style={{ textAlign: 'center', fontSize: 13, color: 'var(--fg-3)', margin: '0 0 8px' }}>Note saved · edit logged in history.</p>
            <div style={{ display: 'flex', justifyContent: 'center' }}><button className="app-btn primary" onClick={onClose}>Done</button></div>
          </>
        ) : (
          <>
            <div style={{ padding: 12, background: 'var(--cream)', borderRadius: 4, fontSize: 12, color: 'var(--fg-2)', display: 'flex', gap: 10, alignItems: 'center', marginBottom: 16 }}>
              <Icon name="info" size={12}/>
              <span>This note is editable until 24h after creation. After that it locks. Every edit is logged with who, when and what changed.</span>
            </div>
            <div className="field"><label>Title</label><input className="stripe-input" value={title} onChange={e => setTitle(e.target.value)}/></div>
            <div className="field">
              <label>Note</label>
              <textarea className="stripe-input" rows="6" value={body} onChange={e => setBody(e.target.value)}
                style={{ fontFamily: 'inherit', resize: 'vertical' }}/>
            </div>
            <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 }}>
              <button className="app-btn ghost" onClick={onClose}>Cancel</button>
              <button className="app-btn primary"
                disabled={saving || (title === note.title && body === note.body)}
                onClick={save}>{saving ? 'Saving…' : 'Save changes'}<Icon name="check" size={14}/></button>
            </div>
          </>
        )}
      </div>
    </div>
  );
}

// ── Capture screen ───────────────────────────────────────────
function ClCapture({ customerId, setRoute, runtimePhotos, setRuntimePhotos }) {
  const c = customerById(customerId);

  // Task 6: Face / Body mode pill
  // Pose lists per Emma's clinic standards (Q44).
  // Face: Front, sides, ¾ angles, plus Back (used for body treatments / lipo).
  // Body: anatomical anterior/posterior + lateral, plus close-up/detail.
  const POSE_SETS = {
    face: ['Front', 'Left side', 'Left ¾', 'Right ¾', 'Right side', 'Back'],
    body: ['Anterior', 'Posterior', 'Left lateral', 'Right lateral', 'Close-up', 'Detail'],
  };
  const [mode, setMode] = React.useState('face');
  const poses = POSE_SETS[mode];

  const [activePose, setActivePose] = React.useState(0);
  const [captured, setCaptured] = React.useState(() => new Array(6).fill(false));

  // Task 6: Reset capture state when mode changes
  React.useEffect(() => {
    setActivePose(0);
    setCaptured(new Array(POSE_SETS[mode].length).fill(false));
  }, [mode]);

  // Task 3: Camera state + refs
  const videoRef = React.useRef(null);
  const canvasRef = React.useRef(null);
  const [camReady, setCamReady] = React.useState(false);
  const [camError, setCamError] = React.useState(null);
  // Default to back camera for clinical demo (point at customer, not selfie).
  // 'environment' = back, 'user' = front. Falls back to whatever camera is
  // available if the device lacks the requested orientation.
  const [facing, setFacing] = React.useState('environment');

  // Task 3: Acquire camera stream — re-runs when `facing` toggles
  React.useEffect(() => {
    let mounted = true;
    let stream;
    if (!navigator.mediaDevices?.getUserMedia) {
      setCamError('NoMediaDevices');
      return;
    }
    setCamReady(false);
    setCamError(null);
    navigator.mediaDevices.getUserMedia({ video: { facingMode: facing }, audio: false })
      .then(s => {
        stream = s;
        if (!mounted) { s.getTracks().forEach(t => t.stop()); return; }
        if (videoRef.current) videoRef.current.srcObject = s;
        setCamReady(true);
      })
      .catch(err => { if (mounted) setCamError(err.name || 'CamError'); });
    return () => { mounted = false; if (stream) stream.getTracks().forEach(t => t.stop()); };
  }, [facing]);

  // Task 7: Alignment overlay toggle
  const [overlayOn, setOverlayOn] = React.useState(false);

  // (Cosmetic exposure indicator removed per Emma — Q44. It was misleading
   // since real exposure detection isn't implemented.)

  // Task 4: Snap to runtimePhotos
  const snap = () => {
    let dataUrl = null;
    const video = videoRef.current;
    const canvas = canvasRef.current;
    if (camReady && video && canvas && video.videoWidth) {
      canvas.width = video.videoWidth;
      canvas.height = video.videoHeight;
      const ctx = canvas.getContext('2d');
      ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
      try { dataUrl = canvas.toDataURL('image/jpeg', 0.85); }
      catch (e) { dataUrl = null; }
    }
    const entry = {
      id: 'rt-' + Date.now() + '-' + activePose,
      customerId: c.id,
      dataUrl,
      pose: poses[activePose],
      ts: Date.now(),
      pins: [],
    };
    setRuntimePhotos(prev => {
      const list = prev[c.id] || [];
      const next = [entry, ...list].slice(0, 12);
      return { ...prev, [c.id]: next };
    });
    setCaptured(prev => { const n = [...prev]; n[activePose] = true; return n; });
    if (activePose < poses.length - 1) setTimeout(() => setActivePose(activePose + 1), 500);
  };

  return (
    <>
      <button className="app-btn ghost small" style={{ marginBottom: 12 }} onClick={() => setRoute({ screen: 'record', customerId: c.id })}>
        <Icon name="back" size={14}/>Back to record
      </button>
      <div className="cl-head">
        <div>
          <span className="eyebrow">Capture · {c.name}</span>
          <h1>{poses[activePose]}</h1>
          <div className="capture-context">
            <Icon name="calendar" size={11}/>
            <span>Linked · HydraFacial · session 3 of 6 · today 14:30</span>
          </div>
        </div>
        <div className="actions">
          <span className="tag">{captured.filter(Boolean).length} / {poses.length} captured</span>
          <button className="app-btn"
            onClick={() => setRoute({ screen: 'record', customerId: c.id })}
            disabled={captured.filter(Boolean).length === 0}>
            Finish session
          </button>
        </div>
      </div>

      <div className="cl-2col">
        <div>
          <div className="capture-row-controls">
            <label className="capture-overlay-toggle">
              <input type="checkbox" checked={overlayOn} onChange={e => setOverlayOn(e.target.checked)}/>
              <span>Show last-session ghost</span>
            </label>
            <button
              className="app-btn ghost small"
              onClick={() => setFacing(f => f === 'user' ? 'environment' : 'user')}
              title="Flip camera">
              <Icon name="camera" size={12}/>
              {facing === 'user' ? 'Front · flip' : 'Back · flip'}
            </button>
          </div>
          <div className="capture-stage">
            {camError ? (
              <div className="feed cam-fallback">
                <Icon name="camera" size={28}/>
                <div className="cam-fallback-title">
                  {camError === 'NotAllowedError' ? 'Camera permission denied' :
                   camError === 'NotFoundError'   ? 'No camera on this device' :
                   camError === 'NoMediaDevices'  ? 'Camera not supported in this browser' :
                   'Camera unavailable'}
                </div>
                <div className="cam-fallback-sub">Capture will save placeholder photos instead.</div>
              </div>
            ) : (
              <video ref={videoRef} className={'feed feed-video' + (facing === 'user' ? ' mirrored' : '')} autoPlay playsInline muted/>
            )}
            <canvas ref={canvasRef} style={{ display: 'none' }}/>
            {overlayOn && <div className="capture-ghost ph ph-portrait"/>}
            <div className="frame-overlay"/>
            <div className="pose-guide"/>
            <div className="badge"><span className="live"/>Live</div>
          </div>
          <div className="capture-controls">
            <button className="app-btn" onClick={() => setActivePose(Math.max(0, activePose - 1))}><Icon name="chevL" size={14}/>Prev</button>
            <button className="shutter" aria-label="Capture" onClick={snap}/>
            <button className="app-btn" onClick={() => setActivePose(Math.min(poses.length - 1, activePose + 1))}>Next<Icon name="chevR" size={14}/></button>
          </div>
        </div>

        <div>
          <div className="panel" style={{ marginBottom: 16, padding: 14 }}>
            <div className="mode-pill">
              <button className={'mode-opt' + (mode === 'face' ? ' active' : '')} onClick={() => setMode('face')}>Face</button>
              <button className={'mode-opt' + (mode === 'body' ? ' active' : '')} onClick={() => setMode('body')}>Body</button>
            </div>
          </div>
          <div className="panel" style={{ marginBottom: 16 }}>
            <div className="panel-head"><h3>Pose checklist</h3><span className="meta">Standardised</span></div>
            <div className="pose-ribbon" style={{ flexDirection: 'column', alignItems: 'stretch' }}>
              {poses.map((p, i) => (
                <button key={p} className={'pose-chip' + (captured[i] ? ' captured' : '') + (i === activePose ? ' active' : '')}
                  style={{ justifyContent: 'flex-start', width: '100%' }}
                  onClick={() => setActivePose(i)}>
                  <span className="step-num">{captured[i] ? <Icon name="check" size={10}/> : i + 1}</span>
                  {p}
                </button>
              ))}
            </div>
          </div>

          <div className="panel">
            <div className="panel-head"><h3>Reference</h3></div>
            <div style={{ fontSize: 12, color: 'var(--fg-3)', marginBottom: 12 }}>Last session · {(PHOTOS[c.id] || [])[0]?.when || '—'}</div>
            <div className={'ph ph-portrait'} style={{ aspectRatio: mode === 'face' ? '4/3' : '3/4', borderRadius: 4 }}/>
            <div style={{ fontSize: 11, color: 'var(--fg-3)', marginTop: 8, textTransform: 'uppercase', letterSpacing: '0.08em' }}>
              Match lighting · same background · neutral expression
            </div>
          </div>
        </div>
      </div>
    </>
  );
}

// ── Inbox ────────────────────────────────────────────────────
function ClInbox() {
  const items = [
    { from: 'Sophie Reynolds', subj: 'Question about home routine', preview: 'Hi Grace, my skin\'s a bit flaky after the retinoid…', when: '08:14', unread: true },
    { from: 'Hannah Whitlock', subj: 'CO2 aftercare — day 4', preview: 'Just checking in, the redness is settling but I\'m a bit…', when: 'Yesterday', unread: true },
    { from: 'Mark Daley', subj: 'Re: 12-week review', preview: 'Sounds good, the 14th works for me. Thanks!', when: 'Yesterday', unread: false },
    { from: 'Lucy Bennett', subj: 'Consent form', preview: 'Have signed and uploaded, see you Thursday.', when: '2 days', unread: false },
  ];
  return (
    <>
      <div className="cl-head"><div><span className="eyebrow">Messages</span><h1>Inbox<DemoNote title="A proper two-way inbox" text="Replies land here AND on the client’s record, so the whole team sees the full conversation — nothing lives in one person’s inbox."/></h1></div></div>
      <div className="panel" style={{ padding: 0 }}>
        {items.map((m, i) => (
          <div key={i} style={{
            padding: '18px 24px', borderBottom: i === items.length - 1 ? 0 : '1px solid var(--line)',
            display: 'grid', gridTemplateColumns: '40px minmax(0, 1fr) auto', gap: 16, alignItems: 'center', cursor: 'pointer',
          }}>
            <span className="avatar sm" style={{ background: m.unread ? 'var(--blush)' : 'var(--cream)' }}>{m.from.split(' ').map(s => s[0]).join('')}</span>
            <div style={{ minWidth: 0 }}>
              <div style={{ display: 'flex', gap: 8, alignItems: 'baseline' }}>
                <b style={{ fontSize: 14, color: 'var(--fg-1)', fontWeight: m.unread ? 600 : 500 }}>{m.from}</b>
                {m.unread && <span style={{ width: 6, height: 6, borderRadius: '50%', background: 'var(--blush-deep)' }}/>}
              </div>
              <div style={{ fontSize: 13, color: 'var(--fg-1)', fontWeight: m.unread ? 500 : 400, marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{m.subj}</div>
              <div style={{ fontSize: 12, color: 'var(--fg-3)', marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{m.preview}</div>
            </div>
            <span style={{ fontSize: 11, color: 'var(--fg-3)', flexShrink: 0 }}>{m.when}</span>
          </div>
        ))}
      </div>
    </>
  );
}

// ── Walk-in modal ────────────────────────────────────────────
// `mode` = 'walkin' (today, slots available now) | 'future' (any date, future slots)
function WalkInModal({ onClose, prefillCustomer = null, mode = 'walkin' }) {
  const [step, setStep] = React.useState(0);
  const [pickedTreat, setPickedTreat] = React.useState(null);
  const [pickedTime, setPickedTime] = React.useState(null);
  const [pickedDate, setPickedDate] = React.useState(mode === 'future' ? offsetDay(7) : todayISO);
  const [bookingNote, setBookingNote] = React.useState('');                       // §5.1 — note at booking
  const [pickedRoom, setPickedRoom] = React.useState(null);                       // Q5 — room ↔ treatment routing
  const [pickedCust, setPickedCust] = React.useState(prefillCustomer || null);
  const [custQuery, setCustQuery] = React.useState(prefillCustomer ? customerById(prefillCustomer)?.name : '');
  const [custFocused, setCustFocused] = React.useState(false);

  // Q5 — routing: which rooms can host the picked treatment.
  // The ROOMS data carries allowedTreatments; if the picked treatment matches
  // none of them, we still let the user pick a room but flag it as a clash.
  const allowedRooms = pickedTreat
    ? ROOMS.filter(r => r.allowedTreatments && r.allowedTreatments.includes(pickedTreat))
    : ROOMS;
  // Auto-select the first allowed room when the treatment changes
  React.useEffect(() => {
    if (pickedTreat && (!pickedRoom || !allowedRooms.find(r => r.id === pickedRoom))) {
      setPickedRoom(allowedRooms[0]?.id || null);
    }
  }, [pickedTreat]);

  // Match by name / phone digits / email
  const qLower = custQuery.trim().toLowerCase();
  const qDigits = qLower.replace(/\D/g, '');
  const matches = !qLower ? CUSTOMERS : CUSTOMERS.filter(c =>
    c.name.toLowerCase().includes(qLower) ||
    c.email.toLowerCase().includes(qLower) ||
    (qDigits && c.phone.replace(/\D/g, '').includes(qDigits))
  );
  const exactCust = pickedCust ? customerById(pickedCust) : null;

  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div>
            <span className="eyebrow">{mode === 'future' ? 'New booking' : 'Walk-in'}</span>
            <h2>{mode === 'future' ? 'Schedule a booking' : 'Book on the spot'}</h2>
          </div>
          <button className="app-btn ghost icon-only" onClick={onClose}><Icon name="close" size={16}/></button>
        </div>

        <div className="step-bar">
          <div className={'step ' + (step >= 0 ? 'active' : '')}/>
          <div className={'step ' + (step >= 1 ? 'active' : '')}/>
          <div className={'step ' + (step >= 2 ? 'active' : '')}/>
          <div className={'step ' + (step >= 3 ? 'active' : '')}/>
        </div>

        {step === 0 && (
          <>
            <div className="field" style={{ position: 'relative' }}>
              <label>Customer</label>
              {exactCust ? (
                <div style={{
                  display: 'flex', alignItems: 'center', gap: 10,
                  padding: '10px 12px', border: '1px solid var(--ink)', borderRadius: 4,
                  background: 'var(--bg-card)',
                }}>
                  <span className="avatar sm" style={{ background: 'var(--blush-soft)' }}>{exactCust.initials}</span>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 14, color: 'var(--fg-1)', fontWeight: 500 }}>{exactCust.name}</div>
                    <div style={{ fontSize: 11, color: 'var(--fg-3)' }}>{exactCust.phone} · {exactCust.email}</div>
                  </div>
                  <button className="app-btn ghost icon-only" onClick={() => { setPickedCust(null); setCustQuery(''); }}><Icon name="close" size={14}/></button>
                </div>
              ) : (
                <>
                  <input
                    className="stripe-input"
                    placeholder="Search by name, phone or email…"
                    value={custQuery}
                    onChange={e => setCustQuery(e.target.value)}
                    onFocus={() => setCustFocused(true)}
                    onBlur={() => setTimeout(() => setCustFocused(false), 150)}
                    autoFocus
                  />
                  {custFocused && (
                    <div className="cust-typeahead">
                      {matches.length === 0 ? (
                        <div className="cust-ta-empty">
                          <span>No match for "{custQuery}"</span>
                          <button className="app-btn small primary" onClick={onClose}><Icon name="plus" size={12}/>Add new customer</button>
                        </div>
                      ) : matches.slice(0, 6).map(c => (
                        <button key={c.id} className="cust-ta-row" onMouseDown={(e) => { e.preventDefault(); setPickedCust(c.id); setCustQuery(c.name); setCustFocused(false); }}>
                          <span className="avatar sm" style={{ background: 'var(--blush-soft)' }}>{c.initials}</span>
                          <div className="cust-ta-body">
                            <b>{c.name}</b>
                            <span>{c.phone} · {c.email}</span>
                          </div>
                          {flagInfo(c.flag) && <span className={'tag ' + flagInfo(c.flag).tone}>{flagInfo(c.flag).label}</span>}
                        </button>
                      ))}
                    </div>
                  )}
                </>
              )}
            </div>
            <div style={{ fontSize: 11, color: 'var(--fg-3)', marginBottom: 16, letterSpacing: '0.06em', textTransform: 'uppercase' }}>Treatment</div>
            {TREATMENTS.slice(0, 4).map(t => (
              <div key={t.id} className={'option-card' + (pickedTreat === t.id ? ' selected' : '')} onClick={() => setPickedTreat(t.id)}>
                <span className="leading">{t.cat[0]}</span>
                <div className="body"><h4>{t.name}</h4><p>{t.mins} min · {t.cat}</p></div>
                <span className="trail">{fmtMoney(t.price)}</span>
              </div>
            ))}
            {/* Patch-test sequencing — the finished system enforces this order */}
            {pickedTreat && pickedCust && PATCH_TEST_TREATMENTS.includes(pickedTreat) && !PATCH_TESTED_CUSTOMERS.includes(pickedCust) && (
              <div style={{ display: 'flex', gap: 10, alignItems: 'flex-start', padding: '12px 14px', marginTop: 10, borderRadius: 4, background: 'var(--blush-soft)', border: '1px solid var(--line)' }}>
                <Icon name="info" size={16} style={{ flexShrink: 0, marginTop: 2, color: 'var(--fg-1)' }}/>
                <div style={{ fontSize: 13, lineHeight: 1.5, color: 'var(--fg-2)' }}>
                  <b style={{ color: 'var(--fg-1)' }}>{treatmentById(pickedTreat).name} needs a patch test first</b> — and {customerById(pickedCust).name.split(' ')[0]} doesn't have one on record.
                  The system books the patch-test consultation first — the treatment stays held until the clinic marks the patch test clear.
                </div>
              </div>
            )}
            {pickedTreat && pickedCust && PATCH_TEST_TREATMENTS.includes(pickedTreat) && PATCH_TESTED_CUSTOMERS.includes(pickedCust) && (
              <div style={{ fontSize: 12, color: 'var(--success)', marginTop: 10 }}>
                <Icon name="check" size={12}/> Patch test on record for {customerById(pickedCust).name.split(' ')[0]} — clear to book.
              </div>
            )}
            <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 }}>
              <button className="app-btn ghost" onClick={onClose}>Cancel</button>
              <button className="app-btn primary" disabled={!pickedTreat || !pickedCust} onClick={() => setStep(1)}>Continue<Icon name="arrow" size={14}/></button>
            </div>
          </>
        )}

        {step === 1 && (
          <>
            {mode === 'future' && (
              <div className="field">
                <label>Date</label>
                <input className="stripe-input" type="date" value={pickedDate} min={todayISO} onChange={e => setPickedDate(e.target.value)}/>
              </div>
            )}
            <div style={{ fontSize: 11, color: 'var(--fg-3)', marginBottom: 8, letterSpacing: '0.06em', textTransform: 'uppercase' }}>
              {mode === 'future' ? 'Available on ' + fmtDate(pickedDate) : 'Available now'}
            </div>
            <div className="slot-grid" style={{ marginBottom: 24 }}>
              {(mode === 'future' ? ['09:30','10:30','11:45','13:00','14:30','16:00'] : ['11:00', '11:30', '13:30', '15:30', '16:00', '17:00']).map(t => (
                <button key={t} className={'slot' + (pickedTime === t ? ' selected' : '')} onClick={() => setPickedTime(t)}>{t}</button>
              ))}
            </div>
            <div className="field">
              <label>Specialist</label>
              <select className="stripe-input">
                {CLINICIANS.map(c => <option key={c.id}>{c.name} · {c.role}</option>)}
              </select>
            </div>
            {/* Q5 — Room picker. Treatments are constrained to specific rooms
                (e.g. CO2 → Room 1, LHR → Room 2, HydraFacial → Upstairs).
                Disallowed rooms render greyed-out with a tooltip. */}
            <div className="field">
              <label>Room</label>
              <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
                {ROOMS.map(r => {
                  const allowed = !pickedTreat || (r.allowedTreatments && r.allowedTreatments.includes(pickedTreat));
                  return (
                    <button key={r.id}
                      className={'pose-chip' + (pickedRoom === r.id ? ' captured' : '')}
                      disabled={!allowed}
                      title={allowed ? '' : 'This treatment is not allowed in ' + r.name}
                      onClick={() => allowed && setPickedRoom(r.id)}
                      style={!allowed ? { opacity: 0.4, cursor: 'not-allowed' } : null}>
                      {r.name}{r.alias ? ' · ' + r.alias : ''}
                    </button>
                  );
                })}
              </div>
              {pickedTreat && allowedRooms.length === 0 && (
                <div style={{ fontSize: 11, color: 'var(--alert)', marginTop: 6 }}>
                  <Icon name="info" size={11}/> No room is set up for this treatment yet. Update room ↔ treatment mapping in Settings.
                </div>
              )}
              {pickedTreat && allowedRooms.length > 0 && (
                <div style={{ fontSize: 11, color: 'var(--fg-3)', marginTop: 6 }}>
                  {allowedRooms.length === 1
                    ? treatmentById(pickedTreat).name + ' is only set up in ' + allowedRooms[0].name + '.'
                    : treatmentById(pickedTreat).name + ' can run in ' + allowedRooms.map(r => r.name).join(' or ') + '.'}
                </div>
              )}
            </div>
            {/* §5.1 — booking note saved to record alongside the appointment */}
            <div className="field">
              <label>Booking note (saved to record)</label>
              <textarea className="stripe-input" rows="2" placeholder="e.g. First time client, mentioned sensitivity. Course of 3 booked."
                value={bookingNote} onChange={e => setBookingNote(e.target.value)}
                style={{ fontFamily: 'inherit', resize: 'vertical' }}/>
            </div>
            <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, marginTop: 16 }}>
              <button className="app-btn ghost" onClick={() => setStep(0)}><Icon name="back" size={14}/>Back</button>
              <button className="app-btn primary" disabled={!pickedTime || !pickedRoom} onClick={() => setStep(2)}>Take deposit<Icon name="arrow" size={14}/></button>
            </div>
          </>
        )}

        {step === 2 && (() => {
          // 50% deposit policy (Q23) — half the treatment price, rounded to whole £
          const treatPrice = treatmentById(pickedTreat)?.price || 0;
          const depositAmount = Math.round(treatPrice * 0.5);
          return (
          <>
            <div style={{ fontSize: 11, color: 'var(--fg-3)', marginBottom: 8, letterSpacing: '0.06em', textTransform: 'uppercase' }}>Take 50% deposit · {fmtMoney(depositAmount)}</div>
            <div className="stripe-summary" style={{ marginBottom: 12 }}>
              <div className="line"><span style={{ color: 'var(--fg-3)' }}>{treatmentById(pickedTreat)?.name}</span><span>Today · {pickedTime}</span></div>
              <div className="line"><span style={{ color: 'var(--fg-3)' }}>Treatment cost</span><span>{fmtMoney(treatPrice)}</span></div>
              <div className="line tot"><span>Deposit due (50%)</span><span>{fmtMoney(depositAmount)}</span></div>
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8, marginBottom: 12 }}>
              {[
                { id: 'card', label: 'Card machine', sub: 'Record the payment', icon: 'pound' },
                { id: 'link', label: 'Stripe link', sub: 'Email to client', icon: 'mail' },
                { id: 'cash', label: 'Cash', sub: 'Mark as paid', icon: 'check' },
              ].map(m => (
                <button key={m.id} className="app-btn" style={{ flexDirection: 'column', height: 'auto', padding: '14px 8px', gap: 6, textAlign: 'center' }}>
                  <Icon name={m.icon} size={18}/>
                  <span style={{ fontSize: 12, fontWeight: 600 }}>{m.label}</span>
                  <span style={{ fontSize: 10, color: 'var(--fg-3)', fontWeight: 400 }}>{m.sub}</span>
                </button>
              ))}
            </div>
            <div style={{ padding: 14, border: '1px dashed var(--line)', borderRadius: 'var(--radius-md)', display: 'flex', alignItems: 'center', gap: 12, background: 'var(--cream)' }}>
              <div style={{ width: 36, height: 36, borderRadius: 6, background: '#635BFF', color: '#fff', display: 'grid', placeItems: 'center', fontSize: 11, fontWeight: 700, letterSpacing: '0.5px' }}>S</div>
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 13, color: 'var(--fg-1)', fontWeight: 500 }}>Online deposits by Stripe</div>
                <div style={{ fontSize: 11, color: 'var(--fg-3)' }}>In the clinic, take payment on your own card machine and record it here</div>
              </div>
            </div>
            <div style={{ fontSize: 12, color: 'var(--fg-3)', marginTop: 12, lineHeight: 1.5 }}>
              Cancellation inside 72 hours retains the deposit — applied automatically and recorded on the client's file.
              <DemoNote title="Policies, enforced" text="Deposits and cancellation rules are set per treatment. The system applies them at booking, at cancellation and at checkout — a written policy your software actually enforces, instead of one reception polices by phone."/>
            </div>
            <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, marginTop: 16 }}>
              <button className="app-btn ghost" onClick={() => setStep(1)}><Icon name="back" size={14}/>Back</button>
              <button className="app-btn primary" onClick={() => setStep(3)}>Charge {fmtMoney(depositAmount)}<Icon name="arrow" size={14}/></button>
            </div>
          </>
          );
        })()}

        {step === 3 && (() => {
          const treatPrice = treatmentById(pickedTreat)?.price || 0;
          const depositAmount = Math.round(treatPrice * 0.5);
          return (
          <>
            <div className="confirm-mark"><Icon name="check" size={36}/></div>
            <h3 style={{ textAlign: 'center', margin: '0 0 8px', fontFamily: 'var(--font-display)', fontWeight: 400, fontSize: 24 }}>Booked & paid</h3>
            <p style={{ textAlign: 'center', color: 'var(--fg-3)', fontSize: 13, marginBottom: 8 }}>
              {exactCust?.name || 'Customer'} · {treatmentById(pickedTreat)?.name} · {mode === 'future' ? fmtDate(pickedDate) : 'today'} at {pickedTime}{pickedRoom ? ' · ' + (ROOMS.find(r => r.id === pickedRoom)?.name || '') : ''}.
            </p>
            <p style={{ textAlign: 'center', color: 'var(--fg-3)', fontSize: 12, marginBottom: 20 }}>
              {fmtMoney(depositAmount)} deposit recorded · confirmation email sent.
            </p>
            <div style={{ display: 'flex', justifyContent: 'center' }}>
              <button className="app-btn primary" onClick={onClose}>Done</button>
            </div>
          </>
          );
        })()}
      </div>
    </div>
  );
}

// ── New customer modal ──────────────────────────────────────
function NewCustomerModal({ onClose }) {
  const [step, setStep] = React.useState(0);
  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div>
            <span className="eyebrow">Records</span>
            <h2>New customer</h2>
          </div>
          <button className="app-btn ghost icon-only" onClick={onClose}><Icon name="close" size={16}/></button>
        </div>

        <div className="step-bar">
          <div className={'step ' + (step >= 0 ? 'active' : '')}/>
          <div className={'step ' + (step >= 1 ? 'active' : '')}/>
          <div className={'step ' + (step >= 2 ? 'active' : '')}/>
        </div>

        {step === 0 && (
          <>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
              <div className="field"><label>First name</label><input className="stripe-input" placeholder="Eleanor"/></div>
              <div className="field"><label>Last name</label><input className="stripe-input" placeholder="Hartwell"/></div>
            </div>
            <div className="field"><label>Email</label><input className="stripe-input" placeholder="eleanor@example.com" type="email"/></div>
            <div className="field"><label>Mobile</label><input className="stripe-input" placeholder="07xxx xxx xxx"/></div>
            <div className="field"><label>Date of birth</label><input className="stripe-input" placeholder="DD / MM / YYYY"/></div>
            <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 8 }}>
              <button className="app-btn ghost" onClick={onClose}>Cancel</button>
              <button className="app-btn primary" onClick={() => setStep(1)}>Continue<Icon name="arrow" size={14}/></button>
            </div>
          </>
        )}

        {step === 1 && (
          <>
            <div className="field"><label>Skin type (Fitzpatrick)</label>
              <select className="stripe-input">
                <option>I — Very pale, always burns</option>
                <option>II — Fair, usually burns</option>
                <option selected>III — Medium, sometimes burns</option>
                <option>IV — Olive, rarely burns</option>
                <option>V — Brown</option>
                <option>VI — Deeply pigmented</option>
              </select>
            </div>
            <div className="field"><label>Allergies</label><input className="stripe-input" placeholder="None known"/></div>
            <div className="field"><label>Current medications</label><input className="stripe-input" placeholder="None"/></div>
            <div className="field"><label>Concerns / goals</label>
              <textarea className="stripe-input" rows="3" style={{ resize: 'vertical', fontFamily: 'inherit' }} placeholder="Pigmentation, fine lines…"/>
            </div>
            <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, marginTop: 8 }}>
              <button className="app-btn ghost" onClick={() => setStep(0)}><Icon name="back" size={14}/>Back</button>
              <button className="app-btn primary" onClick={() => setStep(2)}>Continue<Icon name="arrow" size={14}/></button>
            </div>
          </>
        )}

        {step === 2 && (
          <>
            <div style={{ fontSize: 11, letterSpacing: '0.18em', textTransform: 'uppercase', color: 'var(--fg-3)', margin: '8px 0 12px' }}>Send to customer</div>
            {[
              { n: 'Medical history form', sub: 'Standard intake — required before first treatment' },
              { n: 'Photography & data consent', sub: 'GDPR-compliant — required to take images' },
              { n: 'Welcome email', sub: 'Includes booking link & directions' },
            ].map((d, i) => (
              <label key={i} style={{ display: 'flex', gap: 12, padding: '14px 16px', border: '1px solid var(--line)', borderRadius: 'var(--radius-md)', background: 'var(--bg-card)', marginBottom: 8, cursor: 'pointer', alignItems: 'flex-start' }}>
                <input type="checkbox" defaultChecked style={{ marginTop: 3 }}/>
                <div style={{ flex: 1 }}>
                  <div style={{ fontSize: 14, color: 'var(--fg-1)', fontWeight: 500 }}>{d.n}</div>
                  <div style={{ fontSize: 12, color: 'var(--fg-3)', marginTop: 2 }}>{d.sub}</div>
                </div>
              </label>
            ))}
            <div style={{ fontSize: 12, color: 'var(--fg-3)', lineHeight: 1.5, marginTop: 12 }}>
              An invitation will be sent and the customer record will appear under <b>active</b> once their forms are signed.
            </div>
            <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, marginTop: 16 }}>
              <button className="app-btn ghost" onClick={() => setStep(1)}><Icon name="back" size={14}/>Back</button>
              <button className="app-btn primary" onClick={onClose}>Create customer</button>
            </div>
          </>
        )}
      </div>
    </div>
  );
}

Object.assign(window, { ClinicianShell, ClRecord, ClCapture, ClCustomerList, ClInbox, ClToday, ClTimeOff, WalkInModal, NewCustomerModal, EditNoteModal });
