// CrewMeal.jsx — Admin Portal page for the Crew Meal feature (v10.00).
//
// Two tabs (one nav entry in the sidebar):
//   • Log      — per-day viewer of staff meals logged from the iPad
//                (who ate, when, and the exact meal they picked)
//   • Settings — meal slots (groups) + the choices inside each slot
//
// Data model:
//   crew_meal_groups — id (uuid), name, required, sort_order, archived
//   crew_meal_items  — id (uuid), group_id FK → groups (ON DELETE CASCADE),
//                      name, sort_order, archived
//   crew_meal_logs   — id (uuid), the_date (device-local YYYY-MM-DD),
//                      staff_id (staff.id), items jsonb array of
//                      { group_id, group_name, item_id, item_name }
//                      snapshots in group sort order, eaten_at timestamptz
//
// Log rows snapshot group/item NAMES at log time, so renaming or deleting
// catalog rows never rewrites history — the portal renders the snapshot.
// All top-level idents are Cm/cm/CM_-prefixed per the global-scope
// convention (HANDBOOK §9 "Top-level const is GLOBAL").

// ------------------------------------------------------------------
// Mappers
// ------------------------------------------------------------------
const cmGroupFromRow = (r) => ({
  id: r.id, name: r.name,
  required: !!r.required,
  sortOrder: Number(r.sort_order) || 0,
  archived: !!r.archived,
});
// NOTE: never send restaurant_id — useSupaList injects it on inserts.
const cmGroupToRow = (o) => ({
  id: o.id, name: o.name,
  required: !!o.required,
  sort_order: Number(o.sortOrder) || 0,
  archived: !!o.archived,
  updated_at: new Date().toISOString(),
});

const cmItemFromRow = (r) => ({
  id: r.id, groupId: r.group_id, name: r.name,
  sortOrder: Number(r.sort_order) || 0,
  archived: !!r.archived,
});
const cmItemToRow = (o) => ({
  id: o.id, group_id: o.groupId, name: o.name,
  sort_order: Number(o.sortOrder) || 0,
  archived: !!o.archived,
  updated_at: new Date().toISOString(),
});

// ------------------------------------------------------------------
// Constants + helpers
// ------------------------------------------------------------------
// timestamptz ISO → "2:14 PM" (12-hour, local — matches fmtTime12 style)
const fmtTimeCm = (iso) => {
  if (!iso) return '';
  const d = new Date(iso);
  if (isNaN(d.getTime())) return '';
  return d.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
};

// Slot-rule metadata: header chip rendering + editor radio-cards.
const CM_RULE_META = {
  required: { chip: 'Required · choose one', bg: 'var(--fg-1)',      fg: '#FFFFFF' },
  optional: { chip: 'Optional',              bg: 'var(--bg-sunken)', fg: 'var(--fg-2)' },
};
const CM_RULE_OPTIONS = [
  { id: 'required', title: 'Required — crew must pick one',
    hint: 'Every logged meal includes exactly one choice from this slot (e.g. Entree).' },
  { id: 'optional', title: 'Optional — can be skipped',
    hint: 'Crew can pick one choice or skip the slot entirely (e.g. Rice, Side).' },
];

// Move `id` one step within `list` (already display-sorted) and restamp
// sort_order in 10-step increments across the whole scope. Restamping
// (vs a naive two-row value swap) survives duplicate sort values;
// useSupaList diffs and only writes rows whose sortOrder actually changed.
const cmSwapSort = (list, id, dir, apply) => {
  const idx = list.findIndex(x => x.id === id);
  const nIdx = idx + dir;
  if (idx < 0 || nIdx < 0 || nIdx >= list.length) return;
  const next = list.slice();
  const [moved] = next.splice(idx, 1);
  next.splice(nIdx, 0, moved);
  apply(new Map(next.map((x, i) => [x.id, (i + 1) * 10])));
};

