/* Sprig — Toolbar, Inspector, Agenda panels. */
const { useState, useRef } = React;

// Downscale an image File to a JPEG data URL via an offscreen canvas. Used to make
// both the full image (capped) and a small thumbnail before uploading — no deps.
function fileToScaledDataURL(file, maxDim, quality) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => {
      const s = Math.min(1, maxDim / Math.max(img.width, img.height));
      const w = Math.round(img.width * s), h = Math.round(img.height * s);
      const c = document.createElement('canvas'); c.width = w; c.height = h;
      c.getContext('2d').drawImage(img, 0, 0, w, h);
      URL.revokeObjectURL(img.src);
      resolve(c.toDataURL('image/jpeg', quality));
    };
    img.onerror = reject;
    img.src = URL.createObjectURL(file);
  });
}

function IconBtn({ title, active, onClick, children, theme, danger, disabled }) {
  const [h, setH] = useState(false);
  return React.createElement('button', {
    title, disabled, onClick: disabled ? undefined : onClick,
    onMouseEnter: () => setH(true), onMouseLeave: () => setH(false),
    style: { width: 34, height: 34, display: 'flex', alignItems: 'center', justifyContent: 'center',
      borderRadius: 8, border: 'none', cursor: disabled ? 'default' : 'pointer', fontSize: 15,
      background: active ? theme.accent : (h && !disabled ? theme.hover : 'transparent'),
      color: active ? '#fff' : (danger && h ? theme.bad : theme.text),
      opacity: disabled ? 0.35 : 1,
      transition: 'background .12s' } }, children);
}

function Segment({ value, options, onChange, theme }) {
  return React.createElement('div', { style: { display: 'flex', gap: 2, padding: 3,
    background: theme.chipBg, borderRadius: 9 } },
    options.map(o => React.createElement('button', { key: o.v, title: o.t, onClick: () => onChange(o.v),
      style: { border: 'none', cursor: 'pointer', padding: '5px 9px', borderRadius: 6, fontSize: 13,
        display: 'flex', alignItems: 'center', gap: 5, fontWeight: 500,
        background: value === o.v ? theme.nodeBg : 'transparent',
        color: value === o.v ? theme.text : theme.muted,
        boxShadow: value === o.v ? '0 1px 2px rgba(0,0,0,.08)' : 'none' } }, o.icon, o.label)));
}

