/* ============================================================
   MUIY 2.0 — Today screen
   ============================================================ */

function useToday(store) {
  const active = store.donors.filter((d) => !d.archived);
  const withS = active.map((d) => ({ d, v: statusVisual(d) }));
  const eligible = withS.filter((x) => x.v.s.status === 'ELIGIBLE').sort((a, b) => (b.v.s.daysRested || 0) - (a.v.s.daysRested || 0));
  const soon = withS.filter((x) => x.v.s.status === 'SOON');
  const waiting = withS.filter((x) => x.v.s.status === 'WAITING');
  const deferred = withS.filter((x) => x.v.s.status === 'DEFERRED');

  const trans = [...(store.data.transfusions || [])].sort((a, b) => TC.parse(b.date) - TC.parse(a.date));
  const avgDays = TC.avgTransfusionDays(store.data.transfusions);
  const cycleDays = avgDays ? Math.round(avgDays) : (store.patient.transfusionFrequencyWeeks || 3) * 7;
  const lastT = trans[0];
  const nextDays = lastT ? Math.max(0, cycleDays - TC.relDays(lastT.date)) : cycleDays;
  const nextWeeks = Math.max(1, Math.round(nextDays / 7));

  const hb = TC.hbSeries(store.data);            // unified hbLog — headline metric + trend
  const hbPre = TC.preTransfusionHb(store.data); // source:'transfusion' troughs — pre-transfusion copy
  const fer = TC.ferritinSeries(store.data);
  const avgHb = hb.length ? hb.reduce((s, d) => s + d.value, 0) / hb.length : null;

  return { active, eligible, soon, waiting, deferred, nextDays, nextWeeks, cycleDays, hasTrans: !!lastT, hb, hbPre, fer, avgHb };
}

/* ---- one health-snapshot row card on Home ---- */
function SnapCard({ icon, color, wash, label, value, unit, sub, chip, delay, onClick }) {
  return (
    <button className="card snap-card" onClick={onClick}>
      <div className="leaf snap-leaf" style={{ background: wash, color }}>
        <Icon name={icon} size={21} color={color} fill={icon === 'drop' || icon === 'sparkle' ? color : 'none'} />
      </div>
      <div className="snap-mid">
        <div className="snap-k">{label}</div>
        <div className="snap-v num">{value}{unit && <span className="snap-u">{unit}</span>}</div>
        {(chip || sub) && (
          <div className="snap-meta">
            {chip && (
              <span className="chip-status snap-chip" style={{ background: chip.wash, color: chip.color }}>
                <span className="snap-dot" style={{ background: chip.color }} />{chip.label}
              </span>
            )}
            {sub && <span className="snap-sub num">{sub}</span>}
          </div>
        )}
      </div>
      <Icon name="chevR" size={16} color="var(--ink-3)" style={{ flex: 'none', alignSelf: 'center' }} />
    </button>
  );
}

/* The Today briefing is fully DETERMINISTIC — generated by pickFocus() /
   CompanionInsight below from the live snapshot. No AI/LLM is used on this
   screen (the old tcAI briefing was removed). */

function CallRow({ x, store }) {
  const { d } = x;
  return (
    <div className="drow">
      <Avatar name={d.name} size={46} count={TC.donationCount(d)} photoUrl={d.photoUrl} />
      <button onClick={() => store.openModal('donor', { donor: d })} style={{ flex: 1, minWidth: 0, textAlign: 'left', background: 'none', border: 'none', padding: 0, cursor: 'pointer' }}>
        <div className="row" style={{ gap: 7 }}>
          <span className="dname">{d.name}</span>
          <BloodBadge group={d.bloodGroup} size={10} />
        </div>
        <div className="dmeta" style={{ color: 'var(--green)' }}>Rested {x.v.s.daysRested} days · {TC.donationCount(d)}×</div>
      </button>
      <button className="icon-btn call" style={{ width: 40, height: 40 }} onClick={() => { window.location.href = `tel:${d.mobile}`; }} aria-label="Call"><Icon name="phone" size={18} /></button>
      <button className="icon-btn msg" style={{ width: 40, height: 40 }} onClick={() => store.openModal('contact', { donor: d })} aria-label="Message"><Icon name="chat" size={18} /></button>
    </div>
  );
}

/* ============================================================
   DAILY TIPS — rotate by day-of-year (stable per day).
   ~100 will live here eventually; these are seed samples in the
   app's gentle voice. Drop the full list into DAILY_TIPS.
   ============================================================ */
// The full library (93 BTM tips) is loaded from src/daily-tips.js as
// window.TC_DAILY_TIPS. The inline list below is a minimal fallback so the
// card still renders if that file ever fails to load.
const DAILY_TIPS = (typeof window !== 'undefined' && Array.isArray(window.TC_DAILY_TIPS) && window.TC_DAILY_TIPS.length)
  ? window.TC_DAILY_TIPS
  : [
    { cat: 'Tip', icon: 'sparkle', text: "Take your chelator at the same time each day — pairing it with an everyday habit, like brushing your teeth, makes it easy to remember." },
    { cat: 'Good to know', icon: 'heart', text: "Tea or coffee with meals can lower how much iron you absorb from food — sometimes a quiet helping hand for your iron load." },
  ];
