// reorder-mode.jsx — the Reorder view.
//
// "Just the facts, ma'am": a stripped, print-style read of the WHOLE workout —
// every circuit, every round, each exercise showing name / reps / weight only.
// Nothing here is tappable or editable. The only affordance is a drag handle.
//
// Two variants, switchable at the top so both can be felt side by side:
//
//   Handles (default) — the handle you touch IS the granularity. Row handle
//     moves one exercise; the round bar's handle moves that round; the circuit
//     bar's handle moves the circuit. No modes, no explainer, no picker.
//   Mode buttons — the 7/15 shape: By exercise · By round · By circuit across
//     the top, every row handle obeying the selected mode.
//
// Rules are identical in both: exercises may land in any round of any circuit;
// a round moves only inside its own circuit; circuits reorder among circuits.
// Every legal landing spot is drawn the moment a drag starts. Illegal targets
// never light up. Exercise order is CIRCUIT-WIDE — rounds repeat it — so
// exercise drop zones appear in one bucket per circuit, and the other buckets
// say "same order" rather than pretending a per-round position exists.
//
// DIRECTION: nothing here is one-way. For a list of N items every slot 0..N is
// emitted — above the first, between each pair, below the last — and the only
// slots withheld are the two that would put the dragged item back exactly where
// it started (roSkip). Up and down are the same code path.
// Reaching a far target is separate from generating it, so both are handled:
// the hovered zone is chosen by nearest-edge geometry (not a pixel hit on a
// 26px strip), and the edge auto-scroll runs on a ticker so holding still at
// either end keeps paging — up as readily as down.

const RO_GRAN = [
  { value: 'exercise', label: 'By exercise' },
  { value: 'round',    label: 'By round' },
  { value: 'circuit',  label: 'By circuit' },
];

const roClone = (x) => JSON.parse(JSON.stringify(x));

// Deepened primary — the "drop here" fill. Colour is the only thing that moves
// when a zone goes hot, so it has to carry the whole signal.
const roShade = (hex, k) => {
  const m = /^#?([0-9a-f]{6})$/i.exec(String(hex || ''));
  if (!m) return hex;
  const n = parseInt(m[1], 16);
  const f = (v) => Math.max(0, Math.min(255, Math.round(v * k)));
  return `rgb(${f(n >> 16)}, ${f((n >> 8) & 255)}, ${f(n & 255)})`;
};

function roRounds(c) {
  const lens = c.exercises.map(e => e.sets.length);
  return Math.max(1, c.rounds || (lens.length ? Math.max(...lens) : 1));
}
function roWeight(w, unit) {
  if (w == null) return null;
  const list = Array.isArray(w) ? w : [w];
  const vals = list.map(v => (v && typeof v === 'object') ? v.value : v).filter(v => v != null && v !== '');
  return vals.length ? vals.join(' · ') + ' ' + unit : null;
}
// An exercise entering a circuit takes that circuit's round count.
function roFitSets(ex, rounds) {
  const sets = ex.sets.slice(0, rounds);
  while (sets.length < rounds) {
    const last = sets[sets.length - 1] || { reps: 10, weight: null };
    sets.push({ ...roClone(last), done: false });
  }
  return { ...ex, sets };
}
const roIns = (slot, from) => (slot > from ? slot - 1 : slot);

function roMoveExercise(circuits, from, toCi, slot) {
  const next = circuits.map(c => ({ ...c, exercises: c.exercises.slice() }));
  const [ex] = next[from.ci].exercises.splice(from.ei, 1);
  const target = next[toCi];
  const idx = toCi === from.ci ? roIns(slot, from.ei) : Math.min(slot, target.exercises.length);
  target.exercises.splice(idx, 0, roFitSets(ex, roRounds(target)));
  return next;
}
function roMoveRound(circuits, ci, from, slot) {
  const idx = roIns(slot, from);
  return circuits.map((c, i) => i !== ci ? c : {
    ...c,
    exercises: c.exercises.map(ex => {
      if (ex.sets.length <= from) return ex;
      const sets = ex.sets.slice();
      const [s] = sets.splice(from, 1);
      sets.splice(Math.min(idx, sets.length), 0, s);
      return { ...ex, sets };
    }),
  });
}
function roMoveCircuit(circuits, from, slot) {
  const next = circuits.slice();
  const [c] = next.splice(from, 1);
  next.splice(roIns(slot, from), 0, c);
  return next;
}
// The two no-op slots for an item at `from`: the gap just above it and the gap
// just below it. Everything else in 0..count is a real move, either direction.
const roSkip = (slot, from) => slot === from || slot === from + 1;
// A circuit emptied by dragging its last exercise out is dropped (on confirm).
function roDropEmpty(circuits, ci) {
  return circuits[ci] && circuits[ci].exercises.length === 0
    ? circuits.filter((_, i) => i !== ci) : circuits;
}
function roGlobalIdx(circuits, ci, ei) {
  let n = 0;
  for (let i = 0; i < ci; i++) n += circuits[i].exercises.length;
  return n + ei;
}