// ------------------------------------------------------------------
// Top-level page
// ------------------------------------------------------------------
const CrewMeal = () => {
  const [tab, setTab] = useState('log');
  const [toast, setToast] = useState('');

  const [groups, setGroups] = window.useSupaList('crew_meal_groups', {
    fromRow: cmGroupFromRow,
    toRow: cmGroupToRow,
    initial: [],
  });
  const [items, setItems] = window.useSupaList('crew_meal_items', {
    fromRow: cmItemFromRow,
    toRow: cmItemToRow,
    initial: [],
  });

  return (
    <div className="portal-page-wide" style={{ maxWidth: 1080 }}>
      <div className="portal-page-header">
        <div>
          <h1 className="portal-page-title">Crew Meal</h1>
          <div className="portal-page-subtitle">
            Staff meals logged from the iPad. Review who ate what here; manage the meal slots and choices in Settings.
          </div>
        </div>
      </div>

      <div style={{ display: 'flex', gap: 4, marginBottom: 18, borderBottom: '1px solid var(--border-2)' }}>
        {[
          { id: 'log',      label: 'Log' },
          { id: 'settings', label: 'Settings' },
        ].map(t => (
          <button
            key={t.id}
            onClick={() => setTab(t.id)}
            style={{
              padding: '8px 14px',
              fontSize: 13.5, fontWeight: 500,
              color: tab === t.id ? 'var(--fg-1)' : 'var(--fg-3)',
              background: 'transparent',
              border: 'none',
              borderBottom: '2px solid ' + (tab === t.id ? 'var(--fg-1)' : 'transparent'),
              marginBottom: -1,
              cursor: 'pointer',
            }}
          >{t.label}</button>
        ))}
      </div>

      {tab === 'log' && (
        <CmLogTab setToast={setToast} />
      )}
      {tab === 'settings' && (
        <CmSettingsTab
          groups={groups} setGroups={setGroups}
          items={items} setItems={setItems}
          setToast={setToast}
        />
      )}

      <PToast message={toast} open={!!toast} onClose={() => setToast('')} />
    </div>
  );
};

