// =========================================================
// APP SHELL — stage, tweaks, wiring
// =========================================================

const LS_KEY = 'operator_todo_state_v3';
const LS_TWEAK = 'operator_todo_tweaks_v3';

// Today's weekday index (Mon=0 … Sun=6)
const todayIdx = () => (new Date().getDay() + 6) % 7;

// Build virtual tasks for habits scheduled today. Their ID is habit:<id>
// and their `done` state mirrors the habit's days[todayIdx].
function habitVirtualTasks(habits) {
  const i = todayIdx();
  return habits
    .filter(h => !h.schedule || h.schedule.includes(i))
    .map(h => ({
      id: `habit:${h.id}`,
      habitId: h.id,
      title: h.name,
      context: '@habit',
      done: !!h.days[i],
      starred: false,
      bucket: 'now', // habits default to Now — they're the non-negotiables
      isHabit: true,
      streak: h.streak,
      order: -1000,
      created: Date.now(),
    }));
}

function withDefaults(s) {
  // Back-compat: older saved states won't have `contexts`. Seed the defaults.
  if (!s.contexts || !Array.isArray(s.contexts) || s.contexts.length === 0) {
    s.contexts = [...DEFAULT_CONTEXTS];
  }
  if (!s.sessions || !Array.isArray(s.sessions)) s.sessions = [];
  if (!s.habitHistory || typeof s.habitHistory !== 'object') s.habitHistory = {};
  // Backfill habitHistory from the current `days` arrays (which only
  // represent this week) so older accounts don't lose today's marks.
  const monday = mondayOf(new Date());
  (s.habits || []).forEach(h => {
    s.habitHistory[h.id] = s.habitHistory[h.id] || {};
    (h.days || []).forEach((d, i) => {
      if (d) {
        const iso = isoDate(addDays(monday, i));
        if (!s.habitHistory[h.id][iso]) s.habitHistory[h.id][iso] = 1;
      }
    });
  });
  // Recompute this-week `days` from history so rollover is automatic.
  s.habits = (s.habits || []).map(h => ({
    ...h, days: currentWeekDays(h.id, s.habitHistory),
  }));
  return s;
}

function loadState() {
  // Instant synchronous load from localStorage cache; remote pull happens
  // in an effect once Supabase auth is ready.
  const cached = window.OperatorSync?.readCache().data;
  if (cached) return withDefaults(cached);
  return withDefaults({ tasks: SEED_TASKS, inbox: SEED_INBOX, habits: SEED_HABITS });
}
function saveState(s) {
  // Goes to localStorage immediately + debounced push to Supabase.
  if (window.OperatorSync) window.OperatorSync.schedulePush(s);
  else { try { localStorage.setItem(LS_KEY, JSON.stringify(s)); } catch {} }
}

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "theme": "light",
  "typography": "sans",
  "density": "cozy",
  "showHabits": true
}/*EDITMODE-END*/;

function loadTweaks() {
  try {
    const raw = localStorage.getItem(LS_TWEAK);
    if (raw) return { ...TWEAK_DEFAULTS, ...JSON.parse(raw) };
  } catch {}
  return { ...TWEAK_DEFAULTS };
}

