// TemperatureScreen.jsx — iPad Temperature Log.
//
// Bottom-nav entry. Shows today's scheduled checkpoints as cards
// (configured in Admin Portal → Temperature Log → Schedule). Tapping a card
// opens the logging sheet: a list of every catalog item with four quick
// picks per item:
//   • <140°F · 140–165°F · >165°F   — colored band buttons
//   • "Type °F"                      — opens the same NumPad we use for PINs,
//                                       writes a precise value + derives the band
//
// Staff can finish any time. If they tap Finish with no readings, we warn
// them — there's an extremely rare case where nothing was available, in
// which case they can proceed anyway. PIN gate locks the final submit so
// the log is attributed to whoever was on duty.
//
// Data layout (v5.43 — see migration `temperature_log_v1`):
//   temperature_logs       — id, the_date, schedule_id, finalized_at, ...
//   temperature_readings   — id, log_id, item_id, band, value, created_at
//
// We write to temperature_logs lazily on the first reading (insert with
// finalized_at=NULL), then upsert temperature_readings as the crew picks.
// Tapping Finish flips finalized_at + finalized_by_staff_id on the log row.

const { useState: useTemp, useEffect: useEffectTemp, useMemo: useMemoTemp, useRef: useRefTemp } = React;

// ------------------------------------------------------------------
// Band metadata mirrors the portal so colors stay in lock-step.
// ------------------------------------------------------------------
// Color semantics chosen for food-safety legibility:
//   <140    → red   (out-of-spec hot-holding — flag it)
//   140-165 → gray  (the normal in-spec hot-hold range — nothing to flag)
//   >165    → amber (running hot — heads-up for quality / over-cook risk)
const TEMP_BANDS_IPAD = [
  { id: 'under_140',       label: '<140°F',    short: '<140',   color: '#B91C1C', bg: '#FEE2E2', border: '#FCA5A5' },
  { id: 'between_140_165', label: '140–165°F', short: '140-165', color: '#52525B', bg: '#F4F4F5', border: '#D4D4D8' },
  { id: 'over_165',        label: '>165°F',    short: '>165',   color: '#92400E', bg: '#FEF3C7', border: '#F0CB7D' },
];
const BAND_BY_ID_IPAD = Object.fromEntries(TEMP_BANDS_IPAD.map(b => [b.id, b]));

const deriveBand = (val) => {
  const n = Number(val);
  if (!Number.isFinite(n)) return null;
  if (n < 140) return 'under_140';
  if (n <= 165) return 'between_140_165';
  return 'over_165';
};

const fmtTime12Temp = (timeStr) => {
  if (!timeStr) return '';
  const [hStr, mStr] = String(timeStr).split(':');
  const h = Number(hStr); const m = Number(mStr) || 0;
  if (!Number.isFinite(h)) return timeStr;
  const ap = h >= 12 ? 'PM' : 'AM';
  const h12 = ((h + 11) % 12) + 1;
  return `${h12}:${String(m).padStart(2, '0')} ${ap}`;
};

const todayKeyTemp = () => {
  const d = new Date();
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
};

