// circuits.jsx — circuit/grouping data + new Main Sets screen

// Three naming conventions per exercise + smart columns auto-detect
// Helper: build N identical sets from a template
const setsOf = (n, template) => Array.from({ length: n }, () => ({ done: false, rpe: null, ...template }));

const CIRCUITS = [
  {
    id: 'c1', kind: 'couplet', label: 'Squats & Step and Punch',
    rounds: 2, rest: '60s',
    exercises: [
      {
        id: 'e1',
        systemName: 'SquatShoulderWidthWHF',
        shortName: 'Squats',
        fullName: 'Squats with feet shoulder width with hands free',
        flagged: false,
        sets: [
          // Multi-weight demo: every set carries the same three loads.
          // Real workouts rarely change weight rig mid-circuit, so the
          // labelled list should persist into round 2.
          { reps: 15, weight: [
            { kind: 'ankle weight',    value: 10 },
            { kind: 'chest dumbbell',  value: 4  },
            { kind: 'weight vest',     value: 3  },
          ], weight2: null, equip: null, done: false },
          { reps: 15, weight: [
            { kind: 'ankle weight',    value: 10 },
            { kind: 'chest dumbbell',  value: 4  },
            { kind: 'weight vest',     value: 3  },
          ], weight2: null, equip: null, done: false },
        ],
      },
      {
        id: 'e2',
        systemName: 'StepMediumAndOppCablePunch',
        shortName: 'Step and Opposite Punch',
        fullName: 'Step medium with opp arm cable press',
        flagged: false,
        // Tempo is the whole point of this movement — always surface it,
        // even when global tempo is off or the user is in Basic view.
        focusColumns: ['tempo'],
        sets: [
          { reps: 10, weight: 10, equip: '8', tempo: '2-0-1', rpe: 7, done: false },
          { reps: 10, weight: 10, equip: '8', tempo: '2-0-1', rpe: 7, done: false },
        ],
      },
    ],
  },
  {
    id: 'c2', kind: 'single', label: 'Wall Sit',
    rounds: 5, rest: '45s',
    exercises: [
      {
        id: 'e3',
        systemName: 'WallSitNeutralFeetWShoulderPressNeutralElbowNeutralGrip',
        shortName: 'Wall sit with shoulder press',
        fullName: 'Wall sit with feet hip width with double arm shoulder press with elbows neutral width and neutral grip',
        flagged: false,
        // Equipment hub setting drives the whole press — promote it always.
        focusColumns: ['equip'],
        // Labeled pad settings are part of the rig for THIS exercise, so
        // every set carries the same three settings (trainer sets them
        // once, all rounds use them).
        sets: [
          { reps: '30s', weight: 5, weight2: null, equip: [
            { name: 'Seat Pad',  value: 2 },
            { name: 'Thigh Pad', value: 4 },
            { name: 'Back Pad',  value: 1 },
          ], done: false },
          { reps: '30s', weight: 5, weight2: null, equip: [
            { name: 'Seat Pad',  value: 2 },
            { name: 'Thigh Pad', value: 4 },
            { name: 'Back Pad',  value: 1 },
          ], done: false },
          { reps: '30s', weight: 5, weight2: null, equip: [
            { name: 'Seat Pad',  value: 2 },
            { name: 'Thigh Pad', value: 4 },
            { name: 'Back Pad',  value: 1 },
          ], done: false },
          { reps: '30s', weight: 5, weight2: null, equip: [
            { name: 'Seat Pad',  value: 2 },
            { name: 'Thigh Pad', value: 4 },
            { name: 'Back Pad',  value: 1 },
          ], done: false },
          { reps: '30s', weight: 5, weight2: null, equip: [
            { name: 'Seat Pad',  value: 2 },
            { name: 'Thigh Pad', value: 4 },
            { name: 'Back Pad',  value: 1 },
          ], done: false },
        ],
      },
    ],
  },
  {
    id: 'c3', kind: 'triplet', label: 'Jump Rope & Curls & Chest Press',
    rounds: 4, rest: '60s',
    exercises: [
      {
        id: 'e4',
        systemName: 'JumpRope',
        shortName: 'Jump Rope',
        fullName: 'Jump Rope',
        flagged: false,
        // Cardio intensity — RPE matters more than weight or tempo.
        focusColumns: ['rpe'],
        sets: setsOf(4, { reps: '30s', weight: null, weight2: null, equip: null, rpe: 7 }),
      },
      {
        id: 'e5',
        systemName: 'StanceSingleLegWBicepsCurlsSupinated',
        shortName: 'Single leg stance with biceps curls',
        fullName: 'Single leg stance with supinated biceps curls',
        flagged: false,
        sets: setsOf(4, { reps: 10, weight: 8, weight2: null, equip: null }),
      },
      {
        id: 'e6',
        systemName: 'ChestPressWideAltCableSeatedChair',
        shortName: 'Seated Alternating Chest Press',
        fullName: 'Seated on chair alternating Cable chest press with wide elbows',
        flagged: false,
        // Controlled tempo under load + RPE for progressive overload tracking.
        focusColumns: ['tempo', 'rpe'],
        sets: [
          // Demo of the vertical-expansion pattern: tempo 2-0-6-0 occupies
          // four rows; weight (20) and RPE (7) live on row 1 only.
          { reps: 15, weight: 20, equip: '12', tempo: '2-0-6-0', rpe: 7, done: false },
          { reps: 15, weight: 10, equip: '12', tempo: '2-1-2', rpe: 7, done: false },
          { reps: 15, weight: 10, equip: '12', tempo: '2-1-2', rpe: 7, done: false },
          { reps: 15, weight: 10, equip: '12', tempo: '2-1-2', rpe: 8, done: false },
        ],
      },
    ],
  },
  // Bonus core circuit for variety in demo
  {
    id: 'c4', kind: 'single', label: 'Crunches',
    rounds: 2, rest: '30s',
    exercises: [
      {
        id: 'e7',
        systemName: 'CrunchesHeadCradledWFeetFlatKneesBent',
        shortName: 'Crunches',
        fullName: 'Crunches with head cradled with feet flat with knees bent',
        flagged: false,
        sets: setsOf(2, { reps: 10, weight: null, weight2: null, equip: null }),
      },
    ],
  },
];