const TIP_CAT_ICON = { Tip: 'sparkle', 'Did you know': 'info', Important: 'warn', 'Good to know': 'heart' };
function tcDayOfYear(d) { const s = new Date(d.getFullYear(), 0, 0); return Math.floor((d - s) / 86400000); }

/* warm tones for the companion insight */
const COMP_TONE = {
  good:    { color: 'var(--success)', wash: 'var(--green-wash)', bar: '#65B017', sprite: '#65B017' },
  warn:    { color: 'var(--alert)', wash: 'var(--orange-wash)', bar: 'var(--orange)', sprite: 'var(--orange)' },
  soft:    { color: 'var(--warning)', wash: 'var(--warning-wash)', bar: '#E8A33D', sprite: 'var(--warning)' },
  calm:    { color: 'var(--sky-deep)', wash: 'var(--sky-wash)', bar: '#7FB0C4', sprite: '#3E9AA0' },
};

/* the warm companion check-in card */
function CompanionInsight({ msg }) {
  const t = COMP_TONE[msg.tone] || COMP_TONE.calm;
  const tap = !!msg.action;
  return (
    <div className={'comp-card stagger' + (tap ? ' tap' : '')} style={{ background: t.wash, animationDelay: '120ms' }}
      onClick={tap ? msg.action.onClick : undefined} role={tap ? 'button' : undefined}>
      <span className="comp-bar" style={{ background: t.bar }} />
      <div className="comp-head">
        <span style={{ width: 42, height: 42, flex: 'none', borderRadius: '50%', background: 'var(--surface)', display: 'grid', placeItems: 'center', boxShadow: '0 2px 8px rgba(20,30,25,.06)' }}><Icon name="heart" size={20} color={t.sprite} fill={t.sprite} /></span>
        <span className="comp-lead" style={{ color: t.color }}>{msg.lead}</span>
      </div>
      <p className="comp-line">{msg.line}{msg.emoji ? ' ' + msg.emoji : ''}</p>
      {tap && (
        <span className="comp-cta" style={{ color: t.color }}>{msg.action.label}<Icon name="chevR" size={14} color={t.color} sw={2.6} /></span>
      )}
    </div>
  );
}

/* calm, secondary "Your numbers" grid */
function NumbersGrid({ metrics, spark }) {
  const trendTint = (c) => c === 'var(--success)' ? 'var(--green-wash)' : c === 'var(--ink-3)' ? 'var(--surface-2)' : 'var(--orange-wash)';
  return (
    <div className="numgrid">
      {metrics.map((m) => {
        const drug = m.drugs ? m.drugs.reduce((a, b) => (b.severity > a.severity ? b : a), m.drugs[0]) : null;
        return (
          <button key={m.id} className="numtile" onClick={m.onClick}>
            <span className="numtile-k"><span className="numtile-dot" style={{ background: m.dot }} />{m.k}</span>
            <div className="numtile-v" style={m.namesText && m.namesText.length > 12 ? { fontSize: 14.5 } : null}>{m.drugs ? (m.namesText || drug.name) : <>{m.v}{m.u && <small> {m.u}</small>}</>}</div>
            {m.brandText && <span className="numtile-note" style={{ color: 'var(--ink-3)', marginTop: 3 }}>{m.brandText}</span>}
            {spark && m.spark && m.spark.length > 1 && (
              <div className="spark-row"><Sparkline values={m.spark} color={m.sparkColor || m.dot} height={22} /></div>
            )}
            {m.drugs ? (
              drug.status && <span className="numtile-pill" style={{ color: drug.status.color, background: drug.status.wash, display: 'inline-block', marginTop: 6 }}>{drug.status.label}</span>
            ) : m.trend ? (
              <span className="numtile-note" style={{ color: m.trend.color }}>
                <Icon name={m.trend.icon} size={12} color={m.trend.color} sw={2.6} />{m.trend.label}
              </span>
            ) : m.sub ? (
              <span className="numtile-pill" style={{ color: m.subColor || 'var(--ink-3)', background: m.subWash || 'var(--surface-2)', display: 'inline-flex', alignItems: 'center', gap: 4, marginTop: 6 }}>
                {m.subIcon && <Icon name={m.subIcon} size={11} color={m.subColor || 'var(--ink-3)'} sw={2.4} />}{m.sub}
              </span>
            ) : null}
          </button>
        );
      })}
    </div>
  );
}

/* ============================================================
   FIRST-WEEK ONBOARDING — guided setup for a brand-new profile
   ============================================================ */