// ------------------------------------------------------------------
// Top-level screen — today's checkpoints
// ------------------------------------------------------------------
const TemperatureScreen = ({ currentStaff, showToast }) => {
  const todayKey = useMemoTemp(() => todayKeyTemp(), []);

  // Catalog + schedule come from window.SAMPLE_* (loaded by dataBootstrap).
  const catalog = (window.SAMPLE_TEMPERATURE_ITEMS || []).filter(i => !i.archived);
  const schedule = (window.SAMPLE_TEMPERATURE_SCHEDULE || [])
    .filter(s => !s.archived)
    .slice()
    .sort((a, b) => (a.timeOfDay || '').localeCompare(b.timeOfDay || ''));

  // Today's logs (one per schedule entry), with their readings nested in.
  // logsByScheduleId[scheduleId] = { id, finalized_at, readings: [...] }
  const [logsByScheduleId, setLogsByScheduleId] = useTemp({});
  const [active, setActive] = useTemp(null);   // open sheet for { schedule, log }

  // Fetch today's logs + readings, plus realtime subscribe.
  useEffectTemp(() => {
    if (!window.supa) return;
    let cancelled = false;
    const refetch = async () => {
      const { data: logRows } = await window.supa
        .from('temperature_logs')
        .select('*')
        .eq('restaurant_id', window.RESTAURANT_ID)
        .eq('the_date', todayKey);
      if (cancelled) return;
      const m = {};
      (logRows || []).forEach(l => {
        m[l.schedule_id] = { id: l.id, finalized_at: l.finalized_at, finalized_by_staff_id: l.finalized_by_staff_id, readings: [] };
      });
      const ids = (logRows || []).map(l => l.id);
      if (ids.length > 0) {
        const { data: readingRows } = await window.supa
          .from('temperature_readings')
          .select('*')
          .in('log_id', ids);
        if (cancelled) return;
        (readingRows || []).forEach(r => {
          const entry = Object.values(m).find(e => e.id === r.log_id);
          if (entry) entry.readings.push(r);
        });
      }
      setLogsByScheduleId(m);
    };
    refetch();
    const ch = window.supa
      .channel('ipad-temp-logs:' + window.RESTAURANT_ID + ':' + todayKey)
      .on('postgres_changes', {
        event: '*', schema: 'public', table: 'temperature_logs',
        filter: 'restaurant_id=eq.' + window.RESTAURANT_ID,
      }, () => refetch())
      .on('postgres_changes', {
        event: '*', schema: 'public', table: 'temperature_readings',
      }, () => refetch())
      .subscribe();
    return () => { cancelled = true; window.supa.removeChannel(ch); };
  }, [todayKey]);

  // Determine the "current" or most-relevant checkpoint card so the user
  // sees it highlighted. Definition: the latest scheduled time that's
  // already past, or the next one if none have passed yet.
  const nowMin = (() => { const d = new Date(); return d.getHours() * 60 + d.getMinutes(); })();
  const highlightId = (() => {
    if (schedule.length === 0) return null;
    let active = schedule[0].id;
    for (const s of schedule) {
      const [h, m] = (s.timeOfDay || '0:0').split(':').map(Number);
      const mm = (h || 0) * 60 + (m || 0);
      if (mm <= nowMin) active = s.id;
      else break;
    }
    return active;
  })();

  return (
    <div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
      <div style={{
        padding: '16px 24px', background: 'var(--bg-surface)',
        borderBottom: '1px solid var(--border-1)',
      }}>
        <div style={{ fontSize: 11, color: 'var(--fg-3)', fontWeight: 500, letterSpacing: '0.06em', textTransform: 'uppercase' }}>
          {window.fmtTodayLabel()}
        </div>
        <div data-reload-tap style={{ fontSize: 22, fontWeight: 600, letterSpacing: '-0.02em', color: 'var(--fg-1)', marginTop: 2, lineHeight: 1.1 }}>
          Temperature Log
        </div>
        <div style={{ fontSize: 13, color: 'var(--fg-3)', marginTop: 4 }}>
          {schedule.length === 0
            ? 'No scheduled checkpoints yet — set them up in Admin Portal.'
            : `${schedule.length} checkpoint${schedule.length === 1 ? '' : 's'} · ${catalog.length} item${catalog.length === 1 ? '' : 's'} to check`}
        </div>
      </div>

      <div className="scroll-soft" style={{ flex: 1, overflowY: 'auto', padding: '20px 24px 40px' }}>
        {schedule.length === 0 ? (
          <div style={{
            padding: 40, borderRadius: 14,
            background: 'var(--bg-surface)', border: '1px dashed var(--border-2)',
            textAlign: 'center', color: 'var(--fg-3)', fontSize: 14,
          }}>
            Ask your manager to add temperature checkpoints in Admin Portal → Temperature Log → Schedule.
          </div>
        ) : (
          // Restaurants typically run 2-4 checkpoints per day, so use a
          // full-width single column instead of a grid. Bigger tap targets,
          // clearer summary pills, no awkward half-empty rows.
          <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
            {schedule.map(s => {
              const log = logsByScheduleId[s.id];
              const readings = log?.readings || [];
              const finalized = !!log?.finalized_at;
              const summary = summarizeReadings(readings);
              const finalStaff = log?.finalized_by_staff_id
                ? (window.SAMPLE_STAFF || []).find(st => st.id === log.finalized_by_staff_id)
                : null;
              const isHighlight = s.id === highlightId && !finalized;
              return (
                <button
                  key={s.id}
                  onClick={() => setActive({ schedule: s, log })}
                  className="tap"
                  style={{
                    textAlign: 'left',
                    padding: 18, borderRadius: 16,
                    background: '#FFFFFF',
                    border: '1px solid ' + (isHighlight ? '#1F2937' : 'var(--border-2)'),
                    boxShadow: isHighlight ? '0 0 0 3px rgba(31,41,55,0.08)' : 'none',
                    cursor: 'pointer',
                    transition: 'transform 120ms var(--ease-snap), box-shadow 160ms',
                  }}
                  onMouseDown={e => e.currentTarget.style.transform = 'scale(0.985)'}
                  onMouseUp={e => e.currentTarget.style.transform = ''}
                  onMouseLeave={e => e.currentTarget.style.transform = ''}
                >
                  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
                    <div>
                      <div style={{ fontSize: 22, fontWeight: 700, letterSpacing: '-0.02em', fontFamily: 'var(--font-num)' }}>
                        {fmtTime12Temp(s.timeOfDay)}
                      </div>
                      {s.label && (
                        <div style={{ fontSize: 12, color: 'var(--fg-3)', marginTop: 2 }}>{s.label}</div>
                      )}
                    </div>
                    <StatusBadge finalized={finalized} hasReadings={readings.length > 0} />
                  </div>

                  {readings.length === 0 && !finalized && (
                    <div style={{ fontSize: 12.5, color: 'var(--fg-3)', fontStyle: 'italic', marginTop: 6 }}>
                      Tap to start logging
                    </div>
                  )}

                  {readings.length > 0 && (
                    <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 8 }}>
                      <BandPill count={summary.total} short="tested" tone="solid" />
                      {TEMP_BANDS_IPAD.map(b => (
                        <BandPill key={b.id} count={summary.byBand[b.id] || 0} short={b.short}
                          color={b.color} bg={b.bg} border={b.border} />
                      ))}
                    </div>
                  )}

                  {finalized && finalStaff && (
                    <div style={{ fontSize: 11.5, color: 'var(--fg-3)', marginTop: 10 }}>
                      Finalized by {finalStaff.name}
                    </div>
                  )}
                </button>
              );
            })}
          </div>
        )}
      </div>

      {active && (
        <LogSheet
          schedule={active.schedule}
          existingLog={active.log}
          catalog={catalog}
          currentStaff={currentStaff}
          showToast={showToast}
          todayKey={todayKey}
          onClose={() => setActive(null)}
        />
      )}
    </div>
  );
};

// ------------------------------------------------------------------
// Status badge — three states
// ------------------------------------------------------------------
const StatusBadge = ({ finalized, hasReadings }) => {
  if (finalized) {
    return (
      <span style={{
        display: 'inline-flex', alignItems: 'center', gap: 4,
        padding: '3px 9px', borderRadius: 999,
        fontSize: 10.5, fontWeight: 600, letterSpacing: '0.04em', textTransform: 'uppercase',
        background: '#DCFCE7', color: '#2D6A2A',
      }}>
        <Icon name="check" size={11} strokeWidth={2.6} color="#2D6A2A" /> Done
      </span>
    );
  }
  if (hasReadings) {
    // Blue, not amber — amber is reserved for the 140-165°F band.
    return (
      <span style={{
        padding: '3px 9px', borderRadius: 999,
        fontSize: 10.5, fontWeight: 600, letterSpacing: '0.04em', textTransform: 'uppercase',
        background: '#DBEAFE', color: '#1E40AF',
      }}>In progress</span>
    );
  }
  return (
    <span style={{
      padding: '3px 9px', borderRadius: 999,
      fontSize: 10.5, fontWeight: 600, letterSpacing: '0.04em', textTransform: 'uppercase',
      background: 'var(--bg-sunken)', color: 'var(--fg-3)',
    }}>To do</span>
  );
};

