/* Main Taskhub app shell — modernised */

function App() {
  // ─── Tweaks (persisted) ───
  const [tweaks, setTweaksState] = React.useState(() => {
    let stored = {};
    try { stored = JSON.parse(localStorage.getItem('tashhub-tweaks') || '{}'); } catch (e) {}
    return {
      theme: 'light',
      accent: 'indigo',
      density: 'cozy',
      defaultViewMode: 'list',
      animations: true,
      compactMyDay: false,
      showPulse: true,
      ...stored,
    };
  });
  const setTweak = (key, value) => {
    setTweaksState(t => {
      const next = { ...t, [key]: value };
      try { localStorage.setItem('tashhub-tweaks', JSON.stringify(next)); } catch (e) {}
      return next;
    });
  };
  // Apply theme + accent + density to <html>
  React.useEffect(() => {
    const r = document.documentElement;
    let theme = tweaks.theme;
    if (theme === 'auto') {
      theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
    }
    if (theme === 'dark') r.setAttribute('data-theme', 'dark');
    else r.removeAttribute('data-theme');
    r.setAttribute('data-accent', tweaks.accent);
    r.setAttribute('data-density', tweaks.density);
  }, [tweaks.theme, tweaks.accent, tweaks.density]);

  const [activeView, setActiveView]   = React.useState('my-day');
  const [viewMode, setViewMode]       = React.useState(tweaks.defaultViewMode);
  const [groupBy,  setGroupBy]        = React.useState('status');
  const [sort,     setSort]           = React.useState('priority');
  const [smartFilter, setSmartFilter] = React.useState(null);

  const [filters, setFilters] = React.useState({
    spaces: [], tags: [], assignees: [], priorities: [], queues: [], statuses: [],
  });
  const [searchQ, setSearchQ] = React.useState('');

  const [selectedId, setSelectedId] = React.useState(1);
  const [showDetail, setShowDetail] = React.useState(true);
  const [statusOverrides, setStatusOverrides] = React.useState({});
  const [dateOverrides,   setDateOverrides]   = React.useState({});
  const [priorityOverrides, setPriorityOverrides] = React.useState({});
  const [titleOverrides,    setTitleOverrides]    = React.useState({});
  const [assigneeOverrides, setAssigneeOverrides] = React.useState({});
  const [queueOverrides,    setQueueOverrides]    = React.useState({});
  const [reminderOverrides, setReminderOverrides] = React.useState({});
  const [flagOverrides,     setFlagOverrides]     = React.useState({});
  const [myDayUserOverrides, setMyDayUserOverrides] = React.useState({});
  const [clientOverrides,   setClientOverrides]   = React.useState({});
  const [activityOverrides, setActivityOverrides] = React.useState({});
  const [linkOverrides,     setLinkOverrides]     = React.useState({});
  const [editTitleOnOpen, setEditTitleOnOpen] = React.useState(null);
  const [queueMembership,   setQueueMembership]   = React.useState(() => {
    try {
      const s = JSON.parse(localStorage.getItem('tashhub-queue-members') || 'null');
      if (s) return s;
    } catch (e) {}
    return { 1: [1, 3, 5], 2: [4, 1], 3: [1, 2, 6] };
  });
  const persistMembership = (next) => { setQueueMembership(next); try { localStorage.setItem('tashhub-queue-members', JSON.stringify(next)); } catch (e) {} };
  const [extraQueues,       setExtraQueues]       = React.useState(() => {
    try { return JSON.parse(localStorage.getItem('tashhub-extra-queues') || '[]') || []; }
    catch (e) { return []; }
  });
  const persistExtraQueues = (next) => { setExtraQueues(next); try { localStorage.setItem('tashhub-extra-queues', JSON.stringify(next)); } catch (e) {} };
  const [extraTasks,      setExtraTasks]      = React.useState([]);
  const [bulkSelected,    setBulkSelected]    = React.useState(() => new Set());

  // Saved views (persistent)
  const [savedViews, setSavedViews] = React.useState(() => {
    try { return JSON.parse(localStorage.getItem('tashhub-saved-views') || '[]') || []; }
    catch (e) { return []; }
  });
  const persistViews = (next) => { setSavedViews(next); try { localStorage.setItem('tashhub-saved-views', JSON.stringify(next)); } catch (e) {} };

  // Onboarding (first run)
  const [showOnboarding, setShowOnboarding] = React.useState(() => {
    try { return !localStorage.getItem('tashhub-onboarded'); } catch (e) { return true; }
  });
  const dismissOnboarding = () => {
    setShowOnboarding(false);
    try { localStorage.setItem('tashhub-onboarded', '1'); } catch (e) {}
  };

  // Overlays
  const [paletteOpen, setPaletteOpen]       = React.useState(false);
  const [tweaksOpen,  setTweaksOpen]        = React.useState(false);
  const [inboxOpen,   setInboxOpen]         = React.useState(false);
  const [filterOpen,  setFilterOpen]        = React.useState(false);
  const [cheatOpen,   setCheatOpen]         = React.useState(false);
  const [pulseOpen,   setPulseOpen]         = React.useState(false);
  const [focusTask,   setFocusTask]         = React.useState(null);
  const [personModal, setPersonModal]       = React.useState(null);     // userId
  const [templatePickerOpen, setTemplatePickerOpen] = React.useState(false);
  const [saveViewOpen, setSaveViewOpen]     = React.useState(false);
  const [confettiKey, setConfettiKey]       = React.useState(0);
  const [modalTaskId, setModalTaskId]       = React.useState(null);

  // Toasts
  const [toasts, setToasts] = React.useState([]);
  const pushToast = React.useCallback((toast) => {
    const id = Math.random().toString(36).slice(2);
    setToasts(ts => [...ts, { id, ...toast }]);
    setTimeout(() => setToasts(ts => ts.filter(t => t.id !== id)), 4200);
  }, []);
  const dismissToast = (id) => setToasts(ts => ts.filter(t => t.id !== id));

  const isReport = ['dashboard','workload','time','audit','dashboards','settings','workspace','queues','my-work','views','reports'].includes(activeView) || activeView.startsWith('queue:');

  // Compose tasks
  const allTasks = React.useMemo(() => [...D.tasks, ...extraTasks].map(x => {
    const QUEUE_SPACE_MAP = { 1: [1, 5], 2: [3], 3: [2, 4] };
    let defaultQueueId = null;
    for (const [qid, spaces] of Object.entries(QUEUE_SPACE_MAP)) {
      if (spaces.includes(x.spaceId)) { defaultQueueId = parseInt(qid, 10); break; }
    }
    return {
      ...x,
      statusId:   statusOverrides[x.id]   ?? x.statusId,
      dueDate:    dateOverrides[x.id]     ?? x.dueDate,
      priority:   priorityOverrides[x.id] ?? x.priority,
      title:      titleOverrides[x.id]    ?? x.title,
      assignees:  assigneeOverrides[x.id] ?? x.assignees,
      queueId:    queueOverrides[x.id]    ?? x.queueId ?? defaultQueueId,
      reminder:   reminderOverrides[x.id] !== undefined ? reminderOverrides[x.id] : x.reminder,
      myDayUserIds: myDayUserOverrides[x.id] || (x.myDay ? [1] : []),
      clients:    clientOverrides[x.id]   ?? x.clients,
      activity:   activityOverrides[x.id]
        ? [...activityOverrides[x.id], ...(x.activity || [])]
        : (x.activity || []),
      linkedTaskIds: linkOverrides[x.id] ?? x.linkedTaskIds ?? [],
      myDay:      flagOverrides[x.id]?.myDay      !== undefined
        ? flagOverrides[x.id].myDay
        : (myDayUserOverrides[x.id] ? myDayUserOverrides[x.id].includes(1) : x.myDay),
      important:  flagOverrides[x.id]?.important  !== undefined ? flagOverrides[x.id].important  : x.important,
    };
  }), [extraTasks, statusOverrides, dateOverrides, priorityOverrides, titleOverrides, assigneeOverrides, queueOverrides, reminderOverrides, flagOverrides, myDayUserOverrides, clientOverrides, activityOverrides, linkOverrides]);

  // ─── Activity logging ───
  const logActivity = React.useCallback((taskId, what) => {
    const now = new Date();
    const at = now.toTimeString().slice(0, 5);
    setActivityOverrides(o => ({
      ...o,
      [taskId]: [{ at, who: 1, what }, ...(o[taskId] || [])],
    }));
  }, []);

  // Filter / sort logic
  const filteredTasks = React.useMemo(() => {
    let t = allTasks;
    if (activeView === 'my-day')    t = t.filter(x => x.myDay);
    if (activeView === 'important') t = t.filter(x => x.important);
    if (activeView === 'my-tasks')  t = t.filter(x => x.assignees.includes(1));
    if (activeView === 'mentions')  t = t.filter(x => x.comments.some(c => c.mention));
    if (activeView === 'planned')   t = t.filter(x => x.dueDate);
    if (activeView === 'snoozed')   t = t.filter(x => x.snoozedUntil);
    if (activeView === 'archived')  t = t.filter(x => x.statusId === 6);
    if (activeView === 'all')       t = t.filter(x => x.statusId !== 6 && !x.snoozedUntil);

    if (smartFilter === 'today')       t = t.filter(x => x.dueDate && dayDelta(x.dueDate) === 0);
    if (smartFilter === 'week')        t = t.filter(x => x.dueDate && dayDelta(x.dueDate) >= 0 && dayDelta(x.dueDate) <= 7);
    if (smartFilter === 'overdue')     t = t.filter(x => x.dueDate && dayDelta(x.dueDate) < 0 && x.statusId !== 6);
    if (smartFilter === 'unscheduled') t = t.filter(x => !x.dueDate);

    if (filters.spaces.length)     t = t.filter(x => filters.spaces.includes(x.spaceId));
    if (filters.tags.length)       t = t.filter(x => x.tags.some(id => filters.tags.includes(id)));
    if (filters.priorities.length) t = t.filter(x => filters.priorities.includes(x.priority));
    if (filters.assignees.length)  t = t.filter(x => x.assignees.some(a => filters.assignees.includes(a)));
    if (filters.statuses.length)   t = t.filter(x => filters.statuses.includes(x.statusId));
    if (filters.queues.length) {
      // Map queues → spaces:  1 Front desk → INT (1) + OPS (5); 2 Billing → BILL (3); 3 Clinical leads → CARE (2) + COMP (4)
      const qMap = { 1: [1, 5], 2: [3], 3: [2, 4] };
      const allowed = new Set(filters.queues.flatMap(q => qMap[q] || []));
      t = t.filter(x => allowed.has(x.spaceId));
    }

    if (searchQ.trim()) {
      const q = searchQ.toLowerCase();
      t = t.filter(x =>
        x.title.toLowerCase().includes(q) ||
        x.ticket.toLowerCase().includes(q) ||
        (x.description || '').toLowerCase().includes(q)
      );
    }

    const prioRank = { urgent: 0, high: 1, medium: 2, low: 3 };
    if (sort === 'newest')   t = [...t].sort((a, b) => b.createdAt.localeCompare(a.createdAt));
    if (sort === 'oldest')   t = [...t].sort((a, b) => a.createdAt.localeCompare(b.createdAt));
    if (sort === 'due-soon') t = [...t].sort((a, b) => (a.dueDate || '9999').localeCompare(b.dueDate || '9999'));
    if (sort === 'priority') t = [...t].sort((a, b) => prioRank[a.priority] - prioRank[b.priority]);

    return t;
  }, [allTasks, activeView, filters, searchQ, sort, smartFilter]);

  const selectedTask = React.useMemo(() => {
    const inAll = allTasks.find(t => t.id === selectedId);
    return inAll || filteredTasks[0] || null;
  }, [allTasks, filteredTasks, selectedId]);

  const toggleFilter = (kind, id) => {
    setFilters(f => ({
      ...f,
      [kind]: f[kind].includes(id) ? f[kind].filter(x => x !== id) : [...f[kind], id],
    }));
  };
  const togglePriority = (p) => {
    setFilters(f => ({
      ...f,
      priorities: f.priorities.includes(p) ? f.priorities.filter(x => x !== p) : [...f.priorities, p],
    }));
  };
  const clearFilters = () => { setFilters({ spaces: [], tags: [], assignees: [], priorities: [], queues: [], statuses: [] }); setSmartFilter(null); setSearchQ(''); };

  const handleStatusChange = (taskId, newStatusId) => {
    const prev = allTasks.find(t => t.id === taskId);
    setStatusOverrides(o => ({ ...o, [taskId]: newStatusId }));
    window.api?.patchTask(taskId, { statusId: newStatusId });
    const task = allTasks.find(t => t.id === taskId);
    const newStatus = statusById(newStatusId);
    const oldStatus = prev?.statusId ? statusById(prev.statusId) : null;
    pushToast({ tone: newStatusId === 6 ? 'success' : 'info', title: 'Moved', body: `${task?.ticket || 'Task'} → ${newStatus?.title}` });
    logActivity(taskId, `changed status: ${oldStatus?.title || '—'} → ${newStatus?.title}`);
    if (newStatusId === 6 && prev && prev.priority === 'urgent' && prev.statusId !== 6) {
      setConfettiKey(k => k + 1);
    }
  };

  const handleReschedule = (taskId, isoDate) => {
    const prev = allTasks.find(x => x.id === taskId);
    setDateOverrides(d => ({ ...d, [taskId]: isoDate }));
    window.api?.patchTask(taskId, isoDate ? { dueDate: isoDate } : { clearDue: true });
    const task = allTasks.find(t => t.id === taskId);
    if (isoDate) pushToast({ tone: 'info', title: 'Rescheduled', body: `${task?.ticket || 'Task'} → ${new Date(isoDate + 'T00:00:00').toLocaleDateString('en-GB', { day: 'numeric', month: 'short' })}` });
    else         pushToast({ tone: 'info', title: 'Date cleared', body: `${task?.ticket || 'Task'} is now unscheduled` });
    logActivity(taskId, isoDate ? `set due date to ${isoDate}` : 'cleared due date');
  };

  const handleUpdatePriority = (taskId, p) => {
    const prev = allTasks.find(x => x.id === taskId);
    setPriorityOverrides(o => ({ ...o, [taskId]: p }));
    window.api?.patchTask(taskId, { priority: p });
    const t = allTasks.find(t => t.id === taskId);
    pushToast({ tone: 'info', title: 'Priority updated', body: `${t?.ticket || 'Task'} → ${p}` });
    logActivity(taskId, `set priority: ${prev?.priority} → ${p}`);
  };

  const handleSetClients = (taskId, ids) => {
    setClientOverrides(o => ({ ...o, [taskId]: ids }));
    window.api?.patchTask(taskId, { clients: ids });
    // Auto-fill [patient name] placeholder when first client is added
    const t = allTasks.find(x => x.id === taskId);
    if (t && /\[([^\]]+)\]/.test(t.title) && ids.length > 0) {
      const c = clientById(ids[0]);
      if (c) {
        const newTitle = t.title.replace(/\[([^\]]+)\]/g, c.name);
        setTitleOverrides(o => ({ ...o, [taskId]: newTitle }));
      }
    }
  };

  // Reciprocal link toggle — adds/removes the linked task on both ends
  const handleToggleLink = (taskId, otherId) => {
    const t = allTasks.find(x => x.id === taskId);
    const o = allTasks.find(x => x.id === otherId);
    const current = t?.linkedTaskIds || [];
    const isLinked = current.includes(otherId);
    setLinkOverrides(prev => {
      const next = { ...prev };
      next[taskId]  = isLinked ? current.filter(x => x !== otherId) : [...current, otherId];
      const oCur = prev[otherId] ?? o?.linkedTaskIds ?? [];
      next[otherId] = isLinked ? oCur.filter(x => x !== taskId) : [...new Set([...oCur, taskId])];
      window.api?.patchTask(taskId, { linkedTaskIds: next[taskId] });
      window.api?.patchTask(otherId, { linkedTaskIds: next[otherId] });
      return next;
    });
    pushToast({
      tone: 'info',
      title: isLinked ? 'Unlinked' : 'Linked',
      body: `${t?.ticket} ${isLinked ? '⋄' : '⋈'} ${o?.ticket}`,
    });
    logActivity(taskId,  `${isLinked ? 'unlinked' : 'linked'} ${o?.ticket || `#${otherId}`}`);
    logActivity(otherId, `${isLinked ? 'unlinked' : 'linked'} ${t?.ticket || `#${taskId}`}`);
  };

  const handleUpdateTitle = (taskId, title) => {
    setTitleOverrides(o => ({ ...o, [taskId]: title }));
    window.api?.patchTask(taskId, { title });
    logActivity(taskId, `renamed task to "${title.length > 60 ? title.slice(0, 60) + '…' : title}"`);
  };

  const handleSetAssignees = (taskId, ids) => {
    setAssigneeOverrides(o => ({ ...o, [taskId]: ids }));
    window.api?.patchTask(taskId, { assignees: ids });
  };

  // Combined queues (built-in + user-created), with live member counts
  const liveQueues = React.useMemo(() => {
    return [...D.queues, ...extraQueues].map(q => ({
      ...q,
      members: queueMembership[q.id]?.length ?? q.members,
      memberIds: queueMembership[q.id] || [],
    }));
  }, [extraQueues, queueMembership]);

  React.useEffect(() => { window.__liveQueues = liveQueues; }, [liveQueues]);

  const handleSetReminder = (taskId, iso) => {
    setReminderOverrides(o => ({ ...o, [taskId]: iso }));
    window.api?.patchTask(taskId, iso ? { reminder: iso } : { clearReminder: true });
    const t = allTasks.find(x => x.id === taskId);
    if (iso) pushToast({ tone: 'info', title: 'Reminder set', body: `${t?.ticket || 'Task'} → ${new Date(iso + 'T00:00:00').toLocaleDateString('en-GB', { day: 'numeric', month: 'short' })}` });
    else     pushToast({ tone: 'info', title: 'Reminder cleared', body: `${t?.ticket || 'Task'}` });
    logActivity(taskId, iso ? `set reminder for ${iso}` : 'cleared reminder');
  };

  const handleToggleFlag = (taskId, flag) => {
    const t = allTasks.find(x => x.id === taskId);
    if (flag === 'myDay') {
      const current = t?.myDayUserIds || [];
      const inList = current.includes(1);
      const next = inList ? current.filter(x => x !== 1) : [...current, 1];
      setMyDayUserOverrides(o => ({ ...o, [taskId]: next }));
      setFlagOverrides(o => ({ ...o, [taskId]: { ...(o[taskId] || {}), myDay: !inList } }));
      window.api?.patchTask(taskId, { myDayUserIds: next });
      pushToast({
        tone: !inList ? 'success' : 'info',
        title: !inList ? 'Added to My Day' : 'Removed from My Day',
        body: t?.ticket || 'Task',
      });
      logActivity(taskId, inList ? 'removed task from My Day' : 'added task to My Day');
      return;
    }
    const current = !!t?.[flag];
    setFlagOverrides(o => ({ ...o, [taskId]: { ...(o[taskId] || {}), [flag]: !current } }));
    if (flag === 'important') window.api?.patchTask(taskId, { important: !current });
    pushToast({
      tone: !current ? 'success' : 'info',
      title: !current ? 'Starred' : 'Unstarred',
      body: t?.ticket || 'Task',
    });
    logActivity(taskId, current ? 'unstarred task' : 'starred task');
  };

  const handleSetQueue = (taskId, queueId) => {
    setQueueOverrides(o => ({ ...o, [taskId]: queueId }));
    window.api?.patchTask(taskId, { queueId });
    const t = allTasks.find(x => x.id === taskId);
    const q = queueId ? liveQueues.find(qq => qq.id === queueId) : null;
    pushToast({ tone: 'info', title: 'Queue updated', body: `${t?.ticket || 'Task'} → ${q?.name || 'no queue'}` });
  };

  const handleToggleQueueMember = (queueId, userId) => {
    const cur = queueMembership[queueId] || [];
    const next = cur.includes(userId) ? cur.filter(x => x !== userId) : [...cur, userId];
    persistMembership({ ...queueMembership, [queueId]: next });
    const q = liveQueues.find(qq => qq.id === queueId);
    const u = userById(userId);
    pushToast({ tone: 'info', title: cur.includes(userId) ? 'Left' : 'Joined', body: `${u?.name} ${cur.includes(userId) ? 'left' : 'joined'} ${q?.name}` });
  };

  const handleCreateQueue = ({ name, colour, memberIds }) => {
    const id = 1000 + extraQueues.length;
    const q = { id, name: name.trim(), colour: colour || '#6366F1', members: memberIds?.length || 0, count: 0 };
    persistExtraQueues([...extraQueues, q]);
    persistMembership({ ...queueMembership, [id]: memberIds || [] });
    pushToast({ tone: 'success', title: 'Queue created', body: `${q.name} is ready.` });
    setActiveView(`queue:${id}`);
  };

  const handleCreateTicketInQueue = (queueId, title) => {
    // figure out a sensible space from the queue
    const QUEUE_SPACE_MAP = { 1: [1, 5], 2: [3], 3: [2, 4] };
    const spaceId = (QUEUE_SPACE_MAP[queueId] || [1])[0];
    const id = 1000 + extraTasks.length;
    const t = {
      id,
      ticket: `Q${queueId}-${String(id).slice(-3)}`,
      title: title.trim(),
      description: '',
      spaceId, groupId: null,
      statusId: 1,
      priority: 'medium', type: 'task',
      assignees: [], watchers: [], clients: [], tags: [],
      dueDate: null, estimateMins: 0, actualMins: 0,
      myDay: false, important: false, snoozedUntil: null,
      createdAt: TODAY.toISOString().slice(0, 10),
      updatedAt: TODAY.toISOString().slice(0, 10),
      subtasks: [], checklist: [], comments: [], attachments: [],
      dependencies: { blockedBy: [], blocking: [] },
      activity: [],
      queueId,
    };
    setExtraTasks(ts => [...ts, t]);
    pushToast({ tone: 'success', title: 'Ticket added', body: title.length > 50 ? title.slice(0, 50) + '…' : title });
    setSelectedId(id);
  };

  // Bulk select
  const bulkToggle = (id, shift) => {
    setBulkSelected(s => {
      const next = new Set(s);
      if (next.has(id)) next.delete(id);
      else              next.add(id);
      return next;
    });
  };
  const bulkClear = () => setBulkSelected(new Set());
  const bulkSetStatus   = (sid) => { bulkSelected.forEach(id => setStatusOverrides(o => ({ ...o, [id]: sid })));
                                      pushToast({ tone: 'info', title: 'Bulk status', body: `${bulkSelected.size} tasks → ${statusById(sid)?.title}` });
                                      bulkClear(); };
  const bulkSetPriority = (p)   => { bulkSelected.forEach(id => setPriorityOverrides(o => ({ ...o, [id]: p })));
                                      pushToast({ tone: 'info', title: 'Bulk priority', body: `${bulkSelected.size} tasks → ${p}` });
                                      bulkClear(); };
  const bulkArchive = () => { bulkSelected.forEach(id => setStatusOverrides(o => ({ ...o, [id]: 6 })));
                              pushToast({ tone: 'success', title: 'Archived', body: `${bulkSelected.size} tasks archived` });
                              bulkClear(); };
  const bulkDelete = () => { pushToast({ tone: 'error', title: 'Deleted', body: `${bulkSelected.size} tasks removed (demo only)` }); bulkClear(); };

  // Pulse apply — handle action JSON blocks returned by the AI
  const handlePulseApply = (action) => {
    if (!action || !action.kind) return;
    const t = allTasks.find(x => x.id === action.task_id);
    const ref = t?.ticket || `#${action.task_id}`;
    if (action.kind === 'set_status') {
      const st = D.statuses.find(s => s.title.toLowerCase() === (action.status || '').toLowerCase());
      if (st && action.task_id) handleStatusChange(action.task_id, st.id);
    } else if (action.kind === 'set_due') {
      if (action.due && action.task_id) handleReschedule(action.task_id, action.due);
    } else if (action.kind === 'reassign') {
      pushToast({ tone: 'success', title: 'Reassigned', body: `${ref} → ${action.to}` });
    } else if (action.kind === 'set_priority') {
      pushToast({ tone: 'info', title: 'Priority updated', body: `${ref} → ${action.priority}` });
    } else if (action.kind === 'snooze') {
      pushToast({ tone: 'info', title: 'Snoozed', body: `${ref} until ${action.until}` });
    } else if (action.kind === 'add_tag') {
      pushToast({ tone: 'success', title: 'Tag added', body: `#${action.tag} → ${ref}` });
    } else {
      pushToast({ tone: 'info', title: 'Action applied', body: `${action.kind} on ${ref}` });
    }
  };

  const handleQuickAdd = (title, opts = {}) => {
    const id = 1000 + extraTasks.length;
    const t = {
      id,
      ticket: `NEW-${id}`,
      title,
      description: '',
      spaceId: opts.spaceId || 1, groupId: null,
      statusId: opts.statusId || 1,
      priority: opts.priority || 'medium',
      type:     opts.type     || 'task',
      assignees: [1], watchers: [], clients: [], tags: opts.tags || [],
      dueDate: opts.dueDate || null,
      estimateMins: opts.estimateMins || 0, actualMins: 0,
      myDay: activeView === 'my-day',
      important: false, snoozedUntil: null,
      createdAt: TODAY.toISOString().slice(0, 10),
      updatedAt: TODAY.toISOString().slice(0, 10),
      subtasks: [], checklist: (opts.checklist || []).map((title, i) => ({ id: id * 100 + i, title, done: false })),
      comments: [], attachments: [],
      dependencies: { blockedBy: [], blocking: [] },
      activity: [],
      reminder: opts.reminder || null,
      reminderDaysBefore: opts.reminderDaysBefore ?? null,
    };
    setExtraTasks(ts => [...ts, t]);
    pushToast({ tone: 'success', title: 'Task created', body: title.length > 50 ? title.slice(0, 50) + '…' : title });
    setSelectedId(id);
    // If the title has a [placeholder], cue the detail pane to auto-edit
    if (/\[([^\]]+)\]/.test(title)) {
      setEditTitleOnOpen(id);
    }
  };

  const handleNewFromTemplate = (tmpl) => {
    setTemplatePickerOpen(false);
    if (!tmpl) {
      handleQuickAdd('Untitled task');
      return;
    }
    // Compute reminder from template's reminderDaysBefore + dueDate if applicable
    let reminder = null;
    if (tmpl.reminderDaysBefore != null && tmpl.dueDate) {
      const d = new Date(tmpl.dueDate + 'T00:00:00');
      d.setDate(d.getDate() - tmpl.reminderDaysBefore);
      reminder = d.toISOString().slice(0, 10);
    }
    handleQuickAdd(tmpl.titlePlaceholder || tmpl.name, {
      spaceId: tmpl.spaceId, priority: tmpl.priority, type: tmpl.type,
      tags: tmpl.tags, checklist: tmpl.checklist, estimateMins: tmpl.estimateMins,
      reminder,
      reminderDaysBefore: tmpl.reminderDaysBefore,
    });
  };

  // Saved views
  const handleSaveView = (view) => {
    persistViews([...savedViews, view]);
    pushToast({ tone: 'success', title: 'View saved', body: `“${view.name}” is pinned in your sidebar.` });
  };
  const handleApplySavedView = (view) => {
    const s = view.state || {};
    if (s.filters)     setFilters(s.filters);
    if (s.smartFilter !== undefined) setSmartFilter(s.smartFilter);
    if (s.viewMode)    setViewMode(s.viewMode);
    if (s.groupBy)     setGroupBy(s.groupBy);
    if (s.sort)        setSort(s.sort);
    if (s.searchQ !== undefined) setSearchQ(s.searchQ);
    if (s.activeView)  setActiveView(s.activeView);
  };
  const handleDeleteView = (id) => persistViews(savedViews.filter(v => v.id !== id));

  const handleCompleteFromFocus = (id) => {
    handleStatusChange(id, 6);
    setFocusTask(null);
    pushToast({ tone: 'success', title: 'Nicely done', body: 'Task marked complete.' });
  };

  // Set a global handler so non-list views can trigger the modal via double-click
  React.useEffect(() => {
    window.__openTaskModal = (id) => { setSelectedId(id); setModalTaskId(id); };
    return () => { delete window.__openTaskModal; };
  }, []);
  React.useEffect(() => {
    const onKey = (e) => {
      const tag = (e.target.tagName || '').toLowerCase();
      const inField = tag === 'input' || tag === 'textarea' || tag === 'select' || e.target.isContentEditable;

      if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
        e.preventDefault(); setPaletteOpen(o => !o); return;
      }
      if (e.key === 'Escape') {
        setPaletteOpen(false); setTweaksOpen(false); setInboxOpen(false); setFilterOpen(false); setCheatOpen(false); setFocusTask(null); return;
      }
      if (inField) return;

      if (e.key === '?') { e.preventDefault(); setCheatOpen(o => !o); }
      if (e.key === ',') { e.preventDefault(); setTweaksOpen(o => !o); }
      if (e.key === 'n') { e.preventDefault(); setInboxOpen(o => !o); }
      if (e.key === 'b') { e.preventDefault(); setShowDetail(s => !s); }
      if (e.key === 'j' && (e.metaKey || e.ctrlKey)) { e.preventDefault(); setPulseOpen(o => !o); }
      if (e.key === 'c') { /* create — handled elsewhere */ }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, []);

  const viewLabels = {
    'my-day':    ['My Day',          `${TODAY.toLocaleDateString('en-GB', { weekday: 'long', day: 'numeric', month: 'long' })}`],
    'important': ['Important',       'Starred tasks across spaces'],
    'my-tasks':  ['My Tasks',        'Anything assigned to you'],
    'mentions':  ['Mentions',        'Where you’ve been called in'],
    'planned':   ['Planned',         'Tasks with a due date'],
    'all':       ['All Tasks',       'Across every space you can see'],
    'snoozed':   ['Snoozed',         'Hidden until their wake date'],
    'archived':  ['Archived',        'Closed work · read-only'],
  };
  const [vTitle, vSub] = viewLabels[activeView] || [activeView, ''];

  const activeFilterCount = filters.spaces.length + filters.tags.length + filters.priorities.length + filters.assignees.length + filters.queues.length + filters.statuses.length + (smartFilter ? 1 : 0) + (searchQ.trim() ? 1 : 0);

  return (
    <>
      <TopBar
        activeView={activeView}
        onView={(v) => { setActiveView(v); setSmartFilter(null); }}
        onOpenPalette={() => setPaletteOpen(true)}
        onOpenInbox={() => setInboxOpen(true)}
        onOpenTweaks={() => setTweaksOpen(true)}
        onOpenPulse={() => setPulseOpen(true)}
        unread={3}
      />
      <div style={{ flex: 1, display: 'flex', overflow: 'hidden' }}>
        <Sidebar activeView={activeView} onView={(v) => { setActiveView(v); setSmartFilter(null); }}
                 filters={filters} onToggleFilter={toggleFilter}
                 onOpenSettings={() => setActiveView('settings')}
                 savedViews={savedViews} onApplySavedView={handleApplySavedView} onDeleteSavedView={handleDeleteView} />

        <div style={{ flex: 1, display: 'flex', minWidth: 0 }}>
          <div style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0 }}>

            {isReport ? (
              activeView === 'dashboards' ? <DashboardsView allTasks={allTasks} /> :
              activeView === 'settings'   ? <SettingsView tweaks={tweaks} setTweak={setTweak} /> :
              activeView === 'workspace'  ? <WorkspaceDashboard allTasks={allTasks}
                  onView={(v) => { setActiveView(v); setSmartFilter(null); }}
                  onSelectTask={setSelectedId}
                  onFilterBy={(kind, id) => setFilters(f => ({ ...f, [kind]: f[kind].includes(id) ? f[kind] : [...f[kind], id] }))}
                /> :
              activeView === 'queues'     ? <QueuesIndex allTasks={allTasks}
                  onView={(v) => { setActiveView(v); }}
                  onSelectTask={(id) => { setSelectedId(id); setModalTaskId(id); }}
                  onOpenModal={(id) => setModalTaskId(id)}
                  onSetQueue={handleSetQueue}
                /> :
              activeView === 'my-work'    ? <MyWorkView allTasks={allTasks}
                  onView={(v) => { setActiveView(v); setSmartFilter(null); }}
                  onSelectTask={setSelectedId}
                  onOpenModal={(id) => setModalTaskId(id)}
                  onStatusChange={handleStatusChange}
                /> :
              activeView === 'views'      ? <ViewsHub allTasks={allTasks}
                  onView={(v) => { setActiveView(v); setSmartFilter(null); }}
                /> :
              activeView === 'reports'    ? <ReportsHub allTasks={allTasks}
                  onView={(v) => { setActiveView(v); setSmartFilter(null); }}
                /> :
              activeView.startsWith('queue:') ? <QueueDashboard
                  queueId={parseInt(activeView.slice(6), 10)}
                  allTasks={allTasks}
                  onView={(v) => { setActiveView(v); }}
                  onSelectTask={setSelectedId}
                  onOpenModal={(id) => setModalTaskId(id)}
                  onStatusChange={handleStatusChange}
                  onClaim={(id) => { pushToast({ tone: 'success', title: 'Claimed', body: `Assigned to you` }); }}
                /> :
              <ReportsView which={activeView} />
            ) : (
              <>
                {/* Toolbar */}
                <div style={{ padding: '18px 24px 0', background: 'rgb(var(--bg))' }}>
                  <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap', marginBottom: 14 }}>
                    <div style={{ minWidth: 0 }}>
                      <div style={{ display: 'flex', alignItems: 'baseline', gap: 12 }}>
                        <h1 style={{ margin: 0, fontSize: 24, fontWeight: 700, letterSpacing: '-0.025em', color: 'rgb(var(--ink))' }}>{vTitle}</h1>
                        <span className="num" style={{
                          fontSize: 12, fontWeight: 600, color: 'rgb(var(--muted))',
                          background: 'rgb(var(--paper-2))', padding: '3px 10px', borderRadius: 999,
                        }}>{filteredTasks.length}</span>
                        <ActivityTicker />
                      </div>
                      <p style={{ margin: '4px 0 0', fontSize: 13, color: 'rgb(var(--muted))' }}>{vSub}</p>
                    </div>

                    <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                      <button onClick={() => setPaletteOpen(true)} className="btn btn-secondary" style={{ fontSize: 12 }} title="Command palette">
                        <span style={{ color: 'rgb(var(--muted))' }}>⌕</span>
                        <span style={{ color: 'rgb(var(--muted))' }}>Quick jump</span>
                        <span style={{ fontFamily: 'JetBrains Mono, monospace', fontSize: 10, padding: '1px 5px', background: 'rgb(var(--paper-2))', borderRadius: 4, color: 'rgb(var(--muted))' }}>⌘K</span>
                      </button>
                      <button onClick={() => setInboxOpen(true)} className="btn btn-secondary" style={{ fontSize: 13, position: 'relative', padding: '6px 10px' }} title="Notifications">
                        🔔
                        <span style={{
                          position: 'absolute', top: -3, right: -3,
                          background: 'rgb(var(--accent))', color: 'white',
                          fontSize: 9, padding: '1px 5px', fontWeight: 700,
                          borderRadius: 999, lineHeight: 1.4,
                        }}>3</span>
                      </button>
                      <button onClick={() => setTweaksOpen(true)} className="btn btn-secondary" style={{ fontSize: 13, padding: '6px 10px' }} title="Tweaks (,)">⚙</button>
                      <button onClick={() => setPulseOpen(true)} className="btn btn-secondary" style={{
                        fontSize: 12.5, padding: '6px 12px',
                        display: 'inline-flex', alignItems: 'center', gap: 6,
                        background: 'linear-gradient(135deg, rgb(var(--accent-soft)), rgb(var(--surface)))',
                        borderColor: 'rgb(var(--accent) / 0.3)',
                        color: 'rgb(var(--accent-2))', fontWeight: 700,
                      }} title="Ask Pulse (⌘J)">
                        <span style={{
                          width: 18, height: 18, borderRadius: 5,
                          background: 'linear-gradient(135deg, rgb(var(--accent)), rgb(var(--berry)))',
                          display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                          color: 'white', fontSize: 10, fontWeight: 700,
                        }}>✦</span>
                        Pulse
                        <span style={{ fontFamily: 'JetBrains Mono, monospace', fontSize: 9.5, padding: '1px 5px', background: 'rgb(var(--surface))', borderRadius: 4, color: 'rgb(var(--muted))', fontWeight: 600 }}>⌘J</span>
                      </button>
                      <button onClick={() => setTemplatePickerOpen(true)} className="btn btn-accent" style={{ fontSize: 13 }}>+ New task</button>
                    </div>
                  </div>

                  {/* View mode tabs */}
                  <div style={{ display: 'flex', alignItems: 'center', gap: 12, paddingBottom: 14, borderBottom: '1px solid rgb(var(--rule))', flexWrap: 'wrap' }}>
                    <ViewModeSelector viewMode={viewMode} setViewMode={setViewMode} />

                    {viewMode === 'list' && (
                      <select value={groupBy} onChange={e => setGroupBy(e.target.value)} style={{ fontSize: 12, padding: '5px 8px' }} title="Group by">
                        <option value="status">Group by status</option>
                        <option value="space">Group by space</option>
                        <option value="priority">Group by priority</option>
                        <option value="none">No groups</option>
                      </select>
                    )}

                    <select value={sort} onChange={e => setSort(e.target.value)} style={{ fontSize: 12, padding: '5px 8px' }}>
                      <option value="priority">Sort by priority</option>
                      <option value="due-soon">Sort by due date</option>
                      <option value="newest">Sort by newest</option>
                      <option value="oldest">Sort by oldest</option>
                    </select>

                    <button onClick={() => setFilterOpen(true)} className="btn btn-secondary" style={{ fontSize: 12, padding: '5px 12px', position: 'relative' }}>
                      ⚲ Filter
                      {activeFilterCount > 0 && (
                        <span style={{
                          background: 'rgb(var(--accent))', color: 'white',
                          fontSize: 10, padding: '1px 6px', borderRadius: 999,
                          fontWeight: 700, marginLeft: 2,
                        }}>{activeFilterCount}</span>
                      )}
                    </button>

                    {activeFilterCount > 0 && (
                      <button onClick={() => setSaveViewOpen(true)} className="btn btn-secondary" style={{
                        fontSize: 12, padding: '5px 12px',
                        color: 'rgb(var(--accent-2))', borderColor: 'rgb(var(--accent) / 0.4)',
                      }}>📌 Save view</button>
                    )}

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

                    <div style={{ position: 'relative', flexShrink: 0 }}>
                      <input value={searchQ} onChange={e => setSearchQ(e.target.value)} placeholder="Filter…" style={{ fontSize: 12, padding: '5px 10px 5px 28px', width: 160, maxWidth: '100%' }} />
                      <span style={{ position: 'absolute', left: 9, top: '50%', transform: 'translateY(-50%)', fontSize: 12, color: 'rgb(var(--muted))', pointerEvents: 'none' }}>⌕</span>
                    </div>

                    <button onClick={() => setShowDetail(s => !s)} className="btn btn-secondary" style={{ fontSize: 12, padding: '5px 10px' }} title="Toggle detail pane (B)">{showDetail ? '⊟' : '⊞'}</button>
                  </div>
                </div>

                {/* View body */}
                {viewMode === 'list' && (
                  <ListView
                    tasks={filteredTasks}
                    selectedId={selectedTask?.id}
                    onSelect={setSelectedId}
                    groupBy={groupBy}
                    onQuickAdd={handleQuickAdd}
                    compact={!!tweaks.compactCards}
                    hero={activeView === 'my-day' && (
                      <MyDayHero tasks={filteredTasks}
                        compact={tweaks.compactMyDay}
                        onSelect={setSelectedId}
                        onStartFocus={(t) => setFocusTask(t)} />
                    )}
                    actions={{
                      selectedSet:        bulkSelected,
                      onBulkToggle:       bulkToggle,
                      onStatusChange:     handleStatusChange,
                      onReschedule:       handleReschedule,
                      onUpdatePriority:   handleUpdatePriority,
                      onUpdateTitle:      handleUpdateTitle,
                      onPersonClick:      (uid) => setPersonModal(uid),
                      onOpenModal:        (id) => setModalTaskId(id),
                    }}
                  />
                )}
                {viewMode === 'board' && (
                  <BoardView tasks={filteredTasks} selectedId={selectedTask?.id} onSelect={setSelectedId} onStatusChange={handleStatusChange} compact={!!tweaks.compactCards} />
                )}
                {viewMode === 'calendar' && (
                  <CalendarView tasks={filteredTasks} selectedId={selectedTask?.id} onSelect={setSelectedId} onReschedule={handleReschedule} />
                )}
                {viewMode === 'timeline' && (
                  <TimelineView tasks={filteredTasks} selectedId={selectedTask?.id} onSelect={setSelectedId} />
                )}
                {viewMode === 'cards' && (
                  <CardsView tasks={filteredTasks} selectedId={selectedTask?.id} onSelect={setSelectedId} onStatusChange={handleStatusChange} compact={!!tweaks.compactCards} />
                )}
                {viewMode === 'agenda' && (
                  <AgendaView tasks={filteredTasks} selectedId={selectedTask?.id} onSelect={setSelectedId} onStatusChange={handleStatusChange} />
                )}
                {viewMode === 'focus' && (
                  <FocusView tasks={filteredTasks} selectedId={selectedTask?.id} onSelect={setSelectedId} onStatusChange={handleStatusChange} onStartFocus={(t) => setFocusTask(t)} />
                )}
              </>
            )}

          </div>

          {!isReport && viewMode !== 'focus' && showDetail && (
            <DetailPane task={selectedTask} allTasks={allTasks} onClose={() => setShowDetail(false)} onStatusChange={handleStatusChange} onSetAssignees={handleSetAssignees} onSetQueue={handleSetQueue} onSetReminder={handleSetReminder} onToggleFlag={handleToggleFlag} onSetClients={handleSetClients} onUpdateTitle={handleUpdateTitle} editTitleOnOpen={editTitleOnOpen === selectedTask?.id} clearEditTitleOnOpen={() => setEditTitleOnOpen(null)} onLog={logActivity} onToggleLink={handleToggleLink} onOpenTask={setSelectedId} />
          )}
        </div>
      </div>

      {modalTaskId != null && (
        <TaskDetailModal task={allTasks.find(t => t.id === modalTaskId) || null}
          allTasks={allTasks}
          onClose={() => setModalTaskId(null)}
          onStatusChange={handleStatusChange}
          onSetAssignees={handleSetAssignees}
          onSetQueue={handleSetQueue}
          onSetReminder={handleSetReminder}
          onToggleFlag={handleToggleFlag}
          onSetClients={handleSetClients}
          onUpdateTitle={handleUpdateTitle}
          editTitleOnOpen={editTitleOnOpen === modalTaskId}
          clearEditTitleOnOpen={() => setEditTitleOnOpen(null)}
          onLog={logActivity}
          onToggleLink={handleToggleLink}
          onOpenTask={(id) => setModalTaskId(id)} />
      )}

      {paletteOpen && <CommandPalette onClose={() => setPaletteOpen(false)} onView={(v) => { setActiveView(v); setPaletteOpen(false); }} onSelectTask={(id) => { setSelectedId(id); setPaletteOpen(false); }} />}
      {tweaksOpen  && <TweaksPanel tweaks={tweaks} setTweak={setTweak} onClose={() => setTweaksOpen(false)} />}
      {inboxOpen   && <NotificationDrawer open={inboxOpen} onClose={() => setInboxOpen(false)} onOpenTask={setSelectedId} />}
      {filterOpen  && <FilterPopover filters={filters} smartFilter={smartFilter} onTogglePriority={togglePriority} onSetSmart={setSmartFilter} onClose={() => setFilterOpen(false)} onClear={clearFilters} />}
      {cheatOpen   && <KeyboardCheatsheet open={cheatOpen} onClose={() => setCheatOpen(false)} />}
      {pulseOpen   && <PulsePanel open={pulseOpen} onClose={() => setPulseOpen(false)} tasks={filteredTasks} allTasks={allTasks} onApply={handlePulseApply} onSelectTask={setSelectedId} />}
      {focusTask   && <FocusModeOverlay task={focusTask} onClose={() => setFocusTask(null)} onComplete={handleCompleteFromFocus} />}
      {personModal && <PersonModal userId={personModal} allTasks={allTasks} onClose={() => setPersonModal(null)} onOpenTask={(id) => { setSelectedId(id); setPersonModal(null); }} />}
      {templatePickerOpen && <TemplatePicker open onClose={() => setTemplatePickerOpen(false)} onPick={handleNewFromTemplate} />}
      {saveViewOpen && <SaveViewModal filters={filters} smartFilter={smartFilter} viewMode={viewMode} groupBy={groupBy} sort={sort} searchQ={searchQ} activeView={activeView} onSave={handleSaveView} onClose={() => setSaveViewOpen(false)} />}
      {showOnboarding && <OnboardingHints onDismiss={dismissOnboarding} />}

      <BulkBar
        selectedIds={Array.from(bulkSelected)}
        allTasks={allTasks}
        onClear={bulkClear}
        onStatusAll={bulkSetStatus}
        onPriorityAll={bulkSetPriority}
        onArchiveAll={bulkArchive}
        onDeleteAll={bulkDelete}
      />
      <ConfettiBurst trigger={confettiKey} />

      <ToastStack toasts={toasts} onDismiss={dismissToast} />
    </>
  );
}