function Toolbar({ theme, tw, setTweak, view, setView, onFit, title, setTitle, dark, onToggleTheme,
  agendaOpen, setAgendaOpen, onPresent, taskCount, onFoldAll, onUnfoldAll, onFocusToday, onExport, onImport, saveStatus, onOpenSearch,
  onUndo, onRedo, canUndo, canRedo, onLogout }) {
  return React.createElement('div', { style: { position: 'absolute', top: 0, left: 0, right: 0, height: 56,
    display: 'flex', alignItems: 'center', gap: 14, padding: '0 14px', zIndex: 20,
    background: theme.barBg, borderBottom: `1px solid ${theme.line}`, backdropFilter: 'blur(8px)' } },
    React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 9 } },
      React.createElement('div', { style: { width: 28, height: 28, borderRadius: 8, background: theme.accent,
        display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 16 } }, '🌱'),
      React.createElement('span', { style: { fontWeight: 700, fontSize: 16, letterSpacing: '-0.01em', fontFamily: tw.fontFamily, whiteSpace: 'nowrap' } }, 'Cody - Life map')),
    React.createElement('div', { style: { width: 1, height: 24, background: theme.line } }),
    React.createElement('input', { value: title, onChange: e => setTitle(e.target.value),
      style: { border: 'none', background: 'transparent', color: theme.text, fontSize: 14.5, fontWeight: 600,
        fontFamily: tw.fontFamily, outline: 'none', width: 240, padding: '4px 6px', borderRadius: 6 } }),
    React.createElement('div', { style: { flex: 1 } }),
    React.createElement('button', { onClick: onOpenSearch, title: 'Search (⌘K)',
      style: { display: 'flex', alignItems: 'center', gap: 8, padding: '7px 12px', borderRadius: 9,
        border: `1px solid ${theme.line}`, background: theme.chipBg, color: theme.muted, cursor: 'pointer',
        fontSize: 13, fontFamily: tw.fontFamily } },
      React.createElement('span', null, '🔍'),
      React.createElement('span', null, 'Search'),
      React.createElement('kbd', { style: { fontFamily: tw.monoFamily, fontSize: 10.5, padding: '1px 5px', borderRadius: 5,
        background: theme.nodeBg, border: `1px solid ${theme.line}`, color: theme.muted } }, '⌘K')),
    React.createElement('div', { style: { flex: 1 } }),
    React.createElement(Segment, { theme, value: tw.layout, onChange: v => setTweak('layout', v), options: [
      { v: 'right', t: 'Tree (right)', icon: '→' }, { v: 'both', t: 'Both sides', icon: '↔' },
      { v: 'down', t: 'Org chart', icon: '↓' }, { v: 'radial', t: 'Radial', icon: '✳' } ] }),
    React.createElement('div', { style: { width: 1, height: 24, background: theme.line } }),
    React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 2 } },
      React.createElement(IconBtn, { theme, title: 'Zoom out', onClick: () => setView(v => ({ ...v, scale: Math.max(0.2, v.scale * 0.85) })) }, '−'),
      React.createElement('span', { style: { fontSize: 12, fontFamily: tw.monoFamily, color: theme.muted, width: 38, textAlign: 'center' } }, Math.round(view.scale * 100) + '%'),
      React.createElement(IconBtn, { theme, title: 'Zoom in', onClick: () => setView(v => ({ ...v, scale: Math.min(2.4, v.scale * 1.15) })) }, '+'),
      React.createElement(IconBtn, { theme, title: 'Fit to screen', onClick: onFit }, '⤢')),
    React.createElement('div', { style: { width: 1, height: 24, background: theme.line } }),
    React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 2 } },
      React.createElement(IconBtn, { theme, title: 'Undo (⌘Z)', onClick: onUndo, disabled: !canUndo }, '↶'),
      React.createElement(IconBtn, { theme, title: 'Redo (⌘⇧Z)', onClick: onRedo, disabled: !canRedo }, '↷')),
    React.createElement('div', { style: { width: 1, height: 24, background: theme.line } }),
    React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 2 } },
      React.createElement(IconBtn, { theme, title: 'Fold all', onClick: onFoldAll }, '⊟'),
      React.createElement(IconBtn, { theme, title: 'Unfold all', onClick: onUnfoldAll }, '⊞'),
      React.createElement(IconBtn, { theme, title: 'Focus today (⌘T)', onClick: onFocusToday }, '🧭')),
    React.createElement('div', { style: { width: 1, height: 24, background: theme.line } }),
    React.createElement(IconBtn, { theme, title: 'Agenda & dates', active: agendaOpen, onClick: () => setAgendaOpen(o => !o) },
      React.createElement('span', { style: { position: 'relative' } }, '🗓',
        taskCount ? React.createElement('span', { style: { position: 'absolute', top: -6, right: -9, minWidth: 15, height: 15,
          padding: '0 3px', borderRadius: 999, background: theme.accent, color: '#fff', fontSize: 9.5, fontFamily: tw.monoFamily,
          display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 700 } }, taskCount) : null)),
    React.createElement('div', { style: { width: 1, height: 24, background: theme.line } }),
    React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 2 } },
      React.createElement(IconBtn, { theme, title: 'Export JSON', onClick: onExport }, '⬇'),
      React.createElement(IconBtn, { theme, title: 'Import JSON', onClick: onImport }, '⬆'),
      React.createElement('span', { style: { fontSize: 10.5, fontFamily: tw.monoFamily, color: saveStatus === 'saved' ? theme.good : saveStatus === 'error' ? theme.bad : theme.muted, minWidth: 36 } },
        saveStatus === 'saving' ? '…' : saveStatus === 'saved' ? 'Saved' : saveStatus === 'error' ? 'Error' : '')),
    React.createElement('div', { style: { width: 1, height: 24, background: theme.line } }),
    React.createElement(IconBtn, { theme, title: 'Settings',
      onClick: () => window.postMessage({ type: '__activate_edit_mode' }, window.location.origin) }, '⚙'),
    React.createElement('div', { style: { width: 1, height: 24, background: theme.line } }),
    React.createElement(IconBtn, { theme, title: tw.background === 'plain' ? 'Show grid dots' : 'Hide grid dots',
      active: tw.background !== 'plain', onClick: () => setTweak('background', tw.background === 'plain' ? 'dots' : 'plain') }, '⠿'),
    React.createElement(IconBtn, { theme, title: dark ? 'Light mode' : 'Dark mode', onClick: onToggleTheme }, dark ? '☀' : '☾'),
    React.createElement('div', { style: { width: 1, height: 24, background: theme.line } }),
    React.createElement(IconBtn, { theme, title: 'Log out', onClick: onLogout }, '⎋'),
    React.createElement('button', { onClick: onPresent, style: { display: 'flex', alignItems: 'center', gap: 7,
      padding: '8px 14px', borderRadius: 9, border: 'none', cursor: 'pointer', background: theme.accent, color: '#fff',
      fontSize: 13.5, fontWeight: 600, fontFamily: tw.fontFamily } }, '▶ Present'));
}