const KIND_LABEL = { single: 'Single', couplet: 'Couplet', triplet: 'Triplet', giant: 'Giant Set' };

// Detect which optional columns this circuit needs
function smartColumns(circuit) {
  const cols = { equip: false, tempo: false, rpe: false };
  circuit.exercises.forEach(ex => {
    ex.sets.forEach(s => {
      if (hasAnyValue(s.equip)) cols.equip = true;
      if (hasAnyValue(s.tempo)) cols.tempo = true;
      if (s.rpe != null && s.rpe !== '') cols.rpe = true;
    });
  });
  return cols;
}

// A cell value counts as "present" if any element of the multi-value list
// is non-empty. Lets a set with weight=[10,12,null] still light up the col.
function hasAnyValue(v) {
  if (Array.isArray(v)) return v.some(x => x != null && x !== '');
  if (v == null || v === '') return false;
  if (typeof v === 'string' && v.includes('-')) return v.split('-').some(p => p.trim() !== '');
  return true;
}

// ── Vertical-expansion + labeled-list helpers ────────────────────────────
// Tempo expands VERTICALLY (one row per number). Weight + equipment behave
// differently: weight is summarised inline as "10, 4, 3" in the main row
// and broken out in a labelled box below; equipment never appears in the
// main row at all — it lives only in the breakdown box (the trainer sets
// it once before the workout starts, so per-set lookup is rare).
function expandSet(s) {
  const tempoParts = s.tempo != null && s.tempo !== ''
    ? String(s.tempo).split('-').map(p => p.trim())
    : [];
  const weights = normalizeWeights(s.weight);
  const equips  = normalizeEquip(s.equip);
  // Only tempo drives the vertical row span now — weight/equip live in the
  // expanded box, so they don't grow the main row stack.
  const rowSpan = Math.max(1, tempoParts.length);
  return { tempoParts, weights, equips, rowSpan };
}

// Normalize a `weight` field into [{label, value}]. Accepts a scalar
// ("10" / 10), a bare array ([10, 4, 3]), or an array of labeled entries
// ([{kind, value}]). Empty values are filtered out so the breakdown box
// doesn't render orphan rows.
function normalizeWeights(w) {
  if (w == null || w === '') return [];
  const list = Array.isArray(w) ? w : [w];
  return list
    .map(item => {
      if (item == null || item === '') return null;
      if (typeof item === 'object') {
        return { label: item.kind || item.label || '', value: item.value };
      }
      return { label: '', value: item };
    })
    .filter(x => x !== null && x.value !== '' && x.value != null);
}

// Same shape for equipment. Equipment entries usually carry a name (e.g.
// "Seat Pad") and a small numeric setting, so the {label, value} pair fits
// naturally. Bare string entries ("Hub 4") render with no label.
function normalizeEquip(e) {
  if (e == null || e === '') return [];
  const list = Array.isArray(e) ? e : [e];
  return list
    .map(item => {
      if (item == null || item === '') return null;
      if (typeof item === 'object') {
        return { label: item.name || item.label || '', value: item.value };
      }
      return { label: '', value: item };
    })
    .filter(x => x !== null && x.value !== '' && x.value != null);
}

// Display helpers — what goes in the compact main-row weight cell.
function weightRowText(weights) {
  if (!weights.length) return '';
  return weights.map(w => String(w.value)).join(', ');
}

// Write helpers — update the Nth slot of a multi-value field. They return
// the *new value for that field* so the caller can pass a normal patch to
// onUpdateSet({ tempo: … }) and the rest of the app sees one mutation.
function tempoWithPart(s, idx, val) {
  const parts = s.tempo != null && s.tempo !== '' ? String(s.tempo).split('-') : [];
  while (parts.length <= idx) parts.push('');
  parts[idx] = val;
  // Trim trailing empties so a deletion at the end shrinks the row span.
  while (parts.length > 1 && parts[parts.length - 1] === '') parts.pop();
  return parts.join('-');
}
function listWithSlot(current, idx, val) {
  const list = Array.isArray(current)
    ? [...current]
    : (current != null && current !== '' ? [current] : []);
  while (list.length <= idx) list.push('');
  list[idx] = val;
  while (list.length > 1 && (list[list.length - 1] === '' || list[list.length - 1] == null)) list.pop();
  // Collapse single-item array back to a scalar so simple sets stay simple.
  return list.length <= 1 ? (list[0] ?? '') : list;
}