function App() {
  const [state, setState] = useState(loadState);
  const [tweaks, setTweaks] = useState(loadTweaks);
  const [tweakPanelOpen, setTweakPanelOpen] = useState(false);
  const [contextsPanelOpen, setContextsPanelOpen] = useState(false);
  const [drawerOpen, setDrawerOpen] = useState(false);
  const [view, setView] = useState('today'); // 'today' | 'analytics'
  const [editMode, setEditMode] = useState(false);
  const [remoteHydrated, setRemoteHydrated] = useState(false);
  const [welcomeDismissed, setWelcomeDismissed] = useState(() => {
    try { return localStorage.getItem('operator_welcome_dismissed') === '1'; }
    catch { return false; }
  });

  // Do not save to server until the remote pull has completed. Otherwise
  // any in-flight local setState (e.g. session logging) would push the
  // cached snapshot — possibly older than the server — and clobber data.
  useEffect(() => {
    if (!remoteHydrated) return;
    saveState(state);
  }, [state, remoteHydrated]);

  // Pull latest from Supabase once the user is signed in. If the server has
  // a newer copy than our local cache, overwrite local state with it.
  useEffect(() => {
    let cancelled = false;
    async function hydrate() {
      if (!window.OperatorAuth?.configured) { setRemoteHydrated(true); return; }
      if (!window.OperatorAuth.getUserId()) return;
      const remote = await window.OperatorSync.pullRemote();
      if (cancelled) return;
      const cachedMeta = window.OperatorSync.readCache();
      if (remote && (!cachedMeta.updatedAt || remote.updatedAt > cachedMeta.updatedAt)) {
        const merged = withDefaults({ ...remote.data });
        setState(merged);
        window.OperatorSync.writeCache(merged, remote.updatedAt);
      } else if (!remote) {
        // First sign-in on this account — push the cached seed/local state up.
        window.OperatorSync.pushNow(loadState());
      }
      setRemoteHydrated(true);
    }
    hydrate();
    const off = window.OperatorAuth?.onChange(() => hydrate());
    return () => { cancelled = true; off && off(); };
  }, []);
  useEffect(() => { localStorage.setItem(LS_TWEAK, JSON.stringify(tweaks)); }, [tweaks]);

  // Edit-mode protocol
  useEffect(() => {
    const onMsg = (e) => {
      const d = e.data || {};
      if (d.type === '__activate_edit_mode') { setEditMode(true); setTweakPanelOpen(true); }
      if (d.type === '__deactivate_edit_mode') { setEditMode(false); setTweakPanelOpen(false); }
    };
    window.addEventListener('message', onMsg);
    window.parent.postMessage({ type: '__edit_mode_available' }, '*');
    return () => window.removeEventListener('message', onMsg);
  }, []);

  const setTweak = (key, val) => {
    setTweaks(prev => {
      const next = { ...prev, [key]: val };
      window.parent.postMessage({ type: '__edit_mode_set_keys', edits: { [key]: val } }, '*');
      return next;
    });
  };

  const T = useMemo(() => themeTokens(tweaks.theme), [tweaks.theme]);
  const TY = useMemo(() => typeTokens(tweaks.typography), [tweaks.typography]);
  const D = useMemo(() => densityTokens(tweaks.density), [tweaks.density]);
  const now = useClock();
  const isMobile = useIsMobile();

  // Log a session timestamp once per mount, AFTER the remote hydrate has
  // completed — otherwise we'd push the session-augmented (but possibly
  // out-of-date) cache before pulling the latest from Supabase.
  const sessionLogged = useRef(false);
  useEffect(() => {
    if (!remoteHydrated || sessionLogged.current) return;
    sessionLogged.current = true;
    setState(s => {
      const arr = s.sessions || [];
      const last = arr[arr.length - 1] || 0;
      if (Date.now() - last < 6 * 60 * 60 * 1000) return s;
      const next = [...arr, Date.now()].slice(-180);
      return { ...s, sessions: next };
    });
  }, [remoteHydrated]);

  // Apply CSS vars for fonts
  useEffect(() => {
    document.documentElement.style.setProperty('--sans', TY.sans);
    document.documentElement.style.setProperty('--mono', TY.mono);
    document.documentElement.style.setProperty('--hw', TY.headingWeight);
    document.documentElement.style.setProperty('--tight', TY.tight);
    document.documentElement.style.setProperty('--display-tight', TY.displayTight);
  }, [TY]);

  // Actions
  const addTask = ({ title, context = '', starred = false, bucket = 'next', fromInbox = null, dueAt = null }) => {
    // If title has a time suffix like "at 6pm", extract it
    let finalTitle = title;
    let finalDue = dueAt;
    if (!dueAt) {
      const ex = extractTimeFromTitle(title);
      if (ex) { finalTitle = ex.title; finalDue = ex.dueAt; }
    }
    setState(s => {
      const order = s.tasks.length;
      const newTask = { id: uid(), title: finalTitle, context, starred, bucket, done: false, order, created: Date.now(), dueAt: finalDue };
      return {
        ...s,
        tasks: [newTask, ...s.tasks],
        inbox: fromInbox ? s.inbox.filter(i => i.id !== fromInbox) : s.inbox,
      };
    });
  };
  // Move a task to Now — only one task can be "now" at a time
  const setNow = (id) => setState(s => ({
    ...s,
    tasks: s.tasks.map(t =>
      t.id === id ? { ...t, bucket: 'now' } :
      t.bucket === 'now' ? { ...t, bucket: 'next' } : t
    ),
  }));
  const moveBucket = (id, bucket) => setState(s => ({
    ...s,
    tasks: s.tasks.map(t => t.id === id ? { ...t, bucket } : t),
  }));
  const addToInbox = ({ title }) => {
    setState(s => ({ ...s, inbox: [{ id: uid(), title, created: Date.now() }, ...s.inbox] }));
  };
  const processInbox = (id, ctx) => {
    const item = state.inbox.find(i => i.id === id);
    if (!item) return;
    addTask({ title: item.title, context: ctx || '', fromInbox: id });
  };
  const toggleTask = (id) => {
    // Virtual habit tasks -> toggle today's slot via the habit history.
    if (typeof id === 'string' && id.startsWith('habit:')) {
      const hid = id.slice(6);
      toggleHabitDay(hid, todayIdx());
      return;
    }
    setState(s => ({
      ...s,
      tasks: s.tasks.map(t => {
        if (t.id !== id) return t;
        const nowDone = !t.done;
        return { ...t, done: nowDone, completedAt: nowDone ? Date.now() : null };
      }),
    }));
  };
  const starTask = (id) => setState(s => ({
    ...s, tasks: s.tasks.map(t => t.id === id ? { ...t, starred: !t.starred } : t)
  }));
  const deleteTask = (id) => setState(s => ({ ...s, tasks: s.tasks.filter(t => t.id !== id) }));
  const editTask = (id, title) => setState(s => ({
    ...s, tasks: s.tasks.map(t => t.id === id ? { ...t, title } : t)
  }));
  const editTaskDescription = (id, description) => setState(s => ({
    ...s, tasks: s.tasks.map(t => t.id === id ? { ...t, description } : t)
  }));
  const deleteInbox = (id) => setState(s => ({ ...s, inbox: s.inbox.filter(i => i.id !== id) }));
  const toggleHabitDay = (id, dayIdx) => setState(s => {
    const monday = mondayOf(new Date());
    const iso = isoDate(addDays(monday, dayIdx));
    const hist = { ...(s.habitHistory || {}) };
    const perHabit = { ...(hist[id] || {}) };
    if (perHabit[iso]) delete perHabit[iso]; else perHabit[iso] = 1;
    hist[id] = perHabit;
    return {
      ...s,
      habitHistory: hist,
      habits: s.habits.map(h => h.id === id
        ? { ...h, days: currentWeekDays(id, hist) }
        : h),
    };
  });
  const addHabit = ({ name, target, unit }) => setState(s => ({
    ...s,
    habits: [...s.habits, { id: uid(), name, target, unit, streak: 0, days: [0,0,0,0,0,0,0], schedule: [0,1,2,3,4,5,6] }]
  }));
  const deleteHabit = (id) => setState(s => ({ ...s, habits: s.habits.filter(h => h.id !== id) }));

  // Contexts (user-editable tags like @computer, @deep, etc.)
  const addContext = (raw) => setState(s => {
    let v = (raw || '').trim();
    if (!v) return s;
    if (!v.startsWith('@')) v = '@' + v;
    if (s.contexts.includes(v)) return s;
    return { ...s, contexts: [...s.contexts, v] };
  });
  const renameContext = (oldV, newV) => setState(s => {
    let v = (newV || '').trim();
    if (!v) return s;
    if (!v.startsWith('@')) v = '@' + v;
    if (v === oldV) return s;
    return {
      ...s,
      contexts: s.contexts.map(c => c === oldV ? v : c),
      // Rewrite any tasks currently tagged with the old context.
      tasks: s.tasks.map(t => t.context === oldV ? { ...t, context: v } : t),
    };
  });
  const deleteContext = (v) => setState(s => ({
    ...s,
    contexts: s.contexts.filter(c => c !== v),
    // Strip the tag from tasks that had it.
    tasks: s.tasks.map(t => t.context === v ? { ...t, context: '' } : t),
  }));

  // Today = real tasks only. Habits live exclusively in the Habits panel/tab.
  const allTodayTasks = useMemo(() => state.tasks.filter(t => !t.done), [state.tasks]);

  const nowTask = allTodayTasks.find(t => t.bucket === 'now') || null;
  const nextTasks = allTodayTasks.filter(t => t.bucket === 'next').sort((a,b) => (b.starred - a.starred) || (a.order - b.order));
  const laterTasks = allTodayTasks.filter(t => t.bucket === 'later').sort((a,b) => a.order - b.order);
  const doneTasks = state.tasks.filter(t => t.done);
  const activeTasks = allTodayTasks;
  const focus = nowTask?.title || nextTasks[0]?.title || '';
  // Auto-interrupt: if any task has dueAt <= now and isn't yet Now/Done, promote it.
  // Uses a ref of already-fired IDs so we don't re-fire after the user deliberately moves it.
  const firedRef = useRef(new Set());
  useEffect(() => {
    const n = now.getTime();
    const fireable = state.tasks.find(t =>
      t.dueAt && !t.done && t.bucket !== 'now' && t.dueAt <= n && !firedRef.current.has(t.id)
    );
    if (fireable) {
      firedRef.current.add(fireable.id);
      setState(s => ({
        ...s,
        tasks: s.tasks.map(t =>
          t.id === fireable.id ? { ...t, bucket: 'now' } :
          t.bucket === 'now' ? { ...t, bucket: 'next' } : t
        ),
      }));
    }
  }, [now, state.tasks]);
  const [capture, setCapture] = useState('');
  const [inboxSel, setInboxSel] = useState(0);
  const [inboxCtx, setInboxCtx] = useState('');

  // Global keyboard handling for inbox processing
  useEffect(() => {
    const onKey = (e) => {
      // don't steal from inputs
      const tag = (e.target?.tagName || '').toLowerCase();
      if (tag === 'input' || tag === 'textarea') return;
      const inbox = state.inbox;
      if (inbox.length === 0) return;
      if (e.key === 'ArrowDown' || e.key === 'j') { e.preventDefault(); setInboxSel(i => Math.min(inbox.length - 1, i + 1)); }
      else if (e.key === 'ArrowUp' || e.key === 'k') { e.preventDefault(); setInboxSel(i => Math.max(0, i - 1)); }
      else if (e.key >= '1' && e.key <= '9') {
        const idx = parseInt(e.key, 10) - 1;
        if (idx < state.contexts.length) setInboxCtx(state.contexts[idx]);
      }
      else if (e.key === '0') { setInboxCtx(''); }
      else if (e.key === 't' || e.key === 'T') {
        e.preventDefault();
        const item = inbox[inboxSel]; if (!item) return;
        addTask({ title: item.title, context: inboxCtx, fromInbox: item.id, bucket: 'next' });
        setInboxCtx(''); setInboxSel(s => Math.max(0, Math.min(s, inbox.length - 2)));
      } else if (e.key === 'x' || e.key === 'X' || e.key === 'Delete') {
        e.preventDefault();
        const item = inbox[inboxSel]; if (!item) return;
        deleteInbox(item.id);
        setInboxSel(s => Math.max(0, Math.min(s, inbox.length - 2)));
      }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [state.inbox, inboxSel, inboxCtx, state.contexts]);

  return (
    <div style={{
      minHeight: '100vh', background: T.panel,
      color: T.fg, fontFamily: 'var(--sans)',
    }}>
      {/* First-run welcome + navigation tips. Dismiss flag lives in localStorage
          (per-device) so cross-device sync races can't un-dismiss it. */}
      {remoteHydrated && !welcomeDismissed && (
        <WelcomeModal
          email={window.OperatorAuth?.session?.user?.email || ''}
          onClose={() => {
            try { localStorage.setItem('operator_welcome_dismissed', '1'); } catch {}
            setWelcomeDismissed(true);
          }}
        />
      )}

      {/* global style */}
      <style>{`
        * { box-sizing: border-box; }
        body { margin: 0; background: ${T.panel}; color: ${T.fg}; }
        ::selection { background: ${T.accent}; color: ${T.panel}; }
        input::placeholder { color: ${T.fg3}; }
        button:focus-visible, input:focus-visible { outline: 1px solid ${T.accent}; outline-offset: 2px; }
        ::-webkit-scrollbar { width: 8px; height: 8px; }
        ::-webkit-scrollbar-thumb { background: ${T.line}; }
        ::-webkit-scrollbar-track { background: transparent; }
      `}</style>

      <div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
        {/* Top bar — shown on both desktop + mobile. On mobile it's tighter
            and only keeps theme + sign-out (habits toggle is in the Mobile
            view's own tabs, date lives in the mobile status bar). */}
        <div style={{
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          padding: isMobile ? '10px 14px' : '12px 20px',
          borderBottom: `1px solid ${T.line}`, background: T.panel,
        }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
            <button
              onClick={() => setDrawerOpen(true)}
              aria-label="Open menu"
              style={{
                width: 28, height: 28, padding: 0,
                border: '1px solid rgba(0,0,0,0.15)', background: 'rgba(255,255,255,0.6)',
                color: 'rgba(0,0,0,0.7)', fontSize: 14, cursor: 'pointer',
                display: 'flex', alignItems: 'center', justifyContent: 'center',
              }}
            >☰</button>
            {!isMobile && (
              <span style={{ fontFamily: 'var(--mono)', fontSize: 10, color: 'rgba(0,0,0,0.5)', letterSpacing: '0.1em' }}>
                {view === 'analytics' ? 'ANALYTICS' : fmtDate(now).toUpperCase()}
              </span>
            )}
            {isMobile && (
              <span style={{ fontFamily: 'var(--mono)', fontSize: 10, color: T.fg, letterSpacing: '0.18em' }}>
                {view === 'analytics' ? 'ANALYTICS' : 'OPERATOR'}
              </span>
            )}
          </div>
          <div style={{ display: 'flex', alignItems: 'baseline', gap: 8 }}>
            <button
              onClick={() => setTweak('theme', tweaks.theme === 'dark' ? 'light' : 'dark')}
              style={{
                border: '1px solid rgba(0,0,0,0.15)', background: 'rgba(255,255,255,0.6)',
                color: 'rgba(0,0,0,0.7)', fontFamily: 'var(--mono)', fontSize: 10,
                letterSpacing: '0.12em', padding: '5px 10px', cursor: 'pointer',
              }}
              title="Toggle theme"
            >{tweaks.theme === 'dark' ? '☾' : tweaks.theme === 'warm' ? '◐' : '☀'}{isMobile ? '' : (tweaks.theme === 'dark' ? ' DARK' : tweaks.theme === 'warm' ? ' WARM' : ' LIGHT')}</button>
            {!isMobile && (
              <button
                onClick={() => setTweak('showHabits', !tweaks.showHabits)}
                style={{
                  border: '1px solid rgba(0,0,0,0.15)', background: 'rgba(255,255,255,0.6)',
                  color: 'rgba(0,0,0,0.7)', fontFamily: 'var(--mono)', fontSize: 10,
                  letterSpacing: '0.12em', padding: '5px 10px', cursor: 'pointer',
                }}
                title="Toggle habits panel"
              >{tweaks.showHabits ? '▣ HABITS' : '▢ HABITS'}</button>
            )}
            <button
              onClick={() => setContextsPanelOpen(true)}
              style={{
                border: '1px solid rgba(0,0,0,0.15)', background: 'rgba(255,255,255,0.6)',
                color: 'rgba(0,0,0,0.7)', fontFamily: 'var(--mono)', fontSize: 10,
                letterSpacing: '0.12em', padding: '5px 10px', cursor: 'pointer',
              }}
              title="Manage contexts"
            >@{isMobile ? '' : ' CONTEXTS'}</button>
            {window.OperatorAuth?.configured && (
              <button
                onClick={() => window.OperatorAuth.signOut()}
                style={{
                  border: '1px solid rgba(0,0,0,0.15)', background: 'rgba(255,255,255,0.6)',
                  color: 'rgba(0,0,0,0.6)', fontFamily: 'var(--mono)', fontSize: 10,
                  letterSpacing: '0.12em', padding: '5px 10px', cursor: 'pointer',
                }}
                title="Sign out"
              >↪{isMobile ? '' : ' SIGN OUT'}</button>
            )}
          </div>
        </div>

        {view === 'analytics' ? (
          <Analytics T={T} D={D} state={state} isMobile={isMobile} />
        ) : isMobile ? (
          /* ─── MOBILE: full-viewport MobileView, no side chrome ─── */
          <div style={{ flex: 1, minHeight: 0, background: T.panel }}>
            <MobileView
              T={T} D={D}
              tasks={state.tasks}
              inbox={state.inbox}
              habits={state.habits}
              onAddTask={addTask}
              onToggle={toggleTask}
              onStar={starTask}
              onDelete={deleteTask}
              onDeleteInbox={deleteInbox}
              onAddHabit={addHabit}
              onToggleHabitDay={toggleHabitDay}
              onDeleteHabit={deleteHabit}
              onEdit={editTask}
              onEditDescription={editTaskDescription}
              onSetNow={setNow}
              onMoveBucket={moveBucket}
              showHabits={tweaks.showHabits}
            />
          </div>
        ) : (
          /* ─── DESKTOP: three-column layout with Now/Next/Later, Inbox, Habits ─── */
          <div style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
            <div style={{
              background: T.panel, color: T.fg,
              display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0,
            }}>
              <SystemBar T={T} now={now} taskCount={state.tasks.length} doneCount={doneTasks.length} focus={focus} />

              <div style={{ display: 'grid', gridTemplateColumns: tweaks.showHabits ? '1.6fr 1fr 1fr' : '1.8fr 1fr', flex: 1, minHeight: 0 }}>
                <div style={{ borderRight: `1px solid ${T.line}`, display: 'flex', flexDirection: 'column' }}>
                  <TodayColumn
                    T={T} D={D} now={now}
                    nowTask={nowTask}
                    nextTasks={nextTasks}
                    laterTasks={laterTasks}
                    doneTasks={doneTasks}
                    contexts={state.contexts}
                    onToggle={toggleTask}
                    onStar={starTask}
                    onDelete={deleteTask}
                    onEdit={editTask}
                    onEditDescription={editTaskDescription}
                    onSetNow={setNow}
                    onMoveBucket={moveBucket}
                    onAdd={addTask}
                    capture={capture} setCapture={setCapture}
                  />
                </div>

                <div style={{ borderRight: tweaks.showHabits ? `1px solid ${T.line}` : 'none', display: 'flex', flexDirection: 'column' }}>
                  <div style={{ padding: '22px 22px 14px', borderBottom: `1px solid ${T.line}` }}>
                    <MonoLabel T={T}>INBOX ░ UNPROCESSED</MonoLabel>
                    <div style={{
                      fontFamily: 'var(--sans)', fontSize: 20, color: T.fg,
                      letterSpacing: 'var(--tight)', marginTop: 6, fontWeight: 'var(--hw)',
                    }}>Capture. Process later.</div>
                    <div style={{ fontFamily: 'var(--sans)', fontSize: 12, color: T.fg3, marginTop: 4, lineHeight: 1.5 }}>
                      Dump anything. When ready, assign a context and promote to a task.
                    </div>
                  </div>
                  <QuickInbox T={T} D={D} onCapture={addToInbox} />
                  <KeyboardInbox
                    T={T} D={D}
                    inbox={state.inbox}
                    contexts={state.contexts}
                    selIdx={inboxSel}
                    setSelIdx={setInboxSel}
                    ctx={inboxCtx}
                    setCtx={setInboxCtx}
                    onProcess={processInbox}
                    onDelete={deleteInbox}
                  />
                </div>

                {tweaks.showHabits && (
                  <HabitsPanel T={T} D={D} habits={state.habits} onToggleToday={toggleHabitDay} onAddHabit={addHabit} />
                )}
              </div>

              <div style={{
                display: 'flex', alignItems: 'center', gap: 20,
                padding: '10px 28px', borderTop: `1px solid ${T.line}`,
                fontFamily: 'var(--mono)', fontSize: 10, color: T.fg3, letterSpacing: '0.1em',
              }}>
                <span>SYSTEM NOMINAL</span>
                <span style={{ color: T.accent }}>●</span>
                <span>AUTOSAVE ON</span>
                <div style={{ flex: 1 }} />
                <span>⌘K CAPTURE</span>
                <span>J/K NAV</span>
                <span>1-5 CTX</span>
                <span>T PROMOTE</span>
                <span>X DELETE</span>
              </div>
            </div>
          </div>
        )}
      </div>

      {contextsPanelOpen && (
        <ContextsPanel
          contexts={state.contexts}
          onAdd={addContext}
          onRename={renameContext}
          onDelete={deleteContext}
          onClose={() => setContextsPanelOpen(false)}
        />
      )}

      {tweakPanelOpen && (
        <TweaksPanel
          tweaks={tweaks} setTweak={setTweak} T={T}
          onClose={() => setTweakPanelOpen(false)}
        />
      )}

      {drawerOpen && (
        <NavDrawer
          T={T} view={view} setView={setView}
          onOpenContexts={() => setContextsPanelOpen(true)}
          onOpenTweaks={() => setTweakPanelOpen(true)}
          onClose={() => setDrawerOpen(false)}
        />
      )}

    </div>
  );
}

// Nav drawer — slides in from the left, switches between Today and
// Analytics, and opens the Contexts / Tweaks modals.
function NavDrawer({ T, view, setView, onOpenContexts, onOpenTweaks, onClose }) {
  const Item = ({ id, label, hint, onClick }) => (
    <button
      onClick={onClick}
      style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        width: '100%', textAlign: 'left',
        padding: '14px 18px',
        border: 'none', borderBottom: `1px solid ${T.line}`,
        background: view === id ? T.accentSoft : 'transparent',
        color: view === id ? T.accent : T.fg,
        fontFamily: 'var(--sans)', fontSize: 15, cursor: 'pointer',
      }}
    >
      <span>{label}</span>
      <span style={{ fontFamily: 'var(--mono)', fontSize: 9, color: T.fg3, letterSpacing: '0.12em' }}>{hint}</span>
    </button>
  );
  const Sub = ({ label, onClick }) => (
    <button
      onClick={onClick}
      style={{
        width: '100%', textAlign: 'left',
        padding: '12px 18px',
        border: 'none', borderBottom: `1px solid ${T.line}`,
        background: 'transparent', color: T.fg2,
        fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.14em',
        textTransform: 'uppercase', cursor: 'pointer',
      }}
    >{label}</button>
  );
  return (
    <div
      onClick={onClose}
      style={{
        position: 'fixed', inset: 0, zIndex: 180,
        background: 'rgba(10,11,13,0.38)', backdropFilter: 'blur(2px)',
      }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        style={{
          width: 300, maxWidth: '85vw', height: '100%',
          background: T.panel, color: T.fg, fontFamily: 'var(--sans)',
          borderRight: `1px solid ${T.line}`,
          display: 'flex', flexDirection: 'column',
          boxShadow: '0 30px 80px rgba(0,0,0,0.2)',
        }}
      >
        <div style={{
          padding: '14px 18px', borderBottom: `1px solid ${T.line}`,
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <div style={{ width: 6, height: 6, background: T.accent }} />
            <span style={{ fontFamily: 'var(--mono)', fontSize: 10, letterSpacing: '0.18em' }}>OPERATOR</span>
          </div>
          <button onClick={onClose}
            style={{ border: 'none', background: 'transparent', color: T.fg3, cursor: 'pointer', fontSize: 18 }}>×</button>
        </div>

        <Item id="today" label="Today" hint="⌘ 1" onClick={() => { setView('today'); onClose(); }} />
        <Item id="analytics" label="Analytics" hint="⌘ 2" onClick={() => { setView('analytics'); onClose(); }} />

        <div style={{ flex: 1 }} />

        <Sub label="@ Contexts" onClick={() => { onOpenContexts(); onClose(); }} />
        <Sub label="⚙ Tweaks"  onClick={() => { onOpenTweaks();  onClose(); }} />
        {window.OperatorAuth?.configured && (
          <Sub label="↪ Sign out" onClick={() => { window.OperatorAuth.signOut(); onClose(); }} />
        )}
      </div>
    </div>
  );
}

// Quick inbox capture
function QuickInbox({ T, D, onCapture }) {
  const [val, setVal] = useState('');
  const submit = () => { if (!val.trim()) return; onCapture({ title: val.trim() }); setVal(''); };
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 10,
      padding: '12px 22px', borderBottom: `1px solid ${T.line}`, background: T.panel2,
    }}>
      <span style={{ fontFamily: 'var(--mono)', fontSize: 12, color: T.accent }}>›</span>
      <input
        placeholder="Brain dump here…"
        value={val}
        onChange={(e) => setVal(e.target.value)}
        onKeyDown={(e) => { if (e.key === 'Enter') submit(); }}
        style={{
          flex: 1, border: 'none', outline: 'none', background: 'transparent',
          fontFamily: 'var(--sans)', fontSize: 13, color: T.fg,
        }}
      />
      <span style={{ fontFamily: 'var(--mono)', fontSize: 9, color: T.fg3, letterSpacing: '0.1em' }}>⏎</span>
    </div>
  );
}

// Tweaks panel
function TweaksPanel({ tweaks, setTweak, onClose, T }) {
  const Row = ({ label, children }) => (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 18 }}>
      <MonoLabel T={T}>{label}</MonoLabel>
      <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>{children}</div>
    </div>
  );
  const Opt = ({ val, cur, onClick, children }) => (
    <button
      onClick={onClick}
      style={{
        border: `1px solid ${cur === val ? T.accent : T.line}`,
        background: cur === val ? T.accentSoft : 'transparent',
        color: cur === val ? T.accent : T.fg2,
        fontFamily: 'var(--mono)', fontSize: 10, letterSpacing: '0.1em',
        padding: '6px 12px', cursor: 'pointer', textTransform: 'uppercase',
      }}
    >{children}</button>
  );
  return (
    <div style={{
      position: 'fixed', bottom: 20, right: 20, zIndex: 100,
      width: 300, background: T.panel, border: `1px solid ${T.line}`,
      boxShadow: '0 20px 60px rgba(0,0,0,0.2)',
      fontFamily: 'var(--sans)',
    }}>
      <div style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        padding: '12px 16px', borderBottom: `1px solid ${T.line}`,
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
          <div style={{ width: 6, height: 6, background: T.accent }} />
          <span style={{ fontFamily: 'var(--mono)', fontSize: 10, color: T.fg, letterSpacing: '0.18em' }}>TWEAKS</span>
        </div>
        <button
          onClick={onClose}
          style={{ border: 'none', background: 'transparent', color: T.fg3, cursor: 'pointer', fontSize: 14 }}
        >×</button>
      </div>
      <div style={{ padding: 16 }}>
        <Row label="THEME">
          <Opt val="light" cur={tweaks.theme} onClick={() => setTweak('theme', 'light')}>LIGHT</Opt>
          <Opt val="dark" cur={tweaks.theme} onClick={() => setTweak('theme', 'dark')}>DARK</Opt>
          <Opt val="warm" cur={tweaks.theme} onClick={() => setTweak('theme', 'warm')}>WARM</Opt>
        </Row>
        <Row label="TYPOGRAPHY">
          <Opt val="sans" cur={tweaks.typography} onClick={() => setTweak('typography', 'sans')}>SANS</Opt>
          <Opt val="serif" cur={tweaks.typography} onClick={() => setTweak('typography', 'serif')}>SERIF</Opt>
        </Row>
        <Row label="DENSITY">
          <Opt val="compact" cur={tweaks.density} onClick={() => setTweak('density', 'compact')}>COMPACT</Opt>
          <Opt val="cozy" cur={tweaks.density} onClick={() => setTweak('density', 'cozy')}>COZY</Opt>
          <Opt val="spacious" cur={tweaks.density} onClick={() => setTweak('density', 'spacious')}>SPACIOUS</Opt>
        </Row>
        <Row label="HABITS">
          <Opt val={true} cur={tweaks.showHabits} onClick={() => setTweak('showHabits', true)}>SHOW</Opt>
          <Opt val={false} cur={tweaks.showHabits} onClick={() => setTweak('showHabits', false)}>HIDE</Opt>
        </Row>
      </div>
    </div>
  );
}