// ------------------------------------------------------------------
// Tab 1 — Log (per-day viewer)
// ------------------------------------------------------------------
const CmLogTab = ({ setToast }) => {
  // PDatePicker expects a Date object; isoDate() derives the local
  // YYYY-MM-DD key the iPad also uses for the_date.
  const [date, setDate] = useState(() => new Date());
  const dateKey = isoDate(date);
  const [logs, setLogs] = useState([]); // raw crew_meal_logs rows, eaten_at desc

  useEffect(() => {
    if (!window.supa) return;
    let cancelled = false;
    window.supa
      .from('crew_meal_logs')
      .select('*')
      .eq('restaurant_id', window.RESTAURANT_ID)
      .eq('the_date', dateKey)
      .order('eaten_at', { ascending: false })
      .then(({ data, error }) => {
        if (error) { console.error('crew_meal_logs select failed', error); return; }
        if (cancelled) return;
        setLogs(data || []);
      });

    const ch = window.supa
      .channel('portal-crew-meal-logs:' + window.RESTAURANT_ID + ':' + dateKey)
      .on('postgres_changes', {
        event: '*', schema: 'public', table: 'crew_meal_logs',
        filter: 'restaurant_id=eq.' + window.RESTAURANT_ID,
      }, (payload) => {
        // DELETE payloads only carry the PK (default replica identity),
        // so apply them by id — payload.old has no the_date to check.
        if (payload.eventType === 'DELETE') {
          const id = payload.old && payload.old.id;
          if (!id) return;
          setLogs(prev => prev.filter(r => r.id !== id));
          return;
        }
        const row = payload.new;
        if (!row || row.the_date !== dateKey) return;
        setLogs(prev => {
          const next = prev.some(r => r.id === row.id)
            ? prev.map(r => r.id === row.id ? row : r)
            : [row, ...prev];
          return next.slice().sort((a, b) => (b.eaten_at || '').localeCompare(a.eaten_at || ''));
        });
      })
      .subscribe();
    return () => { cancelled = true; window.supa.removeChannel(ch); };
  }, [dateKey]);

  const staffById = useMemo(() => {
    const m = {};
    (window.SAMPLE_STAFF || []).forEach(s => { m[s.id] = s; });
    return m;
  }, []);

  const removeLog = async (row) => {
    if (!confirm('Remove this crew meal entry?')) return;
    if (!window.supa) return;
    // Optimistic removal; roll back FUNCTIONALLY on failure — restoring a
    // whole pre-click snapshot would clobber realtime changes that landed
    // while the delete was in flight (house convention: ChecklistScreen).
    setLogs(ls => ls.filter(l => l.id !== row.id));
    const { error } = await window.supa
      .from('crew_meal_logs').delete().eq('id', row.id);
    if (error) {
      console.error('crew_meal_logs delete failed', error);
      setLogs(ls => ls.some(l => l.id === row.id) ? ls :
        [...ls, row].sort((a, b) => new Date(b.eaten_at) - new Date(a.eaten_at)));
      return;
    }
    setToast('Entry removed');
  };

  return (
    <>
      <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 16 }}>
        <PDatePicker value={date} onChange={setDate} />
        <div style={{ flex: 1 }} />
        <div style={{ fontSize: 12, color: 'var(--fg-3)' }}>
          {logs.length} meal{logs.length === 1 ? '' : 's'} logged
        </div>
      </div>

      {logs.length === 0 ? (
        <div className="portal-empty" style={{ fontSize: 13 }}>
          No crew meals logged on this day.
        </div>
      ) : (
        <div className="portal-card" style={{ padding: 0 }}>
          <table className="portal-table">
            <thead>
              <tr>
                <th style={{ width: 100 }}>Time</th>
                <th style={{ width: 200 }}>Person</th>
                <th>Meal</th>
                <th style={{ width: 60 }}></th>
              </tr>
            </thead>
            <tbody>
              {logs.map(row => {
                const staff = staffById[row.staff_id];
                const mealItems = Array.isArray(row.items) ? row.items : [];
                return (
                  <tr key={row.id}>
                    <td style={{ fontWeight: 600, fontFamily: 'var(--font-num)', whiteSpace: 'nowrap' }}>
                      {fmtTimeCm(row.eaten_at)}
                    </td>
                    <td>
                      <div style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
                        {staff ? <PAvatar staff={staff} size={20} /> : null}
                        <span style={{ fontSize: 13, fontWeight: 500 }}>
                          {(staff && staff.name) || row.staff_id}
                        </span>
                      </div>
                    </td>
                    <td>
                      {mealItems.length === 0 ? (
                        <span style={{ fontSize: 12.5, color: 'var(--fg-3)' }}>—</span>
                      ) : (
                        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, padding: '2px 0' }}>
                          {mealItems.map((it, i) => (
                            <span key={it.item_id || i} style={{
                              display: 'inline-flex', flexDirection: 'column', alignItems: 'flex-start',
                              padding: '4px 10px', borderRadius: 8,
                              background: 'var(--bg-sunken)',
                              lineHeight: 1.35,
                            }}>
                              <span style={{ fontSize: 12.5, fontWeight: 600, color: 'var(--fg-1)' }}>
                                {it.item_name}
                              </span>
                              <span style={{ fontSize: 11, color: 'var(--fg-3)' }}>
                                {it.group_name}
                              </span>
                            </span>
                          ))}
                        </div>
                      )}
                    </td>
                    <td onClick={e => e.stopPropagation()}>
                      <div className="row-actions">
                        <button onClick={() => removeLog(row)} title="Remove entry"><PIcon name="trash" size={13} /></button>
                      </div>
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      )}
    </>
  );
};