function MainSetsCircuitsScreen({ theme, cardStyle, onBack, onNav, viewMode, setViewMode, circuits, onUpdateSet, onOpenFeedback, notes, onEditNote, tableStyle, setOrder, setSetOrder, columnPrefs }) {
  const [openId, setOpenId] = React.useState('c1');

  const meta = FLOW_SECTIONS.find(s => s.id === 'main');
  const hasMultiExercise = circuits.some(c => c.exercises.length > 1);
  const order = setOrder || 'interleaved';

  return (
    <>
      {/* Sticky header — TopBar + view/order banner travel together while scrolling */}
      <div style={{ position: 'sticky', top: 0, zIndex: 10, background: theme.bg }}>
        <TopBar theme={theme} title="Main Sets" onBack={onBack}
          right={
            <div
              onClick={() => onNav('main-sets-print')}
              title="Print view — read-only, see all sets at a glance"
              style={{
                display: 'inline-flex', alignItems: 'center', gap: 5,
                padding: '5px 11px 5px 9px', borderRadius: 9999,
                cursor: 'pointer', userSelect: 'none',
                background: theme.isDark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.05)',
                border: `1px solid ${theme.border}`,
                color: theme.text,
                fontFamily: theme.uiFamily, fontSize: 10, fontWeight: 700,
                textTransform: 'uppercase', letterSpacing: 0.5,
              }}>
              <Icon name="expand" size={11} color={theme.text} strokeWidth={2.2}/>
              Print view
            </div>
          }/>
        {/* Banner: meta line + view/order toggles. ONE source of truth for both. */}
        <div style={{
          padding: '0 18px 10px',
          display: 'flex', flexDirection: 'column', gap: 8,
          background: theme.bg,
          borderBottom: `1px solid ${theme.border}`,
        }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <DepthDots depth={meta.depth} theme={theme} active/>
            <div style={{ fontFamily: theme.uiFamily, fontSize: 12, color: theme.muted }}>
              {circuits.length} circuits · {meta.est}
            </div>
            <div
              onClick={() => onNav('settings')}
              title="Settings — toggle which optional columns appear globally"
              style={{
                marginLeft: 'auto',
                display: 'inline-flex', alignItems: 'center', gap: 4,
                padding: '4px 9px', borderRadius: 9999,
                cursor: 'pointer', userSelect: 'none',
                background: 'transparent',
                border: `1px solid ${theme.border}`,
                color: theme.muted,
                fontFamily: theme.uiFamily, fontSize: 10, fontWeight: 700,
                textTransform: 'uppercase', letterSpacing: 0.5,
              }}>
              <Icon name="gear" size={12} color={theme.muted} strokeWidth={1.8}/>
              Columns
            </div>
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
            <Segmented theme={theme} label="View" value={viewMode}
              options={[
                { v: 'basic',    label: 'Basic'    },
                { v: 'detailed', label: 'Detailed' },
              ]}
              onChange={setViewMode}/>
            {hasMultiExercise && (
              <Segmented theme={theme} label="Order" value={order}
                options={[
                  { v: 'interleaved', label: 'Interleaved' },
                  { v: 'by-exercise', label: 'By exercise' },
                ]}
                onChange={setSetSetOrderOrDefault(setSetOrder)}/>
            )}
          </div>
        </div>
      </div>

      <div style={{ padding: '14px 18px 100px', display: 'flex', flexDirection: 'column', gap: 10 }}>
        {/* Session-level note row */}
        <SessionNoteRow theme={theme} cardStyle={cardStyle}
          note={notes.session}
          onEdit={() => onEditNote('session', null, 'Session note')}/>

        {circuits.map((c, i) => {
          const isOpen = openId === c.id;
          return (
            <CircuitCard key={c.id} theme={theme} cardStyle={cardStyle}
              circuit={c} index={i} isOpen={isOpen} mode={viewMode} tableStyle={tableStyle}
              order={order}
              columnPrefs={columnPrefs}
              onToggle={() => setOpenId(isOpen ? null : c.id)}
              onUpdateSet={onUpdateSet} onOpenFeedback={onOpenFeedback} onNav={onNav}
              circuitNote={notes.circuit[c.id]}
              exerciseNotes={notes.exercise}
              onEditNote={onEditNote}
            />
          );
        })}
      </div>
    </>
  );
}

// Helper: gracefully no-op if setSetOrder wasn't passed.
function setSetSetOrderOrDefault(fn) { return fn || (() => {}); }

// Reusable segmented control for the sticky banner.
function Segmented({ theme, label, value, options, onChange }) {
  return (
    <div style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
      <span style={{
        fontFamily: theme.uiFamily, fontSize: 10, fontWeight: 700,
        color: theme.muted, textTransform: 'uppercase', letterSpacing: 0.5,
      }}>{label}</span>
      <div style={{
        display: 'inline-flex', gap: 2, padding: 2, borderRadius: 9999,
        background: theme.isDark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.05)',
      }}>
        {options.map(o => (
          <div key={o.v} onClick={() => onChange(o.v)} style={{
            padding: '4px 11px', borderRadius: 9999, cursor: 'pointer',
            background: value === o.v ? theme.primary : 'transparent',
            color: value === o.v ? theme.primaryInk : theme.muted,
            fontFamily: theme.uiFamily, fontSize: 10, fontWeight: 700,
            textTransform: 'uppercase', letterSpacing: 0.4,
            whiteSpace: 'nowrap',
          }}>{o.label}</div>
        ))}
      </div>
    </div>
  );
}

function CircuitCard({ theme, cardStyle, circuit, index, isOpen, mode, tableStyle, order, onToggle, onUpdateSet, onOpenFeedback, onNav, circuitNote, exerciseNotes, onEditNote, columnPrefs }) {
  // Single-exercise circuits are inherently linear — interleaving is a no-op,
  // so we fall back to by-exercise to avoid the legend/badge overhead.
  const effectiveOrder = circuit.exercises.length < 2 ? 'by-exercise' : order;
  const cols = smartColumns(circuit);
  const completed = circuit.exercises.reduce((n, ex) => n + ex.sets.filter(s => s.done).length, 0);
  const total = circuit.exercises.reduce((n, ex) => n + ex.sets.length, 0);
  const pct = completed / total;

  return (
    <Card theme={theme} cardStyle={cardStyle} padding={0}>
      {/* Header — always tappable */}
      <div onClick={onToggle} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '14px', cursor: 'pointer' }}>
        <div style={{
          width: 38, height: 38, borderRadius: 10,
          background: theme.isDark ? 'rgba(232,131,11,0.14)' : 'rgba(232,131,11,0.10)',
          color: theme.primary, fontFamily: theme.displayFamily, fontSize: 16, fontWeight: 700,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
        }}>{String(index + 1).padStart(2, '0')}</div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <span style={{
              fontFamily: theme.uiFamily, fontSize: 9, fontWeight: 700, letterSpacing: 0.5, textTransform: 'uppercase',
              color: theme.muted, padding: '2px 6px', borderRadius: 4,
              background: theme.isDark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.05)',
            }}>{KIND_LABEL[circuit.kind]}</span>
            <span style={{ fontFamily: theme.uiFamily, fontSize: 11, color: theme.muted }}>
              ×{circuit.rounds} · rest {circuit.rest}
            </span>
          </div>
          <div style={{ fontFamily: theme.uiFamily, fontSize: 14, fontWeight: 600, color: theme.text, marginTop: 3 }}>
            {circuit.label}
          </div>
          <div style={{ fontFamily: theme.uiFamily, fontSize: 11, color: theme.muted, marginTop: 2 }}>
            {circuit.exercises.map(e => e.shortName).join(' + ')}
          </div>
        </div>
        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 4 }}>
          <div style={{ fontFamily: theme.monoFamily, fontSize: 11, color: theme.muted }}>{completed}/{total}</div>
          <Icon name={isOpen ? 'chevronDown' : 'chevron'} size={14} color={theme.muted}/>
        </div>
      </div>
      {/* Progress bar */}
      <div style={{ height: 3, background: theme.isDark ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.04)' }}>
        <div style={{ width: `${pct * 100}%`, height: '100%', background: pct === 1 ? theme.success : theme.primary, transition: 'width 0.3s' }}/>
      </div>

      {isOpen && (
        <div style={{ borderTop: `1px solid ${theme.border}` }}>
          {effectiveOrder === 'interleaved' ? (
            <InterleavedBlock theme={theme} circuit={circuit} mode={mode}
              columnPrefs={columnPrefs}
              onUpdateSet={onUpdateSet}
              onOpenFeedback={onOpenFeedback}
              onOpenDetail={(ex) => onNav(`exercise-${ex.id}`, { ex })}
              exerciseNotes={exerciseNotes}
              onEditNote={onEditNote}/>
          ) : (
            circuit.exercises.map((ex, exIdx) => (
              <ExerciseInlineBlock key={ex.id} theme={theme} exercise={ex} exIdx={exIdx} mode={mode} cols={cols} tableStyle={tableStyle}
                columnPrefs={columnPrefs}
                isLast={exIdx === circuit.exercises.length - 1}
                note={exerciseNotes[ex.id]}
                onEditNote={() => onEditNote('exercise', ex.id, ex.shortName)}
                onUpdateSet={(setIdx, patch) => onUpdateSet(ex.id, setIdx, patch)}
                onOpenFeedback={() => onOpenFeedback({ exerciseId: ex.id, exerciseName: ex.shortName })}
                onOpenDetail={() => onNav(`exercise-${ex.id}`, { ex })}
              />
            ))
          )}

          {/* Circuit-level note */}
          <CircuitNoteRow theme={theme} note={circuitNote}
            onEdit={() => onEditNote('circuit', circuit.id, circuit.label)}/>
        </div>
      )}
    </Card>
  );
}