// ---- Image attachment (Inspector field) --------------------------------
// Uploads a downscaled image + thumbnail to /api/upload, stores the returned
// URLs on node.image, and shows a thumbnail with View/Remove controls.
function ImageField({ node, theme, tw, set, onPreviewImage, inputStyle }) {
  const [busy, setBusy] = useState(false);
  const fileRef = useRef(null);

  const handleFile = async (file) => {
    if (!file) return;
    setBusy(true);
    try {
      const [dataUrl, thumbDataUrl] = await Promise.all([
        fileToScaledDataURL(file, 1600, 0.85),
        fileToScaledDataURL(file, 240, 0.7),
      ]);
      const resp = await fetch('/api/upload', {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ dataUrl, thumbDataUrl, name: file.name }),
      }).then(r => r.json());
      if (resp && resp.url) {
        if (node.image) deleteFiles(node.image); // replacing -> drop the old files
        set({ image: resp });
      } else { alert('Image upload failed'); }
    } catch { alert('Image upload failed'); }
    setBusy(false);
  };

  const deleteFiles = (image) => fetch('/api/upload', {
    method: 'DELETE', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ url: image.url, thumbUrl: image.thumbUrl }),
  }).catch(() => {});

  const remove = () => { if (node.image) deleteFiles(node.image); set({ image: null }); };

  const btn = (label, onClick, primary, danger) => React.createElement('button', {
    onClick, style: { flex: 1, padding: '8px', borderRadius: 9, cursor: 'pointer',
      fontSize: 12.5, fontWeight: 600, fontFamily: tw.fontFamily,
      border: primary ? 'none' : `1px solid ${theme.line}`,
      background: primary ? theme.accent : theme.bg,
      color: primary ? '#fff' : (danger ? theme.bad : theme.text) } }, label);

  return React.createElement('div', null,
    React.createElement('input', { ref: fileRef, type: 'file', accept: 'image/*',
      style: { display: 'none' },
      onChange: e => { handleFile(e.target.files[0]); e.target.value = ''; } }),
    node.image
      ? React.createElement('div', null,
          React.createElement('img', { src: node.image.thumbUrl, alt: node.image.name || '',
            onClick: () => onPreviewImage && onPreviewImage(node.image.url),
            style: { width: '100%', maxHeight: 120, objectFit: 'cover', borderRadius: 9,
              border: `1px solid ${theme.line}`, cursor: 'zoom-in', display: 'block' } }),
          node.image.name && React.createElement('div', { style: { fontSize: 11, color: theme.muted,
            marginTop: 6, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } }, node.image.name),
          React.createElement('div', { style: { display: 'flex', gap: 8, marginTop: 8 } },
            btn('Replace', () => fileRef.current && fileRef.current.click()),
            btn('Remove', remove, false, true)))
      : React.createElement('button', { onClick: () => fileRef.current && fileRef.current.click(), disabled: busy,
          style: { width: '100%', padding: '9px', borderRadius: 9, cursor: busy ? 'default' : 'pointer',
            border: `1px dashed ${theme.line}`, background: theme.bg, color: theme.muted,
            fontSize: 12.5, fontWeight: 600, fontFamily: tw.fontFamily } },
          busy ? 'Uploading…' : '⬆ Upload image'));
}

// Full-size image overlay; click backdrop or press Esc to dismiss.
function Lightbox({ url, onClose }) {
  React.useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') { e.stopPropagation(); onClose(); } };
    window.addEventListener('keydown', onKey, true);
    return () => window.removeEventListener('keydown', onKey, true);
  }, [onClose]);
  return React.createElement('div', { onPointerDown: onClose,
    style: { position: 'fixed', inset: 0, zIndex: 50, background: 'rgba(0,0,0,.75)',
      display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 } },
    React.createElement('img', { src: url, onPointerDown: e => e.stopPropagation(),
      style: { maxWidth: '90%', maxHeight: '90%', objectFit: 'contain', borderRadius: 8,
        boxShadow: '0 20px 60px rgba(0,0,0,.5)' } }));
}