// ------------------------------------------------------------------
// Tab 2 — Settings (groups + items, ChecklistTemplate model)
// ------------------------------------------------------------------
const CmSettingsTab = ({ groups, setGroups, items, setItems, setToast }) => {
  const [editingGroup, setEditingGroup] = useState(null); // group object or 'new'
  const [editingItem, setEditingItem]   = useState(null); // item object or { groupId, _new: true }
  const [showArchived, setShowArchived] = useState(false);

  const activeGroups = groups
    .filter(g => !g.archived)
    .slice()
    .sort((a, b) => (a.sortOrder - b.sortOrder) || a.name.localeCompare(b.name));
  const archivedGroups = groups
    .filter(g => g.archived)
    .slice()
    .sort((a, b) => (a.sortOrder - b.sortOrder) || a.name.localeCompare(b.name));

  const itemsByGroup = useMemo(() => {
    const m = {};
    items.forEach(i => { (m[i.groupId] = m[i.groupId] || []).push(i); });
    Object.values(m).forEach(arr =>
      arr.sort((a, b) => (a.sortOrder - b.sortOrder) || a.name.localeCompare(b.name)));
    return m;
  }, [items]);

  const archivedCount =
    groups.filter(g => g.archived).length + items.filter(i => i.archived).length;

  // ---- Group ops ----
  const saveGroup = (draft) => {
    if (!draft.name.trim()) return;
    if (draft.id && groups.some(g => g.id === draft.id)) {
      setGroups(gs => gs.map(g => g.id === draft.id ? { ...g, ...draft, name: draft.name.trim() } : g));
      setToast('Group updated');
    } else {
      const id = crypto.randomUUID();
      const maxSort = groups.reduce((m, g) => Math.max(m, g.sortOrder || 0), 0);
      setGroups(gs => [...gs, {
        id,
        name: draft.name.trim(),
        required: !!draft.required,
        sortOrder: maxSort + 10,
        archived: false,
      }]);
      setToast('Group added');
    }
    setEditingGroup(null);
  };

  const moveGroup = (id, dir) => {
    cmSwapSort(activeGroups, id, dir, (renumbered) => {
      setGroups(gs => gs.map(g => {
        const so = renumbered.get(g.id);
        return so != null && so !== g.sortOrder ? { ...g, sortOrder: so } : g;
      }));
    });
  };

  const archiveGroup = (id) => {
    setGroups(gs => gs.map(g => g.id === id ? { ...g, archived: true } : g));
    setToast('Group archived — hidden from the iPad');
  };
  const restoreGroup = (id) => {
    setGroups(gs => gs.map(g => g.id === id ? { ...g, archived: false } : g));
    setToast('Group restored');
  };
  const deleteGroup = (group) => {
    const childCount = (itemsByGroup[group.id] || []).length;
    const warn = childCount > 0
      ? `Delete "${group.name}" and its ${childCount} item${childCount === 1 ? '' : 's'} permanently? Past crew meal entries keep the names they were logged with.`
      : `Delete "${group.name}" permanently? Past crew meal entries keep the names they were logged with.`;
    if (!confirm(warn)) return;
    // crew_meal_items cascade via the FK ON DELETE CASCADE; dropping them
    // from local state keeps the UI immediate (the redundant client-side
    // deletes are no-ops against already-cascaded rows).
    setItems(is => is.filter(i => i.groupId !== group.id));
    setGroups(gs => gs.filter(g => g.id !== group.id));
    setToast('Group deleted');
  };

  // ---- Item ops ----
  const saveItem = (draft) => {
    if (!draft.name.trim()) return;
    if (draft.id && items.some(i => i.id === draft.id)) {
      setItems(is => is.map(i => i.id === draft.id ? { ...i, ...draft, name: draft.name.trim() } : i));
      setToast('Item updated');
    } else {
      const id = crypto.randomUUID();
      const scope = itemsByGroup[draft.groupId] || [];
      const maxSort = scope.reduce((m, i) => Math.max(m, i.sortOrder || 0), 0);
      setItems(is => [...is, {
        id,
        groupId: draft.groupId,
        name: draft.name.trim(),
        sortOrder: maxSort + 10,
        archived: false,
      }]);
      setToast('Item added');
    }
    setEditingItem(null);
  };

  const moveItem = (groupId, id, dir) => {
    const scope = (itemsByGroup[groupId] || []).filter(i => !i.archived);
    cmSwapSort(scope, id, dir, (renumbered) => {
      setItems(is => is.map(it => {
        const so = renumbered.get(it.id);
        return so != null && so !== it.sortOrder ? { ...it, sortOrder: so } : it;
      }));
    });
  };

  const archiveItem = (id) => {
    setItems(is => is.map(i => i.id === id ? { ...i, archived: true } : i));
    setToast('Item archived — hidden from the iPad');
  };
  const restoreItem = (id) => {
    setItems(is => is.map(i => i.id === id ? { ...i, archived: false } : i));
    setToast('Item restored');
  };
  const deleteItem = (item) => {
    if (!confirm(`Delete "${item.name}" permanently? Past crew meal entries keep the name it was logged with.`)) return;
    setItems(is => is.filter(i => i.id !== item.id));
    setToast('Item deleted');
  };

  const editingItemGroup = editingItem
    ? groups.find(g => g.id === editingItem.groupId)
    : null;

  return (
    <>
      <div style={{ display: 'flex', alignItems: 'center', marginBottom: 14, gap: 10 }}>
        <div style={{ fontSize: 13, color: 'var(--fg-2)' }}>
          Meal slots the iPad walks crew through — each slot lists the choices they can pick from.
        </div>
        <div style={{ flex: 1 }} />
        <button onClick={() => setShowArchived(s => !s)} style={{
          fontSize: 12, color: showArchived ? 'var(--fg-1)' : 'var(--fg-3)',
          padding: '6px 10px', borderRadius: 8,
          background: showArchived ? 'var(--bg-sunken)' : 'transparent',
        }}>
          {showArchived
            ? `Showing archived (${archivedCount})`
            : `Show archived (${archivedCount})`}
        </button>
        <PBtn variant="primary" icon="plus" onClick={() => setEditingGroup('new')}>New group</PBtn>
      </div>

      {activeGroups.length === 0 && (
        <div className="portal-empty" style={{ marginBottom: 14 }}>
          <div style={{ fontSize: 13.5, marginBottom: 14 }}>
            No meal slots yet. Groups are the steps crew walk through on the iPad — an Entree slot, a Rice slot, a Side slot.
          </div>
          <PBtn variant="primary" icon="plus" onClick={() => setEditingGroup('new')}>Add your first group</PBtn>
        </div>
      )}

      {activeGroups.map((group, gi) => (
        <CmGroupCard
          key={group.id}
          group={group}
          activeItems={(itemsByGroup[group.id] || []).filter(i => !i.archived)}
          archivedItems={showArchived ? (itemsByGroup[group.id] || []).filter(i => i.archived) : []}
          canMoveUp={gi > 0}
          canMoveDown={gi < activeGroups.length - 1}
          onMoveGroup={(dir) => moveGroup(group.id, dir)}
          onEditGroup={() => setEditingGroup(group)}
          onArchiveGroup={() => archiveGroup(group.id)}
          onRestoreGroup={() => restoreGroup(group.id)}
          onDeleteGroup={() => deleteGroup(group)}
          onAddItem={() => setEditingItem({ groupId: group.id, _new: true })}
          onEditItem={(item) => setEditingItem(item)}
          onMoveItem={(id, dir) => moveItem(group.id, id, dir)}
          onArchiveItem={archiveItem}
          onRestoreItem={restoreItem}
          onDeleteItem={deleteItem}
        />
      ))}

      {showArchived && archivedGroups.length > 0 && (
        <>
          <div style={{
            margin: '22px 0 10px',
            fontSize: 11, fontWeight: 600, letterSpacing: '0.06em',
            textTransform: 'uppercase', color: 'var(--fg-3)',
          }}>
            Archived groups
          </div>
          {archivedGroups.map(group => (
            <CmGroupCard
              key={group.id}
              group={group}
              activeItems={(itemsByGroup[group.id] || []).filter(i => !i.archived)}
              archivedItems={(itemsByGroup[group.id] || []).filter(i => i.archived)}
              onRestoreGroup={() => restoreGroup(group.id)}
              onDeleteGroup={() => deleteGroup(group)}
            />
          ))}
        </>
      )}

      {editingGroup && (
        <CmGroupEditor
          initial={editingGroup === 'new' ? null : editingGroup}
          onClose={() => setEditingGroup(null)}
          onSave={saveGroup}
        />
      )}
      {editingItem && (
        <CmItemEditor
          initial={editingItem._new ? null : editingItem}
          groupId={editingItem.groupId}
          groupName={(editingItemGroup && editingItemGroup.name) || ''}
          onClose={() => setEditingItem(null)}
          onSave={saveItem}
        />
      )}
    </>
  );
};