// Stacked metric tile: big count on top, small label below. Replaces the
// inline "1 <140" pill — that layout parsed as a math expression rather
// than "1 reading in the <140 band". The two-line layout removes the
// ambiguity entirely and gives the count its own visual weight.
const BandPill = ({ count, short, tone, color, bg, border }) => {
  const isSolid = tone === 'solid';
  return (
    <div style={{
      display: 'inline-flex', flexDirection: 'column', alignItems: 'center',
      justifyContent: 'center',
      minWidth: 64, padding: '5px 12px 6px',
      borderRadius: 10,
      background: isSolid ? 'var(--fg-1)' : (bg || 'var(--bg-sunken)'),
      border: '1px solid ' + (isSolid ? 'var(--fg-1)' : (border || 'transparent')),
      lineHeight: 1,
    }}>
      <span style={{
        fontSize: 17, fontWeight: 700,
        fontFamily: 'var(--font-num)',
        color: isSolid ? '#fff' : (color || 'var(--fg-2)'),
        letterSpacing: '-0.01em',
      }}>{count}</span>
      <span style={{
        marginTop: 3,
        fontSize: 9.5, fontWeight: 700,
        letterSpacing: '0.06em', textTransform: 'uppercase',
        color: isSolid ? 'rgba(255,255,255,0.78)' : (color || 'var(--fg-2)'),
        opacity: isSolid ? 1 : 0.85,
      }}>{short}</span>
    </div>
  );
};

const summarizeReadings = (readings) => {
  const out = { total: readings.length, byBand: {} };
  readings.forEach(r => { out.byBand[r.band] = (out.byBand[r.band] || 0) + 1; });
  return out;
};