// ---- Inspector ---------------------------------------------------------
function Field({ label, theme, children }) {
  return React.createElement('div', { style: { marginBottom: 16 } },
    React.createElement('div', { style: { fontSize: 11, fontWeight: 600, letterSpacing: '0.04em', textTransform: 'uppercase',
      color: theme.muted, marginBottom: 7 } }, label), children);
}

// Curated icon palette for nodes. Grouped loosely by theme; recents float to the front.
const ICONS = [
  '🎯','🚀','🔥','⭐','🌟','✨','💡','🧠','❤️','🏆','🚩','🏁',
  '✅','☑️','⚠️','❗','❓','🔔','⏰','⏳','📌','📎','🔗',
  '📅','📊','📈','📉','💰','💵','💳','💼',
  '💭','📝','✏️','📋','📁','📦','📚','📖',
  '💬','📧','📞','🤝','👥','👤',
  '🛠️','⚙️','🔧','🐛','🔒','🔑',
  '🌱','🍀','🎉','🎁','☕','🎨','🎵','💻','📱','🌐','⚡','♻️','☀️','🌙',
  '1️⃣','2️⃣','3️⃣','4️⃣','5️⃣','6️⃣','7️⃣','8️⃣','9️⃣','🔟','0️⃣','🔢',
];
const ICON_RECENT_KEY = 'sprig.recentIcons';
function loadRecentIcons() {
  try { return (JSON.parse(localStorage.getItem(ICON_RECENT_KEY)) || []).filter(ic => ICONS.includes(ic)); }
  catch { return []; }
}

// Icon picker with a "recently used first" ordering, persisted across sessions.
function IconPicker({ value, theme, onPick }) {
  const [recent, setRecent] = useState(loadRecentIcons);
  const pick = (ic) => {
    if (ic) {
      const next = [ic, ...recent.filter(x => x !== ic)].slice(0, 8);
      setRecent(next);
      try { localStorage.setItem(ICON_RECENT_KEY, JSON.stringify(next)); } catch {}
    }
    onPick(ic);
  };
  const ordered = [...recent, ...ICONS.filter(ic => !recent.includes(ic))];
  const cell = (key, on, fontSize, label, click) => React.createElement('button', {
    key, onClick: click, style: { width: 28, height: 28, borderRadius: 7, cursor: 'pointer', fontSize,
      border: `1px solid ${on ? theme.accent : theme.line}`,
      background: on ? theme.hover : theme.bg, color: on ? '#fff' : theme.muted } }, label);
  return React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 5 } },
    cell('__none', !value, 12, '∅', () => pick(null)),
    ordered.map(ic => cell(ic, value === ic, 15, ic, () => pick(ic))));
}