function firstWeekTasks(store) {
  const d = store.data;
  const meds = ((store.patient && store.patient.medications) || []).length;
  const trans = (d.transfusions || []).length;
  const iron = (d.ferritinLog || []).length;
  const donors = (d.donors || []).filter((x) => !x.archived).length;
  return [
    { key: 'med', done: meds > 0, icon: 'pill', wash: 'var(--orange-wash)', color: 'var(--orange)', label: 'Add your chelation medication', desc: 'Powers your dose targets & daily reminders', go: () => store.goHealth('chelation') },
    { key: 'trans', done: trans > 0, icon: 'drop', wash: 'var(--orange-wash)', color: 'var(--alert)', label: 'Log your latest transfusion', desc: 'Starts your next-transfusion forecast', go: () => store.openModal('logTransfusion', {}) },
    { key: 'iron', done: iron > 0, icon: 'chart', wash: 'var(--green-wash)', color: 'var(--success)', label: 'Add a recent ferritin (iron) result', desc: 'Begins your iron-trend tracking', go: () => store.openModal('logFerritin', {}) },
    { key: 'donor', done: donors > 0, icon: 'users', wash: 'var(--sky-wash)', color: 'var(--sky-deep)', label: 'Add a blood donor', desc: 'Build your ready-to-call pool', go: () => store.openModal('editDonor', {}) },
  ];
}

function GettingStarted({ store, tasks }) {
  const done = tasks.filter((t) => t.done).length;
  const pct = Math.round((done / tasks.length) * 100);
  const first = store.patient.preferredName || (store.patient.name || 'there').split(' ')[0];
  return (
    <section className="card gs-card stagger" aria-label="Getting started" style={{ padding: '17px 17px 14px', marginBottom: 16 }}>
      <div className="row" style={{ gap: 12, alignItems: 'center', marginBottom: 4 }}>
        <span style={{ width: 40, height: 40, flex: 'none', borderRadius: 13, background: 'linear-gradient(135deg,#FF8A2B,#FC6B05)', display: 'grid', placeItems: 'center', boxShadow: '0 8px 18px -10px rgba(252,107,5,.7)' }}><Icon name="sparkle" size={20} color="#fff" /></span>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontWeight: 800, fontSize: 16.5, letterSpacing: '-.01em', color: 'var(--ink)' }}>{done === 0 ? `Welcome, ${first}` : 'A few more to go'}</div>
          <div className="body" style={{ fontSize: 12.5, marginTop: 1 }}>{done === 0 ? 'Add your first details to bring MUIY to life.' : `${done} of ${tasks.length} done — your insights grow as you add more.`}</div>
        </div>
      </div>
      <div className="gs-bar" role="progressbar" aria-valuenow={pct} aria-valuemin={0} aria-valuemax={100} aria-label="Setup progress" style={{ height: 6, borderRadius: 999, background: 'var(--surface-2)', overflow: 'hidden', margin: '10px 0 14px' }}>
        <div style={{ height: '100%', width: pct + '%', borderRadius: 999, background: 'linear-gradient(90deg,var(--orange),var(--orange-2))', transition: 'width .5s cubic-bezier(.22,1,.36,1)' }} />
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
        {tasks.map((tk) => (
          <button key={tk.key} className="gs-row" onClick={tk.done ? undefined : tk.go} aria-label={tk.label + (tk.done ? ' (done)' : '')}
            style={{ display: 'flex', gap: 12, alignItems: 'center', width: '100%', textAlign: 'left', padding: '10px 11px', borderRadius: 14, border: 'none', background: tk.done ? 'transparent' : 'var(--surface-2)', cursor: tk.done ? 'default' : 'pointer', opacity: tk.done ? 0.62 : 1 }}>
            <span style={{ width: 34, height: 34, flex: 'none', borderRadius: 10, background: tk.done ? 'var(--eligible-wash)' : tk.wash, color: tk.done ? 'var(--eligible)' : tk.color, display: 'grid', placeItems: 'center' }}>
              <Icon name={tk.done ? 'check' : tk.icon} size={17} color={tk.done ? 'var(--eligible)' : tk.color} sw={tk.done ? 3 : 2} />
            </span>
            <span style={{ flex: 1, minWidth: 0 }}>
              <span style={{ display: 'block', fontWeight: 700, fontSize: 13.5, color: 'var(--ink)', textDecoration: tk.done ? 'line-through' : 'none' }}>{tk.label}</span>
              {!tk.done && <span className="body" style={{ display: 'block', fontSize: 11.5, marginTop: 1 }}>{tk.desc}</span>}
            </span>
            {!tk.done && <Icon name="chevR" size={16} color="var(--ink-3)" style={{ flex: 'none' }} />}
          </button>
        ))}
      </div>
    </section>
  );
}

/* Live clock hour for the greeting + Living-sky phase. A bare TC.now() read
   only refreshes when something else re-renders, so an app left open (or a
   PWA resumed from the background) showed a stale sky/greeting. Ticks every
   minute and on resume/focus; setState with the same hour is a no-op. */
function useClockHour() {
  const [hour, setHour] = React.useState(() => TC.now().getHours());
  React.useEffect(() => {
    const tick = () => setHour(TC.now().getHours());
    const id = setInterval(tick, 60 * 1000);
    document.addEventListener('visibilitychange', tick);
    window.addEventListener('focus', tick);
    return () => { clearInterval(id); document.removeEventListener('visibilitychange', tick); window.removeEventListener('focus', tick); };
  }, []);
  return hour;
}