function CommandPalette({ onClose, onView, onSelectTask }) {
  const [q, setQ] = React.useState('');
  const items = React.useMemo(() => {
    const out = [];
    [
      ['my-day',    'My Day',           '☀'],
      ['important', 'Important',        '★'],
      ['my-tasks',  'My Tasks',         '◉'],
      ['mentions',  'Mentions',         '@'],
      ['planned',   'Planned',          '◳'],
      ['all',       'All Tasks',        '⊞'],
      ['dashboard', 'Dashboard',        '◐'],
      ['workload',  'Workload report',  '⊟'],
      ['time',      'Time report',      '⏱'],
      ['audit',     'Audit log',        '⌬'],
    ].forEach(([key, label, glyph]) => {
      out.push({ kind: 'view', key, label, hint: 'Navigate', glyph });
    });
    D.tasks.forEach(t => {
      out.push({ kind: 'task', key: t.id, label: t.title, hint: t.ticket, glyph: '◇' });
    });
    if (!q.trim()) return out.slice(0, 10);
    const ql = q.toLowerCase();
    return out.filter(i => i.label.toLowerCase().includes(ql) || (i.hint || '').toLowerCase().includes(ql)).slice(0, 14);
  }, [q]);

  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, background: 'rgba(15,23,42,0.4)',
      backdropFilter: 'blur(4px)',
      display: 'flex', alignItems: 'flex-start', justifyContent: 'center',
      paddingTop: '12vh', zIndex: 100,
    }}>
      <div onClick={e => e.stopPropagation()} className="fadein shadow-pop" style={{
        background: 'rgb(var(--surface))', width: 580,
        border: '1px solid rgb(var(--rule))',
        borderRadius: 14,
        overflow: 'hidden',
      }}>
        <div style={{ position: 'relative', borderBottom: '1px solid rgb(var(--rule))' }}>
          <input autoFocus value={q} onChange={e => setQ(e.target.value)}
            placeholder="Search tasks, views, reports…"
            style={{ width: '100%', padding: '16px 18px 16px 46px', fontSize: 15, border: 'none', background: 'transparent', borderRadius: 0 }} />
          <span style={{ position: 'absolute', left: 18, top: '50%', transform: 'translateY(-50%)', fontSize: 16, color: 'rgb(var(--muted))', pointerEvents: 'none' }}>⌕</span>
        </div>
        <div style={{ maxHeight: 420, overflowY: 'auto', padding: 6 }}>
          {items.length === 0 && <p style={{ padding: '24px', textAlign: 'center', color: 'rgb(var(--muted))', fontSize: 13 }}>Nothing matches “{q}”</p>}
          {items.map((it) => (
            <button key={`${it.kind}-${it.key}`}
              onClick={() => it.kind === 'view' ? onView(it.key) : onSelectTask(it.key)}
              style={{
                display: 'flex', alignItems: 'center', gap: 12,
                width: '100%', padding: '9px 12px',
                fontSize: 13.5, textAlign: 'left',
                borderRadius: 8,
              }}
              onMouseEnter={e => e.currentTarget.style.background = 'rgb(var(--accent-soft) / 0.5)'}
              onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
              <span style={{
                width: 28, height: 28, borderRadius: 7,
                background: it.kind === 'view' ? 'rgb(var(--accent-soft))' : 'rgb(var(--paper-2))',
                color: it.kind === 'view' ? 'rgb(var(--accent))' : 'rgb(var(--muted))',
                display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                fontSize: 13,
              }}>{it.glyph}</span>
              <span style={{ flex: 1, color: 'rgb(var(--ink))', fontWeight: 500 }}>{it.label}</span>
              <span style={{ fontSize: 11, color: 'rgb(var(--muted))', fontFamily: 'JetBrains Mono, monospace' }}>{it.hint}</span>
            </button>
          ))}
        </div>
        <div style={{ padding: '10px 18px', borderTop: '1px solid rgb(var(--rule))', background: 'rgb(var(--bg))', display: 'flex', gap: 16, fontSize: 11, color: 'rgb(var(--muted))', fontFamily: 'JetBrains Mono, monospace' }}>
          <span>↑↓ navigate</span>
          <span>↵ open</span>
          <span>esc close</span>
        </div>
      </div>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById('app'));