// ------------------------------------------------------------------
// LogSheet — fullscreen-ish modal that lists catalog items and writes
// readings in real time. Finalize via PIN.
// ------------------------------------------------------------------
const LogSheet = ({ schedule, existingLog, catalog, currentStaff, showToast, todayKey, onClose }) => {
  // Local cache of readings by item_id. Reflects what's currently saved
  // (so the UI updates immediately on tap; realtime will reconcile later).
  // Each entry: { id, band, value (nullable) }.
  const [readingsByItem, setReadingsByItem] = useTemp(() => {
    const m = {};
    (existingLog?.readings || []).forEach(r => { m[r.item_id] = r; });
    return m;
  });
  const [logId, setLogId] = useTemp(existingLog?.id || null);
  const [finalized, setFinalized] = useTemp(!!existingLog?.finalized_at);
  const [pinGate, setPinGate] = useTemp(null);
  const [keypad, setKeypad] = useTemp(null); // { itemId }
  const [confirmEmpty, setConfirmEmpty] = useTemp(false);

  // Non-basic items the crew has explicitly added to this checkpoint's
  // visible list. They sit alongside basics + items that already have a
  // reading. We keep these in local state only — if the sheet is closed
  // and reopened, items with no reading drop off the visible list (but
  // can be re-added in seconds via the picker, and anything with a
  // reading sticks around because readingsByItem brings it back).
  const [extras, setExtras] = useTemp(() => new Set());
  const [picker, setPicker] = useTemp(false);

  // Refetch readings whenever the underlying log changes via realtime.
  useEffectTemp(() => {
    if (!logId || !window.supa) return;
    let cancelled = false;
    const refetch = async () => {
      const { data } = await window.supa
        .from('temperature_readings')
        .select('*')
        .eq('log_id', logId);
      if (cancelled) return;
      const m = {};
      (data || []).forEach(r => { m[r.item_id] = r; });
      setReadingsByItem(m);
    };
    const ch = window.supa
      .channel('ipad-temp-readings:' + logId)
      .on('postgres_changes', {
        event: '*', schema: 'public', table: 'temperature_readings',
        filter: 'log_id=eq.' + logId,
      }, () => refetch())
      .subscribe();
    return () => { cancelled = true; window.supa.removeChannel(ch); };
  }, [logId]);

  // Ensure a temperature_logs row exists before writing readings to it.
  // Returns the log id. Idempotent: subsequent calls reuse the cached id.
  const ensureLog = async () => {
    if (logId) return logId;
    const newId = 'tl-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 8);
    if (!window.supa) { setLogId(newId); return newId; }
    // INSERT with onConflict do-nothing on the unique (restaurant, date, schedule)
    // index so concurrent iPads can't double-create the row.
    const { data, error } = await window.supa
      .from('temperature_logs')
      .upsert({
        id: newId,
        restaurant_id: window.RESTAURANT_ID,
        the_date: todayKey,
        schedule_id: schedule.id,
      }, { onConflict: 'restaurant_id,the_date,schedule_id' })
      .select('id')
      .single();
    if (error) { console.error('temperature_logs upsert failed', error); setLogId(newId); return newId; }
    setLogId(data.id);
    return data.id;
  };

  // Record (or update) a reading for one item.
  const setReading = async (item, band, value) => {
    if (finalized) return;
    const lid = await ensureLog();
    const existing = readingsByItem[item.id];
    const row = {
      id: existing?.id || ('tr-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 6)),
      log_id: lid,
      item_id: item.id,
      band,
      value: value === null || value === undefined || value === '' ? null : Number(value),
      recorded_by_staff_id: currentStaff?.id || null,
    };
    // Optimistic local update.
    setReadingsByItem(m => ({ ...m, [item.id]: row }));
    if (!window.supa) return;
    const { error } = await window.supa
      .from('temperature_readings')
      .upsert(row, { onConflict: 'log_id,item_id' });
    if (error) console.error('temperature_readings upsert failed', error);
  };

  const clearReading = async (item) => {
    if (finalized) return;
    const existing = readingsByItem[item.id];
    if (!existing) return;
    setReadingsByItem(m => { const n = { ...m }; delete n[item.id]; return n; });
    if (!window.supa) return;
    const { error } = await window.supa.from('temperature_readings').delete().eq('id', existing.id);
    if (error) console.error('temperature_readings delete failed', error);
  };

  const totalReadings = Object.keys(readingsByItem).length;
  const summary = useMemoTemp(() => summarizeReadings(Object.values(readingsByItem)), [readingsByItem]);

  // Visible item list = basic items ∪ items already logged ∪ items the
  // crew explicitly added via the picker. Non-basic items with no
  // reading and no explicit add stay hidden so the line stays short
  // and matches "what's actually out today."
  const visibleItems = useMemoTemp(() => {
    const out = [];
    const seen = new Set();
    const push = (it) => { if (!seen.has(it.id)) { seen.add(it.id); out.push(it); } };
    catalog.forEach(it => {
      if (it.isBasic || readingsByItem[it.id] || extras.has(it.id)) push(it);
    });
    return out;
  }, [catalog, readingsByItem, extras]);

  // Non-basic items not yet on the visible list — what the picker shows.
  const pickableItems = useMemoTemp(() => {
    return catalog.filter(it =>
      !it.isBasic && !readingsByItem[it.id] && !extras.has(it.id)
    );
  }, [catalog, readingsByItem, extras]);

  const addExtra = (itemId) => {
    setExtras(prev => {
      const next = new Set(prev);
      next.add(itemId);
      return next;
    });
  };
  const removeExtra = async (item) => {
    // If the item also has a reading, removing it from extras alone
    // wouldn't hide it (readingsByItem still includes it). Clear the
    // reading too so "remove" means "this isn't on the line after all."
    if (readingsByItem[item.id]) {
      await clearReading(item);
    }
    setExtras(prev => {
      if (!prev.has(item.id)) return prev;
      const next = new Set(prev);
      next.delete(item.id);
      return next;
    });
  };

  // Finalize (PIN-gated). If nothing's been logged we ask first.
  const attemptFinish = () => {
    if (finalized) { onClose(); return; }
    if (totalReadings === 0) { setConfirmEmpty(true); return; }
    openPinAndFinalize();
  };
  const openPinAndFinalize = () => {
    setPinGate({
      purpose: `Finalize ${fmtTime12Temp(schedule.timeOfDay)} log`,
      onSuccess: async (staff) => {
        const lid = await ensureLog();
        const finalizedAt = new Date().toISOString();
        // Optimistic.
        setFinalized(true);
        setPinGate(null);
        if (window.supa) {
          const { error } = await window.supa
            .from('temperature_logs')
            .update({ finalized_at: finalizedAt, finalized_by_staff_id: staff.id })
            .eq('id', lid);
          if (error) {
            console.error('temperature_logs finalize failed', error);
            setFinalized(false);
            return;
          }
        }
        showToast && showToast({ message: 'Temperature log finalized', icon: 'check', staff });
        // Brief pause so the toast appears before we close the sheet.
        setTimeout(() => onClose(), 350);
      },
    });
  };

  return (
    <div style={{
      position: 'absolute', inset: 0,
      background: 'var(--bg-page)',
      display: 'flex', flexDirection: 'column',
      zIndex: 30,
    }}>
      {/* Header */}
      <div style={{
        flexShrink: 0,
        padding: '14px 22px',
        background: '#FFFFFF',
        borderBottom: '1px solid var(--border-1)',
        display: 'flex', alignItems: 'center', gap: 14,
      }}>
        <button onClick={onClose} className="tap" style={{
          height: 36, padding: '0 14px',
          borderRadius: 10,
          background: 'transparent',
          border: '1px solid var(--border-2)',
          color: 'var(--fg-2)',
          fontSize: 13, fontWeight: 600,
          display: 'inline-flex', alignItems: 'center', gap: 6,
          cursor: 'pointer', flexShrink: 0,
        }}>
          <Icon name="x" size={14} strokeWidth={2} />
          Close
        </button>
        <div style={{ minWidth: 0, flex: 1 }}>
          <div style={{ fontSize: 10, fontWeight: 700, color: 'var(--fg-3)', textTransform: 'uppercase', letterSpacing: '0.08em' }}>
            Temperature log · {fmtTime12Temp(schedule.timeOfDay)}
            {schedule.label && <span> · {schedule.label}</span>}
          </div>
          <div style={{ fontSize: 17, fontWeight: 600, color: 'var(--fg-1)', letterSpacing: '-0.015em', display: 'flex', alignItems: 'center', gap: 10 }}>
            <span>{totalReadings} of {visibleItems.length} logged</span>
            {finalized && (
              <span style={{
                fontSize: 10.5, fontWeight: 600, letterSpacing: '0.04em', textTransform: 'uppercase',
                padding: '3px 9px', borderRadius: 999,
                background: '#DCFCE7', color: '#2D6A2A',
              }}>Finalized</span>
            )}
          </div>
        </div>
        <Btn variant="primary" size="lg" onClick={attemptFinish}>
          {finalized ? 'Close' : 'Finish'}
        </Btn>
      </div>

      {/* Summary band */}
      {totalReadings > 0 && (
        <div style={{
          flexShrink: 0,
          padding: '10px 22px',
          background: 'var(--bg-page)',
          borderBottom: '1px solid var(--border-1)',
          display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap',
        }}>
          <BandPill count={summary.total} short="tested" tone="solid" />
          {TEMP_BANDS_IPAD.map(b => (
            <BandPill key={b.id} count={summary.byBand[b.id] || 0} short={b.short}
              color={b.color} bg={b.bg} border={b.border} />
          ))}
        </div>
      )}

      {/* Item list */}
      <div className="scroll-soft" style={{ flex: 1, overflowY: 'auto', padding: '18px 22px 40px' }}>
        {catalog.length === 0 ? (
          <div style={{
            padding: 40, borderRadius: 14,
            background: 'var(--bg-surface)', border: '1px dashed var(--border-2)',
            textAlign: 'center', color: 'var(--fg-3)', fontSize: 14,
          }}>
            Catalog is empty — ask your manager to add items in Admin Portal.
          </div>
        ) : (
          <>
            {visibleItems.length > 0 && (
              <div style={{
                background: '#FFFFFF', borderRadius: 14,
                border: '1px solid var(--border-2)', overflow: 'hidden',
              }}>
                {visibleItems.map((item, idx) => (
                  <ItemRow
                    key={item.id}
                    item={item}
                    reading={readingsByItem[item.id]}
                    isLast={idx === visibleItems.length - 1}
                    disabled={finalized}
                    // Non-basic items show a "Remove" affordance so a
                    // mistapped add can be undone. Basic items are
                    // permanent for the checkpoint.
                    onRemove={!item.isBasic ? () => removeExtra(item) : null}
                    onPickBand={(band) => setReading(item, band, null)}
                    onCustom={() => setKeypad({ itemId: item.id })}
                    onClear={() => clearReading(item)}
                  />
                ))}
              </div>
            )}

            {!finalized && pickableItems.length > 0 && (
              <button
                onClick={() => setPicker(true)}
                className="tap"
                style={{
                  marginTop: visibleItems.length > 0 ? 14 : 0,
                  width: '100%',
                  padding: '14px 18px',
                  borderRadius: 14,
                  background: '#FFFFFF',
                  border: '1.5px dashed var(--border-2)',
                  cursor: 'pointer',
                  display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 10,
                  fontSize: 14, fontWeight: 600, color: 'var(--fg-2)',
                }}
              >
                <Icon name="plus" size={16} strokeWidth={2.2} color="var(--fg-2)" />
                Add item ({pickableItems.length} available)
              </button>
            )}

            {visibleItems.length === 0 && pickableItems.length === 0 && (
              <div style={{
                padding: 40, borderRadius: 14,
                background: 'var(--bg-surface)', border: '1px dashed var(--border-2)',
                textAlign: 'center', color: 'var(--fg-3)', fontSize: 14,
              }}>
                No catalog items marked basic. Ask your manager to mark a few staples as Basic in Admin Portal.
              </div>
            )}
          </>
        )}
      </div>

      {keypad && (() => {
        const item = catalog.find(c => c.id === keypad.itemId);
        if (!item) { setKeypad(null); return null; }
        return (
          <CustomTempKeypad
            item={item}
            initial={readingsByItem[item.id]?.value}
            onCancel={() => setKeypad(null)}
            onCommit={(val) => {
              const band = deriveBand(val);
              if (!band) { setKeypad(null); return; }
              setReading(item, band, val);
              setKeypad(null);
            }}
          />
        );
      })()}

      {picker && (
        <AddItemPicker
          items={pickableItems}
          onCancel={() => setPicker(false)}
          onAdd={(itemIds) => {
            setExtras(prev => {
              const next = new Set(prev);
              itemIds.forEach(id => next.add(id));
              return next;
            });
            setPicker(false);
          }}
        />
      )}

      {confirmEmpty && (
        <Modal open onClose={() => setConfirmEmpty(false)} width={420} title="Nothing logged yet">
          <div style={{ padding: '8px 24px 22px' }}>
            <div style={{ fontSize: 14, color: 'var(--fg-1)', lineHeight: 1.5, marginBottom: 6 }}>
              No readings have been entered. Are you sure you want to finish this checkpoint?
            </div>
            <div style={{ fontSize: 12.5, color: 'var(--fg-3)', lineHeight: 1.5 }}>
              It's fine to finalize empty if nothing was available to test — just confirming so it isn't an accidental tap.
            </div>
            <div style={{ display: 'flex', gap: 10, marginTop: 22, justifyContent: 'flex-end' }}>
              <Btn variant="secondary" onClick={() => setConfirmEmpty(false)}>Keep logging</Btn>
              <Btn variant="primary" onClick={() => { setConfirmEmpty(false); openPinAndFinalize(); }}>
                Finish anyway
              </Btn>
            </div>
          </div>
        </Modal>
      )}

      {pinGate && (
        <TemperaturePinGate
          purpose={pinGate.purpose}
          onCancel={() => setPinGate(null)}
          onSuccess={pinGate.onSuccess}
        />
      )}
    </div>
  );
};

// ------------------------------------------------------------------
// Single item row — name, current reading badge, 4 quick-pick buttons.
// ------------------------------------------------------------------
const ItemRow = ({ item, reading, isLast, disabled, onPickBand, onCustom, onClear, onRemove }) => {
  const band = reading ? BAND_BY_ID_IPAD[reading.band] : null;
  const hasCustom = reading && reading.value !== null && reading.value !== undefined;
  return (
    <div style={{
      display: 'grid',
      gridTemplateColumns: 'minmax(0, 1.4fr) auto',
      gap: 14,
      padding: '14px 18px',
      borderBottom: isLast ? 'none' : '1px solid var(--border-2)',
      alignItems: 'center',
      background: reading ? '#FBFAF8' : 'transparent',
    }}>
      <div style={{ minWidth: 0 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
          <div style={{ fontSize: 16, fontWeight: 600, color: 'var(--fg-1)' }}>
            {item.name}
          </div>
          {!item.isBasic && (
            <span style={{
              fontSize: 9.5, fontWeight: 700, letterSpacing: '0.08em',
              padding: '2px 6px', borderRadius: 999,
              background: 'var(--bg-sunken)', color: 'var(--fg-3)',
              textTransform: 'uppercase',
            }}>Added</span>
          )}
        </div>
        {reading && (
          <div style={{ marginTop: 6, display: 'flex', alignItems: 'center', gap: 8 }}>
            <span style={{
              display: 'inline-flex', alignItems: 'center', gap: 5,
              padding: '3px 9px', borderRadius: 6,
              fontSize: 12, fontWeight: 700,
              background: band?.bg || 'var(--bg-sunken)',
              color: band?.color || 'var(--fg-2)',
              border: '1px solid ' + (band?.border || 'transparent'),
              fontFamily: 'var(--font-num)',
            }}>
              {hasCustom ? `${Number(reading.value)}°F` : (band?.short || '—')}
            </span>
            {!disabled && (
              <button onClick={onClear} style={{
                fontSize: 11.5, color: 'var(--fg-3)',
                background: 'transparent', border: 'none',
                cursor: 'pointer', padding: '4px 6px',
              }}>Clear</button>
            )}
          </div>
        )}
        {!disabled && onRemove && !reading && (
          <div style={{ marginTop: 6 }}>
            <button onClick={onRemove} style={{
              fontSize: 11.5, color: 'var(--fg-3)',
              background: 'transparent', border: 'none',
              cursor: 'pointer', padding: '4px 0',
            }}>Remove</button>
          </div>
        )}
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0 }}>
        {TEMP_BANDS_IPAD.map(b => {
          const isActive = reading && !hasCustom && reading.band === b.id;
          return (
            <button
              key={b.id}
              disabled={disabled}
              onClick={() => onPickBand(b.id)}
              className="tap"
              style={{
                height: 44, padding: '0 14px',
                borderRadius: 10,
                fontSize: 14, fontWeight: 600,
                fontFamily: 'var(--font-num)',
                background: isActive ? b.bg : '#FFFFFF',
                color: isActive ? b.color : 'var(--fg-1)',
                border: '1.5px solid ' + (isActive ? b.border : 'var(--border-2)'),
                cursor: disabled ? 'not-allowed' : 'pointer',
                opacity: disabled ? 0.5 : 1,
                whiteSpace: 'nowrap',
              }}
            >
              {b.short}
            </button>
          );
        })}
        <button
          disabled={disabled}
          onClick={onCustom}
          className="tap"
          style={{
            height: 44, padding: '0 14px',
            borderRadius: 10,
            fontSize: 14, fontWeight: 600,
            background: hasCustom ? 'var(--fg-1)' : '#FFFFFF',
            color: hasCustom ? '#fff' : 'var(--fg-1)',
            border: '1.5px solid ' + (hasCustom ? 'var(--fg-1)' : 'var(--border-2)'),
            cursor: disabled ? 'not-allowed' : 'pointer',
            opacity: disabled ? 0.5 : 1,
            display: 'inline-flex', alignItems: 'center', gap: 5,
          }}
        >
          <Icon name="edit" size={13} strokeWidth={2} color={hasCustom ? '#fff' : 'var(--fg-2)'} />
          {hasCustom ? `${Number(reading.value)}°F` : 'Type °F'}
        </button>
      </div>
    </div>
  );
};

// ------------------------------------------------------------------
// Custom temperature keypad — centered modal with our NumPad
// ------------------------------------------------------------------
const CustomTempKeypad = ({ item, initial, onCancel, onCommit }) => {
  const [val, setVal] = useTemp(initial === null || initial === undefined ? '' : String(initial));
  const band = deriveBand(val);
  const bandMeta = band ? BAND_BY_ID_IPAD[band] : null;
  return (
    <Modal open onClose={onCancel} width={420}>
      <div style={{ padding: '22px 24px 24px', textAlign: 'center' }}>
        <div style={{ fontSize: 10.5, fontWeight: 700, color: 'var(--fg-3)', letterSpacing: '0.08em', textTransform: 'uppercase' }}>
          Temperature
        </div>
        <div style={{ fontSize: 18, fontWeight: 600, color: 'var(--fg-1)', letterSpacing: '-0.015em', marginTop: 4 }}>
          {item.name}
        </div>

        <div style={{
          marginTop: 18, marginBottom: 8,
          fontSize: 56, fontWeight: 600,
          fontFamily: 'var(--font-num)',
          color: val === '' ? 'var(--fg-3)' : 'var(--fg-1)',
          letterSpacing: '-0.03em',
          height: 72, display: 'flex', alignItems: 'center', justifyContent: 'center',
        }}>
          {val === '' ? '—' : <>{val}<span style={{ fontSize: 24, color: 'var(--fg-3)', marginLeft: 4 }}>°F</span></>}
        </div>

        {bandMeta && (
          <div style={{
            display: 'inline-flex', alignItems: 'center', gap: 6,
            padding: '4px 12px', borderRadius: 999,
            fontSize: 12, fontWeight: 600,
            background: bandMeta.bg, color: bandMeta.color,
            border: '1px solid ' + bandMeta.border,
            marginBottom: 18,
          }}>
            {bandMeta.label}
          </div>
        )}

        <NumPad value={val} onChange={setVal} maxLen={3} />

        <div style={{ display: 'flex', gap: 10, marginTop: 22 }}>
          <Btn variant="secondary" onClick={onCancel} fullWidth>Cancel</Btn>
          <Btn variant="primary" disabled={!Number.isFinite(Number(val)) || val === ''} onClick={() => onCommit(val)} fullWidth>
            Save reading
          </Btn>
        </div>
      </div>
    </Modal>
  );
};

// ------------------------------------------------------------------
// PIN gate — mirrors TruckPinGate so the patterns stay consistent.
// ------------------------------------------------------------------
const TemperaturePinGate = ({ purpose, onCancel, onSuccess }) => {
  const [pin, setPin] = useTemp('');
  const [err, setErr] = useTemp('');
  const [shake, setShake] = useTemp(false);

  const tryPin = (p) => {
    const staff = window.SAMPLE_STAFF.find(s => s.pin === p);
    if (staff) {
      onSuccess(staff);
    } else {
      setErr('PIN not recognized');
      setShake(true);
      setTimeout(() => setShake(false), 450);
      setTimeout(() => { setPin(''); setErr(''); }, 600);
    }
  };

  return (
    <Modal open onClose={onCancel} width={420}>
      <div style={{ padding: '24px 24px 22px', textAlign: 'center' }}>
        <div style={{ fontSize: 11, fontWeight: 700, color: 'var(--fg-3)', textTransform: 'uppercase', letterSpacing: '0.08em' }}>
          PIN required
        </div>
        <div style={{ fontSize: 19, fontWeight: 600, color: 'var(--fg-1)', letterSpacing: '-0.015em', marginTop: 6, marginBottom: 20 }}>
          {purpose}
        </div>

        <div style={{
          display: 'flex', justifyContent: 'center', gap: 14,
          marginBottom: 8,
          animation: shake ? 'tempPinShake 0.45s ease' : undefined,
        }}>
          {[0,1,2,3].map(i => (
            <div key={i} style={{
              width: 18, height: 18, borderRadius: 999,
              background: i < pin.length ? 'var(--fg-1)' : 'transparent',
              border: '2px solid ' + (err ? 'var(--danger)' : 'var(--border-1)'),
              transition: 'all 120ms ease',
            }} />
          ))}
        </div>
        <div style={{ height: 18, fontSize: 12.5, color: err ? 'var(--danger)' : 'var(--fg-3)', marginBottom: 16 }}>
          {err || 'Enter your 4-digit PIN'}
        </div>

        <NumPad value={pin} onChange={setPin} onEnter={tryPin} maxLen={4} />

        <div style={{ marginTop: 18 }}>
          <Btn variant="ghost" onClick={onCancel} fullWidth>Cancel</Btn>
        </div>
      </div>
      <style>{`
        @keyframes tempPinShake {
          0%, 100% { transform: translateX(0); }
          20% { transform: translateX(-8px); }
          40% { transform: translateX(8px); }
          60% { transform: translateX(-4px); }
          80% { transform: translateX(4px); }
        }
      `}</style>
    </Modal>
  );
};

// ------------------------------------------------------------------
// Add-item picker — multi-select non-basic catalog items to add to
// the current checkpoint's visible list. Mirrors the temperature
// PIN gate / keypad sheet styling so it feels like the rest of the
// flow.
// ------------------------------------------------------------------
const AddItemPicker = ({ items, onCancel, onAdd }) => {
  const [selected, setSelected] = useTemp(() => new Set());
  const toggle = (id) => setSelected(prev => {
    const next = new Set(prev);
    if (next.has(id)) next.delete(id); else next.add(id);
    return next;
  });
  const count = selected.size;
  // Preserve the catalog's owner-configured order. The caller passes
  // `items` already filtered/ordered by sort_order (the iPad catalog
  // ultimately comes from a `.order('sort_order')` query in
  // dataBootstrap), so reorder here would override the owner's intent.
  const sorted = items;
  return (
    <Modal open onClose={onCancel} width={520}>
      <div style={{
        display: 'flex', flexDirection: 'column',
        maxHeight: '78vh',
      }}>
        <div style={{
          padding: '20px 22px 14px',
          borderBottom: '1px solid var(--border-1)',
          flexShrink: 0,
        }}>
          <div style={{ fontSize: 10.5, fontWeight: 700, color: 'var(--fg-3)', textTransform: 'uppercase', letterSpacing: '0.08em' }}>
            Add items to this checkpoint
          </div>
          <div style={{ fontSize: 17, fontWeight: 600, color: 'var(--fg-1)', letterSpacing: '-0.015em', marginTop: 4 }}>
            What else is out on the line right now?
          </div>
          <div style={{ fontSize: 12.5, color: 'var(--fg-3)', marginTop: 4, lineHeight: 1.45 }}>
            Pick anything you're hot-holding today. You can record their temperatures after.
          </div>
        </div>

        <div className="scroll-soft" style={{ overflowY: 'auto', flex: 1, padding: '10px 0' }}>
          {sorted.length === 0 ? (
            <div style={{ padding: 30, textAlign: 'center', color: 'var(--fg-3)', fontSize: 13 }}>
              All non-basic items are already on the list.
            </div>
          ) : sorted.map(it => {
            const isOn = selected.has(it.id);
            return (
              <button
                key={it.id}
                onClick={() => toggle(it.id)}
                className="tap"
                style={{
                  width: '100%',
                  padding: '14px 22px',
                  display: 'flex', alignItems: 'center', gap: 14,
                  background: isOn ? '#FBFAF8' : 'transparent',
                  border: 'none',
                  borderBottom: '1px solid var(--border-2)',
                  cursor: 'pointer',
                  textAlign: 'left',
                }}
              >
                <div style={{
                  width: 22, height: 22, borderRadius: 6,
                  border: '2px solid ' + (isOn ? 'var(--fg-1)' : 'var(--border-1)'),
                  background: isOn ? 'var(--fg-1)' : 'transparent',
                  display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                  flexShrink: 0,
                }}>
                  {isOn && <Icon name="check" size={13} strokeWidth={2.6} color="#fff" />}
                </div>
                <div style={{ fontSize: 15, fontWeight: 500, color: 'var(--fg-1)' }}>
                  {it.name}
                </div>
              </button>
            );
          })}
        </div>

        <div style={{
          padding: '14px 22px',
          borderTop: '1px solid var(--border-1)',
          display: 'flex', gap: 10, flexShrink: 0,
        }}>
          <Btn variant="secondary" onClick={onCancel} fullWidth>Cancel</Btn>
          <Btn variant="primary" disabled={count === 0} onClick={() => onAdd([...selected])} fullWidth>
            {count === 0 ? 'Add' : `Add ${count} item${count === 1 ? '' : 's'}`}
          </Btn>
        </div>
      </div>
    </Modal>
  );
};

// ------------------------------------------------------------------
// Full-screen temperature reminder — fires when a scheduled checkpoint's
// time has arrived and today's log for it isn't finalized yet. Mirrors the
// Daily Checklist reminder; the only action is "Open Temperature Log" (it
// never auto-fills readings). Uses the shared `attn-flash` colour-cycling
// treatment so the takeover pulses for attention.
// ------------------------------------------------------------------
const TemperatureReminderOverlay = ({ schedule, itemCount, onOpen, onDismiss }) => {
  const [mounted, setMounted] = useTemp(false);
  useEffectTemp(() => {
    const raf = requestAnimationFrame(() => setMounted(true));
    return () => cancelAnimationFrame(raf);
  }, []);

  return (
    <div className="attn-flash" style={{
      position: 'absolute', inset: 0, zIndex: 901,
      // Flash teal → red → amber (matches the temp-band semantics) for attention.
      '--flash-a': '#0D9488', '--flash-b': '#DC2626', '--flash-c': '#D97706',
      transition: 'opacity 260ms ease-out, transform 260ms ease-out',
      color: '#FFFFFF',
      display: 'flex', flexDirection: 'column',
      padding: '64px 56px 52px',
      overflow: 'hidden',
      opacity: mounted ? 1 : 0,
      transform: mounted ? 'scale(1)' : 'scale(0.97)',
    }}>
      {/* Decorative rings */}
      <div style={{ position: 'absolute', top: -180, right: -180, width: 480, height: 480, borderRadius: '50%', border: '1px solid rgba(255,255,255,0.12)', pointerEvents: 'none' }} />
      <div style={{ position: 'absolute', top: -100, right: -100, width: 320, height: 320, borderRadius: '50%', border: '1px solid rgba(255,255,255,0.10)', pointerEvents: 'none' }} />

      <div style={{ display: 'flex', alignItems: 'center', gap: 14, position: 'relative', zIndex: 1 }}>
        <i className="ri-temp-hot-line" style={{ fontSize: 24, color: '#fff', lineHeight: 1 }} />
        <div style={{ fontSize: 13, fontWeight: 700, letterSpacing: '0.18em', textTransform: 'uppercase' }}>
          Temperature check due
        </div>
      </div>

      <div style={{ height: 1, background: 'rgba(255,255,255,0.18)', marginTop: 22, marginBottom: 28, position: 'relative', zIndex: 1 }} />

      <div style={{ fontSize: 16, opacity: 0.8, position: 'relative', zIndex: 1, fontFamily: 'var(--font-num)' }}>
        {fmtTime12Temp(schedule.timeOfDay)}
      </div>

      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', position: 'relative', zIndex: 1 }}>
        <div style={{ fontSize: 56, fontWeight: 500, letterSpacing: '-0.03em', lineHeight: 1.12, textWrap: 'balance', maxWidth: 640 }}>
          {schedule.label || 'Temperature checkpoint'}
        </div>
        <div style={{ fontSize: 18, opacity: 0.78, marginTop: 14, maxWidth: 600 }}>
          {itemCount > 0
            ? `Log all ${itemCount} item${itemCount === 1 ? '' : 's'} now while they're at temp. Tap to open the log — readings are never auto-filled.`
            : 'Open the temperature log and record this checkpoint’s readings.'}
        </div>
      </div>

      <div style={{ display: 'flex', gap: 14, position: 'relative', zIndex: 1 }}>
        <button onClick={onDismiss} style={{
          padding: '0 24px', height: 88, borderRadius: 20,
          background: 'rgba(255,255,255,0.14)', color: '#FFFFFF',
          fontSize: 18, fontWeight: 500, border: 'none', cursor: 'pointer',
        }}>Dismiss</button>
        <button onClick={onOpen} style={{
          flex: 1, height: 88, borderRadius: 20,
          background: '#FFFFFF', color: '#0F766E',
          fontSize: 24, fontWeight: 600, letterSpacing: '-0.01em',
          display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 12,
          cursor: 'pointer', boxShadow: '0 12px 32px rgba(0,0,0,0.25)', border: 'none',
        }}>
          <i className="ri-temp-hot-line" style={{ fontSize: 24, color: '#0F766E', lineHeight: 1 }} />
          Open Temperature Log
        </button>
      </div>
    </div>
  );
};

// ------------------------------------------------------------------
// Reminder scheduler hook — mirrors useChecklistReminder. Polls every 30s
// for any non-archived checkpoint whose time_of_day has passed today and
// whose log isn't finalized; surfaces the most relevant one. Temperature
// schedule has no per-checkpoint reminder cadence (unlike checklist
// groups), so we re-nag every RE_NAG_MIN minutes while still unlogged —
// missing a temp check is a food-safety issue, so a single pop isn't enough.
// Returns [reminderSchedule, dismiss].
// ------------------------------------------------------------------
const RE_NAG_MIN = 15;
function useTemperatureReminder() {
  const [reminderId, setReminderId] = useTemp(null); // schedule id
  const lastShownRef = useRefTemp({});  // { schedId+'@'+date: ms epoch }
  const finalizedRef = useRefTemp({});  // { schedId: true } for today
  // Gate firing until the first finalized-logs load completes. Otherwise the
  // mount-time tick races ahead of the (async) query with an empty
  // finalizedRef and wrongly fires for a checkpoint that's already done.
  const readyRef = useRefTemp(false);

  // Load today's finalized checkpoints; refresh every minute. Polling (not
  // realtime) keeps the reminder decoupled from the App mount lifecycle —
  // same choice as useChecklistReminder.
  useEffectTemp(() => {
    let cancelled = false;
    const refresh = async () => {
      if (!window.supa) { readyRef.current = true; return; }
      const dk = todayKeyTemp();
      const { data } = await window.supa.from('temperature_logs')
        .select('schedule_id, finalized_at')
        .eq('restaurant_id', window.RESTAURANT_ID)
        .eq('the_date', dk);
      if (cancelled) return;
      const m = {};
      (data || []).forEach(l => { if (l.finalized_at) m[l.schedule_id] = true; });
      finalizedRef.current = m;
      readyRef.current = true;
    };
    refresh();
    const t = setInterval(refresh, 60000);
    return () => { cancelled = true; clearInterval(t); };
  }, []);

  // Poll the schedule.
  useEffectTemp(() => {
    const tick = () => {
      if (reminderId) return; // already showing
      if (!readyRef.current) return; // wait until finalized state is known
      const now = new Date();
      const dk = todayKeyTemp();
      const nowMin = now.getHours() * 60 + now.getMinutes();
      const sched = (window.SAMPLE_TEMPERATURE_SCHEDULE || [])
        .filter(s => !s.archived)
        .slice()
        .sort((a, b) => (a.timeOfDay || '').localeCompare(b.timeOfDay || ''));
      for (const s of sched) {
        const [h, m] = (s.timeOfDay || '00:00').split(':').map(Number);
        const trigMin = (h || 0) * 60 + (m || 0);
        if (nowMin < trigMin) continue;            // not yet due
        if (finalizedRef.current[s.id]) continue;  // already logged + finalized

        const key = s.id + '@' + dk;
        const last = lastShownRef.current[key];
        if (last === undefined) {
          // First time it's due today — always pop once.
          lastShownRef.current[key] = Date.now();
          setReminderId(s.id);
          return;
        }
        const sinceMin = (Date.now() - last) / 60000;
        if (sinceMin >= RE_NAG_MIN) {
          lastShownRef.current[key] = Date.now();
          setReminderId(s.id);
          return;
        }
      }
    };
    tick();
    const t = setInterval(tick, 30000); // 30s poll → lag ≤ 30s
    return () => clearInterval(t);
  }, [reminderId]);

  const dismiss = () => setReminderId(null);
  const schedule = (window.SAMPLE_TEMPERATURE_SCHEDULE || []).find(s => s.id === reminderId) || null;
  return [schedule, dismiss];
}

Object.assign(window, {
  TemperatureScreen,
  TemperatureReminderOverlay,
  useTemperatureReminder,
});