function TodayScreen({ store }) {
  const t = useToday(store);
  const { patient } = store;

  // ---- blood-group-aware donor readiness: SAME-group donors (the patient's exact group) ----
  // We log donors of other groups too (for friends), but the patient's own readiness is
  // measured against donors who share their exact blood group.
  const groupLabel = patient.bloodGroup || null;
  const isSame = (x) => !!groupLabel && x.d.bloodGroup === patient.bloodGroup;
  const compatEligible = t.eligible.filter(isSame);
  const compatSoon = t.soon.filter(isSame);
  const compatDeferred = t.deferred.filter(isSame);
  const sameGroupTotal = t.active.filter((d) => d.bloodGroup === patient.bloodGroup).length;

  const stats = [
    { k: 'eligible', n: t.eligible.length, c: compatEligible.length, l: 'Eligible', color: 'var(--green)', blob: '#65B017', icon: 'checkCircle' },
    { k: 'soon', n: t.soon.length, c: compatSoon.length, l: 'Due soon', color: 'var(--orange)', blob: '#FFB62B', icon: 'clock' },
    { k: 'deferred', n: t.deferred.length, c: compatDeferred.length, l: 'Deferred', color: 'var(--slate)', blob: '#9BB7BB', icon: 'shield' },
  ];

  // ---- health snapshot derivations ----
  const targets = TC.hbTargets(patient);
  const hbLatest = t.hb[0] || null;
  const hbSt = hbLatest ? TC.hbStatus(hbLatest.value, targets) : null;
  const hbChipLabel = hbSt ? (hbSt.key === 'on' ? 'Within target' : hbSt.label) : '';
  // pre-transfusion (trough) view — used for clinical “pre-transfusion Hb” messaging
  const hbPreLatest = t.hbPre[0] || null;
  const hbPreSt = hbPreLatest ? TC.hbStatus(hbPreLatest.value, targets) : null;
  const hbPreChipLabel = hbPreSt ? (hbPreSt.key === 'on' ? 'Within target' : hbPreSt.label) : '';

  const ferLatest = t.fer[0] || null;
  const ferSt = ferLatest ? TC.ferritinStatus(ferLatest.value) : null;

  const meds = (patient.medications || []).filter((m) => TC.findChelator(m.brand));
  const medGeneric = (m) => { const c = TC.findChelator(m.brand); return (c && c.chelator) || m.name || m.brand; };
  const medNames = meds.map(medGeneric);
  const humanJoin = (arr) => arr.length <= 1 ? (arr[0] || '') : arr.length === 2 ? `${arr[0]} and ${arr[1]}` : `${arr.slice(0, -1).join(', ')} and ${arr[arr.length - 1]}`;
  const medList = humanJoin(medNames);
  const chelChip = (m) => {
    const daily = TC.medDailyMg(m);
    const mkd = (daily && patient.weightKg) ? daily / patient.weightKg : null;
    if (mkd == null) return null;
    const dc = TC.doseCategory(mkd, m.brand);
    if (!dc) return null;
    const lbl = dc.key === 'standard' ? 'Optimal dose' : dc.key === 'severe' ? 'High dose' : dc.label;
    return { chip: { label: lbl, color: dc.color, wash: dc.wash }, daily, mkd };
  };

  const nextDate = TC.inDaysDate(t.nextDays);
  const nextUrgent = t.nextDays <= 3;

  // ---- predictive Hb forecast + routine-lab status (feed the smart focus) ----
  const forecast = TC.hbForecast(store.data, targets);
  // Routine blood-count status comes from the canonical monitoring registry
  // (window.MON), not a separate copy — one registry, one source of truth.
  const monProfile = MON.profileOf(store.patient);
  MON.setActiveProfile(monProfile);
  const cbcTest = MON.REGISTRY.find((z) => z.id === 'haemoglobin');
  const cbcSt = cbcTest ? MON.statusOf(cbcTest, monProfile, store.data.testLogs) : null;

  // ---- one-line insight sentences (generated from the live data) ----
  const fmt1 = (n) => (Math.round(n * 10) / 10).toString();
  // small up/down trend vs the previous reading (favourable = green, otherwise warm)
  const trendOf = (latest, prev, goodWhenUp, fmtDelta) => {
    if (latest == null || prev == null) return null;
    const delta = latest - prev;
    if (Math.abs(delta) < 1e-9) return { icon: 'trendFlat', color: 'var(--ink-3)', label: 'No change' };
    const up = delta > 0;
    const favorable = goodWhenUp ? up : !up;
    return {
      icon: up ? 'trendUp' : 'trendDown',
      color: favorable ? 'var(--success)' : 'var(--alert)',
      label: (up ? '+' : '−') + fmtDelta(Math.abs(delta)),
    };
  };
  const hbTrend = trendOf(hbLatest ? hbLatest.value : null, t.hb[1] ? t.hb[1].value : null, true, (n) => fmt1(n));
  const ferPrev = t.fer[1] ? t.fer[1].value : null;
  const ferTrend = trendOf(ferLatest ? ferLatest.value : null, ferPrev, false, (n) => Math.round(n).toLocaleString());

  const ferIns = TC.ferritinInsight(t.fer, 180);
  const DOSE_STATUS = {
    standard: { label: 'Optimal', color: 'var(--success)', wash: 'var(--green-wash)' },
    below:    { label: 'Low',     color: 'var(--warning)', wash: 'var(--waiting-wash)' },
    severe:   { label: 'High',    color: 'var(--orange)', wash: 'var(--orange-wash)' },
    above:    { label: 'High',    color: 'var(--danger)', wash: 'var(--danger-wash)' },
  };

  // ---- compact metric strip folded into the Focus card (Hb · Ferritin · Chelation · Next) ----
  const metrics = [];
  if (hbLatest) metrics.push({ id: 'hb', k: 'Hemoglobin', v: fmt1(hbLatest.value), u: 'g/dL', dot: hbSt ? hbSt.color : 'var(--sky-deep)', sparkColor: hbSt ? hbSt.color : 'var(--sky-deep)', spark: t.hb.slice(0, 8).map((d) => d.value).reverse(), trend: hbTrend, onClick: () => store.goHealth('records') });
  if (ferLatest) metrics.push({ id: 'fer', k: 'Ferritin', v: Math.round(ferLatest.value).toLocaleString(), u: 'ng/mL', dot: ferSt ? ferSt.color : 'var(--orange)', sparkColor: ferSt ? ferSt.color : 'var(--orange)', spark: t.fer.slice(0, 8).map((d) => d.value).reverse(), trend: ferTrend, onClick: () => store.goHealth('monitoring', 'ferritin') });
  if (meds.length) {
    const drugs = meds.map((m) => {
      const info = chelChip(m);
      const dc = (info && info.mkd != null) ? TC.doseCategory(info.mkd, m.brand) : null;
      return { id: m.id, name: m.brand || m.name, status: dc ? (DOSE_STATUS[dc.key] || null) : null, severity: dc ? ({ standard: 0, below: 1, severe: 2, above: 3 }[dc.key] || 0) : -1 };
    });
    const worst = drugs.reduce((a, b) => (b.severity > a.severity ? b : a), drugs[0]);
    const aggDot = worst.status ? worst.status.color : 'var(--orange)';
    metrics.push({ id: 'chel', k: meds.length > 1 ? `Chelation · ${meds.length}` : 'Chelation', namesText: meds.map((m) => m.brand || medGeneric(m)).join(' · '), brandText: null, drugs, dot: aggDot, onClick: () => store.goHealth('chelation') });
  }
  if (t.hasTrans) {
    const ready = compatEligible.length;
    const gl = groupLabel ? `${groupLabel} ` : '';
    metrics.push({
      id: 'next', k: 'Next transfusion',
      v: t.nextDays <= 0 ? 'Due now' : `${t.nextDays}`,
      u: t.nextDays <= 0 ? '' : (t.nextDays === 1 ? 'day' : 'days'),
      dot: nextUrgent ? 'var(--orange)' : 'var(--sky-deep)',
      sub: ready ? `${ready} ${gl}donor${ready === 1 ? '' : 's'} rested` : `No ${gl}donors rested yet`,
      subIcon: ready ? 'checkCircle' : 'user',
      subColor: ready ? 'var(--success)' : 'var(--ink-3)',
      subWash: ready ? 'var(--green-wash)' : 'var(--surface-2)',
      onClick: () => store.goHealth('records', 'forecast'),
    });
  }
  const trendTint = (c) => c === 'var(--success)' ? 'var(--green-wash)' : c === 'var(--ink-3)' ? 'transparent' : 'var(--orange-wash)';

  // ---- SMART FOCUS: one synthesised, prioritised insight that reasons across the data ----
  const TONE = {
    good:    { color: 'var(--success)', wash: 'var(--green-wash)', bar: '#65B017' },
    warn:    { color: 'var(--alert)', wash: 'var(--orange-wash)', bar: 'var(--orange)' },
    soft:    { color: 'var(--warning)', wash: 'var(--warning-wash)', bar: '#E8A33D' },
    neutral: { color: 'var(--sky-deep)', wash: 'var(--sky-wash)', bar: '#7FB0C4' },
  };
  const bandTxt = `${fmt1(targets.belowMax)}–${fmt1(targets.onMax)} g/dL`;
  const dName = (groupLabel ? groupLabel + ' ' : '') ;
  const donorWord = (n) => `${n} ${dName}donor${n === 1 ? '' : 's'}`;
  const pickFocus = () => {
    // 1 — elevated iron with a recognised clinical threshold
    if (ferLatest) {
      const risk = TC.ferritinRisk(ferLatest.value);
      if (risk && (risk.level === 'concern' || risk.level === 'severe')) {
        return { tone: 'warn', lead: 'Worth keeping an eye on',
          line: `Your iron is sitting a little high at ${Math.round(ferLatest.value).toLocaleString()} ng/mL. Staying steady with your chelation${meds.length ? ` — ${medList} — ` : ' '}is what helps most; it could be worth talking the trend through at your next visit.`,
          action: { label: 'See your iron trend', onClick: () => store.goHealth('monitoring') } };
      }
    }
    // 2 — transfusion is close; is the right blood on standby?
    if (t.hasTrans && t.nextDays <= 6) {
      const when = t.nextDays <= 0 ? 'is likely due around now' : `is about ${t.nextDays} day${t.nextDays === 1 ? '' : 's'} away`;
      if (compatEligible.length < 3) {
        return { tone: 'soft', lead: 'A gentle nudge',
          line: `Your next transfusion ${when}, and ${donorWord(compatEligible.length)} ${compatEligible.length === 1 ? 'is' : 'are'} rested${compatSoon.length ? `, with ${compatSoon.length} due soon` : ''}. When you have a quiet moment, lining one up early means one less thing to think about.`,
          action: { label: 'Open your donor pool', onClick: () => store.goPool('eligible') } };
      }
      return { tone: 'good', lead: "You're all set", emoji: '🤍',
        line: `Your next transfusion ${when}, and ${donorWord(compatEligible.length)} are rested and ready when you need them. Nothing to chase today.`,
        action: { label: "See who's ready", onClick: () => store.goPool('eligible') } };
    }
    // 3 — pre-transfusion Hb below target
    if (hbPreSt && hbPreSt.key === 'below' && hbPreLatest) {
      return { tone: 'soft', lead: 'Worth noticing',
        line: `Your pre-transfusion Hb is ${fmt1(hbPreLatest.value)} g/dL, a touch below your ${bandTxt} band.${t.hasTrans && t.nextDays > 0 ? ` With your next transfusion about ${t.nextDays} day${t.nextDays === 1 ? '' : 's'} away, it might be worth a word with your care team about timing.` : ' It might be worth a word with your care team about timing.'}`,
        action: { label: 'See your Hb trend', onClick: () => store.goHealth('records') } };
    }
    // 4 — iron jumped sharply since the last test
    if (ferIns && ferIns.kind === 'jump') {
      const pct = ferIns.pctPrev != null ? ` (up ${Math.round(ferIns.pctPrev)}%)` : '';
      return { tone: 'soft', lead: 'Worth a look',
        line: `Your iron ticked up to ${Math.round(ferIns.latest.value).toLocaleString()} ng/mL since last time${pct}.${meds.length ? ` Keeping ${medList} consistent is the gentlest way to steer it back down.` : ' Reviewing your chelation routine is worthwhile.'}`,
        action: { label: 'See your iron trend', onClick: () => store.goHealth('monitoring') } };
    }
    // 5 — iron is easing / new low (celebrate the work)
    if (ferIns && (ferIns.kind === 'lowest' || ferIns.kind === 'improving')) {
      return { tone: 'good', lead: 'Good progress', emoji: '🌱',
        line: ferIns.kind === 'lowest'
          ? `Your iron is the lowest it's been on record — down to ${Math.round(ferIns.latest.value).toLocaleString()} ng/mL. All that steady chelation is quietly paying off. Keep being kind to yourself.`
          : `Your iron load is easing — down to ${Math.round(ferIns.latest.value).toLocaleString()} ng/mL. Your routine is working; keep it gently going.`,
        action: { label: 'See your iron trend', onClick: () => store.goHealth('monitoring') } };
    }
    // 6 — a routine blood count is overdue
    if (cbcSt && cbcSt.status === 'OVERDUE' && !cbcSt.never) {
      return { tone: 'soft', lead: 'A small check-in',
        line: `It's been ${cbcSt.daysSince} days since your last blood count. A fresh one keeps everything tracking nicely — no rush, just whenever it suits.`,
        action: { label: 'View your checkups', onClick: () => store.goHealth('records') } };
    }
    // 7 — Hb comfortably on target (reassure)
    if (hbPreSt && (hbPreSt.key === 'on' || hbPreSt.key === 'above') && hbPreLatest) {
      return { tone: 'good', lead: 'Holding steady', emoji: '☁️',
        line: `Your Hb is holding at ${fmt1(hbPreLatest.value)} g/dL, right in your ${bandTxt} band${compatEligible.length ? `, with ${donorWord(compatEligible.length)} ready for next time` : ''}. Keep your gentle routine going.`,
        action: null };
    }
    // default — gentle steady-state
    return { tone: 'calm', lead: 'Holding steady',
      line: `Everything's holding steady today${compatEligible.length ? `, and ${donorWord(compatEligible.length)} ${compatEligible.length === 1 ? 'is' : 'are'} ready` : ''}. Nothing to chase — just keep your logs ticking over.`,
      action: null };
  };
  const focus = pickFocus();

  const h = useClockHour();
  const greet = h < 12 ? 'Good morning,' : h < 17 ? 'Good afternoon,' : 'Good evening,';
  const first = patient.preferredName || (patient.name || 'there').split(' ')[0];

  return (
    <div className="tc-scroll">
      {(() => {
        const sceneVariant = (store.settings || {}).sceneVariant || 'living';
        const isLiving = sceneVariant === 'living';
        const phase = isLiving ? skyPhase(h) : null;
        return (
          <div className={'home-hero scene scene--' + sceneVariant + (isLiving && phase === 'night' ? ' home-hero--night' : '')}>
            <div className="scene-fade" key={sceneVariant}>
              {isLiving ? <LivingSky phase={phase} /> : <SceneArt variant={sceneVariant} />}
            </div>
            <div className="hero-scrim" />
            <div className="home-top">
              <div className="home-greet">
                <div className="sm">{greet}</div>
                <div className="lg">{first}<BloodBadge group={patient.bloodGroup} size={12} /></div>
              </div>
            </div>
          </div>
        );
      })()}

      <div className="under-scene">
        <InstallAppCard store={store} />
        {(() => {
          const gsTasks = firstWeekTasks(store);
          const gsDone = gsTasks.filter((x) => x.done).length;
          if (gsDone < gsTasks.length) return <GettingStarted store={store} tasks={gsTasks} />;
          return null;
        })()}
        {/* quiet heads-up: the nearest upcoming appointment (hidden when none) */}
        <NextAppointmentCard store={store} />
        {/* chelation daily check-in — one-tap dose logging, sits under a Today header */}
        {meds.length > 0 && (
          <>
            <SectionHead title="Today" icon="checkCircle" iconColor="var(--orange)" spark={false} />
            <HomeCheckin store={store} variant={(store.settings || {}).checkinVariant || 'single'} onOpenSkip={(s) => store.openModal('skipDose', { slot: s })} />
          </>
        )}

        {/* pool overview — the stat blobs below act as the tap-through to the pool */}
        <SectionHead title="My donor pool" />
        {groupLabel && (
          <div className="pool-sub">
            <Icon name="drop" size={12} color="var(--ink-3)" fill="var(--ink-3)" />
            <span>Readiness for your <strong>{patient.bloodGroup}</strong> type · {`${sameGroupTotal} ${sameGroupTotal === 1 ? 'donor' : 'donors'}`}</span>
          </div>
        )}
        <div className="stat-grid">
          {stats.map((c, i) => (
            <button key={c.k} className="stat stagger" style={{ animationDelay: `${120 + i * 40}ms` }} onClick={() => store.goPool(c.k)}>
              <span className="blob" style={{ background: c.blob }} />
              <div className="n num" style={{ color: c.color }}>{groupLabel ? c.c : c.n}</div>
              <div className="l">{c.l}</div>
            </button>
          ))}
        </div>

        {/* insights — numbers kept calm + secondary */}
        {metrics.length > 0 && (
          <>
            <SectionHead title="Your numbers" icon="chart" iconColor="var(--sky-deep)" spark={false} />
            <NumbersGrid metrics={metrics} spark={store.sparklines} />
          </>
        )}

        <DailyTipCard store={store} />
        <MedicalDisclaimer style={{ marginTop: 14, padding: '0 2px' }} />
      </div>
    </div>
  );
}