// ─── Session note (top of Main Sets) ─────────────────────────────────────
function SessionNoteRow({ theme, cardStyle, note, onEdit }) {
  const has = note && note.trim();
  return (
    <Card theme={theme} cardStyle={cardStyle} padding={0}
      onClick={onEdit}
      style={{
        cursor: 'pointer',
        background: has ? (theme.isDark ? 'rgba(255,182,39,0.10)' : 'rgba(255,182,39,0.12)') : 'transparent',
        borderColor: has ? theme.primary + '55' : theme.border,
        borderStyle: has ? 'solid' : 'dashed',
      }}
    >
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '11px 14px' }}>
        <Icon name="notes" size={16} color={has ? theme.primary : theme.muted}/>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontFamily: theme.uiFamily, fontSize: 10, fontWeight: 700, letterSpacing: 0.5, textTransform: 'uppercase', color: theme.muted }}>
            Session note
          </div>
          <div style={{
            fontFamily: theme.uiFamily, fontSize: 13, marginTop: 2,
            color: has ? theme.text : theme.muted, fontStyle: has ? 'normal' : 'italic',
            whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
          }}>
            {has ? note : 'Add a note about today\'s session…'}
          </div>
        </div>
        <Icon name="edit" size={14} color={theme.muted}/>
      </div>
    </Card>
  );
}

// ─── Circuit-level note (bottom of expanded circuit) ─────────────────────
function CircuitNoteRow({ theme, note, onEdit }) {
  const has = note && note.trim();
  return (
    <div onClick={onEdit} style={{
      display: 'flex', alignItems: 'flex-start', gap: 8,
      padding: '10px 14px',
      background: has ? (theme.isDark ? 'rgba(255,182,39,0.06)' : 'rgba(255,182,39,0.08)') : 'transparent',
      borderTop: `1px dashed ${theme.border}`,
      cursor: 'pointer',
    }}>
      <Icon name="notes" size={13} color={has ? theme.primary : theme.muted}/>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontFamily: theme.uiFamily, fontSize: 9, fontWeight: 700, letterSpacing: 0.5, textTransform: 'uppercase', color: theme.muted }}>
          Circuit note
        </div>
        <div style={{ fontFamily: theme.uiFamily, fontSize: 12, color: has ? theme.text : theme.muted, fontStyle: has ? 'normal' : 'italic', marginTop: 1 }}>
          {has ? note : 'Add a note for this circuit…'}
        </div>
      </div>
    </div>
  );
}