root.render(<App />);

function SpaceSwitcher({ spaces, allTasks, selected, onToggle, onClear }) {
  const counts = React.useMemo(() => {
    const out = {};
    spaces.forEach(s => { out[s.id] = allTasks.filter(t => t.spaceId === s.id && t.statusId !== 6).length; });
    return out;
  }, [allTasks, spaces]);
  const total = allTasks.filter(t => t.statusId !== 6).length;
  const allOn = selected.length === 0;
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap',
      paddingBottom: 12,
      borderBottom: '1px dashed rgb(var(--rule))',
      marginBottom: 14,
    }}>
      <span style={{ fontSize: 10.5, fontWeight: 700, color: 'rgb(var(--muted))', letterSpacing: '0.06em', textTransform: 'uppercase', marginRight: 4 }}>Space</span>
      <button onClick={onClear} style={{
        display: 'inline-flex', alignItems: 'center', gap: 6,
        padding: '4px 12px', fontSize: 12, fontWeight: 600,
        background: allOn ? 'rgb(var(--ink))' : 'rgb(var(--surface))',
        color: allOn ? 'white' : 'rgb(var(--ink-2))',
        border: '1px solid ' + (allOn ? 'rgb(var(--ink))' : 'rgb(var(--rule))'),
        borderRadius: 999,
      }}>
        All
        <span style={{ fontSize: 10, fontWeight: 600, opacity: 0.7 }}>{total}</span>
      </button>
      {spaces.map(sp => {
        const active = selected.includes(sp.id);
        return (
          <button key={sp.id} onClick={() => onToggle(sp.id)} style={{
            display: 'inline-flex', alignItems: 'center', gap: 6,
            padding: '4px 12px', fontSize: 12, fontWeight: 600,
            background: active ? sp.colour + '14' : 'rgb(var(--surface))',
            color: active ? sp.colour : 'rgb(var(--ink-2))',
            border: '1px solid ' + (active ? sp.colour + '60' : 'rgb(var(--rule))'),
            borderRadius: 999,
          }}>
            <span style={{ width: 7, height: 7, background: sp.colour, borderRadius: 2 }} />
            {sp.name}
            <span style={{ fontSize: 10, fontWeight: 600, opacity: 0.7 }}>{counts[sp.id] || 0}</span>
          </button>
        );
      })}
    </div>
  );
}