function ReorderScreen({ theme, circuits: incoming, unit, onCancel, onSave }) {
  const [direct, setDirect] = React.useState(true);   // handles decide, vs mode buttons
  const [gran, setGran] = React.useState('exercise'); // mode-buttons variant only
  const [draft, setDraft] = React.useState(() => roClone(incoming));
  const [drag, setDrag] = React.useState(null);       // { kind, ci, ri, ei, label }
  const [over, setOver] = React.useState(null);       // hovered zone key
  const [ghost, setGhost] = React.useState({ x: 0, y: 0 });
  const [moves, setMoves] = React.useState(0);
  const [confirmDel, setConfirmDel] = React.useState(null); // { name, ci, run }
  const zones = React.useRef({});                     // zone key → action
  zones.current = {};
  const rootRef = React.useRef(null);
  const scroller = React.useRef(null);
  const ptr = React.useRef({ x: 0, y: 0 });           // last pointer, for the scroll ticker

  // The real scroll container is the device-frame content DIV, not the page.
  const findScroller = () => {
    if (scroller.current) return scroller.current;
    let el = rootRef.current;
    while (el && el !== document.body) {
      const oy = getComputedStyle(el).overflowY;
      if ((oy === 'auto' || oy === 'scroll') && el.scrollHeight > el.clientHeight + 4) { scroller.current = el; return el; }
      el = el.parentElement;
    }
    return document.scrollingElement || document.documentElement;
  };

  // Nearest-edge pick, not a pixel hit. A 26px dashed strip is easy to sail past
  // in either direction; measuring distance to every live zone makes reaching one
  // upward exactly as easy as downward.
  const pickZone = (x, y) => {
    const els = (rootRef.current || document).querySelectorAll('[data-rozone]');
    let best = null, bestD = Infinity;
    els.forEach(el => {
      const r = el.getBoundingClientRect();
      if (x < r.left - 30 || x > r.right + 30) return;
      const d = y < r.top ? r.top - y : y > r.bottom ? y - r.bottom : 0;
      if (d < bestD) { bestD = d; best = el.getAttribute('data-rozone'); }
    });
    return bestD <= 28 ? best : null;
  };

  // Edge paging. The bands are deliberately deep and equal at both ends: the
  // sticky header covers the top of the scroller and the save bar covers the
  // bottom, so a shallow band is unreachable from visible content.
  const autoScroll = (y) => {
    const sc = findScroller();
    const page = sc === document.scrollingElement || sc === document.documentElement;
    const box = page ? { top: 0, bottom: window.innerHeight } : sc.getBoundingClientRect();
    const BAND = 150;
    const top = box.top + BAND, bot = box.bottom - BAND;
    let dy = 0;
    if (y < top) dy = -Math.min(26, 7 + (top - y) / 4);
    else if (y > bot) dy = Math.min(26, 7 + (y - bot) / 4);
    if (!dy) return false;
    const before = sc.scrollTop;
    sc.scrollBy(0, dy);
    return sc.scrollTop !== before;
  };

  // Held still at an edge, the list keeps paging and the hovered zone is
  // re-read, because content is moving under a stationary finger.
  React.useEffect(() => {
    if (!drag) return;
    const id = setInterval(() => {
      if (!autoScroll(ptr.current.y)) return;
      const next = pickZone(ptr.current.x, ptr.current.y);
      setOver(v => (v === next ? v : next));
    }, 16);
    return () => clearInterval(id);
  }, [drag]);

  const beginDrag = (e, u) => {
    e.preventDefault(); e.stopPropagation();
    // Capture on the root, not the handle: a circuit drag collapses the list, so
    // the handle under the finger unmounts mid-gesture. The root always survives.
    try { (rootRef.current || e.currentTarget).setPointerCapture(e.pointerId); } catch (err) {}
    ptr.current = { x: e.clientX, y: e.clientY };
    setDrag(u); setOver(null); setGhost({ x: e.clientX, y: e.clientY });
  };
  const moveDrag = (e) => {
    if (!drag) return;
    ptr.current = { x: e.clientX, y: e.clientY };
    setGhost({ x: e.clientX, y: e.clientY });
    setOver(pickZone(e.clientX, e.clientY));
    autoScroll(e.clientY);
  };
  const endDrag = () => {
    if (!drag) return;
    const act = over ? zones.current[over] : null;
    const d = drag;
    const key = over;
    setDrag(null); setOver(null);
    if (!act) return;
    // Last exercise leaving its circuit is allowed — but the now-empty circuit
    // gets named before it disappears.
    if (d.kind === 'exercise') {
      const toCi = Number(String(key).split(':')[1]);
      const src = draft[d.ci];
      if (toCi !== d.ci && src && src.exercises.length === 1) {
        setConfirmDel({ ci: d.ci, name: src.label ? `Circuit ${d.ci + 1} — ${src.label}` : `Circuit ${d.ci + 1}`, run: act });
        return;
      }
    }
    setDraft(act()); setMoves(m => m + 1);
  };

  // A legal landing spot. Same size idle or hot — nothing under the finger ever
  // resizes, so the list never pops. Going hot only darkens it.
  const hotFill = roShade(theme.primary, 0.62);
  const Zone = ({ k, act, wide }) => {
    zones.current[k] = act;
    const hot = over === k;
    return (
      <div data-rozone={k} style={{
        height: wide ? 34 : 30, margin: wide ? '7px 0' : '5px 0',
        borderRadius: 12, transition: 'background 0.12s linear, border-color 0.12s linear, color 0.12s linear',
        border: `1.5px dashed ${hot ? hotFill : exRgba(theme.primary, 0.45)}`,
        background: hot ? hotFill : exRgba(theme.primary, 0.05),
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        fontFamily: theme.monoFamily, fontSize: 10, fontWeight: 700, letterSpacing: 0.6,
        textTransform: 'uppercase', color: hot ? '#FFFFFF' : 'transparent',
      }}>Drop here</div>
    );
  };

  // What's moving right now decides which zones exist — never the picker.
  const kind = drag ? drag.kind : null;
  const exDrag = kind === 'exercise';
  const rdDrag = kind === 'round';
  const ciDrag = kind === 'circuit';

  // Min hams: a mode shows handles for exactly one level, so there is never a
  // handle on screen that does something other than what the tab says.
  // Handles variant shows all three, because that is its whole proposition.
  const showEx = direct || gran === 'exercise';
  const showRd = direct || gran === 'round';
  const showCi = direct || gran === 'circuit';
  const rowUnit = (ci, r, ei, ex) => ({ kind: 'exercise', ci, ri: r, ei, label: ex.shortName });

  const explainer = direct
    ? 'Drag a handle. The row moves one exercise, the round bar moves that round, the circuit bar moves the whole circuit.'
    : gran === 'exercise' ? 'Drag a handle to move it. Exercises can land in any round of any circuit.'
    : gran === 'round' ? 'Drag a handle to move it. Rounds stay inside their own circuit.'
    : 'Drag a handle to move it. Circuits reorder among circuits.';

  return (
    <div ref={rootRef} onPointerMove={moveDrag} onPointerUp={endDrag} onPointerCancel={endDrag}
      style={{ padding: '4px 18px 20px', touchAction: drag ? 'none' : 'auto' }}>

      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, marginBottom: 12 }}>
        <div style={{ flex: 1, fontSize: 12.5, color: theme.muted, lineHeight: 1.5 }}>{explainer}</div>
        <div style={{ display: 'flex', gap: 2, padding: 2, borderRadius: 9999, background: exRgba(theme.text, 0.06), flexShrink: 0 }}>
          {[{ v: true, l: 'Handles' }, { v: false, l: 'Modes' }].map(o => (
            <button key={o.l} onClick={() => { setDirect(o.v); setDrag(null); setOver(null); }} style={{
              padding: '5px 10px', borderRadius: 9999, border: 'none', cursor: 'pointer',
              background: direct === o.v ? theme.surface : 'transparent',
              color: direct === o.v ? theme.text : theme.muted,
              boxShadow: direct === o.v ? '0 1px 2px rgba(26,20,16,0.12)' : 'none',
              fontFamily: theme.uiFamily, fontSize: 10.5, fontWeight: 700,
            }}>{o.l}</button>
          ))}
        </div>
      </div>

      {!direct && (
        <div style={{ position: 'sticky', top: 96, zIndex: 20, padding: '4px 0 10px', background: theme.bg }}>
          <div style={{ display: 'flex', gap: 4, padding: 4, borderRadius: 12, background: exRgba(theme.text, 0.05) }}>
            {RO_GRAN.map(o => {
              const on = o.value === gran;
              return (
                <button key={o.value} onClick={() => { setGran(o.value); setDrag(null); setOver(null); }} style={{
                  flex: 1, padding: '10px 4px', borderRadius: 9, border: 'none', cursor: 'pointer',
                  background: on ? theme.surface : 'transparent', color: on ? theme.text : theme.muted,
                  boxShadow: on ? '0 1px 3px rgba(26,20,16,0.10)' : 'none',
                  fontFamily: theme.uiFamily, fontSize: 12.5, fontWeight: on ? 700 : 600, whiteSpace: 'nowrap',
                }}>{o.label}</button>
              );
            })}
          </div>
        </div>
      )}

      {ciDrag && !roSkip(0, drag.ci) && (
        <Zone k="ci:0" wide act={() => roMoveCircuit(draft, drag.ci, 0)} />
      )}

      {draft.map((c, ci) => {
        const rounds = roRounds(c);
        const accent = exerciseColor(theme, roGlobalIdx(draft, ci, 0));
        const ghostCircuit = ciDrag && drag.ci === ci;
        // A round drag draws its own cage: the owning circuit lights up, every
        // other circuit drops to grayscale. Readable, scrollable, plainly out of
        // bounds — no sentence needed to say a round can't leave its circuit.
        const boxed = rdDrag && drag.ci === ci;
        const caged = rdDrag && drag.ci !== ci;
        // Exercise order is circuit-wide, so zones live in exactly one bucket
        // per circuit: the round you grabbed from, or the first one elsewhere.
        const zoneRound = exDrag ? (ci === drag.ci ? Math.min(drag.ri || 0, rounds - 1) : 0) : -1;
        return (
          <React.Fragment key={c.id || ci}>
            {ciDrag ? (
              // Circuit drag collapses the workout to one bar per circuit, so a
              // trip from first to last is a short drag instead of a long haul.
              <div style={{
                display: 'flex', alignItems: 'center', gap: 10, marginBottom: 8,
                padding: '11px 12px', borderRadius: 14,
                border: `${ghostCircuit ? 2 : 1.5}px solid ${ghostCircuit ? theme.primary : exRgba(theme.text, theme.isDark ? 0.2 : 0.14)}`,
                background: ghostCircuit ? exRgba(theme.primary, 0.12) : theme.surface,
              }}>
                <span style={{ width: 8, height: 8, borderRadius: 3, background: accent, flexShrink: 0 }} />
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontFamily: theme.monoFamily, fontSize: 9.5, fontWeight: 700, letterSpacing: 1, textTransform: 'uppercase', color: theme.muted }}>
                    Circuit {ci + 1}
                  </div>
                  <div style={{ fontSize: 14, fontWeight: 700, letterSpacing: -0.2, marginTop: 1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                    {c.label || 'New circuit'}
                  </div>
                </div>
                <div style={{ fontFamily: theme.monoFamily, fontSize: 10, color: theme.muted, whiteSpace: 'nowrap', flexShrink: 0 }}>
                  {rounds} round{rounds === 1 ? '' : 's'}
                </div>
                <RoHandle theme={theme} level="circuit" onDown={(e) => beginDrag(e, { kind: 'circuit', ci, label: c.label || `Circuit ${ci + 1}` })} />
              </div>
            ) : (
            <div style={{
              borderRadius: 18, marginBottom: 12,
              padding: boxed ? '11.5px 11.5px 13.5px' : '12px 12px 14px',
              border: `${boxed ? 2 : 1.5}px solid ${boxed ? theme.primary : exRgba(theme.text, theme.isDark ? 0.2 : 0.14)}`,
              background: boxed ? exRgba(theme.primary, 0.1) : 'transparent',
              filter: caged ? 'grayscale(1)' : 'none',
              opacity: caged ? 0.7 : 1,
            }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 }}>
                <span style={{ width: 8, height: 8, borderRadius: 3, background: accent, flexShrink: 0 }} />
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontFamily: theme.monoFamily, fontSize: 10, fontWeight: 700, letterSpacing: 1, textTransform: 'uppercase', color: theme.muted }}>
                    Circuit {ci + 1}
                  </div>
                  <div style={{ fontSize: 15, fontWeight: 700, letterSpacing: -0.2, marginTop: 2, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                    {c.label || 'New circuit'}
                  </div>
                </div>
                <div style={{ fontFamily: theme.monoFamily, fontSize: 10, color: theme.muted, whiteSpace: 'nowrap', flexShrink: 0 }}>
                  {rounds} round{rounds === 1 ? '' : 's'}
                </div>
                {showCi && (
                  <RoHandle theme={theme} level="circuit" onDown={(e) => beginDrag(e, { kind: 'circuit', ci, label: c.label || `Circuit ${ci + 1}` })} />
                )}
              </div>

              {c.exercises.length === 0 && (
                <div style={{ padding: '12px 2px 4px' }}>
                  <div style={{ fontSize: 12.5, color: theme.muted, fontStyle: 'italic', marginBottom: 4 }}>
                    Empty circuit — {exDrag ? 'drop an exercise in.' : 'nothing to order yet.'}
                  </div>
                  {exDrag && (
                    <Zone k={`ex:${ci}:e:0`} wide act={() => roMoveExercise(draft, { ci: drag.ci, ei: drag.ei }, ci, 0)} />
                  )}
                </div>
              )}

              {rdDrag && drag.ci === ci && !roSkip(0, drag.ri) && (
                <Zone k={`rd:${ci}:0`} act={() => roMoveRound(draft, ci, drag.ri, 0)} />
              )}

              {c.exercises.length > 0 && Array.from({ length: rounds }).map((_, r) => {
                // Every exercise index gets a slot, even one with no set in this
                // round — the row is skipped, the landing spot is not.
                const items = c.exercises.map((ex, ei) => ({ ex, ei, s: ex.sets[r] }));
                const ghostRound = rdDrag && drag.ci === ci && drag.ri === r;
                const ordering = exDrag && r === zoneRound;
                return (
                  <React.Fragment key={r}>
                    <div style={{
                      borderRadius: 14, marginBottom: 8,
                      padding: ghostRound ? '7.5px 8.5px 9.5px' : '9px 10px 11px',
                      background: exRgba(accent, theme.isDark ? 0.1 : 0.06),
                      border: ghostRound ? `2.5px solid ${theme.primary}` : `1px solid ${exRgba(theme.text, theme.isDark ? 0.16 : 0.1)}`,
                      opacity: exDrag && !ordering ? 0.5 : 1,
                    }}>
                      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
                        <div style={{ fontFamily: theme.monoFamily, fontSize: 11, fontWeight: 700, color: theme.muted, letterSpacing: 0.4 }}>
                          ROUND {r + 1}
                        </div>
                        <div style={{ flex: 1, height: 1, background: exRgba(theme.text, 0.08) }} />
                        {exDrag && !ordering && (
                          <div style={{ fontFamily: theme.monoFamily, fontSize: 9.5, fontWeight: 700, letterSpacing: 0.5, textTransform: 'uppercase', color: theme.muted }}>
                            same order
                          </div>
                        )}
                        {showRd && (
                          <RoHandle theme={theme} level="round" onDown={(e) => beginDrag(e, { kind: 'round', ci, ri: r, label: `Round ${r + 1}` })} />
                        )}
                      </div>

                      {ordering && !(drag.ci === ci && roSkip(0, drag.ei)) && (
                        <Zone k={`ex:${ci}:${r}:0`} act={() => roMoveExercise(draft, { ci: drag.ci, ei: drag.ei }, ci, 0)} />
                      )}

                      {items.map(({ ex, ei, s }) => {
                        const exC = exerciseColor(theme, roGlobalIdx(draft, ci, ei));
                        const w = s ? roWeight(s.weight, unit) : null;
                        const reps = s ? (typeof s.reps === 'number' ? `${s.reps} reps` : `${s.reps}`) : '';
                        const isGhost = exDrag && drag.ci === ci && drag.ei === ei;
                        return (
                          <React.Fragment key={ex.id || ei}>
                            {s && (
                            <div style={{
                              display: 'flex', alignItems: 'center', gap: 10, padding: '10px 10px 10px 11px',
                              borderRadius: 12, background: theme.surface,
                              border: `1px solid ${isGhost ? theme.primary : theme.border}`,
                              opacity: exDrag && !isGhost && ordering ? 0.72 : 1,
                              marginBottom: 7,
                            }}>
                              <span style={{ width: 8, height: 8, borderRadius: 3, background: exC, flexShrink: 0 }} />
                              <div style={{ flex: 1, minWidth: 0, fontSize: 13.5, fontWeight: 600, letterSpacing: -0.15, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                                {ex.shortName}
                              </div>
                              <div style={{ fontFamily: theme.monoFamily, fontSize: 11.5, color: theme.muted, flexShrink: 0 }}>
                                {reps}{w ? ` · ${w}` : ''}
                              </div>
                              {showEx && (
                                <RoHandle theme={theme} level="exercise" onDown={(e) => beginDrag(e, rowUnit(ci, r, ei, ex))} />
                              )}
                            </div>
                            )}
                            {ordering && !(drag.ci === ci && roSkip(ei + 1, drag.ei)) && (
                              <Zone k={`ex:${ci}:${r}:${ei + 1}`} act={() => roMoveExercise(draft, { ci: drag.ci, ei: drag.ei }, ci, ei + 1)} />
                            )}
                          </React.Fragment>
                        );
                      })}
                    </div>

                    {rdDrag && drag.ci === ci && !roSkip(r + 1, drag.ri) && (
                      <Zone k={`rd:${ci}:${r + 1}`} act={() => roMoveRound(draft, ci, drag.ri, r + 1)} />
                    )}
                  </React.Fragment>
                );
              })}
            </div>
            )}

            {ciDrag && !roSkip(ci + 1, drag.ci) && (
              <Zone k={`ci:${ci + 1}`} wide act={() => roMoveCircuit(draft, drag.ci, ci + 1)} />
            )}
          </React.Fragment>
        );
      })}

      {/* save modes — temporary vs permanent. Its own bar, hard edge, no fade. */}
      <div style={{
        position: 'sticky', bottom: 0, zIndex: 25, marginTop: 14,
        marginLeft: -18, marginRight: -18, padding: '12px 18px 18px',
        background: theme.bg, borderTop: `1px solid ${exRgba(theme.text, theme.isDark ? 0.2 : 0.15)}`,
      }}>
        {/* Three real choices, not one hero and two afterthoughts. Same shell,
            same size; only the permanent one is tinted, because it is the only
            one that changes tomorrow's workout. */}
        <div style={{ display: 'flex', gap: 8, alignItems: 'stretch' }}>
          <button onClick={onCancel} style={{
            flex: 0.8, padding: '12px 6px', borderRadius: 13, cursor: 'pointer', border: `1px solid ${theme.border}`,
            background: 'transparent', color: theme.muted,
            fontFamily: theme.uiFamily, fontSize: 12.5, fontWeight: 600, lineHeight: 1.25, letterSpacing: -0.1,
          }}>Cancel</button>
          <button onClick={() => onSave(draft, 'today')} disabled={!moves} style={{
            flex: 1.1, padding: '12px 6px', borderRadius: 13, cursor: moves ? 'pointer' : 'default',
            border: `1px solid ${theme.border}`, background: theme.surface, color: theme.text, opacity: moves ? 1 : 0.45,
            fontFamily: theme.uiFamily, fontSize: 12.5, fontWeight: 600, lineHeight: 1.25, letterSpacing: -0.1,
          }}>Just for today</button>
          <button onClick={() => onSave(draft, 'permanent')} disabled={!moves} style={{
            flex: 1.1, padding: '12px 6px', borderRadius: 13, cursor: moves ? 'pointer' : 'default',
            border: `1px solid ${exRgba(theme.primary, 0.55)}`, background: exRgba(theme.primary, 0.1),
            color: theme.primary, opacity: moves ? 1 : 0.45,
            fontFamily: theme.uiFamily, fontSize: 12.5, fontWeight: 700, lineHeight: 1.25, letterSpacing: -0.1,
          }}>Make this the new order</button>
        </div>
        <div style={{ fontFamily: theme.monoFamily, fontSize: 10, color: theme.muted, textAlign: 'center', marginTop: 8 }}>
          {moves ? `${moves} move${moves === 1 ? '' : 's'} — choose how long it sticks` : 'Nothing moved yet'}
        </div>
      </div>

      {confirmDel && (
        <div onPointerDown={(e) => e.stopPropagation()} style={{
          position: 'fixed', inset: 0, zIndex: 300, display: 'flex', alignItems: 'center', justifyContent: 'center',
          padding: 24, background: 'rgba(26,20,16,0.45)',
        }}>
          <div style={{
            width: '100%', maxWidth: 300, borderRadius: 18, padding: '18px 16px 14px',
            background: theme.surface, border: `1px solid ${theme.border}`, boxShadow: '0 18px 44px rgba(0,0,0,0.3)',
          }}>
            <div style={{ fontSize: 14.5, fontWeight: 700, letterSpacing: -0.2, lineHeight: 1.45, marginBottom: 4 }}>
              This will delete {confirmDel.name}
            </div>
            <div style={{ fontSize: 12.5, color: theme.muted, lineHeight: 1.5, marginBottom: 14 }}>
              It was its last exercise, so the circuit goes with it.
            </div>
            <div style={{ display: 'flex', gap: 9 }}>
              <button onClick={() => setConfirmDel(null)} style={{
                flex: 1, padding: '12px 8px', borderRadius: 13, cursor: 'pointer', border: `1px solid ${theme.border}`,
                background: 'transparent', color: theme.muted, fontFamily: theme.uiFamily, fontSize: 13.5, fontWeight: 600,
              }}>Cancel</button>
              <button onClick={() => {
                setDraft(roDropEmpty(confirmDel.run(), confirmDel.ci));
                setMoves(m => m + 1); setConfirmDel(null);
              }} style={{
                flex: 1, padding: '12px 8px', borderRadius: 13, border: 'none', cursor: 'pointer',
                background: theme.primary, color: theme.primaryInk,
                fontFamily: theme.uiFamily, fontSize: 13.5, fontWeight: 800, letterSpacing: -0.15,
              }}>Continue</button>
            </div>
          </div>
        </div>
      )}

      {drag && (
        <div style={{
          position: 'fixed', left: ghost.x, top: ghost.y, transform: 'translate(-50%, -150%)',
          zIndex: 200, pointerEvents: 'none', padding: '7px 12px', borderRadius: 9999,
          background: theme.primary, color: theme.primaryInk, boxShadow: '0 8px 22px rgba(0,0,0,0.28)',
          fontFamily: theme.uiFamily, fontSize: 12.5, fontWeight: 700, whiteSpace: 'nowrap',
        }}>{drag.label}</div>
      )}
    </div>
  );
}