function Inspector({ node, theme, tw, onChange, onAddChild, onAddSibling, onDelete, onToggleDayGoal, onApplyTemplate, onPreviewImage, onCopy, onPaste, hasClipboard }) {
  if (!node) return null;
  const { PALETTE, PALETTE_ORDER, SHAPES, NODE_TYPES } = window.SPRIG;
  const set = (patch) => onChange(node.id, patch);
  const inputStyle = { width: '100%', boxSizing: 'border-box', padding: '8px 10px', borderRadius: 8,
    border: `1px solid ${theme.line}`, background: theme.bg, color: theme.text, fontSize: 13, fontFamily: tw.fontFamily, outline: 'none' };

  function fmtDate(iso) {
    const d = new Date(iso + 'T00:00');
    const day = d.getDate();
    const ord = [,'st','nd','rd'][((day % 100 - 20) % 10) || day % 100 > 10 ? 0 : day % 10] || 'th';
    return `${day}${ord} ${d.toLocaleString('default', { month: 'long' })} ${d.getFullYear()}`;
  }

  const activeType = NODE_TYPES.find(t => t.id === node.nodeType) || NODE_TYPES[0];

  return React.createElement('div', { style: { position: 'absolute', top: 68, right: 12, bottom: 12, width: 268, zIndex: 18,
    background: theme.panel, border: `1px solid ${theme.line}`, borderRadius: 14, padding: 16, overflowY: 'auto',
    boxShadow: theme.shadow, fontFamily: tw.fontFamily } },
    React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 14 } },
      React.createElement('span', { style: { fontWeight: 700, fontSize: 14 } }, 'Node'),
      React.createElement('div', { style: { display: 'flex', gap: 4 } },
        React.createElement(IconBtn, { theme, title: 'Copy node + children (⌘C)', onClick: () => onCopy(node.id) }, '📋'),
        React.createElement(IconBtn, { theme, title: hasClipboard ? 'Paste as child here (⌘V)' : 'Copy a node first',
          disabled: !hasClipboard, onClick: () => onPaste(node.id) }, '📌'),
        React.createElement(IconBtn, { theme, title: 'Add child (Tab)', onClick: () => onAddChild(node.id) }, '＋'),
        React.createElement(IconBtn, { theme, title: 'Delete (⌫)', danger: true, onClick: () => onDelete(node.id) }, '🗑'))),

    React.createElement(Field, { label: 'Type', theme },
      React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 5 } },
        NODE_TYPES.map(t => {
          const sel = node.nodeType === t.id;
          return React.createElement('button', {
            key: String(t.id), title: t.label,
            onClick: () => {
              const patch = { nodeType: t.id };
              if (t.id !== null) {
                patch.icon = t.icon; patch.color = t.color; patch.shape = t.shape;
                if (t.task) patch.task = true;
                // give the node the type's default name if it's still a placeholder
                if (t.defaultText && ['New idea', 'Untitled', ''].includes((node.text || '').trim())) patch.text = t.defaultText;
              } else {
                patch.nodeType = null;
              }
              set(patch);
              // types with a template generate their child scaffold (only when empty)
              if (t.template && onApplyTemplate) onApplyTemplate(node.id, t.template);
            },
            style: { padding: '4px 10px', borderRadius: 999, cursor: 'pointer', fontSize: 12, fontWeight: 600,
              border: `1.5px solid ${sel ? theme.accent : theme.line}`,
              background: sel ? theme.accent : theme.bg,
              color: sel ? '#fff' : theme.muted },
          }, t.id ? `${t.icon ? t.icon + ' ' : ''}${t.label}` : '— Generic');
        })),
      // Type-specific extra UI
      (activeType.extra === 'datepicker' || activeType.extra === 'duedate') &&
        React.createElement('div', { style: { marginTop: 8 } },
          React.createElement('input', {
            type: 'date',
            defaultValue: '',
            onChange: e => {
              if (!e.target.value) return;
              if (activeType.extra === 'datepicker') {
                set({ text: fmtDate(e.target.value) });
              } else {
                set({ due: e.target.value });
              }
            },
            style: { ...inputStyle },
          }))),

    React.createElement(Field, { label: 'Color', theme },
      React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 7 } },
        PALETTE_ORDER.map(k => { const pal = PALETTE[k]; const sel = node.color === k;
          return React.createElement('button', { key: k, title: pal.name, onClick: () => set({ color: k }),
            style: { width: 26, height: 26, borderRadius: 8, cursor: 'pointer',
              background: pal.fill || theme.nodeBg, border: pal.fill ? 'none' : `1.5px solid ${theme.line}`,
              boxShadow: sel ? `0 0 0 2px ${theme.panel}, 0 0 0 3.5px ${theme.accent}` : 'none' } },
            !pal.fill ? React.createElement('span', { style: { fontSize: 10, color: theme.muted } }, 'A') : null); }))),

    React.createElement(Field, { label: 'Shape', theme },
      React.createElement(Segment, { theme, value: node.shape, onChange: v => set({ shape: v }),
        options: SHAPES.map(s => ({ v: s, label: s === 'underline' ? 'text' : s })) })),

    React.createElement('div', { style: { height: 1, background: theme.line, margin: '4px 0 16px' } }),

    React.createElement(Field, { label: 'Task', theme },
      React.createElement('label', { style: { display: 'flex', alignItems: 'center', gap: 9, cursor: 'pointer', fontSize: 13, marginBottom: node.task ? 10 : 0 } },
        React.createElement('span', { onClick: () => set({ task: !node.task }), style: { width: 36, height: 21, borderRadius: 999, flexShrink: 0,
          background: node.task ? theme.good : theme.line, position: 'relative', transition: 'background .15s' } },
          React.createElement('span', { style: { position: 'absolute', top: 2, left: node.task ? 17 : 2, width: 17, height: 17,
            borderRadius: 999, background: '#fff', transition: 'left .15s', boxShadow: '0 1px 2px rgba(0,0,0,.3)' } })),
        React.createElement('span', null, 'Track as a task')),
      node.task && React.createElement('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } },
        React.createElement('input', { type: 'date', value: node.due || '', onChange: e => set({ due: e.target.value || null }), style: { ...inputStyle, flex: 1 } }))),

    node.nodeType !== 'daygoals' && (() => { const goalMarked = node.isDayGoal || node.isDayGoalClone;
      return React.createElement(Field, { label: 'Day Goal', theme },
        React.createElement('label', { style: { display: 'flex', alignItems: 'center', gap: 9, cursor: 'pointer', fontSize: 13 } },
          React.createElement('span', { onClick: () => onToggleDayGoal && onToggleDayGoal(node.id), style: { width: 36, height: 21, borderRadius: 999, flexShrink: 0,
            background: goalMarked ? theme.accent : theme.line, position: 'relative', transition: 'background .15s' } },
            React.createElement('span', { style: { position: 'absolute', top: 2, left: goalMarked ? 17 : 2, width: 17, height: 17,
              borderRadius: 999, background: '#fff', transition: 'left .15s', boxShadow: '0 1px 2px rgba(0,0,0,.3)' } })),
          React.createElement('span', null, node.isDayGoalClone ? '🎯 Day goal (linked copy)' : '🎯 Mark as day goal'))); })(),

    React.createElement(Field, { label: 'Note', theme },
      React.createElement('textarea', { value: node.note, onChange: e => set({ note: e.target.value }), rows: 3,
        placeholder: 'Add a note or comment…', style: { ...inputStyle, resize: 'vertical', lineHeight: 1.5 } })),

    React.createElement(Field, { label: 'Link', theme },
      React.createElement('input', { value: node.link, onChange: e => set({ link: e.target.value }), placeholder: 'https://…', style: inputStyle })),

    React.createElement(Field, { label: 'Image', theme },
      React.createElement(ImageField, { node, theme, tw, set, onPreviewImage, inputStyle })),

    React.createElement(Field, { label: 'Icon', theme },
      React.createElement(IconPicker, { value: node.icon, theme, onPick: (ic) => set({ icon: ic }) })),

    React.createElement('div', { style: { display: 'flex', gap: 8, marginTop: 4 } },
      React.createElement('button', { onClick: () => onAddSibling(node.id), style: { flex: 1, padding: '9px', borderRadius: 9,
        border: `1px solid ${theme.line}`, background: theme.bg, color: theme.text, cursor: 'pointer', fontSize: 12.5, fontWeight: 600, fontFamily: tw.fontFamily } }, '↵ Sibling'),
      React.createElement('button', { onClick: () => onAddChild(node.id), style: { flex: 1, padding: '9px', borderRadius: 9,
        border: 'none', background: theme.accent, color: '#fff', cursor: 'pointer', fontSize: 12.5, fontWeight: 600, fontFamily: tw.fontFamily } }, '⇥ Child')));
}