function ViewModeSelector({ viewMode, setViewMode }) {
  const options = [
    ['list',     '☰', 'List'],
    ['board',    '▤', 'Board'],
    ['calendar', '▦', 'Calendar'],
    ['timeline', '⊟', 'Timeline'],
    ['cards',    '◇', 'Cards'],
    ['agenda',   '☉', 'Agenda'],
    ['focus',    '◉', 'Focus'],
  ];
  const wrapRef = React.useRef(null);
  const [compact, setCompact] = React.useState(false);
  const [open, setOpen] = React.useState(false);

  React.useEffect(() => {
    if (!wrapRef.current) return;
    const el = wrapRef.current;
    const check = () => {
      const parent = el.parentElement;
      if (!parent) return;
      setCompact(parent.clientWidth < 720);
    };
    check();
    const ro = new ResizeObserver(check);
    if (el.parentElement) ro.observe(el.parentElement);
    return () => ro.disconnect();
  }, []);

  if (compact) {
    const cur = options.find(o => o[0] === viewMode) || options[0];
    return (
      <span ref={wrapRef} style={{ position: 'relative' }}>
        <button onClick={() => setOpen(o => !o)} style={{
          display: 'inline-flex', alignItems: 'center', gap: 8,
          padding: '6px 12px',
          background: 'rgb(var(--surface))', border: '1px solid rgb(var(--rule))',
          borderRadius: 8, fontSize: 12.5, fontWeight: 600,
          color: 'rgb(var(--ink))', boxShadow: 'var(--sh-sm)',
        }}>
          <span style={{ color: 'rgb(var(--accent))' }}>{cur[1]}</span>
          {cur[2]}
          <span style={{ fontSize: 10, color: 'rgb(var(--muted))' }}>▾</span>
        </button>
        {open && (
          <>
            <span onClick={() => setOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 60 }} />
            <div className="shadow-pop fadein" style={{
              position: 'absolute', top: '100%', left: 0, marginTop: 4,
              background: 'rgb(var(--surface))', border: '1px solid rgb(var(--rule))',
              borderRadius: 10, padding: 4, zIndex: 61, minWidth: 170,
            }}>
              {options.map(([k, glyph, label]) => (
                <button key={k} onClick={() => { setViewMode(k); setOpen(false); }}
                  style={{
                    display: 'flex', alignItems: 'center', gap: 10, width: '100%',
                    padding: '7px 10px', borderRadius: 6, textAlign: 'left',
                    fontSize: 12.5, fontWeight: viewMode === k ? 700 : 500,
                    color: viewMode === k ? 'rgb(var(--accent-2))' : 'rgb(var(--ink))',
                    background: viewMode === k ? 'rgb(var(--accent-soft))' : 'transparent',
                  }}
                  onMouseEnter={e => { if (viewMode !== k) e.currentTarget.style.background = 'rgb(var(--paper-2))'; }}
                  onMouseLeave={e => { if (viewMode !== k) e.currentTarget.style.background = 'transparent'; }}>
                  <span style={{ fontSize: 13, width: 14, textAlign: 'center', color: viewMode === k ? 'rgb(var(--accent))' : 'rgb(var(--muted))' }}>{glyph}</span>
                  <span style={{ flex: 1 }}>{label}</span>
                  {viewMode === k && <span style={{ color: 'rgb(var(--accent))', fontSize: 11 }}>✓</span>}
                </button>
              ))}
            </div>
          </>
        )}
      </span>
    );
  }
  return (
    <div ref={wrapRef} style={{
      display: 'inline-flex', gap: 2, padding: 3,
      background: 'rgb(var(--paper-2))', borderRadius: 10,
    }}>
      {options.map(([k, glyph, label]) => (
        <button key={k} onClick={() => setViewMode(k)}
          style={{
            padding: '5px 12px', fontSize: 12.5, fontWeight: 600,
            background: viewMode === k ? 'rgb(var(--surface))' : 'transparent',
            color: viewMode === k ? 'rgb(var(--ink))' : 'rgb(var(--muted))',
            borderRadius: 7,
            boxShadow: viewMode === k ? 'var(--sh-sm)' : 'none',
            display: 'inline-flex', alignItems: 'center', gap: 6,
            transition: 'all .14s', whiteSpace: 'nowrap',
          }}>
          <span>{glyph}</span> {label}
        </button>
      ))}
    </div>
  );
}