function ExerciseInlineBlock({ theme, exercise, exIdx, mode, cols, tableStyle, isLast, onUpdateSet, onOpenFeedback, onOpenDetail, note, onEditNote, columnPrefs }) {
  const ex = exercise;
  const detailed = mode === 'detailed';
  const accent = exerciseColor(theme, exIdx);
  const weightUnit = (columnPrefs && columnPrefs.weightUnit) || 'lb';

  // Detect which optional columns THIS exercise has data for…
  const exDataCols = {
    equip: cols.equip && ex.sets.some(s => s.equip != null && s.equip !== ''),
    tempo: cols.tempo && ex.sets.some(s => s.tempo != null && s.tempo !== ''),
    rpe:   cols.rpe   && ex.sets.some(s => s.rpe   != null && s.rpe   !== ''),
  };
  // Per-exercise focus override (e.g. focusColumns: ['tempo'] on a tempo-
  // critical movement) — promotes the column above global prefs and Basic.
  const focusCols = exerciseFocusCols(ex);
  // …gate them through global column prefs AND the basic/detailed mask AND
  // any focus overrides.
  const exCols = effectiveColumns(exDataCols, columnPrefs, mode, focusCols);
  // ⋯ chip: row has data AND that column is not currently visible (basic
  // OR settings-hidden — same affordance, both cases).
  const hiddenLabels = [];
  if (exDataCols.equip && !exCols.equip) hiddenLabels.push('Equip');
  if (exDataCols.tempo && !exCols.tempo) hiddenLabels.push('Tempo');
  if (exDataCols.rpe   && !exCols.rpe)   hiddenLabels.push('RPE');
  const hasHidden = hiddenLabels.length > 0;
  // Focus labels — promoted columns that override Basic / Settings.
  const focusLabels = [];
  if (focusCols.equip && exDataCols.equip) focusLabels.push('Equip');
  if (focusCols.tempo && exDataCols.tempo) focusLabels.push('Tempo');
  if (focusCols.rpe   && exDataCols.rpe)   focusLabels.push('RPE');
  const hasFocus = focusLabels.length > 0;

  return (
    <div style={{
      padding: '12px 14px 12px 18px',
      borderBottom: isLast ? 'none' : `1px solid ${theme.border}`,
      borderLeft: `3px solid ${accent}`,
      background: exRgba(accent, theme.isDark ? 0.05 : 0.035),
    }}>
      {/* Exercise header */}
      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, marginBottom: 8 }}>
        <div onClick={onOpenDetail} style={{
          width: 28, height: 28, borderRadius: 7, flexShrink: 0,
          background: exRgba(accent, theme.isDark ? 0.20 : 0.14),
          display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
        }}>
          <Icon name="play" size={11} color={accent}/>
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <div style={{ fontFamily: theme.uiFamily, fontSize: 13, fontWeight: 600, color: theme.text }}>
              {detailed ? ex.fullName : ex.shortName}
            </div>
            {ex.flagged && <div style={{ width: 6, height: 6, borderRadius: 9999, background: theme.danger }}/>}
            {hasFocus && (
              <div
                title={`Focus: ${focusLabels.join(' · ')} — promoted into Basic for this exercise`}
                style={{
                  display: 'inline-flex', alignItems: 'center', gap: 4,
                  padding: '2px 7px 2px 5px', borderRadius: 9999,
                  background: accent,
                  color: exerciseChipInk(theme, exIdx),
                  fontFamily: theme.uiFamily, fontSize: 9, fontWeight: 700,
                  textTransform: 'uppercase', letterSpacing: 0.5,
                }}>
                <span style={{ fontSize: 10, lineHeight: 1 }}>★</span>
                <span>Focus · {focusLabels.join(' · ')}</span>
              </div>
            )}
            {hasHidden && (
              <div
                title={`Hidden: ${hiddenLabels.join(' · ')} — switch to Detailed or enable in Settings`}
                style={{
                  display: 'inline-flex', alignItems: 'center', gap: 3,
                  padding: '1px 6px 2px', borderRadius: 9999,
                  background: exRgba(accent, theme.isDark ? 0.22 : 0.14),
                  color: accent,
                  fontFamily: theme.monoFamily, fontSize: 13, fontWeight: 700,
                  lineHeight: 1, letterSpacing: 1,
                }}>⋯</div>
            )}
          </div>
          {detailed && (
            <div style={{ fontFamily: theme.monoFamily, fontSize: 9, color: theme.muted, marginTop: 2, opacity: 0.7 }}>
              {ex.systemName}
            </div>
          )}
          {detailed && ex.flagged && ex.flagNote && (
            <div style={{ fontFamily: theme.uiFamily, fontSize: 11, color: theme.danger, marginTop: 4 }}>
              ⚐ {ex.flagNote}
            </div>
          )}
        </div>
        <button onClick={onEditNote} title="Add note" style={{
          padding: '4px 9px', borderRadius: 9999,
          background: note ? theme.primary : 'transparent',
          border: `1px solid ${note ? theme.primary : theme.border}`,
          color: note ? theme.primaryInk : theme.muted,
          fontFamily: theme.uiFamily, fontSize: 10, fontWeight: 600, cursor: 'pointer', whiteSpace: 'nowrap',
          display: 'inline-flex', alignItems: 'center', gap: 4,
        }}>
          <Icon name="notes" size={11} color={note ? theme.primaryInk : theme.muted} strokeWidth={2}/>
          Note
        </button>
        <button onClick={onOpenFeedback} title="Pain or feedback" style={{
          padding: '4px 9px', borderRadius: 9999, background: 'transparent',
          border: `1px solid ${theme.border}`, color: theme.muted,
          fontFamily: theme.uiFamily, fontSize: 10, fontWeight: 600, cursor: 'pointer', whiteSpace: 'nowrap',
          display: 'inline-flex', alignItems: 'center', gap: 4,
        }}>
          <Icon name="flag" size={11} color={theme.muted} strokeWidth={2}/>
          Pain
        </button>
      </div>

      {/* Exercise-level note preview */}
      {note && (
        <div onClick={onEditNote} style={{
          marginLeft: 38, marginBottom: 8, padding: '6px 10px',
          background: theme.isDark ? 'rgba(255,182,39,0.08)' : 'rgba(255,182,39,0.10)',
          borderLeft: `2px solid ${theme.primary}`,
          borderRadius: '0 6px 6px 0',
          fontFamily: theme.uiFamily, fontSize: 11, color: theme.text, lineHeight: 1.4,
          cursor: 'pointer',
        }}>{note}</div>
      )}

      {/* Set table — non-negotiable cols + smart cols */}
      <SetTable theme={theme} ex={ex} exIdx={exIdx} cols={exCols} tableStyle={tableStyle} weightUnit={weightUnit} onUpdateSet={onUpdateSet}/>
    </div>
  );
}

// Router — picks the table style
function SetTable({ theme, ex, exIdx, cols, tableStyle, weightUnit, onUpdateSet }) {
  if (tableStyle === 'swipe')  return <SwipeTable  theme={theme} ex={ex} exIdx={exIdx} cols={cols} onUpdateSet={onUpdateSet}/>;
  if (tableStyle === 'flip')   return <FlipTable   theme={theme} ex={ex} exIdx={exIdx} cols={cols} onUpdateSet={onUpdateSet}/>;
  if (tableStyle === 'expand') return <ExpandTable theme={theme} ex={ex} exIdx={exIdx} cols={cols} onUpdateSet={onUpdateSet}/>;
  return <ClassicTable theme={theme} ex={ex} exIdx={exIdx} cols={cols} weightUnit={weightUnit} onUpdateSet={onUpdateSet}/>;
}