/* Map a tip to a relevant place in the patient's own data — deterministic,
   keyword-driven (NO AI). Lets a general tip lead straight to the screen it
   relates to, so reading it can turn into doing something. */
function tipRelatedAction(tip, store) {
  const t = ((tip.text || '') + ' ' + (tip.cat || '')).toLowerCase();
  const has = (...ks) => ks.some((k) => t.includes(k));
  if (has('ferritin', 'iron overload', 'iron load', 'liver iron', 'lic', 'chelat', 'deferasirox', 'exjade', 'jadenu', 'asunra', 'desferal', 'deferoxamine', 'desferrioxamine', 'deferiprone', 'ferriprox', 'dose', 'reminder', 'adherence'))
    return { label: 'See your iron & chelation', icon: 'pill', go: () => store.goHealth('chelation') };
  if (has('mri', 't2*', 'cardiac', 'echocardiogram', 'thyroid', 'bone', 'dexa', 'osteoporosis', 'vitamin d', 'growth', 'puberty', 'endocr', 'eye', 'hearing', 'kidney', 'creatinine', 'diabetes', 'fbc', 'blood count', 'white cell'))
    return { label: 'Open your monitoring schedule', icon: 'chart', go: () => store.goHealth('monitoring') };
  if (has('transfusion', 'haemoglobin', 'hemoglobin', 'pre-transfusion', 'units of blood', 'red cell'))
    return { label: 'See your transfusion records', icon: 'drop', go: () => store.goHealth('records') };
  return null;
}

