/* global React, ReactDOM, Icon, IconBtn, useLiveQuery, useKvRecord, SendToProvider, db,
   TodoTool, NoteTool, JsonTool, DiffTool, PasswordTool, BookmarkTool,
   useSettings, SettingsModal, OnboardingFlow, ESSENTIAL_TOOL_IDS,
   TOGGLEABLE_TOOLS */
const { useState, useEffect, useRef, useMemo, useCallback } = React;

const TOOLS = [
  { id: 'home',     label: 'Home',           icon: 'home',          section: 'Workspace', component: 'HomeTool',      sub: 'Pinned items · widgets · recent activity' },
  { id: 'catalog',  label: 'All apps',       icon: 'apps',          section: 'Workspace', component: 'CatalogTool',   sub: 'Browse and manage every tool' },
  { id: 'todo',     label: 'Todo',           icon: 'check_box',     section: 'Lists',   component: 'TodoTool',      sub: 'Tasks · subtasks · projects' },
  { id: 'note',     label: 'Notes',          icon: 'description',   section: 'Lists',   component: 'NoteTool',      sub: 'Markdown · backlinks' },
  { id: 'bookmark', label: 'Bookmarks',      icon: 'bookmark',      section: 'Lists',   component: 'BookmarkTool',  sub: 'Quick links' },
  { id: 'json',     label: 'JSON viewer',    icon: 'data_object',   section: 'Tools',   component: 'JsonTool',      sub: 'Tree · JSONPath · format' },
  { id: 'diff',     label: 'Diff viewer',    icon: 'difference',    section: 'Tools',   component: 'DiffTool',      sub: 'Compare two texts' },
  { id: 'encode',   label: 'Encode/Decode',  icon: 'enhanced_encryption', section: 'Tools', component: 'EncodeTool', sub: 'JWT · Base64 · URL' },
  { id: 'totp',     label: '2FA codes',      icon: 'lock_clock',    section: 'Tools',   component: 'TotpTool',      sub: 'TOTP from 2FA keys · vault' },
  { id: 'regex',    label: 'Regex tester',   icon: 'pattern',       section: 'Tools',   component: 'RegexTool',     sub: 'Live match · capture groups · replace' },
  { id: 'cron',     label: 'Cron builder',   icon: 'schedule',      section: 'Tools',   component: 'CronTool',      sub: 'Visual builder · plain-language' },
  { id: 'color',    label: 'Color picker',   icon: 'palette',       section: 'Tools',   component: 'ColorTool',     sub: 'HEX/RGB/HSL · contrast · palettes' },
  { id: 'convert',  label: 'Quick converter',icon: 'sync_alt',      section: 'Tools',   component: 'ConvertTool',   sub: 'JSON · YAML · CSV · timestamps · units' },
  { id: 'journal',  label: 'Daily journal',  icon: 'auto_stories',  section: 'Lists',   component: 'JournalTool',   sub: 'One entry per day · auto-summary' },
  { id: 'flashcard',label: 'Flashcards',     icon: 'style',         section: 'Lists',   component: 'FlashcardTool', sub: 'Spaced repetition · custom schemas' },
  { id: 'password', label: 'Passwords',      icon: 'key',           section: 'Tools',   component: 'PasswordTool',  sub: 'Generate · vault' },
  { id: 'tetris',   label: 'Block Classic',  icon: 'grid_view',     section: 'Games',   component: 'TetrisTool',    sub: 'Falling-block puzzle · cozy mode' },
  { id: 'pomodoro', label: 'Pomodoro',       icon: 'timer',         section: 'Lists',   component: 'PomodoroTool',  sub: 'Focus timer · break cycles · session log' },
  { id: 'calculator', label: 'Calculator',   icon: 'calculate',     section: 'Tools',   component: 'CalculatorTool', sub: 'Arithmetic · history · keyboard' },
  { id: 'scratchpad', label: 'Scratch pad',  icon: 'edit_note',     section: 'Tools',   component: 'ScratchpadTool', sub: 'One textarea · always saved' },
  { id: 'worldclock', label: 'World clock',  icon: 'public',        section: 'Tools',   component: 'WorldClockTool', sub: 'Analog · digital · home zone offset' },
  { id: 'qr',       label: 'QR generator',   icon: 'qr_code_2',     section: 'Tools',   component: 'QRTool',         sub: 'Text · Wi-Fi · vCard · email · SMS' },
  { id: 'timetracker', label: 'Time Tracker', icon: 'timelapse',     section: 'Tools',   component: 'TimeTrackerTool', sub: 'Timer · entries · calendar · summary' },
  { id: 'image',    label: 'All image tools', icon: 'grid_view',    section: 'Image',   component: 'ImageTool',      sub: 'Overview · every converter & editor', group: 'image' },
  ...(window.IMAGE_NAV || []),
  ...(window.CRYPTO_NAV || []),
  { id: 'habits',   label: 'Habits',         icon: 'check_circle',  section: 'Lists',   component: 'HabitsTool',     sub: 'Daily check-ins · streaks · 4-week grid' },
  { id: 'mood',     label: 'Mood',           icon: 'mood',          section: 'Lists',   component: 'MoodTool',       sub: 'Daily check-in · 12-week heatmap' },
  { id: 'breathing', label: 'Breathing',     icon: 'air',           section: 'Lists',   component: 'BreathingTool',  sub: 'Box · 4-7-8 · coherent · guided circle' },
  { id: 'g2048',    label: '2048',           icon: 'grid_4x4',      section: 'Games',   component: 'G2048Tool',      sub: 'Slide-and-merge · reach 2048' },
  { id: 'minesweeper', label: 'Minesweeper', icon: 'grid_on',       section: 'Games',   component: 'MinesweeperTool',sub: 'Classic · three difficulties · best times' },
  { id: 'reading', label: 'Reading',         icon: 'auto_stories',  section: 'Lists',   component: 'ReadingTool',    sub: 'Books · status · progress · ratings' },
  { id: 'writer',  label: 'Writer',          icon: 'edit',          section: 'Lists',   component: 'WriterTool',     sub: 'Distraction-free · word targets · sessions' },
  { id: 'wordsearch', label: 'Word search',  icon: 'search',        section: 'Games',   component: 'WordSearchTool', sub: 'Six themes · diagonals · timed' },
  { id: 'sudoku',  label: 'Sudoku',          icon: 'apps',          section: 'Games',   component: 'SudokuTool',     sub: 'Classic 9\u00d79 \u00b7 pencil marks \u00b7 conflict hints' },
  { id: 'solitaire', label: 'Solitaire',     icon: 'style',         section: 'Games',   component: 'SolitaireTool',  sub: 'Klondike \u00b7 draw one \u00b7 click to move' },
  { id: 'ambient', label: 'Ambient',         icon: 'graphic_eq',    section: 'Lists',   component: 'AmbientTool',    sub: 'Rain \u00b7 ocean \u00b7 forest \u00b7 fire \u00b7 caf\u00e9' },
  { id: 'keytest', label: 'Keyboard test',   icon: 'keyboard',      section: 'Hardware', component: 'KeyboardTesterTool', sub: 'Mac \u00b7 Windows \u00b7 key-by-key check' },
  { id: 'mictest', label: 'Mic test',        icon: 'mic',           section: 'Hardware', component: 'MicTesterTool',  sub: 'Live waveform \u00b7 input level meter' },
  { id: 'camtest', label: 'Camera test',     icon: 'videocam',      section: 'Hardware', component: 'CameraTesterTool', sub: 'Preview \u00b7 resolution \u00b7 fps \u00b7 snapshot' },
  { id: 'refreshrate', label: 'Refresh rate', icon: 'speed',        section: 'Hardware', component: 'RefreshRateTool', sub: 'Measured display Hz \u00b7 frame-time graph' },
];
const TOOL_BY_ID = Object.fromEntries(TOOLS.map(t => [t.id, t]));