function ClassicTable({ theme, ex, exIdx, cols, weightUnit, onUpdateSet }) {
  const wtLabel = `Wt (${weightUnit || 'lb'})`;
  const accent = exerciseColor(theme, exIdx);
  // Equipment never appears as a row column anymore — it lives only in the
  // per-set expanded breakdown. Same for individual weight entries when a
  // set carries more than one load (those collapse to a comma list in row).
  const colsList = ['Set', 'Reps', wtLabel];
  if (cols.tempo) colsList.push('Tempo');
  if (cols.rpe)   colsList.push('RPE');
  colsList.push('✓');
  colsList.push(''); // chevron slot

  // Build grid template based on present cols. Weight gets more width now
  // that it may carry a comma list like "10, 4, 3".
  const parts = ['24px', '0.8fr', '1.4fr'];
  if (cols.tempo) parts.push('46px');
  if (cols.rpe)   parts.push('34px');
  parts.push('26px');
  parts.push('20px'); // chevron
  const grid = parts.join(' ');

  return (
    <div style={{ borderRadius: 8, overflow: 'hidden', border: `1px solid ${theme.border}` }}>
      <div style={{
        display: 'grid', gridTemplateColumns: grid, gap: 6,
        padding: '6px 8px 6px 12px', background: theme.isDark ? 'rgba(255,255,255,0.03)' : 'rgba(0,0,0,0.025)',
        fontFamily: theme.uiFamily, fontSize: 9, fontWeight: 600, color: theme.muted,
        textTransform: 'uppercase', letterSpacing: 0.4,
      }}>
        {colsList.map((c, i) => <div key={i} style={{ textAlign: 'left' }}>{c}</div>)}
      </div>
      {ex.sets.map((s, setIdx) => (
        <SetGroup key={setIdx} theme={theme} accent={accent}
          grid={grid} cols={cols} set={s} setIdx={setIdx} exIdx={exIdx}
          onUpdateSet={onUpdateSet}
          showSetCol
        />
      ))}
    </div>
  );
}

// Renders one set as a *vertical group* of rows. The colored accent bar +
// background tint span the full group so multi-value sets read as one unit.
// When the set has multi-value weight OR any equipment data, a tappable
// chevron toggles a labelled breakdown box at the bottom of the group.
function SetGroup({ theme, accent, grid, cols, set, setIdx, exIdx, onUpdateSet, showSetCol }) {
  const { tempoParts, weights, equips, rowSpan } = expandSet(set);
  const hasBreakdown = weights.length > 1 || equips.length > 0 || weights.some(w => w.label);
  const [open, setOpen] = React.useState(false);
  const groupBg = set.done
    ? (theme.isDark ? 'rgba(52,211,153,0.10)' : 'rgba(46,125,50,0.07)')
    : exerciseSetTint(theme, exIdx, setIdx);

  // Row weight cell content: single value editable; multi values render as
  // a static comma list (edits happen in the expanded breakdown).
  const weightRow = weightRowText(weights);

  return (
    <div style={{
      borderTop: setIdx === 0 ? 'none' : `0.5px solid ${theme.border}`,
      borderLeft: `4px solid ${accent}`,
      background: groupBg,
    }}>
      {Array.from({ length: rowSpan }).map((_, rowIdx) => {
        const primary = rowIdx === 0;
        return (
          <div key={rowIdx} style={{
            display: 'grid', gridTemplateColumns: grid, gap: 6,
            padding: '7px 8px 7px 8px', alignItems: 'center',
            borderTop: rowIdx === 0 ? 'none' : `0.5px dotted ${theme.border}`,
            fontFamily: theme.monoFamily, fontSize: 12, color: theme.text,
          }}>
            {showSetCol && (
              <Cell theme={theme}
                value={primary ? setIdx + 1 : null}
                editable={false}
                emphasis={primary}
                accent={accent}/>
            )}
            <Cell theme={theme}
              value={primary ? (set.reps ?? '') : null}
              editable={primary}
              onChange={v => onUpdateSet(setIdx, { reps: v })}/>
            {primary ? (
              weights.length > 1 ? (
                <WeightListCell theme={theme} text={weightRow}/>
              ) : (
                <Cell theme={theme}
                  value={weights.length === 1 ? weights[0].value : null}
                  editable={weights.length === 1}
                  onChange={v => onUpdateSet(setIdx, { weight: listWithSlot(set.weight, 0, v) })}/>
              )
            ) : <Dash theme={theme}/>}
            {cols.tempo && (
              <Cell theme={theme}
                value={tempoParts[rowIdx] != null ? tempoParts[rowIdx] : null}
                editable={tempoParts[rowIdx] != null}
                onChange={v => onUpdateSet(setIdx, { tempo: tempoWithPart(set, rowIdx, v) })}/>
            )}
            {cols.rpe && (
              <Cell theme={theme}
                value={primary ? (set.rpe ?? '') : null}
                editable={primary}
                onChange={v => onUpdateSet(setIdx, { rpe: v })}/>
            )}
            <div style={{ display: 'flex', justifyContent: 'flex-start' }}>
              {primary
                ? <DoneCheck theme={theme} done={set.done} onClick={() => onUpdateSet(setIdx, { done: !set.done })}/>
                : <Dash theme={theme}/>}
            </div>
            <div style={{ display: 'flex', justifyContent: 'flex-start' }}>
              {primary && hasBreakdown ? (
                <ExpandChevron theme={theme} open={open} accent={accent}
                  onClick={() => setOpen(v => !v)}/>
              ) : <span/>}
            </div>
          </div>
        );
      })}
      {hasBreakdown && open && (
        <ExpandedBreakdown theme={theme} accent={accent}
          weights={weights} equips={equips}
          onChangeWeight={(idx, v) => onUpdateSet(setIdx, { weight: replaceLabeled(set.weight, idx, v, 'kind') })}
          onChangeEquip={(idx, v)  => onUpdateSet(setIdx, { equip:  replaceLabeled(set.equip,  idx, v, 'name') })}
        />
      )}
    </div>
  );
}