// ---- Agenda ------------------------------------------------------------
function Agenda({ nodes, theme, tw, onPick, onClose, onToggleDone, today }) {
  const [filter, setFilter] = useState('all'); // all | today | week | overdue
  const tasks = nodes.filter(n => n.task && n.due);
  const inWeek = (d) => { const t = new Date(today + 'T00:00'); const x = new Date(d + 'T00:00');
    const diff = (x - t) / 86400000; return diff >= 0 && diff < 7; };
  let list = tasks;
  if (filter === 'today') list = tasks.filter(n => n.due === today);
  else if (filter === 'week') list = tasks.filter(n => inWeek(n.due));
  else if (filter === 'overdue') list = tasks.filter(n => n.due < today && !n.done);
  list = list.slice().sort((a, b) => (a.due < b.due ? -1 : a.due > b.due ? 1 : 0));

  // group by date
  const groups = {}; for (const n of list) (groups[n.due] = groups[n.due] || []).push(n);
  const dates = Object.keys(groups).sort();
  const fmt = (d) => { const x = new Date(d + 'T00:00');
    const lbl = x.toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' });
    return d === today ? 'Today · ' + lbl : lbl; };
  const done = tasks.filter(t => t.done).length;

  return React.createElement('div', { style: { position: 'absolute', top: 68, left: 12, bottom: 12, width: 300, zIndex: 18,
    background: theme.panel, border: `1px solid ${theme.line}`, borderRadius: 14, display: 'flex', flexDirection: 'column',
    boxShadow: theme.shadow, fontFamily: tw.fontFamily, overflow: 'hidden' } },
    React.createElement('div', { style: { padding: '15px 16px 11px' } },
      React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 3 } },
        React.createElement('span', { style: { fontWeight: 700, fontSize: 15 } }, 'Agenda'),
        React.createElement(IconBtn, { theme, title: 'Close', onClick: onClose }, '✕')),
      React.createElement('div', { style: { fontSize: 12, color: theme.muted, fontFamily: tw.monoFamily } },
        `${done}/${tasks.length} done · ${tasks.length - done} open`)),
    React.createElement('div', { style: { display: 'flex', gap: 4, padding: '0 12px 11px', flexWrap: 'wrap' } },
      [['all','All'],['today','Today'],['week','This week'],['overdue','Overdue']].map(([v, l]) =>
        React.createElement('button', { key: v, onClick: () => setFilter(v),
          style: { padding: '5px 10px', borderRadius: 999, cursor: 'pointer', fontSize: 12, fontWeight: 500,
            border: `1px solid ${filter === v ? theme.accent : theme.line}`,
            background: filter === v ? theme.accent : 'transparent', color: filter === v ? '#fff' : theme.muted } }, l))),
    React.createElement('div', { style: { flex: 1, overflowY: 'auto', padding: '0 12px 14px' } },
      dates.length === 0 ? React.createElement('div', { style: { color: theme.muted, fontSize: 13, textAlign: 'center', padding: '30px 0' } }, 'No tasks here.')
      : dates.map(d => React.createElement('div', { key: d, style: { marginBottom: 14 } },
          React.createElement('div', { style: { fontSize: 11.5, fontWeight: 700, color: d === today ? theme.accent : theme.muted,
            fontFamily: tw.monoFamily, marginBottom: 7, letterSpacing: '0.02em' } }, fmt(d)),
          groups[d].map(n => { const overdue = n.due < today && !n.done;
            return React.createElement('div', { key: n.id, onClick: () => onPick(n.id),
              style: { display: 'flex', alignItems: 'flex-start', gap: 9, padding: '8px 9px', borderRadius: 9, cursor: 'pointer',
                background: theme.bg, border: `1px solid ${theme.line}`, marginBottom: 6 } },
              React.createElement('span', { onClick: (e) => { e.stopPropagation(); onToggleDone(n.id); },
                style: { width: 16, height: 16, marginTop: 1, flexShrink: 0, borderRadius: 5, cursor: 'pointer',
                  border: `1.5px solid ${n.done ? theme.good : theme.muted}`, background: n.done ? theme.good : 'transparent',
                  color: '#fff', fontSize: 11, display: 'flex', alignItems: 'center', justifyContent: 'center' } }, n.done ? '✓' : ''),
              React.createElement('span', { style: { fontSize: 13, lineHeight: 1.35, color: theme.text,
                textDecoration: n.done ? 'line-through' : 'none', opacity: n.done ? 0.55 : 1 } }, n.text,
                overdue && React.createElement('span', { style: { marginLeft: 6, fontSize: 10.5, color: theme.bad, fontFamily: tw.monoFamily, fontWeight: 700 } }, 'OVERDUE'))); }))) ));
}