function ReasoningCard({ T }) {
  const [open, setOpen] = useState(false);
  return (
    <div style={{
      position: 'fixed', bottom: 20, left: 20, zIndex: 50,
      maxWidth: open ? 420 : 180,
      transition: 'max-width 0.2s',
    }}>
      <button
        onClick={() => setOpen(!open)}
        style={{
          width: '100%', textAlign: 'left',
          border: '1px solid rgba(0,0,0,0.12)', background: 'rgba(255,255,255,0.85)',
          backdropFilter: 'blur(8px)',
          color: 'rgba(0,0,0,0.7)', fontFamily: 'var(--mono)', fontSize: 10,
          letterSpacing: '0.12em', padding: '10px 14px', cursor: 'pointer',
        }}
      >
        <span style={{ color: 'oklch(52% 0.12 220)' }}>●</span> DESIGN NOTES {open ? '▾' : '▸'}
        {open && (
          <div style={{
            marginTop: 10, fontFamily: 'var(--sans)', fontSize: 12,
            letterSpacing: 0, textTransform: 'none', lineHeight: 1.55,
            color: 'rgba(0,0,0,0.75)',
          }}>
            <p style={{ margin: '0 0 8px' }}><b>System:</b> GTD-lite with Now/Next/Later. One single target locked as NOW — everything else dims. Next = today's queue. Later = not today. Habits scheduled for today auto-appear in Now as non-negotiables.</p>
            <p style={{ margin: '0 0 8px' }}><b>Aesthetic:</b> HUD-precision mono readouts, generous negative space, one cold accent used sparingly.</p>
            <p style={{ margin: '0 0 8px' }}><b>Keyboard inbox:</b> J/K or arrows to navigate, 1–5 to tag context, T to promote to task, X to delete. Process 10 items in 10 seconds.</p>
            <p style={{ margin: 0 }}><b>Habits = Today:</b> Gym, family dinner, etc. show up as tasks you check off. Checking marks the day. One list, one glance.</p>
          </div>
        )}
      </button>
    </div>
  );
}