// ------------------------------------------------------------------
// Rule chip — "Required · choose one" (ink) vs "Optional" (sunken grey)
// ------------------------------------------------------------------
const CmRuleChip = ({ required }) => {
  const meta = required ? CM_RULE_META.required : CM_RULE_META.optional;
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center',
      fontSize: 11, fontWeight: 500,
      padding: '2px 8px', borderRadius: 999,
      background: meta.bg, color: meta.fg,
      whiteSpace: 'nowrap', flexShrink: 0,
    }}>{meta.chip}</span>
  );
};

// ------------------------------------------------------------------
// Group card — header (name + rule chip + actions) with items inline.
// Handles both active and archived groups; archived cards are dimmed
// and only offer restore / delete-forever.
// ------------------------------------------------------------------
const CmGroupCard = ({
  group, activeItems = [], archivedItems = [],
  canMoveUp, canMoveDown,
  onMoveGroup, onEditGroup, onArchiveGroup, onRestoreGroup, onDeleteGroup,
  onAddItem, onEditItem, onMoveItem, onArchiveItem, onRestoreItem, onDeleteItem,
}) => {
  const isArchived = !!group.archived;
  return (
    <div className="portal-card" style={{ padding: 0, marginBottom: 14, opacity: isArchived ? 0.75 : 1 }}>
      {/* Whole header row is clickable to edit (active groups). Action
          buttons stop propagation so archive doesn't double-fire as
          "edit then archive". row-actions opacity is forced visible —
          the default CSS only un-hides on tr:hover and this card isn't
          a table row (ChecklistTemplate GroupCard precedent). */}
      <div
        onClick={isArchived ? undefined : onEditGroup}
        title={isArchived ? undefined : 'Edit group'}
        className="portal-card-header"
        style={{ alignItems: 'center', cursor: isArchived ? 'default' : 'pointer' }}
      >
        <div style={{ flex: 1, minWidth: 0, display: 'flex', alignItems: 'center', gap: 10 }}>
          <div className="portal-card-title" style={{ fontSize: 15 }}>{group.name}</div>
          <CmRuleChip required={group.required} />
          {isArchived && (
            <span style={{
              fontSize: 10, fontWeight: 700, letterSpacing: '0.06em',
              padding: '1px 6px', borderRadius: 999,
              background: 'var(--bg-sunken)', color: 'var(--fg-3)',
              textTransform: 'uppercase',
            }}>Archived</span>
          )}
        </div>
        <div className="row-actions" style={{ opacity: 1 }} onClick={e => e.stopPropagation()}>
          {isArchived ? (
            <>
              <button onClick={onRestoreGroup} title="Restore"><PIcon name="check" size={13} /></button>
              <button onClick={onDeleteGroup} title="Delete forever"><PIcon name="trash" size={13} /></button>
            </>
          ) : (
            <>
              <button onClick={() => onMoveGroup(-1)} disabled={!canMoveUp} title="Move up"
                style={{ opacity: canMoveUp ? 1 : 0.3 }}><PIcon name="chevronU" size={13} /></button>
              <button onClick={() => onMoveGroup(1)} disabled={!canMoveDown} title="Move down"
                style={{ opacity: canMoveDown ? 1 : 0.3 }}><PIcon name="chevronD" size={13} /></button>
              <button onClick={onEditGroup} title="Edit group"><PIcon name="edit" size={13} /></button>
              <button onClick={onArchiveGroup} title="Archive group"><PIcon name="archive" size={13} /></button>
            </>
          )}
        </div>
      </div>

      <div style={{ padding: '4px 16px 12px' }}>
        {activeItems.length === 0 && archivedItems.length === 0 && (
          <div style={{ padding: '12px 4px 4px', fontSize: 12.5, color: 'var(--fg-3)', lineHeight: 1.5 }}>
            No choices in this slot yet — add the meals crew can pick from.
          </div>
        )}

        {activeItems.map((item, idx) => (
          <div
            key={item.id}
            onClick={isArchived ? undefined : () => onEditItem(item)}
            title={isArchived ? undefined : 'Edit item'}
            style={{
              display: 'flex', alignItems: 'center', gap: 10,
              padding: '8px 4px',
              borderBottom: '1px solid var(--border-2)',
              cursor: isArchived ? 'default' : 'pointer',
            }}
          >
            <div style={{ flex: 1, fontSize: 13, color: 'var(--fg-1)' }}>{item.name}</div>
            {!isArchived && (
              <div className="row-actions" style={{ opacity: 1 }} onClick={e => e.stopPropagation()}>
                <button onClick={() => onMoveItem(item.id, -1)} disabled={idx === 0} title="Move up"
                  style={{ opacity: idx === 0 ? 0.3 : 1 }}><PIcon name="chevronU" size={13} /></button>
                <button onClick={() => onMoveItem(item.id, 1)} disabled={idx === activeItems.length - 1} title="Move down"
                  style={{ opacity: idx === activeItems.length - 1 ? 0.3 : 1 }}><PIcon name="chevronD" size={13} /></button>
                <button onClick={() => onEditItem(item)} title="Edit"><PIcon name="edit" size={13} /></button>
                <button onClick={() => onArchiveItem(item.id)} title="Archive"><PIcon name="archive" size={13} /></button>
              </div>
            )}
          </div>
        ))}

        {archivedItems.map(item => (
          <div
            key={item.id}
            style={{
              display: 'flex', alignItems: 'center', gap: 10,
              padding: '8px 4px',
              borderBottom: '1px solid var(--border-2)',
              opacity: 0.65,
            }}
          >
            <div style={{ flex: 1, fontSize: 13, color: 'var(--fg-2)' }}>
              {item.name}
              <span style={{
                marginLeft: 8,
                fontSize: 10, fontWeight: 700, letterSpacing: '0.06em',
                padding: '1px 6px', borderRadius: 999,
                background: 'var(--bg-sunken)', color: 'var(--fg-3)',
                textTransform: 'uppercase',
              }}>Archived</span>
            </div>
            {!isArchived && (
              <div className="row-actions" style={{ opacity: 1 }} onClick={e => e.stopPropagation()}>
                <button onClick={() => onRestoreItem(item.id)} title="Restore"><PIcon name="check" size={13} /></button>
                <button onClick={() => onDeleteItem(item)} title="Delete forever"><PIcon name="trash" size={13} /></button>
              </div>
            )}
          </div>
        ))}

        {!isArchived && (
          <button onClick={onAddItem} style={{
            marginTop: 10,
            fontSize: 12.5, color: 'var(--fg-2)',
            padding: '6px 10px', borderRadius: 7,
            background: 'transparent',
            border: '1px dashed var(--border-1)',
            width: '100%', textAlign: 'left',
            cursor: 'pointer',
          }}>
            <PIcon name="plus" size={12} /> Add item
          </button>
        )}
      </div>
    </div>
  );
};