// Static read-only cell for the comma-separated weight list. Kept separate
// from <Cell> because we don't want trainers editing a synthesised string
// — the source of truth is the labelled list in the breakdown box.
function WeightListCell({ theme, text }) {
  return (
    <div style={{
      fontFamily: theme.monoFamily, fontSize: 12, fontWeight: 600,
      color: theme.text, textAlign: 'left',
      whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
    }} title={text}>{text}</div>
  );
}

function ExpandChevron({ theme, open, accent, onClick }) {
  // Matches the chevron pattern used on circuit-card headers: a small
  // gray right-facing chevron when collapsed, rotated down when open.
  return (
    <div onClick={onClick} style={{
      width: 22, height: 22, borderRadius: 9999,
      background: open ? accent : 'transparent',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      cursor: 'pointer', userSelect: 'none',
    }} title={open ? 'Hide details' : 'Show weight + equipment'}>
      <Icon name={open ? 'chevronDown' : 'chevron'} size={14}
        color={open ? '#fff' : theme.muted}/>
    </div>
  );
}

// Replace the Nth entry in a labelled list, preserving its label. Used by
// the breakdown editors so a tweak to "Weight 2 → 5" updates only that
// slot, not the whole array.
function replaceLabeled(current, idx, val, labelKey) {
  const list = Array.isArray(current) ? [...current] : (current != null && current !== '' ? [current] : []);
  const prev = list[idx];
  const existingLabel = (prev && typeof prev === 'object') ? (prev[labelKey] || prev.label || '') : '';
  list[idx] = existingLabel
    ? { [labelKey]: existingLabel, value: val }
    : val;
  return list;
}

// The labelled detail box that appears below a set's main rows when the
// trainer taps the chevron. Two columns: weight entries on the left,
// equipment entries on the right (or stacked if either side is empty).
function ExpandedBreakdown({ theme, accent, weights, equips, onChangeWeight, onChangeEquip }) {
  // A lone unlabeled weight is already shown in the main row, so we don't
  // duplicate it here — only surface the weight column when there are
  // multiple loads OR at least one carries a label worth reading.
  const showWeights = weights.length > 1 || weights.some(w => w.label);
  const showEquips  = equips.length  > 0;
  if (!showWeights && !showEquips) return null;
  const showBoth = showWeights && showEquips;
  return (
    <div style={{
      padding: '10px 12px 12px',
      borderTop: `0.5px dashed ${theme.border}`,
      background: theme.isDark ? 'rgba(255,255,255,0.025)' : 'rgba(0,0,0,0.02)',
      display: 'grid',
      gridTemplateColumns: showBoth ? '1fr 1fr' : '1fr',
      gap: 14,
    }}>
      {showWeights && (
        <BreakdownColumn theme={theme} accent={accent} title="Weight"
          entries={weights}
          slotLabel={(i) => `Weight ${i + 1}`}
          onChange={onChangeWeight}/>
      )}
      {showEquips && (
        <BreakdownColumn theme={theme} accent={accent} title="Equipment"
          entries={equips}
          slotLabel={(i, e) => e.label || `Setting ${i + 1}`}
          hideSlotPrefix
          onChange={onChangeEquip}/>
      )}
    </div>
  );
}

function BreakdownColumn({ theme, accent, title, entries, slotLabel, hideSlotPrefix, onChange }) {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
      <div style={{
        fontFamily: theme.uiFamily, fontSize: 9, fontWeight: 700,
        color: theme.muted, textTransform: 'uppercase', letterSpacing: 0.6,
      }}>{title}</div>
      {entries.map((entry, i) => {
        const slot = slotLabel(i, entry);
        return (
          <div key={i} style={{
            display: 'grid', gridTemplateColumns: hideSlotPrefix ? '1fr 56px' : 'auto 1fr 56px',
            gap: 8, alignItems: 'center',
          }}>
            {!hideSlotPrefix && (
              <span style={{
                fontFamily: theme.uiFamily, fontSize: 10, fontWeight: 700,
                color: accent, letterSpacing: 0.4, whiteSpace: 'nowrap',
              }}>{slot}</span>
            )}
            <span style={{
              fontFamily: theme.uiFamily, fontSize: 11, fontWeight: 500,
              color: theme.text, whiteSpace: 'nowrap',
              overflow: 'hidden', textOverflow: 'ellipsis',
            }} title={entry.label}>
              {hideSlotPrefix ? entry.label : entry.label}
            </span>
            <BreakdownInput theme={theme} value={entry.value}
              onChange={v => onChange && onChange(i, v)}/>
          </div>
        );
      })}
    </div>
  );
}

function BreakdownInput({ theme, value, onChange }) {
  return (
    <div style={{ display: 'flex', justifyContent: 'flex-start' }}>
      <div style={{
        display: 'inline-flex', alignItems: 'center',
        padding: '2px 6px', borderRadius: 4,
        background: theme.isDark ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.04)',
      }}>
        <input type="text" value={value == null ? '' : value}
          onChange={e => onChange && onChange(e.target.value)}
          style={{
            width: '3.4ch', minWidth: 0, background: 'transparent', border: 'none', outline: 'none',
            fontFamily: theme.monoFamily, fontSize: 12, fontWeight: 600,
            color: theme.text, textAlign: 'left',
          }}/>
      </div>
    </div>
  );
}

function DoneCheck({ theme, done, onClick }) {
  return (
    <div onClick={onClick} style={{
      width: 22, height: 22, borderRadius: 5,
      background: done ? theme.success : 'transparent',
      border: done ? 'none' : `1.5px solid ${theme.border}`,
      display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
    }}>
      {done && <Icon name="check" size={12} color="#fff" strokeWidth={3}/>}
    </div>
  );
}