// Auth gate — shown when Supabase is configured but no active session.
// Sends a magic link to the email you enter; the link returns you here
// signed in and the app hydrates from the server.
function AuthGate() {
  // mode: 'magic' | 'password-in' | 'password-up'
  const [mode, setMode] = useState('magic');
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [sent, setSent] = useState(false);
  const [needsConfirm, setNeedsConfirm] = useState(false);
  const [err, setErr] = useState('');
  const [busy, setBusy] = useState(false);

  const reset = (nextMode) => {
    setMode(nextMode); setErr(''); setSent(false); setNeedsConfirm(false);
  };

  const submit = async (e) => {
    e.preventDefault();
    if (!email.trim()) return;
    setBusy(true); setErr('');
    try {
      if (mode === 'magic') {
        await window.OperatorAuth.signIn(email.trim());
        setSent(true);
      } else if (mode === 'password-in') {
        await window.OperatorAuth.signInWithPassword(email.trim(), password);
      } else if (mode === 'password-up') {
        if (password.length < 6) throw new Error('Password must be at least 6 characters.');
        const { needsConfirmation } = await window.OperatorAuth.signUpWithPassword(email.trim(), password);
        if (needsConfirmation) setNeedsConfirm(true);
      }
    } catch (e) { setErr(e.message || String(e)); }
    finally { setBusy(false); }
  };

  const Pill = ({ active, children, onClick }) => (
    <button
      type="button" onClick={onClick}
      style={{
        flex: 1, padding: '8px 10px',
        border: '1px solid rgba(10,11,13,0.15)',
        background: active ? '#0A0B0D' : 'transparent',
        color: active ? '#FFFFFF' : 'rgba(10,11,13,0.7)',
        fontFamily: 'var(--mono)', fontSize: 10, letterSpacing: '0.14em',
        cursor: 'pointer',
      }}
    >{children}</button>
  );

  return (
    <div style={{
      minHeight: '100vh', background: '#E8E5DD', color: '#0A0B0D',
      fontFamily: 'var(--sans)',
      display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24,
    }}>
      <div style={{
        width: 380, maxWidth: '100%', background: '#FFFFFF',
        border: '1px solid rgba(10,11,13,0.08)',
        padding: 28, boxShadow: '0 1px 3px rgba(0,0,0,0.06), 0 10px 40px rgba(0,0,0,0.08)',
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 18 }}>
          <div style={{ width: 6, height: 6, background: 'oklch(52% 0.12 220)' }} />
          <span style={{ fontFamily: 'var(--mono)', fontSize: 10, letterSpacing: '0.18em' }}>OPERATOR</span>
        </div>
        <div style={{ fontSize: 22, fontWeight: 500, letterSpacing: '-0.01em', marginBottom: 6 }}>
          {mode === 'password-up' ? 'Create account' : 'Sign in to sync'}
        </div>
        <div style={{ fontSize: 13, color: 'rgba(10,11,13,0.62)', lineHeight: 1.5, marginBottom: 16 }}>
          Tasks, inbox, and habits sync across every device you sign in on.
        </div>

        {/* Mode tabs */}
        <div style={{ display: 'flex', gap: 6, marginBottom: 14 }}>
          <Pill active={mode === 'magic'} onClick={() => reset('magic')}>MAGIC LINK</Pill>
          <Pill active={mode === 'password-in' || mode === 'password-up'} onClick={() => reset('password-in')}>PASSWORD</Pill>
        </div>

        {sent && mode === 'magic' ? (
          <div style={{
            fontSize: 13, padding: 14, border: '1px solid rgba(10,11,13,0.08)',
            background: '#F0EDE6',
          }}>
            Link sent to <b>{email}</b>. Open it on this device (or any other)
            to sign in.
          </div>
        ) : needsConfirm ? (
          <div style={{
            fontSize: 13, padding: 14, border: '1px solid rgba(10,11,13,0.08)',
            background: '#F0EDE6',
          }}>
            Confirmation email sent to <b>{email}</b>. Click the link, then
            come back and sign in with your password.
          </div>
        ) : (
          <form onSubmit={submit}>
            <input
              type="email" required placeholder="you@example.com"
              value={email} onChange={(e) => setEmail(e.target.value)}
              style={{
                width: '100%', padding: '10px 12px', fontSize: 14,
                border: '1px solid rgba(10,11,13,0.15)', outline: 'none',
                fontFamily: 'var(--sans)', marginBottom: 10, background: '#FFFFFF',
              }}
            />
            {mode !== 'magic' && (
              <input
                type="password" required minLength={6} placeholder="Password (6+ characters)"
                value={password} onChange={(e) => setPassword(e.target.value)}
                style={{
                  width: '100%', padding: '10px 12px', fontSize: 14,
                  border: '1px solid rgba(10,11,13,0.15)', outline: 'none',
                  fontFamily: 'var(--sans)', marginBottom: 10, background: '#FFFFFF',
                }}
              />
            )}
            <button
              type="submit" disabled={busy}
              style={{
                width: '100%', padding: '10px 12px',
                background: '#0A0B0D', color: '#FFFFFF', border: 'none',
                fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.14em',
                cursor: busy ? 'default' : 'pointer', opacity: busy ? 0.6 : 1,
              }}
            >{busy
              ? (mode === 'magic' ? 'SENDING…' : mode === 'password-up' ? 'CREATING…' : 'SIGNING IN…')
              : (mode === 'magic' ? 'SEND MAGIC LINK' : mode === 'password-up' ? 'CREATE ACCOUNT' : 'SIGN IN')
            }</button>
            {mode === 'password-in' && (
              <div style={{ fontSize: 12, color: 'rgba(10,11,13,0.62)', marginTop: 10, textAlign: 'center' }}>
                New here?{' '}
                <button type="button" onClick={() => reset('password-up')}
                  style={{ background: 'none', border: 'none', color: '#0A0B0D', textDecoration: 'underline', cursor: 'pointer', fontSize: 12, padding: 0 }}>
                  Create an account
                </button>
              </div>
            )}
            {mode === 'password-up' && (
              <div style={{ fontSize: 12, color: 'rgba(10,11,13,0.62)', marginTop: 10, textAlign: 'center' }}>
                Already have an account?{' '}
                <button type="button" onClick={() => reset('password-in')}
                  style={{ background: 'none', border: 'none', color: '#0A0B0D', textDecoration: 'underline', cursor: 'pointer', fontSize: 12, padding: 0 }}>
                  Sign in
                </button>
              </div>
            )}
            {err && <div style={{ color: '#b22', fontSize: 12, marginTop: 10 }}>{err}</div>}
          </form>
        )}
      </div>
    </div>
  );
}