// ------------------------------------------------------------------
// Group editor modal — name + required/optional radio-card pair
// (PrepCatalog prepMode picker idiom)
// ------------------------------------------------------------------
const CmGroupEditor = ({ initial, onClose, onSave }) => {
  const [name, setName] = useState((initial && initial.name) || '');
  // New slots default to required — the common case is "everyone picks
  // an entree"; optional is the deliberate call.
  const [required, setRequired] = useState(initial ? !!initial.required : true);
  const canSave = !!name.trim();
  return (
    <PModal open onClose={onClose} width={520}
      title={initial ? `Edit group — ${initial.name}` : 'New group'}
      footer={
        <>
          <PBtn variant="ghost" onClick={onClose}>Cancel</PBtn>
          <PBtn variant="primary" disabled={!canSave} onClick={() => onSave({
            ...(initial || {}),
            id: initial ? initial.id : null,
            name: name.trim(),
            required,
          })}>{initial ? 'Save changes' : 'Add group'}</PBtn>
        </>
      }
    >
      <div style={{ padding: '14px 18px', display: 'flex', flexDirection: 'column', gap: 16 }}>
        <PField label="Name">
          <input
            className="portal-input"
            value={name}
            placeholder="e.g. Entree, Rice, Side"
            onChange={e => setName(e.target.value)}
            autoFocus
            style={{ height: 36 }}
          />
        </PField>

        <PField label="Rule" hint="How the iPad treats this slot when crew logs a meal.">
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            {CM_RULE_OPTIONS.map(opt => {
              const selected = (opt.id === 'required') === required;
              return (
                <button
                  key={opt.id}
                  type="button"
                  onClick={() => setRequired(opt.id === 'required')}
                  style={{
                    textAlign: 'left',
                    padding: '10px 14px',
                    background: selected ? 'var(--bg-sunken)' : '#FFFFFF',
                    border: '1.5px solid ' + (selected ? 'var(--fg-1)' : 'var(--border-2)'),
                    borderRadius: 10,
                    cursor: 'pointer',
                    display: 'flex', alignItems: 'flex-start', gap: 10,
                  }}
                >
                  <span style={{
                    flexShrink: 0,
                    width: 18, height: 18, borderRadius: 999, marginTop: 2,
                    border: '2px solid ' + (selected ? 'var(--fg-1)' : 'var(--border-1)'),
                    background: selected ? 'var(--fg-1)' : 'transparent',
                    display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                  }}>
                    {selected && <span style={{ width: 7, height: 7, borderRadius: 999, background: '#fff' }} />}
                  </span>
                  <div style={{ flex: 1 }}>
                    <div style={{ fontSize: 13.5, fontWeight: 600, color: 'var(--fg-1)' }}>
                      {opt.title}
                    </div>
                    <div style={{ fontSize: 11.5, color: 'var(--fg-3)', marginTop: 2, lineHeight: 1.4 }}>
                      {opt.hint}
                    </div>
                  </div>
                </button>
              );
            })}
          </div>
        </PField>
      </div>
    </PModal>
  );
};