// ---- Global Search (command palette) -----------------------------------
function Search({ nodes, byId, theme, tw, onClose, onPick }) {
  const [q, setQ] = useState('');
  const [active, setActive] = useState(0);
  const inputRef = useRef(null);
  React.useEffect(() => { if (inputRef.current) inputRef.current.focus(); }, []);

  const query = q.trim().toLowerCase();
  const results = React.useMemo(() => {
    if (!query) return [];
    const scored = [];
    for (const n of nodes) {
      const text = (n.text || '').toLowerCase();
      const note = (n.note || '').toLowerCase();
      const link = (n.link || '').toLowerCase();
      const ti = text.indexOf(query), ni = note.indexOf(query), li = link.indexOf(query);
      if (ti === -1 && ni === -1 && li === -1) continue;
      const score = ti !== -1 ? ti : (ni !== -1 ? 1000 + ni : 2000 + li);
      scored.push({ node: n, score, inNote: ti === -1 && ni !== -1, inLink: ti === -1 && ni === -1 });
    }
    scored.sort((a, b) => a.score - b.score);
    return scored.slice(0, 40);
  }, [query, nodes]);

  React.useEffect(() => { setActive(0); }, [query]);

  function pathOf(n) {
    const parts = []; let cur = byId[n.parentId], guard = 0;
    while (cur && guard++ < 30) { parts.unshift(cur.text); cur = byId[cur.parentId]; }
    return parts.join(' › ');
  }
  function highlight(text) {
    const i = text.toLowerCase().indexOf(query);
    if (i === -1 || !query) return text;
    return [text.slice(0, i),
      React.createElement('mark', { key: 'm', style: { background: theme.accent, color: '#fff', borderRadius: 3, padding: '0 2px' } }, text.slice(i, i + query.length)),
      text.slice(i + query.length)];
  }
  const onKeyDown = (e) => {
    if (e.key === 'Escape') { e.preventDefault(); onClose(); }
    else if (e.key === 'ArrowDown') { e.preventDefault(); setActive(a => Math.min(results.length - 1, a + 1)); }
    else if (e.key === 'ArrowUp') { e.preventDefault(); setActive(a => Math.max(0, a - 1)); }
    else if (e.key === 'Enter') { e.preventDefault(); const r = results[active]; if (r) onPick(r.node.id); }
  };

  return React.createElement('div', {
    onPointerDown: onClose,
    style: { position: 'fixed', inset: 0, zIndex: 40, background: 'rgba(0,0,0,.35)',
      display: 'flex', justifyContent: 'center', alignItems: 'flex-start', paddingTop: '12vh' } },
    React.createElement('div', {
      onPointerDown: e => e.stopPropagation(),
      style: { width: 'min(560px, 92vw)', background: theme.panel, border: `1px solid ${theme.line}`,
        borderRadius: 14, boxShadow: theme.shadow, overflow: 'hidden', fontFamily: tw.fontFamily } },
      React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 10, padding: '14px 16px', borderBottom: `1px solid ${theme.line}` } },
        React.createElement('span', { style: { fontSize: 16 } }, '🔍'),
        React.createElement('input', { ref: inputRef, value: q, onChange: e => setQ(e.target.value), onKeyDown,
          placeholder: 'Search nodes by text, note or link…',
          style: { flex: 1, border: 'none', background: 'transparent', outline: 'none', color: theme.text, fontSize: 15, fontFamily: tw.fontFamily } }),
        React.createElement('kbd', { style: { fontFamily: tw.monoFamily, fontSize: 11, padding: '2px 6px', borderRadius: 5, background: theme.chipBg, border: `1px solid ${theme.line}`, color: theme.muted } }, 'Esc')),
      React.createElement('div', { style: { maxHeight: '52vh', overflowY: 'auto' } },
        query && results.length === 0
          ? React.createElement('div', { style: { padding: '22px 16px', color: theme.muted, fontSize: 13, textAlign: 'center' } }, 'No matches')
          : results.map((r, i) => { const n = r.node; const path = pathOf(n); const sel = i === active;
              return React.createElement('div', { key: n.id,
                onPointerEnter: () => setActive(i), onClick: () => onPick(n.id),
                style: { display: 'flex', alignItems: 'center', gap: 10, padding: '10px 16px', cursor: 'pointer',
                  background: sel ? theme.hover : 'transparent', borderLeft: `3px solid ${sel ? theme.accent : 'transparent'}` } },
                React.createElement('span', { style: { fontSize: 15, width: 18, flexShrink: 0, textAlign: 'center' } }, n.icon || '•'),
                React.createElement('div', { style: { minWidth: 0, flex: 1 } },
                  React.createElement('div', { style: { fontSize: 13.5, color: theme.text, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } }, highlight(n.text || 'Untitled')),
                  r.inLink && n.link
                    ? React.createElement('div', { style: { fontSize: 11, color: theme.accent, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', marginTop: 2 } }, highlight(n.link))
                    : path && React.createElement('div', { style: { fontSize: 11, color: theme.muted, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', marginTop: 2 } }, path)),
                r.inNote && React.createElement('span', { style: { fontSize: 10, color: theme.muted, fontFamily: tw.monoFamily, flexShrink: 0 } }, 'note'),
                r.inLink && React.createElement('span', { style: { fontSize: 10, color: theme.muted, fontFamily: tw.monoFamily, flexShrink: 0 } }, 'link')); })),
      results.length > 0 && React.createElement('div', { style: { padding: '8px 16px', borderTop: `1px solid ${theme.line}`, fontSize: 11, color: theme.muted, display: 'flex', gap: 14, fontFamily: tw.monoFamily } },
        React.createElement('span', null, '↑↓ navigate'),
        React.createElement('span', null, '↵ jump'),
        React.createElement('span', null, `${results.length} result${results.length === 1 ? '' : 's'}`))));
}

Object.assign(window, { SprigToolbar: Toolbar, SprigInspector: Inspector, SprigAgenda: Agenda, SprigIconBtn: IconBtn, SprigSearch: Search, SprigLightbox: Lightbox });