// Reset-password modal — shown when the user lands here via a Supabase
// password-recovery email. Takes a new password and updates the account.
function PasswordResetModal() {
  const [pw, setPw] = useState('');
  const [pw2, setPw2] = useState('');
  const [err, setErr] = useState('');
  const [busy, setBusy] = useState(false);
  const submit = async (e) => {
    e.preventDefault();
    setErr('');
    if (pw.length < 6) return setErr('Password must be at least 6 characters.');
    if (pw !== pw2) return setErr("Passwords don't match.");
    setBusy(true);
    try { await window.OperatorAuth.updatePassword(pw); }
    catch (e) { setErr(e.message || String(e)); }
    finally { setBusy(false); }
  };
  return (
    <div style={{
      minHeight: '100vh', background: '#E8E5DD', color: '#0A0B0D',
      fontFamily: 'var(--sans)',
      display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24,
    }}>
      <div style={{
        width: 380, maxWidth: '100%', background: '#FFFFFF',
        border: '1px solid rgba(10,11,13,0.08)',
        padding: 28, boxShadow: '0 1px 3px rgba(0,0,0,0.06), 0 10px 40px rgba(0,0,0,0.08)',
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 18 }}>
          <div style={{ width: 6, height: 6, background: 'oklch(52% 0.12 220)' }} />
          <span style={{ fontFamily: 'var(--mono)', fontSize: 10, letterSpacing: '0.18em' }}>OPERATOR · RESET</span>
        </div>
        <div style={{ fontSize: 22, fontWeight: 500, letterSpacing: '-0.01em', marginBottom: 6 }}>
          Set a new password
        </div>
        <div style={{ fontSize: 13, color: 'rgba(10,11,13,0.62)', lineHeight: 1.5, marginBottom: 16 }}>
          Pick 6+ characters. After this you'll be signed in.
        </div>
        <form onSubmit={submit}>
          <input
            type="password" required minLength={6} placeholder="New password"
            value={pw} onChange={(e) => setPw(e.target.value)} autoFocus
            style={{
              width: '100%', padding: '10px 12px', fontSize: 14,
              border: '1px solid rgba(10,11,13,0.15)', outline: 'none',
              fontFamily: 'var(--sans)', marginBottom: 10, background: '#FFFFFF',
            }}
          />
          <input
            type="password" required minLength={6} placeholder="Confirm new password"
            value={pw2} onChange={(e) => setPw2(e.target.value)}
            style={{
              width: '100%', padding: '10px 12px', fontSize: 14,
              border: '1px solid rgba(10,11,13,0.15)', outline: 'none',
              fontFamily: 'var(--sans)', marginBottom: 10, background: '#FFFFFF',
            }}
          />
          <button
            type="submit" disabled={busy}
            style={{
              width: '100%', padding: '10px 12px',
              background: '#0A0B0D', color: '#FFFFFF', border: 'none',
              fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.14em',
              cursor: busy ? 'default' : 'pointer', opacity: busy ? 0.6 : 1,
            }}
          >{busy ? 'UPDATING…' : 'UPDATE PASSWORD'}</button>
          {err && <div style={{ color: '#b22', fontSize: 12, marginTop: 10 }}>{err}</div>}
        </form>
      </div>
    </div>
  );
}