/* Static, deeper elaboration of a tip — shown inline in the detail sheet.
   Pulls authored copy from window.TC_TIP_DETAILS (parallel to DAILY_TIPS,
   matched by index). No network, no cost; renders nothing if absent. */
function TipDeeper({ tip }) {
  const idx = DAILY_TIPS.indexOf(tip);
  const detail = (window.TC_TIP_DETAILS || [])[idx];
  if (!detail) return null;
  const paras = String(detail).split(/\n+/).map((s) => s.trim()).filter(Boolean);
  return (
    <div className="tipd-deeper">
      <span className="tipd-deeper-eyebrow"><Icon name="sparkle" size={13} color="#B87A14" fill="#B87A14" />Going a little deeper</span>
      {paras.map((para, i) => <p key={i}>{para}</p>)}
      <div className="tipd-deeper-note">
        <Icon name="info" size={13} color="var(--ink-3)" style={{ flex: 'none', marginTop: 1 }} />
        <span>General guidance for thalassaemia — always confirm specifics with your care team.</span>
      </div>
    </div>
  );
}

/* Full-tip detail sheet — opened by tapping the Tip of the day card.
   Shows the tip in comfortable type, a link to the related part of the
   patient's own data, and a way to pull another tip from the library. */
function TipDetailSheet({ store, close, tip }) {
  const [cur, setCur] = React.useState(tip || DAILY_TIPS[0]);
  const icon = cur.icon || TIP_CAT_ICON[cur.cat] || 'sparkle';
  const filled = icon === 'drop' || icon === 'sparkle' || icon === 'heart';
  const act = tipRelatedAction(cur, store);
  const another = () => {
    if (DAILY_TIPS.length < 2) return;
    let i = Math.floor(Math.random() * DAILY_TIPS.length);
    if (DAILY_TIPS[i].text === cur.text) i = (i + 1) % DAILY_TIPS.length;
    setCur(DAILY_TIPS[i]);
  };
  return (
    <MSheet onClose={close}>
      <div className="row" style={{ gap: 12, alignItems: 'center', marginBottom: 14 }}>
        <div className="tip-leaf" style={{ width: 44, height: 44, borderRadius: 14 }}><Icon name={icon} size={22} color="var(--warning)" fill={filled ? 'var(--warning)' : 'none'} /></div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div className="tip-eyebrow">Tip of the day</div>
          <div style={{ fontSize: 16, fontWeight: 800, color: 'var(--ink)', letterSpacing: '-.01em', marginTop: 1 }}>{cur.cat}</div>
        </div>
      </div>
      <p className="tipd-body">{cur.text}</p>
      <TipDeeper tip={cur} />
      <div className="row" style={{ gap: 10, marginTop: 14 }}>
        <button className="btn btn-ghost" style={{ flex: 1 }} onClick={close}>Close</button>
      </div>
    </MSheet>
  );
}