// Universal table cell. value=null → renders a dash; editable=true wraps in
// an input. Left-aligned everywhere — anchored numerals stay still as the
// user types instead of jumping around in a centered cell.
function Cell({ theme, value, editable, emphasis, accent, onChange }) {
  if (value === null || value === undefined) return <Dash theme={theme}/>;
  if (editable) {
    // The hit-target is intentionally TIGHT — just wide enough for 2–3 digits.
    // Trainers scroll the table constantly mid-workout, and a full-width tap
    // target invites the keyboard at the wrong moment. Left-anchored in the
    // cell so the visible value stays put as the user types.
    return (
      <div style={{ display: 'flex', justifyContent: 'flex-start' }}>
        <div style={{
          display: 'inline-flex', alignItems: 'center',
          padding: '2px 5px', borderRadius: 4,
          background: theme.isDark ? 'rgba(255,255,255,0.03)' : 'rgba(0,0,0,0.02)',
        }}>
          <input type="text" value={value === null || value === undefined ? '' : value}
            onChange={e => onChange && onChange(e.target.value)}
            style={{
              width: '3.2ch', minWidth: 0, background: 'transparent', border: 'none', outline: 'none',
              fontFamily: theme.monoFamily, fontSize: 12, fontWeight: 600,
              color: theme.text, textAlign: 'left',
            }}/>
        </div>
      </div>
    );
  }
  // Read-only value (e.g. Set # column on row 0).
  return (
    <div style={{
      fontFamily: theme.monoFamily, fontSize: 12, fontWeight: emphasis ? 700 : 600,
      color: emphasis && accent ? accent : theme.text,
      textAlign: 'left',
    }}>{value}</div>
  );
}

function Dash({ theme }) {
  return (
    <div style={{
      fontFamily: theme.monoFamily, fontSize: 12, fontWeight: 600,
      color: theme.muted, opacity: 0.4, textAlign: 'left', paddingLeft: 5,
      userSelect: 'none',
    }}>—</div>
  );
}

function CellInput({ theme, value, suffix, onChange }) {
  return (
    <div style={{ display: 'flex', justifyContent: 'flex-start' }}>
      <div style={{ display: 'inline-flex', alignItems: 'baseline', gap: 2, padding: '2px 4px', borderRadius: 4, background: theme.isDark ? 'rgba(255,255,255,0.03)' : 'rgba(0,0,0,0.02)' }}>
        <input type="text" value={value === null || value === undefined ? '' : value} onChange={e => onChange(e.target.value)}
          style={{ width: '3.2ch', minWidth: 0, background: 'transparent', border: 'none', outline: 'none',
            fontFamily: theme.monoFamily, fontSize: 12, fontWeight: 600, color: theme.text, textAlign: 'left' }}/>
        {suffix && <span style={{ fontFamily: theme.uiFamily, fontSize: 8, color: theme.muted }}>{suffix}</span>}
      </div>
    </div>
  );
}

// ─── Note editor — bottom sheet for session/circuit/exercise notes ───────
function NoteEditor({ theme, editor, notes, onSave, onClose }) {
  const initial = editor.scope === 'session' ? notes.session : (notes[editor.scope]?.[editor.id] || '');
  const [val, setVal] = React.useState(initial);
  const inputRef = React.useRef(null);
  React.useEffect(() => { setTimeout(() => inputRef.current?.focus(), 80); }, []);

  const scopeLabel = editor.scope === 'session' ? 'Session' : editor.scope === 'circuit' ? 'Circuit' : 'Exercise';
  const placeholder = {
    session: 'How is the overall session going? Energy, focus, anything to remember…',
    circuit: 'Notes about this circuit — pacing, rest changes, observations…',
    exercise: 'Form cues, modifications, how it felt…',
  }[editor.scope];

  return (
    <div onClick={onClose} style={{
      position: 'absolute', inset: 0, zIndex: 450,
      background: 'rgba(10,5,3,0.55)',
      display: 'flex', alignItems: 'flex-end', justifyContent: 'center',
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        width: '100%', background: theme.bg,
        borderTopLeftRadius: 18, borderTopRightRadius: 18,
        padding: '16px 18px 22px',
        borderTop: `1px solid ${theme.border}`,
        maxHeight: '85%',
        display: 'flex', flexDirection: 'column',
      }}>
        <div style={{ width: 40, height: 4, borderRadius: 9999, background: theme.border, margin: '0 auto 14px' }}/>

        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
          <span style={{
            fontFamily: theme.uiFamily, fontSize: 9, fontWeight: 700, letterSpacing: 0.6, textTransform: 'uppercase',
            padding: '3px 8px', borderRadius: 4,
            background: theme.primary, color: theme.primaryInk,
          }}>{scopeLabel} Note</span>
        </div>
        <div style={{ fontFamily: theme.displayFamily, fontSize: 19, fontWeight: 600, color: theme.text, marginBottom: 12, lineHeight: 1.2 }}>
          {editor.label || (editor.scope === 'session' ? 'Today\'s session' : '')}
        </div>

        <textarea
          ref={inputRef}
          value={val}
          onChange={e => setVal(e.target.value)}
          placeholder={placeholder}
          style={{
            width: '100%', minHeight: 130, resize: 'vertical',
            border: `1px solid ${theme.border}`, borderRadius: 10,
            padding: 12, marginBottom: 14,
            fontFamily: theme.uiFamily, fontSize: 14, color: theme.text,
            background: theme.isDark ? 'rgba(255,255,255,0.03)' : 'rgba(0,0,0,0.02)',
            outline: 'none', boxSizing: 'border-box', lineHeight: 1.5,
          }}
        />

        <div style={{ display: 'flex', gap: 10 }}>
          <button onClick={onClose} style={{
            flex: 1, padding: '12px', borderRadius: 10,
            background: 'transparent', border: `1px solid ${theme.border}`, color: theme.text,
            fontFamily: theme.uiFamily, fontSize: 13, fontWeight: 600, cursor: 'pointer',
          }}>Cancel</button>
          <button onClick={() => onSave(val)} style={{
            flex: 2, padding: '12px', borderRadius: 10,
            background: theme.primary, color: theme.primaryInk, border: 'none',
            fontFamily: theme.uiFamily, fontSize: 13, fontWeight: 600, cursor: 'pointer',
          }}>Save note</button>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { CIRCUITS, KIND_LABEL, smartColumns, expandSet, normalizeWeights, normalizeEquip, weightRowText, tempoWithPart, listWithSlot, replaceLabeled, SetGroup, Cell, Dash, ExpandedBreakdown, hasAnyValue, MainSetsCircuitsScreen, NoteEditor });