// Root — decides whether to show the auth gate or the app.
function Root() {
  const [authReady, setAuthReady] = useState(!window.OperatorAuth?.configured);
  const [hasSession, setHasSession] = useState(!!window.OperatorAuth?.session);
  const [recovering, setRecovering] = useState(!!window.OperatorAuth?.recovering);
  useEffect(() => {
    if (!window.OperatorAuth?.configured) return;
    const off = window.OperatorAuth.onChange((s) => {
      setHasSession(!!s);
      setRecovering(!!window.OperatorAuth.recovering);
      setAuthReady(true);
    });
    const t = setTimeout(() => setAuthReady(true), 400);
    return () => { off && off(); clearTimeout(t); };
  }, []);
  if (!window.OperatorAuth?.configured) return <App />; // local-only mode
  if (!authReady) return null;
  if (recovering) return <PasswordResetModal />;
  if (!hasSession) return <AuthGate />;
  return <App />;
}

// Welcome modal — shown the first time a user signs in (or on a fresh
// install where no `onboarded` flag is present on their state). Dismissing
// sets `onboarded: true` which syncs to Supabase and suppresses it forever
// after on every device.
function WelcomeModal({ onClose, email }) {
  const isMobile = useIsMobile();
  const Row = ({ k, children }) => (
    <div style={{
      display: 'flex',
      flexDirection: isMobile ? 'column' : 'row',
      gap: isMobile ? 4 : 14,
      alignItems: isMobile ? 'flex-start' : 'baseline',
      padding: isMobile ? '10px 0' : '8px 0',
    }}>
      <div style={{
        fontFamily: 'var(--mono)', fontSize: 10, letterSpacing: '0.14em',
        color: 'rgba(10,11,13,0.5)',
        minWidth: isMobile ? 0 : 110,
        textTransform: 'uppercase',
      }}>{k}</div>
      <div style={{ fontSize: 13.5, color: '#0A0B0D', lineHeight: 1.5 }}>{children}</div>
    </div>
  );
  return (
    <div style={{
      position: 'fixed', inset: 0, zIndex: 200, overflowY: 'auto',
      background: 'rgba(10,11,13,0.38)', backdropFilter: 'blur(2px)',
      display: 'flex', alignItems: 'flex-start', justifyContent: 'center',
      padding: isMobile ? 12 : 24,
    }}>
      <div style={{
        width: '100%', maxWidth: 520, background: '#FFFFFF',
        border: '1px solid rgba(10,11,13,0.1)',
        boxShadow: '0 30px 80px rgba(0,0,0,0.25)',
        padding: isMobile ? 18 : 28,
        margin: isMobile ? '12px 0' : 'auto',
        fontFamily: 'var(--sans)', color: '#0A0B0D',
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 14 }}>
          <div style={{ width: 6, height: 6, background: 'oklch(52% 0.12 220)' }} />
          <span style={{ fontFamily: 'var(--mono)', fontSize: 10, letterSpacing: '0.18em' }}>OPERATOR · WELCOME</span>
        </div>
        <div style={{
          fontSize: isMobile ? 22 : 26, fontWeight: 500,
          letterSpacing: '-0.01em', marginBottom: 6,
          overflowWrap: 'anywhere', wordBreak: 'break-word',
        }}>
          You're in{email ? `, ${email}` : ''}.
        </div>
        <div style={{ fontSize: 13.5, color: 'rgba(10,11,13,0.62)', lineHeight: 1.55, marginBottom: 18 }}>
          A GTD-lite to-do built around a single locked target. Everything syncs
          across every device you sign in on. Here's the fast tour.
        </div>

        <div style={{ borderTop: '1px solid rgba(10,11,13,0.08)', paddingTop: 6 }}>
          <Row k="Capture">
            Type anything into the <b>Brain dump</b> box. Don't think — just
            dump. Process later.
          </Row>
          <Row k="Now / Next / Later">
            Promote one task to <b>Now</b> — that's your locked target.
            Everything else dims. <b>Next</b> is today's queue. <b>Later</b> is
            not today.
          </Row>
          <Row k="Schedule">
            Type <code style={{ fontFamily: 'var(--mono)', fontSize: 12, background: '#F0EDE6', padding: '1px 5px' }}>"Call Sam at 3pm"</code> or
            <code style={{ fontFamily: 'var(--mono)', fontSize: 12, background: '#F0EDE6', padding: '1px 5px', marginLeft: 4 }}>"Gym at 6:30pm"</code>
            — the time is extracted and auto-promotes to Now when it hits.
          </Row>
          <Row k="Keyboard (inbox)">
            <b>J / K</b> or arrows navigate · <b>1–5</b> tag a context ·
            <b> T</b> promote to task · <b>X</b> delete.
          </Row>
          <Row k="Habits">
            Recurring non-negotiables (gym, family dinner). Scheduled habits
            appear in Now as checkable items. Click a weekday square to mark
            it done.
          </Row>
          <Row k="Sync">
            Your data lives in the cloud. Sign in on any device with the same
            email to see the same list.
          </Row>
        </div>

        <button
          onClick={onClose}
          style={{
            marginTop: 20, width: '100%', padding: '11px 12px',
            background: '#0A0B0D', color: '#FFFFFF', border: 'none',
            fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.14em',
            cursor: 'pointer',
          }}
        >LET'S GO</button>
      </div>
    </div>
  );
}