// ------------------------------------------------------------------
// Item editor modal — name only
// ------------------------------------------------------------------
const CmItemEditor = ({ initial, groupId, groupName, onClose, onSave }) => {
  const [name, setName] = useState((initial && initial.name) || '');
  const canSave = !!name.trim();
  const commit = () => onSave({
    ...(initial || {}),
    id: initial ? initial.id : null,
    groupId: (initial && initial.groupId) || groupId,
    name: name.trim(),
  });
  return (
    <PModal open onClose={onClose} width={460}
      title={initial
        ? `Edit item — ${initial.name}`
        : ('New item' + (groupName ? ` — ${groupName}` : ''))}
      footer={
        <>
          <PBtn variant="ghost" onClick={onClose}>Cancel</PBtn>
          <PBtn variant="primary" disabled={!canSave} onClick={commit}>
            {initial ? 'Save changes' : 'Add item'}
          </PBtn>
        </>
      }
    >
      <div style={{ padding: '14px 18px' }}>
        <PField label="Name">
          <input
            className="portal-input"
            value={name}
            placeholder="e.g. Chicken Teriyaki"
            onChange={e => setName(e.target.value)}
            onKeyDown={e => { if (e.key === 'Enter' && canSave) commit(); }}
            autoFocus
            style={{ height: 36 }}
          />
        </PField>
      </div>
    </PModal>
  );
};

window.CrewMeal = CrewMeal;