// ============================================================
// Universal search (⌘K)
// ============================================================
function UniversalSearch({ open, onClose, onPick, isToolEnabled = () => true }) {
  const [q, setQ] = useState('');
  const [active, setActive] = useState(0);
  const inputRef = useRef(null);
  const all = useLiveQuery(['todos', 'notes', 'jsonDocs', 'bookmarks', 'flashcardCollections', 'flashcardCards', 'timeEntries']) || {};
  // Live kv records for tools whose data lives in a single kv blob
  const habitState = useKvRecord('habitTracker') || {};
  const readingState = useKvRecord('readingTracker') || {};
  const scratchState = useKvRecord('scratchpad') || {};

  useEffect(() => { if (open) { setQ(''); setActive(0); setTimeout(() => inputRef.current?.focus(), 30); } }, [open]);

  const results = useMemo(() => {
    if (!open) return [];
    const sections = [];
    const query = q.trim().toLowerCase();
    const match = (s) => !query || (s || '').toLowerCase().includes(query);

    const toolHits = TOOLS.filter(t => isToolEnabled(t.id) && (match(t.label) || match(t.sub)));
    if (toolHits.length) sections.push({ label: 'Open tool', items: toolHits.map(t => ({
      kind: 'tool', id: t.id, title: t.label, meta: t.sub, icon: t.icon,
    })) });

    if (query) {
      if (isToolEnabled('todo')) {
        const todoHits = (all.todos || []).filter(t => match(t.title) || (t.tags || []).some(match)).slice(0, 5);
        if (todoHits.length) sections.push({ label: 'Todos', items: todoHits.map(t => ({
          kind: 'todo', id: t.id, title: t.title, meta: t.project ? `· ${t.project}` : (t.done ? '· done' : ''),
          icon: t.done ? 'check_circle' : 'radio_button_unchecked',
        })) });
      }
      if (isToolEnabled('note')) {
        const noteHits = (all.notes || []).filter(n => match(n.title) || match(n.body)).slice(0, 5);
        if (noteHits.length) sections.push({ label: 'Notes', items: noteHits.map(n => ({
          kind: 'note', id: n.id, title: n.title, meta: n.folder ? `· ${n.folder}` : '', icon: 'description',
        })) });
      }
      if (isToolEnabled('json')) {
        const jsonHits = (all.jsonDocs || []).filter(j => match(j.title) || match(j.body)).slice(0, 4);
        if (jsonHits.length) sections.push({ label: 'JSON', items: jsonHits.map(j => ({
          kind: 'json', id: j.id, title: j.title, meta: '', icon: 'data_object',
        })) });
      }
      if (isToolEnabled('bookmark')) {
        const bmHits = (all.bookmarks || []).filter(b => match(b.title) || match(b.url) || (b.tags || []).some(match)).slice(0, 5);
        if (bmHits.length) sections.push({ label: 'Bookmarks', items: bmHits.map(b => ({
          kind: 'bookmark', id: b.id, title: b.title, meta: b.url.replace(/^https?:\/\//, ''), icon: 'bookmark',
        })) });
      }
      if (isToolEnabled('flashcard')) {
        // Search cards by any field value, plus collection names.
        const cols = all.flashcardCollections || [];
        const colById = Object.fromEntries(cols.map(c => [c.id, c]));
        const colHits = cols.filter(c => match(c.name) || match(c.description) || match(c.srcLang) || match(c.tgtLang)).slice(0, 3);
        const cardHits = (all.flashcardCards || []).filter(card => {
          const col = colById[card.collectionId];
          if (!col) return false;
          for (const f of col.schema || []) {
            const v = card.fields?.[f.id];
            if (v == null) continue;
            const text = Array.isArray(v) ? v.join(' ') : String(v);
            if (match(text)) return true;
          }
          return false;
        }).slice(0, 5);
        const items = [
          ...colHits.map(c => ({ kind: 'flashcard', id: c.id, title: c.name, meta: `Collection · ${c.srcLang || '?'} → ${c.tgtLang || '?'}`, icon: 'style' })),
          ...cardHits.map(card => {
            const col = colById[card.collectionId];
            const promptVal = card.fields?.[col?.promptField];
            const promptType = col?.schema.find(f => f.id === col.promptField)?.type;
            return {
              kind: 'flashcard',
              id: card.collectionId,
              title: window.flashcards?.fieldPreview(promptVal, promptType) || 'Card',
              meta: col ? `Card in ${col.name}` : 'Card',
              icon: 'layers',
            };
          }),
        ];
        if (items.length) sections.push({ label: 'Flashcards', items });
      }
      if (isToolEnabled('timetracker')) {
        const ttHits = (all.timeEntries || []).filter(e => match(e.description) || (e.tags || []).some(match)).slice(0, 5);
        if (ttHits.length) sections.push({ label: 'Time entries', items: ttHits.map(e => ({
          kind: 'timetracker', id: e.id,
          title: e.description || '(no description)',
          meta: window.ttFmtDuration ? `· ${window.ttFmtDuration(Math.max(0, (e.stoppedAt || Date.now()) - e.startedAt), { seconds: false })}` : '',
          icon: 'timelapse',
        })) });
      }
      if (isToolEnabled('reading')) {
        const books = readingState.books || [];
        const hits = books.filter(b => match(b.title) || match(b.author) || match(b.notes)).slice(0, 5);
        if (hits.length) sections.push({ label: 'Books', items: hits.map(b => ({
          kind: 'reading', id: b.id, title: b.title || 'Untitled', meta: b.author || (b.status === 'reading' ? 'reading now' : ''), icon: 'auto_stories',
        })) });
      }
      if (isToolEnabled('habits')) {
        const habits = (habitState.habits || []).filter(h => !h.archived);
        const hits = habits.filter(h => match(h.name)).slice(0, 5);
        if (hits.length) sections.push({ label: 'Habits', items: hits.map(h => ({
          kind: 'habits', id: h.id, title: h.name, meta: '', icon: h.icon || 'check_circle',
        })) });
      }
      if (isToolEnabled('scratchpad')) {
        const body = scratchState.body || '';
        if (match(body) && body.length > 0) {
          // Snippet centered on the match
          const idx = body.toLowerCase().indexOf(query);
          const start = Math.max(0, idx - 20);
          const snippet = (start > 0 ? '…' : '') + body.slice(start, start + 80).replace(/\n/g, ' ') + (body.length > start + 80 ? '…' : '');
          sections.push({ label: 'Scratch pad', items: [{
            kind: 'scratchpad', id: 'scratchpad', title: 'Match in scratch pad',
            meta: snippet, icon: 'edit_note',
          }] });
        }
      }
    }
    return sections;
  }, [open, q, all, isToolEnabled, habitState, readingState, scratchState]);

  const flat = useMemo(() => results.flatMap(s => s.items), [results]);
  useEffect(() => { setActive(0); }, [flat.length]);

  if (!open) return null;

  const pick = (item) => {
    if (item.kind === 'tool') onPick({ tool: item.id });
    else if (item.kind === 'bookmark') {
      const b = (all.bookmarks || []).find(x => x.id === item.id);
      if (b) window.open(b.url, '_blank');
      onClose();
    }
    else onPick({ tool: item.kind, id: item.id });
  };

  return (
    <div className="us-overlay" onClick={onClose}>
      <div className="us-panel" onClick={e => e.stopPropagation()}>
        <div className="us-input-wrap">
          <Icon name="search" />
          <input
            ref={inputRef}
            className="us-input"
            placeholder="Search across tools, todos, notes, JSON, bookmarks…"
            value={q}
            onChange={e => setQ(e.target.value)}
            onKeyDown={(e) => {
              if (e.key === 'Escape') onClose();
              else if (e.key === 'ArrowDown') { e.preventDefault(); setActive(a => Math.min(a+1, flat.length-1)); }
              else if (e.key === 'ArrowUp')   { e.preventDefault(); setActive(a => Math.max(0, a-1)); }
              else if (e.key === 'Enter')     { e.preventDefault(); if (flat[active]) pick(flat[active]); }
            }}
          />
          <kbd>esc</kbd>
        </div>
        <div className="us-results">
          {results.length === 0 ? (
            <div style={{ padding: 24, textAlign: 'center', color: 'var(--text-muted)', fontSize: 13 }}>No matches.</div>
          ) : results.map((sec, si) => {
            const startIdx = results.slice(0, si).reduce((acc, s) => acc + s.items.length, 0);
            return (
              <div key={sec.label}>
                <div className="us-section-label">{sec.label}</div>
                {sec.items.map((it, i) => {
                  const idx = startIdx + i;
                  return (
                    <button
                      key={it.kind + '_' + it.id}
                      className={`us-item ${active === idx ? 'is-active' : ''}`}
                      onMouseEnter={() => setActive(idx)}
                      onClick={() => pick(it)}
                    >
                      <Icon name={it.icon} />
                      <span className="us-item-title">{it.title}</span>
                      <span className="us-item-meta">{it.meta}</span>
                    </button>
                  );
                })}
              </div>
            );
          })}
        </div>
        <div className="us-foot">
          <span><kbd>↑↓</kbd> navigate</span><span><kbd>↵</kbd> open</span><span><kbd>esc</kbd> close</span>
        </div>
      </div>
    </div>
  );
}

// ============================================================
// Nav item with star
// ============================================================
function NavItem({ tool, active, count, starred, collapsed, customizing, dragOver, onSelect, onToggleStar, onRemove, dragProps }) {
  return (
    <div
      className={`nav-item-wrap ${collapsed ? 'is-collapsed' : ''} ${customizing ? 'is-customizing' : ''} ${dragOver ? 'is-drop-target' : ''}`}
      {...(customizing ? dragProps : {})}
    >
      {customizing && !collapsed && (
        <span className="nav-drag-handle" title="Drag to reorder or move between sections"><Icon name="drag_indicator" /></span>
      )}
      <button
        className={`nav-item ${active ? 'active' : ''}`}
        onClick={customizing ? undefined : onSelect}
        title={collapsed ? tool.label : ''}
      >
        <Icon name={tool.icon} />
        {!collapsed && <span className="nav-item-label">{tool.label}</span>}
        {!collapsed && !customizing && count != null && count > 0 && <span className="nav-count">{count}</span>}
      </button>
      {!collapsed && customizing && (
        <button
          className="nav-remove"
          onClick={(e) => { e.stopPropagation(); onRemove(); }}
          title="Remove from menu"
        >
          <Icon name="remove_circle_outline" />
        </button>
      )}
      {!collapsed && !customizing && (
        <button
          className={`nav-star ${starred ? 'is-starred' : ''}`}
          onClick={(e) => { e.stopPropagation(); onToggleStar(); }}
          title={starred ? 'Remove from Starred' : 'Add to Starred'}
        >
          <Icon name={starred ? 'star' : 'star_outline'} />
        </button>
      )}
    </div>
  );
}

// Default section order = the order sections first appear in TOOLS.
const DEFAULT_SECTION_ORDER = (() => {
  const seen = [];
  for (const t of TOOLS) if (!seen.includes(t.section)) seen.push(t.section);
  return seen;
})();
const SECTION_ICONS = {
  Workspace: 'workspaces', Lists: 'format_list_bulleted', Tools: 'build',
  Games: 'sports_esports', Image: 'image', Hardware: 'memory', Encryption: 'lock',
};

// ============================================================
// App shell
// ============================================================
function App() {
  const [tool, setTool] = useState(() => new URL(location.href).hash.slice(1) || 'home');
  const [searchOpen, setSearchOpen] = useState(false);
  const [settingsOpen, setSettingsOpen] = useState(false);
  const [settings, updateSettings, settingsLoaded] = useSettings();

  // Sidebar collapsed state — stored in db.kv
  const [collapsed, setCollapsed] = useState(false);
  const [starred, setStarred] = useState([]);
  const [prefsLoaded, setPrefsLoaded] = useState(false);

  // Personalization: collapsed sections, section order, per-section tool order,
  // and a "customize" edit mode for drag-reordering.
  const [collapsedSections, setCollapsedSections] = useState([]);
  const [sectionOrder, setSectionOrder] = useState([]);
  const [toolOrder, setToolOrder] = useState({});
  const [customizing, setCustomizing] = useState(false);
  const [dragOverKey, setDragOverKey] = useState(null);
  const dragRef = useRef(null);
  // Add/remove items + sections
  const [hiddenTools, setHiddenTools] = useState([]);   // removed from sidebar (still in All apps)
  const [customSections, setCustomSections] = useState([]); // user-created section names
  const [toolSection, setToolSection] = useState({});   // toolId -> section override

  // First-run onboarding flag. `null` = still loading, `true` = done,
  // `false` = first launch, show the OnboardingFlow.
  const [onboardingDone, setOnboardingDone] = useState(null);

  useEffect(() => {
    Promise.all([
      db.kv.get('sidebarCollapsed'),
      db.kv.get('starredTools'),
      db.kv.get('onboardingComplete'),
      db.kv.get('collapsedSections'),
      db.kv.get('sectionOrder'),
      db.kv.get('toolOrder'),
      db.kv.get('hiddenTools'),
      db.kv.get('customSections'),
      db.kv.get('toolSection'),
    ]).then(([c, s, ob, cs, so, to, ht, csec, ts]) => {
      if (c?.v != null) setCollapsed(c.v);
      if (Array.isArray(s?.v)) setStarred(s.v);
      else setStarred([]); // no stars by default — user picks them
      if (Array.isArray(cs?.v)) setCollapsedSections(cs.v);
      if (Array.isArray(so?.v)) setSectionOrder(so.v);
      if (to?.v && typeof to.v === 'object' && !Array.isArray(to.v)) setToolOrder(to.v);
      if (Array.isArray(ht?.v)) setHiddenTools(ht.v);
      if (Array.isArray(csec?.v)) setCustomSections(csec.v);
      if (ts?.v && typeof ts.v === 'object' && !Array.isArray(ts.v)) setToolSection(ts.v);
      setOnboardingDone(ob?.v === true);
      setPrefsLoaded(true);
    });
  }, []);

  const finishOnboarding = useCallback(async ({ disabledTools }) => {
    // Persist the chosen tool set and mark onboarding complete.
    // Starring is left to the user — nothing is starred automatically.
    updateSettings({ disabledTools });
    await db.kv.put({ k: 'onboardingComplete', v: true });
    setOnboardingDone(true);
  }, [updateSettings]);

  const counts = useLiveQuery(['todos', 'notes', 'jsonDocs', 'bookmarks', 'passwords', 'flashcardCards', 'timeEntries']);
  // Live tool state for tools whose records live in a single kv blob
  const habitState = useKvRecord('habitTracker') || {};
  const readingState = useKvRecord('readingTracker') || {};

  useEffect(() => { location.hash = tool; }, [tool]);

  // ⌘K + ⌘, for settings
  useEffect(() => {
    const onKey = (e) => {
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') { e.preventDefault(); setSearchOpen(s => !s); }
      if ((e.metaKey || e.ctrlKey) && e.key === ',')              { e.preventDefault(); setSettingsOpen(s => !s); }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, []);

  const toggleCollapsed = () => {
    setCollapsed(c => {
      const next = !c;
      db.kv.put({ k: 'sidebarCollapsed', v: next });
      return next;
    });
  };

  const toggleStar = (id) => {
    setStarred(prev => {
      const next = prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id];
      db.kv.put({ k: 'starredTools', v: next });
      return next;
    });
  };

  const toggleSection = (name) => {
    setCollapsedSections(prev => {
      const next = prev.includes(name) ? prev.filter(x => x !== name) : [...prev, name];
      db.kv.put({ k: 'collapsedSections', v: next });
      return next;
    });
  };

  // Reorder a section relative to a target section.
  const reorderSections = (from, to) => {
    if (from === to) return;
    const order = sections.map(s => s[0]);
    const fi = order.indexOf(from), ti = order.indexOf(to);
    if (fi === -1 || ti === -1) return;
    const next = [...order];
    next.splice(fi, 1);
    next.splice(ti, 0, from);
    setSectionOrder(next);
    db.kv.put({ k: 'sectionOrder', v: next });
  };

  // Move a tool: reorder within its section, or relocate to another section.
  // beforeId === null appends to the end of the target section.
  const moveItem = (fromSection, toSection, fromId, beforeId) => {
    if (fromSection === toSection && fromId === beforeId) return;
    const srcList = (sections.find(s => s[0] === fromSection)?.[1] || []).map(t => t.id);
    const tgtList = (sections.find(s => s[0] === toSection)?.[1] || []).map(t => t.id);

    if (fromSection === toSection) {
      const fi = srcList.indexOf(fromId), ti = srcList.indexOf(beforeId);
      if (fi === -1 || ti === -1) return;
      const next = [...srcList];
      next.splice(fi, 1);
      next.splice(ti, 0, fromId);
      setToolOrder(prev => { const out = { ...prev, [fromSection]: next }; db.kv.put({ k: 'toolOrder', v: out }); return out; });
      return;
    }

    // Cross-section move: update the section override…
    const t = TOOL_BY_ID[fromId];
    setToolSection(prev => {
      const out = { ...prev };
      if (t && t.section === toSection) delete out[fromId];   // back to its home section
      else out[fromId] = toSection;
      db.kv.put({ k: 'toolSection', v: out });
      return out;
    });
    // …and rebuild explicit ordering for both sections.
    const newSrc = srcList.filter(id => id !== fromId);
    const newTgt = tgtList.filter(id => id !== fromId);
    const ti = beforeId ? newTgt.indexOf(beforeId) : -1;
    if (ti === -1) newTgt.push(fromId); else newTgt.splice(ti, 0, fromId);
    setToolOrder(prev => {
      const out = { ...prev, [fromSection]: newSrc, [toSection]: newTgt };
      db.kv.put({ k: 'toolOrder', v: out });
      return out;
    });
  };

  // Remove a tool from the sidebar (still reachable via All apps + search).
  const removeItem = (id) => {
    setHiddenTools(prev => {
      const next = prev.includes(id) ? prev : [...prev, id];
      db.kv.put({ k: 'hiddenTools', v: next });
      return next;
    });
  };
  const addItem = (id) => {
    setHiddenTools(prev => {
      const next = prev.filter(x => x !== id);
      db.kv.put({ k: 'hiddenTools', v: next });
      return next;
    });
  };

  const addSection = async () => {
    const name = (await window.lhDialog.prompt({
      title: 'New section', placeholder: 'Section name', icon: 'create_new_folder', confirmLabel: 'Create',
    }))?.trim();
    if (!name) return;
    const exists = customSections.includes(name) || sections.some(s => s[0] === name);
    if (exists) { window.lhDialog.confirm({ title: 'Section exists', message: `“${name}” already exists.`, confirmLabel: 'OK' }); return; }
    setCustomSections(prev => { const next = [...prev, name]; db.kv.put({ k: 'customSections', v: next }); return next; });
    setSectionOrder(prev => {
      const base = prev.length ? prev : sections.map(s => s[0]);
      const next = [...base, name];
      db.kv.put({ k: 'sectionOrder', v: next });
      return next;
    });
  };

  const renameSection = async (name) => {
    const next = (await window.lhDialog.prompt({ title: 'Rename section', initial: name, icon: 'edit' }))?.trim();
    if (!next || next === name) return;
    if (sections.some(s => s[0] === next)) {
      window.lhDialog.confirm({ title: 'Section exists', message: `“${next}” already exists.`, confirmLabel: 'OK' }); return;
    }
    const toolsIn = sections.find(s => s[0] === name)?.[1] || [];
    // Point every tool currently in this section at the new name (works for
    // built-in sections too — they become overrides), and rename any other
    // overrides that referenced the old name.
    setToolSection(prev => {
      const o = { ...prev };
      for (const t of toolsIn) o[t.id] = next;
      for (const k of Object.keys(o)) if (o[k] === name) o[k] = next;
      db.kv.put({ k: 'toolSection', v: o });
      return o;
    });
    // Register the new name and drop the old one if it was custom.
    setCustomSections(prev => {
      const o = prev.filter(s => s !== name);
      if (!o.includes(next)) o.push(next);
      db.kv.put({ k: 'customSections', v: o });
      return o;
    });
    // Preserve the section's position in the order.
    setSectionOrder(() => {
      const cur = sections.map(s => s[0]);
      const o = cur.map(s => s === name ? next : s);
      db.kv.put({ k: 'sectionOrder', v: o });
      return o;
    });
    setToolOrder(prev => { const o = { ...prev }; if (o[name]) { o[next] = o[name]; delete o[name]; } db.kv.put({ k: 'toolOrder', v: o }); return o; });
    setCollapsedSections(prev => { const o = prev.map(s => s === name ? next : s); db.kv.put({ k: 'collapsedSections', v: o }); return o; });
  };

  const deleteSection = async (name) => {
    const toolsIn = sections.find(s => s[0] === name)?.[1] || [];
    // Tools whose *home* section is this one get removed from the menu;
    // tools that were merely moved in revert to their home section.
    const protectedIds = ['home', 'catalog'];
    const nativeIds = toolsIn.filter(t => t.section === name && !protectedIds.includes(t.id)).map(t => t.id);
    const movedIn = toolsIn.filter(t => t.section !== name).length;
    const ok = await window.lhDialog.confirm({
      title: `Delete “${name}”?`,
      message: !toolsIn.length ? 'This empty section will be removed.'
        : `${nativeIds.length ? `${nativeIds.length} item(s) will be removed from the menu (re-add them anytime). ` : ''}${movedIn ? `${movedIn} item(s) will move back to their original section.` : ''}`.trim(),
      confirmLabel: 'Delete', danger: true,
    });
    if (!ok) return;
    setToolSection(prev => { const o = {}; for (const [k, v] of Object.entries(prev)) if (v !== name) o[k] = v; db.kv.put({ k: 'toolSection', v: o }); return o; });
    if (nativeIds.length) setHiddenTools(prev => { const o = [...new Set([...prev, ...nativeIds])]; db.kv.put({ k: 'hiddenTools', v: o }); return o; });
    setCustomSections(prev => { const o = prev.filter(s => s !== name); db.kv.put({ k: 'customSections', v: o }); return o; });
    setSectionOrder(prev => { const o = prev.filter(s => s !== name); db.kv.put({ k: 'sectionOrder', v: o }); return o; });
    setCollapsedSections(prev => { const o = prev.filter(s => s !== name); db.kv.put({ k: 'collapsedSections', v: o }); return o; });
    setToolOrder(prev => { const o = { ...prev }; delete o[name]; db.kv.put({ k: 'toolOrder', v: o }); return o; });
  };

  const resetMenu = () => {
    setCollapsedSections([]); db.kv.put({ k: 'collapsedSections', v: [] });
    setSectionOrder([]);      db.kv.put({ k: 'sectionOrder', v: [] });
    setToolOrder({});         db.kv.put({ k: 'toolOrder', v: {} });
    setHiddenTools([]);       db.kv.put({ k: 'hiddenTools', v: [] });
    setCustomSections([]);    db.kv.put({ k: 'customSections', v: [] });
    setToolSection({});       db.kv.put({ k: 'toolSection', v: {} });
  };

  const disabledTools = settings.disabledTools || [];
  const isToolEnabled = useCallback((id) => {
    if (id === 'home' || id === 'catalog') return true;
    const t = TOOL_BY_ID[id];
    // Grouped tools (e.g. the per-op image entries) share their group's
    // master toggle so Settings stays a single switch for the whole family.
    const gate = (t && t.group) ? t.group : id;
    return !disabledTools.includes(gate);
  }, [disabledTools]);

  // If the current tool gets disabled, fall back to home.
  useEffect(() => {
    if (!isToolEnabled(tool)) setTool('home');
  }, [tool, isToolEnabled]);

  const secOf = useCallback((t) => toolSection[t.id] || t.section, [toolSection]);

  const sections = useMemo(() => {
    const map = {};
    // Custom sections always exist (even when empty) so users can drag tools in.
    for (const name of customSections) map[name] = [];
    for (const t of TOOLS) {
      if (!isToolEnabled(t.id)) continue;
      if (hiddenTools.includes(t.id)) continue;
      const sec = secOf(t);
      if (!map[sec]) map[sec] = [];
      map[sec].push(t);
    }
    // Order tools within each section per saved toolOrder (unknown ids fall to end).
    for (const sec of Object.keys(map)) {
      const order = toolOrder[sec];
      if (Array.isArray(order) && order.length) {
        map[sec].sort((a, b) => {
          const ia = order.indexOf(a.id), ib = order.indexOf(b.id);
          if (ia === -1 && ib === -1) return 0;
          if (ia === -1) return 1;
          if (ib === -1) return -1;
          return ia - ib;
        });
      }
    }
    // Order sections per saved sectionOrder, falling back to default order.
    const ref = sectionOrder.length ? sectionOrder : DEFAULT_SECTION_ORDER;
    return Object.entries(map).sort((a, b) => {
      const ia = ref.indexOf(a[0]), ib = ref.indexOf(b[0]);
      if (ia === -1 && ib === -1) return 0;
      if (ia === -1) return 1;
      if (ib === -1) return -1;
      return ia - ib;
    });
  }, [isToolEnabled, sectionOrder, toolOrder, hiddenTools, customSections, secOf]);

  // Enabled tools currently removed from the sidebar — offered in the "add" picker.
  const hiddenList = useMemo(
    () => TOOLS.filter(t => isToolEnabled(t.id) && hiddenTools.includes(t.id)
      && t.id !== 'home' && t.id !== 'catalog'),
    [isToolEnabled, hiddenTools]
  );

  const starredTools = useMemo(
    () => starred.map(id => TOOL_BY_ID[id]).filter(t => t && isToolEnabled(t.id)),
    [starred, isToolEnabled]
  );

  const countFor = (id) => {
    if (!counts) return null;
    if (id === 'todo')     return counts.todos?.filter(t => !t.done).length;
    if (id === 'note')     return counts.notes?.length;
    if (id === 'json')     return counts.jsonDocs?.length;
    if (id === 'bookmark') return counts.bookmarks?.length;
    if (id === 'password') return counts.passwords?.length;
    if (id === 'flashcard') {
      // Due cards today only — mirrors the home-page badge.
      const list = counts.flashcardCards || [];
      if (!window.flashcards) return null;
      return list.filter(c => window.flashcards.isDue(c)).length || null;
    }
    if (id === 'writer') {
      // Sessions are notes in folder 'Writing'
      const n = (counts.notes || []).filter(x => x.folder === 'Writing').length;
      return n || null;
    }
    if (id === 'habits') {
      // Show habits not yet done today — actionable nudge
      const habits = (habitState.habits || []).filter(h => !h.archived);
      const today = new Date();
      const todayKey = `${today.getFullYear()}-${String(today.getMonth()+1).padStart(2,'0')}-${String(today.getDate()).padStart(2,'0')}`;
      const completions = habitState.completions || {};
      return habits.filter(h => !completions[h.id]?.[todayKey]).length || null;
    }
    if (id === 'timetracker') {
      // Entries started today — "what have I tracked so far"
      const dayStart = new Date(); dayStart.setHours(0,0,0,0);
      return (counts.timeEntries || []).filter(e => e.startedAt >= dayStart.getTime()).length || null;
    }
    if (id === 'reading') {
      // Currently-reading count (actionable: books in progress)
      return (readingState.books || []).filter(b => b.status === 'reading').length || null;
    }
    return null;
  };

  const current = TOOL_BY_ID[tool] || TOOLS[0];
  const Component = window[current.component];

  // Brand title split
  const title = settings.appTitle || 'Toolio';
  const splitAt = Math.min(Math.max(settings.brandSplit ?? 4, 0), title.length);
  const head = title.slice(0, splitAt);
  const tail = title.slice(splitAt);

  if (!settingsLoaded || !prefsLoaded || onboardingDone === null) {
    return <div className="boot-loader"><div className="brand-tile"><Icon name="grid_view" /></div></div>;
  }

  if (onboardingDone === false) {
    return (
      <OnboardingFlow
        appTitle={settings.appTitle}
        brandIcon={settings.brandIcon}
        onFinish={finishOnboarding}
      />
    );
  }

  return (
    <SendToProvider currentTool={tool} setTool={setTool} disabledTools={disabledTools}>
      <div className={`app ${collapsed ? 'is-collapsed' : ''}`}>
        <aside className="sidebar">
          <div className="sidebar-brand">
            <button className="brand-tile" onClick={toggleCollapsed} title={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}>
              <Icon name={settings.brandIcon || 'grid_view'} />
            </button>
            {!collapsed && (
              <div className="brand-name">
                {head}{tail && <span>{tail}</span>}
              </div>
            )}
            {!collapsed && (
              <button className="sidebar-collapse-btn" onClick={toggleCollapsed} title="Collapse sidebar">
                <Icon name="left_panel_close" />
              </button>
            )}
          </div>

          {!collapsed ? (
            <button className="sidebar-search" onClick={() => setSearchOpen(true)}>
              <Icon name="search" />
              <input readOnly placeholder="Search everything…" />
              <kbd>⌘K</kbd>
            </button>
          ) : (
            <button
              className="sidebar-icon-btn"
              onClick={() => setSearchOpen(true)}
              title="Search (⌘K)"
            >
              <Icon name="search" />
            </button>
          )}

          <div className="sidebar-scroll">
            {/* STARRED SECTION */}
            {starredTools.length > 0 && (
              <>
                <div className="nav-section-label">
                  {!collapsed && <><Icon name="star" />Starred</>}
                  {collapsed && <Icon name="star" />}
                </div>
                {starredTools.map(t => (
                  <NavItem
                    key={'star_' + t.id}
                    tool={t}
                    active={tool === t.id}
                    count={countFor(t.id)}
                    starred={true}
                    collapsed={collapsed}
                    onSelect={() => setTool(t.id)}
                    onToggleStar={() => toggleStar(t.id)}
                  />
                ))}
              </>
            )}

            {sections.map(([section, tools]) => {
              const folded = !customizing && collapsedSections.includes(section);
              const dropSec = dragOverKey === 'sec:' + section;
              return (
              <React.Fragment key={section}>
                {!collapsed ? (
                  <div
                    className={`nav-section-label is-toggle ${customizing ? 'is-customizing' : ''} ${dropSec ? 'is-drop-target' : ''}`}
                    draggable={customizing}
                    onDragStart={customizing ? (e) => { dragRef.current = { type: 'section', section }; e.dataTransfer.effectAllowed = 'move'; } : undefined}
                    onDragOver={customizing ? (e) => { const d = dragRef.current; if (d?.type === 'section' || d?.type === 'item') { e.preventDefault(); setDragOverKey('sec:' + section); } } : undefined}
                    onDragLeave={customizing ? () => setDragOverKey(k => k === 'sec:' + section ? null : k) : undefined}
                    onDrop={customizing ? (e) => {
                      e.preventDefault();
                      const d = dragRef.current;
                      if (d?.type === 'section') reorderSections(d.section, section);
                      else if (d?.type === 'item') moveItem(d.section, section, d.id, null);
                      dragRef.current = null; setDragOverKey(null);
                    } : undefined}
                  >
                    {customizing && <span className="nav-drag-handle"><Icon name="drag_indicator" /></span>}
                    <button
                      className="nav-section-toggle"
                      onClick={() => !customizing && toggleSection(section)}
                      title={folded ? 'Expand section' : 'Collapse section'}
                    >
                      <Icon name="chevron_right" className={`nav-section-chevron ${folded ? '' : 'is-open'}`} />
                      <span className="nav-section-name">{section}</span>
                      <span className="nav-section-meta">{tools.length}</span>
                    </button>
                    {customizing && (
                      <>
                        <button className="nav-section-act" onClick={() => renameSection(section)} title="Rename section"><Icon name="edit" /></button>
                        <button className="nav-section-act is-danger" onClick={() => deleteSection(section)} title="Delete section"><Icon name="delete" /></button>
                      </>
                    )}
                  </div>
                ) : (
                  <div className="nav-section-label"><span className="nav-section-dot" /></div>
                )}
                {!folded && tools.map(t => (
                  <NavItem
                    key={t.id}
                    tool={t}
                    active={tool === t.id}
                    count={countFor(t.id)}
                    starred={starred.includes(t.id)}
                    collapsed={collapsed}
                    customizing={customizing}
                    dragOver={dragOverKey === 'item:' + t.id}
                    onSelect={() => setTool(t.id)}
                    onToggleStar={() => toggleStar(t.id)}
                    onRemove={() => removeItem(t.id)}
                    dragProps={{
                      draggable: true,
                      onDragStart: (e) => { dragRef.current = { type: 'item', section, id: t.id }; e.dataTransfer.effectAllowed = 'move'; },
                      onDragOver: (e) => { if (dragRef.current?.type === 'item') { e.preventDefault(); setDragOverKey('item:' + t.id); } },
                      onDragLeave: () => setDragOverKey(k => k === 'item:' + t.id ? null : k),
                      onDrop: (e) => { e.preventDefault(); const d = dragRef.current; if (d?.type === 'item') moveItem(d.section, section, d.id, t.id); dragRef.current = null; setDragOverKey(null); },
                      onDragEnd: () => { dragRef.current = null; setDragOverKey(null); },
                    }}
                  />
                ))}
                {customizing && !folded && tools.length === 0 && (
                  <div
                    className={`nav-section-empty ${dragOverKey === 'empty:' + section ? 'is-drop-target' : ''}`}
                    onDragOver={(e) => { if (dragRef.current?.type === 'item') { e.preventDefault(); setDragOverKey('empty:' + section); } }}
                    onDragLeave={() => setDragOverKey(k => k === 'empty:' + section ? null : k)}
                    onDrop={(e) => { e.preventDefault(); const d = dragRef.current; if (d?.type === 'item') moveItem(d.section, section, d.id, null); dragRef.current = null; setDragOverKey(null); }}
                  >
                    Drag items here
                  </div>
                )}
              </React.Fragment>
              );
            })}

            {!collapsed && (
              <div className="sidebar-customize-row">
                <button
                  className={`sidebar-customize-btn ${customizing ? 'is-active' : ''}`}
                  onClick={() => { setCustomizing(c => !c); setDragOverKey(null); }}
                >
                  <Icon name={customizing ? 'check' : 'tune'} />
                  {customizing ? 'Done' : 'Customize menu'}
                </button>
                {customizing && (
                  <button className="sidebar-reset-btn" onClick={resetMenu} title="Reset menu to defaults">
                    <Icon name="restart_alt" />
                  </button>
                )}
              </div>
            )}

            {!collapsed && customizing && (
              <div className="sidebar-customize-panel">
                <button className="customize-add-section" onClick={addSection}>
                  <Icon name="create_new_folder" />New section
                </button>
                {hiddenList.length > 0 && (
                  <div className="customize-hidden">
                    <div className="customize-hidden-label">Removed from menu</div>
                    {hiddenList.map(t => (
                      <button key={t.id} className="customize-hidden-item" onClick={() => addItem(t.id)} title="Add back to menu">
                        <Icon name={t.icon} />
                        <span className="customize-hidden-name">{t.label}</span>
                        <Icon name="add_circle" className="customize-hidden-add" />
                      </button>
                    ))}
                  </div>
                )}
              </div>
            )}
          </div>

          {/* Foot */}
          {collapsed ? (
            <div className="sidebar-foot-collapsed">
              <button className="sidebar-icon-btn" onClick={() => setSettingsOpen(true)} title="Settings">
                <Icon name="settings" />
              </button>
              <button className="sidebar-icon-btn" onClick={toggleCollapsed} title="Expand sidebar">
                <Icon name="left_panel_open" />
              </button>
            </div>
          ) : (
            <div className="sidebar-foot">
              <div className="avatar">{(title[0] || 'L').toUpperCase()}</div>
              <div className="meta">
                <div className="name">Your space</div>
                <div className="sub">Stored locally</div>
              </div>
              <button
                className="iconbtn"
                title="Settings"
                onClick={() => setSettingsOpen(true)}
              >
                <Icon name="settings" />
              </button>
            </div>
          )}
        </aside>

        <header className="header">
          <div className="page-title">
            <Icon name={current.icon} />
            <span>{current.label}</span>
          </div>
          <div className="page-sub">{current.sub}</div>
          <div className="header-spacer" />
          <div className="header-actions">
            <button className="lh-btn ghost" onClick={() => setSearchOpen(true)}>
              <Icon name="search" />
              Search
              <kbd className="header-kbd">⌘K</kbd>
            </button>
            <IconBtn name="settings" title="Settings (⌘,)" onClick={() => setSettingsOpen(true)} />
            <IconBtn
              name={settings.mode === 'dark' ? 'light_mode' : 'dark_mode'}
              title="Toggle light/dark"
              onClick={() => updateSettings({ mode: settings.mode === 'dark' ? 'light' : 'dark' })}
            />
          </div>
        </header>

        <main className="main">
          {Component
            ? <Component key={tool} />
            : <div className="lh-empty"><Icon name="construction" /><div className="title">Coming soon</div></div>}
        </main>
      </div>

      <UniversalSearch
        open={searchOpen}
        onClose={() => setSearchOpen(false)}
        onPick={({ tool: t }) => { setTool(t); setSearchOpen(false); }}
        isToolEnabled={isToolEnabled}
      />
      <SettingsModal
        open={settingsOpen}
        onClose={() => setSettingsOpen(false)}
        settings={settings}
        update={updateSettings}
      />
    </SendToProvider>
  );
}

db.ready.then(() => {
  const root = ReactDOM.createRoot(document.getElementById('root'));
  root.render(
    <>
      <App />
      <DialogHost />
      <InfoPagesHost />
    </>
  );
});