// Contexts manager — add / rename / delete the user's context tags.
function ContextsPanel({ contexts, onAdd, onRename, onDelete, onClose }) {
  const isMobile = useIsMobile();
  const [newCtx, setNewCtx] = useState('');
  const [editing, setEditing] = useState(null);
  const [editVal, setEditVal] = useState('');
  const add = () => {
    if (!newCtx.trim()) return;
    onAdd(newCtx.trim());
    setNewCtx('');
  };
  const commitEdit = () => {
    if (editing && editVal.trim() && editVal.trim() !== editing) onRename(editing, editVal.trim());
    setEditing(null); setEditVal('');
  };
  return (
    <div style={{
      position: 'fixed', inset: 0, zIndex: 200, overflowY: 'auto',
      background: 'rgba(10,11,13,0.38)', backdropFilter: 'blur(2px)',
      display: 'flex', alignItems: 'flex-start', justifyContent: 'center',
      padding: isMobile ? 12 : 24,
    }}>
      <div style={{
        width: '100%', maxWidth: 460, background: '#FFFFFF',
        border: '1px solid rgba(10,11,13,0.1)',
        boxShadow: '0 30px 80px rgba(0,0,0,0.25)',
        padding: isMobile ? 18 : 24,
        margin: isMobile ? '12px 0' : 'auto',
        fontFamily: 'var(--sans)', color: '#0A0B0D',
      }}>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 14 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <div style={{ width: 6, height: 6, background: 'oklch(52% 0.12 220)' }} />
            <span style={{ fontFamily: 'var(--mono)', fontSize: 10, letterSpacing: '0.18em' }}>CONTEXTS</span>
          </div>
          <button onClick={onClose}
            style={{ border: 'none', background: 'transparent', color: 'rgba(10,11,13,0.4)', cursor: 'pointer', fontSize: 18 }}>×</button>
        </div>
        <div style={{ fontSize: 13, color: 'rgba(10,11,13,0.62)', lineHeight: 1.5, marginBottom: 14 }}>
          Tags you can stick on tasks (e.g. <code style={{ fontFamily: 'var(--mono)' }}>@calls</code>, <code style={{ fontFamily: 'var(--mono)' }}>@deep</code>).
          Keyboard shortcut <b>1–9</b> in the inbox assigns by position.
        </div>

        <div style={{ display: 'flex', gap: 6, marginBottom: 14 }}>
          <input
            placeholder="new context (e.g. focus)"
            value={newCtx}
            onChange={(e) => setNewCtx(e.target.value)}
            onKeyDown={(e) => { if (e.key === 'Enter') add(); }}
            style={{
              flex: 1, padding: '9px 12px', fontSize: 14,
              border: '1px solid rgba(10,11,13,0.15)', outline: 'none',
              fontFamily: 'var(--sans)', background: '#FFFFFF',
            }}
          />
          <button onClick={add}
            style={{
              border: 'none', background: '#0A0B0D', color: '#FFFFFF',
              fontFamily: 'var(--mono)', fontSize: 10, letterSpacing: '0.14em',
              padding: '9px 14px', cursor: 'pointer',
            }}>ADD</button>
        </div>

        <div style={{ borderTop: '1px solid rgba(10,11,13,0.08)' }}>
          {contexts.length === 0 && (
            <div style={{
              padding: '24px 0', textAlign: 'center',
              fontFamily: 'var(--mono)', fontSize: 10, color: 'rgba(10,11,13,0.4)', letterSpacing: '0.12em',
            }}>─── EMPTY ───</div>
          )}
          {contexts.map((c, i) => (
            <div key={c} style={{
              display: 'flex', alignItems: 'center', gap: 10,
              padding: '10px 0', borderBottom: '1px solid rgba(10,11,13,0.06)',
            }}>
              <span style={{ fontFamily: 'var(--mono)', fontSize: 10, color: 'rgba(10,11,13,0.4)', minWidth: 18 }}>{i + 1}</span>
              {editing === c ? (
                <input
                  value={editVal}
                  onChange={(e) => setEditVal(e.target.value)}
                  onKeyDown={(e) => { if (e.key === 'Enter') commitEdit(); if (e.key === 'Escape') { setEditing(null); setEditVal(''); } }}
                  onBlur={commitEdit}
                  autoFocus
                  style={{
                    flex: 1, padding: '6px 8px', fontSize: 13,
                    border: '1px solid rgba(10,11,13,0.25)', outline: 'none',
                    fontFamily: 'var(--mono)', background: '#FFFFFF',
                  }}
                />
              ) : (
                <button onClick={() => { setEditing(c); setEditVal(c); }}
                  style={{
                    flex: 1, textAlign: 'left',
                    border: 'none', background: 'transparent', cursor: 'pointer',
                    fontFamily: 'var(--mono)', fontSize: 13, color: '#0A0B0D', padding: '6px 0',
                  }}
                >{c}</button>
              )}
              <button onClick={() => onDelete(c)} aria-label={`Delete ${c}`}
                style={{
                  border: 'none', background: 'transparent', color: 'rgba(10,11,13,0.4)',
                  fontSize: 16, lineHeight: 1, cursor: 'pointer', padding: '4px 8px',
                }}>×</button>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { App, Root, AuthGate, TweaksPanel, QuickInbox, ReasoningCard, WelcomeModal, ContextsPanel });