// Three-line grab handle, one progression across the three levels: size, corner,
// line weight, ink and chrome all step the same direction, so "bigger and darker
// grabs more" is legible without anyone having to think about it.
const RO_HANDLE = {
  circuit:  { s: 34, r: 11,  w: 16, sw: 2.1,  ink: 0.85, bg: 0.10,  bd: 0.22 },
  round:    { s: 30, r: 9.5, w: 14, sw: 1.75, ink: 0.60, bg: 0.07,  bd: 0.15 },
  exercise: { s: 26, r: 8,   w: 12, sw: 1.4,  ink: 0.42, bg: 0.045, bd: 0.09 },
};
function RoHandle({ theme, level, small, strong, onDown }) {
  const h = RO_HANDLE[level || (strong ? 'circuit' : small ? 'round' : 'exercise')];
  const gap = h.sw * 2.6;
  const box = h.sw + gap * 2;
  return (
    <div onPointerDown={onDown} title="Drag to move" style={{
      width: h.s, height: h.s, borderRadius: h.r, flexShrink: 0, cursor: 'grab', touchAction: 'none',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      background: exRgba(theme.text, h.bg), border: `1px solid ${exRgba(theme.text, h.bd)}`,
    }}>
      <svg width={h.w} height={box} viewBox={`0 0 ${h.w} ${box}`} style={{ pointerEvents: 'none' }}>
        <g stroke={exRgba(theme.text, h.ink)} strokeWidth={h.sw} strokeLinecap="round">
          {[0, 1, 2].map(i => (
            <line key={i} x1={h.sw / 2} y1={h.sw / 2 + i * gap} x2={h.w - h.sw / 2} y2={h.sw / 2 + i * gap} />
          ))}
        </g>
      </svg>
    </div>
  );
}

Object.assign(window, { ReorderScreen, RoHandle, roRounds, roMoveExercise, roMoveRound, roMoveCircuit, roSkip, roDropEmpty });