/* daily tip card */
function DailyTipCard({ store }) {
  const [idx] = React.useState(() => Math.floor(Math.random() * DAILY_TIPS.length));
  const tip = DAILY_TIPS[idx];
  const icon = tip.icon || TIP_CAT_ICON[tip.cat] || 'sparkle';
  return (
    <button className="card tip-card stagger" style={{ animationDelay: '160ms', padding: '15px 16px' }} onClick={() => store.openModal('tipDetail', { tip })} aria-label={'Tip of the day: ' + tip.cat + '. Tap to read more.'}>
      <div className="tip-leaf"><Icon name={icon} size={18} color="var(--warning)" fill={icon === 'drop' || icon === 'sparkle' || icon === 'heart' ? 'var(--warning)' : 'none'} /></div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div className="row" style={{ gap: 7, marginBottom: 4 }}>
          <span className="tip-eyebrow">Tip of the day</span>
          <span className="tip-cat">{tip.cat}</span>
        </div>
        <p className="tip-text">{tip.text}</p>
        <span className="tip-more">Read more<Icon name="chevR" size={13} color="var(--orange)" sw={2.6} /></span>
      </div>
    </button>
  );
}

/* ---- quiet one-line heads-up: the nearest upcoming appointment (Today) ---- */
function NextAppointmentCard({ store }) {
  const A = TC.appts;
  const a = A && A.next(store.data);
  if (!a) return null;
  const m = A.typeMeta(a.type);
  const dU = A.daysUntil(a);
  const soon = dU <= 3;
  return (
    <button className="card stagger" onClick={() => store.openModal('appointment', { edit: a })}
      style={{ display: 'flex', alignItems: 'center', gap: 13, width: '100%', textAlign: 'left', cursor: 'pointer', border: 'none', fontFamily: 'inherit', padding: '13px 15px', marginBottom: 16, animationDelay: '90ms' }}>
      <span className="leaf" style={{ width: 42, height: 42, flex: 'none', background: m.wash, color: m.color, borderRadius: 13 }}>
        <Icon name={m.icon} size={20} color={m.color} fill={m.icon === 'sparkle' || m.icon === 'drop' || m.icon === 'heart' ? m.color : 'none'} />
      </span>
      <span style={{ flex: 1, minWidth: 0 }}>
        <span className="tip-eyebrow" style={{ color: soon ? 'var(--orange)' : 'var(--ink-3)' }}>Next appointment · {A.whenLabel(a)}</span>
        <span style={{ display: 'block', fontWeight: 800, fontSize: 14.5, color: 'var(--ink)', letterSpacing: '-.01em', marginTop: 2, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{a.title}</span>
        <span className="body" style={{ display: 'block', fontSize: 12, marginTop: 1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
          {A.fmtTime(a.time) || 'Time not set'}{a.location ? ` · ${a.location}` : ''}
        </span>
      </span>
      <Icon name="chevR" size={16} color="var(--ink-3)" style={{ flex: 'none' }} />
    </button>
  );
}

Object.assign(window, { TodayScreen, useToday, TipDetailSheet, DailyTipCard, NextAppointmentCard });
