/* === Brain Dump screen — masonry card wall for notes, ideas, to-dos,
   checklists, and habits. State lives at state.brainDump
   ({ notes, tags, pinOrder }) and rides on the same useHouseholdState
   sync as the rest of the app. Privacy filter is client-side (Option A
   in the handoff doc): notes with privacy:'private' are hidden from
   every household member except their author. === */

function BrainDumpScreen({ state, setState, setOpenModal, setEditingBrainNote, setPendingBrainDraft, userId }) {
  const bd = state.brainDump || { notes: [], tags: [], pinOrder: [] };
  const allNotes = bd.notes || [];
  const brainTags = bd.tags || [];

  // Privacy filter — hide other members' private notes. Notes without
  // an authorId (legacy / pre-auth seeds) stay visible as Family. This
  // is a CLIENT FILTER, not a security boundary — see Option A in the
  // handoff doc. Document this clearly in the UI tooltip on the privacy
  // toggle.
  const visibleByPrivacy = allNotes.filter(n =>
    n.privacy === 'public' || !n.authorId || n.authorId === userId
  );

  const [query, setQuery] = React.useState('');
  const [typeFilter, setTypeFilter] = React.useState('all');
  const [activeTag, setActiveTag] = React.useState(null);
  const [activeSub, setActiveSub] = React.useState(null);
  const [sort, setSort] = React.useState('newest');
  const [groupByTag, setGroupByTag] = React.useState(false);
  // When false (default) the wall hides archived notes. When true, the wall
  // shows ONLY archived notes — a separate view, not a mix. Tag rail and
  // counts derive from whichever set is currently on screen.
  const [showArchived, setShowArchived] = React.useState(false);
  const [tagManagerOpen, setTagManagerOpen] = React.useState(false);
  const [flagFilter, setFlagFilter] = React.useState('all');
  // 'all' | 'family' | 'private' — scopes the wall to shared-with-household
  // notes or just-mine notes. Operates over the privacy-visible set so
  // counts and chips never reveal teammates' private notes.
  const [privacyFilter, setPrivacyFilter] = React.useState('all');
  // Right-click radial menu: `{ x, y, noteId }` when open, else null.
  const [radial, setRadial] = React.useState(null);
  const visibleNotes = visibleByPrivacy.filter(n => !!n.archived === showArchived);
  const archivedCount = visibleByPrivacy.filter(n => n.archived).length;
  const [composerOpen, setComposerOpen] = React.useState(false);
  const [draft, setDraft] = React.useState(() => ({
    type: 'note', title: '', body: '',
    tagsList: [], newParent: '', subInput: '',
    color: null, due: '', privacy: 'private', pinned: false, flag: null,
    items: [], newItem: '',
    mode: 'check', target: '', targetUnit: 'm', cadence: 'daily',
  }));
  const [pickerParent, setPickerParent] = React.useState(null);
  const [expanded, setExpanded] = React.useState({});

  // Drag-reorder for the pinned grid. We accumulate the new order in
  // local state during the drag and flush to state.brainDump.pinOrder
  // on drop/dragEnd — mirrors how typing-heavy fields coalesce writes
  // to avoid emitting a debounced save per pixel.
  const [dragId, setDragId] = React.useState(null);
  const [localPinOrder, setLocalPinOrder] = React.useState(null);

  // Auto-clear the picker when the composer is closed.
  React.useEffect(() => { if (!composerOpen) setPickerParent(null); }, [composerOpen]);

  // New tags created during a composer session are buffered here and only
  // flushed into the shared library on save — keeps orphan tag names out of
  // the household blob when the user types a tag then abandons the note.
  const [pendingTags, setPendingTags] = React.useState([]);

  const commitTag = (name, color, sub) => {
    setPendingTags(prev => {
      const libTag = (bd.tags || []).find(t => t.name.toLowerCase() === name.toLowerCase());
      if (libTag) {
        // Tag exists in the shared library — only buffer a sub-addition if
        // it's genuinely new (not already on the library record or pending).
        if (!sub) return prev;
        const alreadyInLib = (libTag.subs || []).includes(sub);
        if (alreadyInLib) return prev;
        const existingPending = prev.find(p => p.name.toLowerCase() === libTag.name.toLowerCase());
        if (existingPending) {
          if ((existingPending.subs || []).includes(sub)) return prev;
          return prev.map(p => p === existingPending ? { ...p, subs: [...(p.subs || []), sub] } : p);
        }
        return [...prev, { name: libTag.name, color: libTag.color, subs: [sub] }];
      }
      // Brand-new parent tag — buffer until the note saves. Allow color
      // re-pick after creation so the editor's color row can override the
      // auto-assigned palette color while the tag is still in pending.
      const existing = prev.find(p => p.name.toLowerCase() === name.toLowerCase());
      if (existing) {
        const updateColor = color && existing.color !== color;
        const addSub = sub && !(existing.subs || []).includes(sub);
        if (!updateColor && !addSub) return prev;
        return prev.map(p => p === existing
          ? { ...p,
              color: updateColor ? color : p.color,
              subs: addSub ? [...(p.subs || []), sub] : p.subs,
            }
          : p);
      }
      const total = (bd.tags || []).length + prev.length;
      const c = color || BRAIN_TAG_PALETTE[total % BRAIN_TAG_PALETTE.length];
      return [...prev, { name, color: c, subs: sub ? [sub] : [] }];
    });
  };

  // Apply pendingTags to state.brainDump.tags. Called from submitDraft so
  // saving a note is what makes its tags real. Returns nothing — works
  // inside setState's updater.
  const mergePendingTagsInto = (bdState) => {
    if (!pendingTags.length) return bdState.tags || [];
    const out = [...(bdState.tags || [])];
    pendingTags.forEach(p => {
      const i = out.findIndex(t => t.name.toLowerCase() === p.name.toLowerCase());
      if (i < 0) { out.push(p); return; }
      const merged = { ...out[i] };
      (p.subs || []).forEach(sb => {
        if (!(merged.subs || []).includes(sb)) merged.subs = [...(merged.subs || []), sb];
      });
      out[i] = merged;
    });
    return out;
  };

  const openComposer = () => {
    setDraft({
      type: 'note', title: '', body: '',
      tagsList: [], newParent: '', subInput: '',
      color: null, due: '', privacy: 'private', pinned: false,
      items: [], newItem: '',
      mode: 'check', target: '', targetUnit: 'm', cadence: 'daily',
    });
    setPendingTags([]);
    setComposerOpen(true);
  };
  const closeComposer = () => { setComposerOpen(false); setPendingTags([]); };

  // Hand the in-progress draft + pending tag buffer off to BrainNoteModal
  // (which renders with bigBody=true), then close the inline composer.
  // The modal seeds its initial state from the snapshot so the user
  // keeps typing without a save round-trip.
  const expandToModal = () => {
    if (!setPendingBrainDraft || !setOpenModal) return;
    setPendingBrainDraft({ draft, pendingTags });
    setEditingBrainNote && setEditingBrainNote(null);
    setOpenModal('brain-note');
    setComposerOpen(false);
    setPendingTags([]);
    setPickerParent(null);
  };

  const submitDraft = () => {
    // Flush any unconfirmed input — typing a tag name into "+ new tag" and
    // hitting Save without pressing Enter would otherwise discard it.
    const flushed = flushBrainDraftInputs(draft, pickerParent, pendingTags, brainTags);
    const fd = flushed.draft;
    const hasItems = fd.type === 'checklist' && fd.items.some(i => i.text.trim());
    const isHabit = fd.type === 'habit';
    if (!fd.title.trim() && !fd.body.trim() && !hasItems && !isHabit) return;
    if (isHabit && !fd.title.trim()) return;
    const base = {
      type: fd.type,
      title: fd.title.trim(),
      body: (fd.type === 'todo' || fd.type === 'checklist' || isHabit) ? '' : fd.body.trim(),
      tags: fd.tagsList,
      color: fd.color,
      pinned: fd.pinned,
      privacy: fd.privacy,
      flag: fd.flag || undefined,
    };
    if (fd.type === 'todo') base.due = fd.due || undefined;
    if (fd.type === 'checklist') {
      base.items = fd.items.filter(i => i.text.trim()).map(i => ({ id: i.id, text: i.text.trim(), done: i.done }));
    }
    if (isHabit) {
      base.mode = fd.mode || 'check';
      base.cadence = fd.cadence === 'weekly' ? 'weekly' : 'daily';
      if (base.mode === 'timer') {
        base.targetUnit = ['s','m','h'].includes(fd.targetUnit) ? fd.targetUnit : 'm';
        if (fd.target !== '' && fd.target != null) {
          const n = Math.max(1, parseInt(fd.target, 10) || 1);
          base.target = n * (HABIT_UNIT_SECONDS[base.targetUnit] || 60);
        }
      } else if (base.mode === 'count' && fd.target !== '' && fd.target != null) {
        base.target = Math.max(1, parseInt(fd.target, 10) || 1);
      }
      base.history = {};
    }
    const id = 'bn' + Date.now().toString(36);
    const flushedPending = flushed.pendingTags;
    setState(s => {
      const bd2 = s.brainDump || { notes: [], tags: [], pinOrder: [] };
      const newNote = {
        ...base, id, createdAt: isoDate(TODAY),
        authorId: userId || null, source: 'app',
      };
      if (fd.type === 'todo') newNote.done = false;
      const notes = [newNote, ...(bd2.notes || [])];
      const pinOrder = base.pinned ? [...(bd2.pinOrder || []), id] : (bd2.pinOrder || []);
      // Inline the merge so flushed-but-not-yet-state pending tags land too.
      let tags = [...(bd2.tags || [])];
      flushedPending.forEach(p => {
        const i = tags.findIndex(t => t.name.toLowerCase() === p.name.toLowerCase());
        if (i < 0) { tags.push(p); return; }
        const merged = { ...tags[i] };
        (p.subs || []).forEach(sb => {
          if (!(merged.subs || []).includes(sb)) merged.subs = [...(merged.subs || []), sb];
        });
        tags[i] = merged;
      });
      return { ...s, brainDump: { ...bd2, notes, tags, pinOrder } };
    });
    setPendingTags([]);
    setPickerParent(null);
    setComposerOpen(false);
  };

  // ─── per-note mutators ─────────────────────────────
  const updateNote = (id, updater) => setState(s => {
    const bd2 = s.brainDump || { notes: [], tags: [], pinOrder: [] };
    return { ...s, brainDump: { ...bd2, notes: bd2.notes.map(n => n.id === id ? updater(n) : n) } };
  });
  const toggleDone = (id) => updateNote(id, n => ({ ...n, done: !n.done }));
  const toggleItem = (id, itemId) => updateNote(id, n => ({
    ...n, items: (n.items || []).map(it => it.id === itemId ? { ...it, done: !it.done } : it),
  }));
  const togglePin = (id) => setState(s => {
    const bd2 = s.brainDump || { notes: [], tags: [], pinOrder: [] };
    const note = bd2.notes.find(n => n.id === id);
    if (!note) return s;
    const willPin = !note.pinned;
    const notes = bd2.notes.map(n => n.id === id ? { ...n, pinned: willPin } : n);
    let pinOrder = (bd2.pinOrder || []).filter(x => x !== id);
    if (willPin) pinOrder = [...pinOrder, id];
    return { ...s, brainDump: { ...bd2, notes, pinOrder } };
  });
  // Habit actions — single entry point dispatched by mode.
  // check: toggle today between 0 and 1
  // inc:   bump today's count by 1
  // timer: toggle the running timer; on stop, accumulate elapsed seconds
  const handleHabitAction = (id, action) => setState(s => {
    const bd2 = s.brainDump || { notes: [], tags: [], pinOrder: [] };
    const today = isoDate(TODAY);
    const notes = bd2.notes.map(n => {
      if (n.id !== id) return n;
      const history = { ...(n.history || {}) };
      if (action === 'check') {
        if ((history[today] || 0) >= 1) delete history[today];
        else history[today] = 1;
        return { ...n, history };
      }
      if (action === 'inc') {
        history[today] = (history[today] || 0) + 1;
        return { ...n, history };
      }
      if (action === 'timer') {
        if (n.timerStartedAt) {
          const elapsed = Math.max(0, Math.floor((Date.now() - new Date(n.timerStartedAt).getTime()) / 1000));
          history[today] = (history[today] || 0) + elapsed;
          const { timerStartedAt, ...rest } = n;
          return { ...rest, history };
        }
        return { ...n, timerStartedAt: new Date().toISOString() };
      }
      return n;
    });
    return { ...s, brainDump: { ...bd2, notes } };
  });

  // Archive removes a card from the default wall but keeps it around to
  // restore later. Archiving auto-unpins — a pinned-but-hidden card is just
  // confusing. Unarchive does NOT restore the pin (the user can re-pin).
  const setArchived = (id, archived) => setState(s => {
    const bd2 = s.brainDump || { notes: [], tags: [], pinOrder: [] };
    const notes = bd2.notes.map(n => n.id === id
      ? { ...n, archived: archived || undefined, pinned: archived ? false : n.pinned }
      : n);
    const pinOrder = archived ? (bd2.pinOrder || []).filter(x => x !== id) : (bd2.pinOrder || []);
    return { ...s, brainDump: { ...bd2, notes, pinOrder } };
  });

  // Set or clear a card's flag. Pass null to clear. Used by the
  // right-click radial and the composer.
  const setFlag = (id, flag) => setState(s => {
    const bd2 = s.brainDump || { notes: [], tags: [], pinOrder: [] };
    const notes = bd2.notes.map(n => n.id === id
      ? { ...n, flag: flag || undefined }
      : n);
    return { ...s, brainDump: { ...bd2, notes } };
  });

  // Toggle an inline `[]` / `[x]` checkbox in a note body at the given
  // character index. The body string is the source of truth — flipping a
  // box just rewrites that occurrence, so reorders and edits to the text
  // move the checkbox naturally.
  const toggleBodyCheck = (id, charIndex) => setState(s => {
    const bd2 = s.brainDump || { notes: [], tags: [], pinOrder: [] };
    const notes = bd2.notes.map(n => n.id === id
      ? { ...n, body: toggleBrainBodyCheck(n.body || '', charIndex) }
      : n);
    return { ...s, brainDump: { ...bd2, notes } };
  });

  // ─── drag handlers ─────────────────────────────────
  const onDragStart = (id, e) => {
    setDragId(id);
    try { e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', id); } catch (_) {}
  };
  const onDragEnter = (id) => {
    if (!dragId || dragId === id) return;
    const baseline = localPinOrder || pinnedIdsOrdered;
    const ids = baseline.slice();
    const from = ids.indexOf(dragId), to = ids.indexOf(id);
    if (from < 0 || to < 0 || from === to) return;
    ids.splice(from, 1);
    ids.splice(to, 0, dragId);
    setLocalPinOrder(ids);
  };
  const onDragEnd = () => {
    if (localPinOrder) {
      const newOrder = localPinOrder;
      setState(s => {
        const bd2 = s.brainDump || { notes: [], tags: [], pinOrder: [] };
        return { ...s, brainDump: { ...bd2, pinOrder: newOrder } };
      });
      setLocalPinOrder(null);
    }
    setDragId(null);
  };

  // ─── filtering / sorting ───────────────────────────
  const matchesQuery = (n, q) => {
    if (!q) return true;
    q = q.toLowerCase().trim();
    if (q[0] === '#') {
      return brainExtractHashtags(n).some(h => { const t = h.toLowerCase(); return t === q || t.startsWith(q); });
    }
    const hay = [
      n.title, n.body,
      (n.tags || []).map(t => t.tag + ' ' + (t.sub || '')).join(' '),
      (n.items || []).map(i => i.text).join(' '),
      brainResolveDateTokensForSearch(n.title),
      brainResolveDateTokensForSearch(n.body),
    ].join(' ').toLowerCase();
    return hay.includes(q);
  };

  const filtered = visibleNotes.filter(n => {
    if (typeFilter !== 'all' && n.type !== typeFilter) return false;
    if (flagFilter !== 'all' && (n.flag || '') !== flagFilter) return false;
    if (privacyFilter === 'family' && n.privacy !== 'public') return false;
    if (privacyFilter === 'private' && n.privacy === 'public') return false;
    if (activeTag && !(n.tags || []).some(t => t.tag === activeTag)) return false;
    if (activeSub && !(n.tags || []).some(t => t.tag === activeTag && t.sub === activeSub)) return false;
    if (!matchesQuery(n, query)) return false;
    return true;
  });

  const sorted = (() => {
    const cmp = (a, b) => sort === 'oldest'
      ? (a.createdAt < b.createdAt ? -1 : 1)
      : (a.createdAt > b.createdAt ? -1 : 1);
    return [...filtered].sort(cmp);
  })();

  // pinned vs rest, with ordered pinned list
  const effectivePinOrder = localPinOrder || (bd.pinOrder || []);
  const pinned = sorted.filter(n => n.pinned);
  const rest = sorted.filter(n => !n.pinned);
  const pinIndex = (id) => { const i = effectivePinOrder.indexOf(id); return i < 0 ? 9999 : i; };
  const pinnedOrdered = pinned.slice().sort((a, b) => pinIndex(a.id) - pinIndex(b.id));
  const pinnedIdsOrdered = pinnedOrdered.map(n => n.id);

  // Tag visibility — a tag shows up for you only if a note you can see uses
  // it. The shared library (state.brainDump.tags) stays the source of truth
  // for {name, color, subs} so colors stay consistent across the household
  // and dedup works, but a "Therapy" tag attached only to your private notes
  // never appears in your spouse's rail. See the user-facing privacy note in
  // ../DECISIONS.md → "Brain dump privacy".
  const usedTagNames = new Set();
  visibleNotes.forEach(n => (n.tags || []).forEach(t => usedTagNames.add(t.tag)));
  const visibleBrainTags = brainTags.filter(t => usedTagNames.has(t.name));
  // The composer/picker also needs to see pendingTags (just-typed but
  // unsaved) plus any tag currently on the draft (so editing a note keeps
  // its existing tags visible in the picker even if no other visible note
  // uses them).
  const draftTagNames = new Set((draft.tagsList || []).map(t => t.tag));
  const composerTagSet = new Map();
  visibleBrainTags.forEach(t => composerTagSet.set(t.name.toLowerCase(), t));
  brainTags.forEach(t => { if (draftTagNames.has(t.name) && !composerTagSet.has(t.name.toLowerCase())) composerTagSet.set(t.name.toLowerCase(), t); });
  pendingTags.forEach(t => { if (!composerTagSet.has(t.name.toLowerCase())) composerTagSet.set(t.name.toLowerCase(), t); });
  const composerBrainTags = [...composerTagSet.values()];

  // Counts shown on parent tag chips — based on the privacy-filtered set.
  const todosOpen = visibleNotes.filter(n => n.type === 'todo' && !n.done).length;
  const metaLine = `${visibleNotes.length} ${visibleNotes.length === 1 ? 'note' : 'notes'} · ${visibleBrainTags.length} ${visibleBrainTags.length === 1 ? 'tag' : 'tags'} · ${todosOpen} to-do${todosOpen === 1 ? '' : 's'} open`;

  // ─── masonry distribution ──────────────────────────
  const [cols, setCols] = React.useState(3);
  const wallRef = React.useRef(null);
  React.useEffect(() => {
    const calc = () => {
      const w = wallRef.current ? wallRef.current.clientWidth : window.innerWidth - 272;
      let c = w > 900 ? 3 : w > 540 ? 2 : 1;
      setCols(Math.max(1, c));
    };
    calc();
    window.addEventListener('resize', calc);
    return () => window.removeEventListener('resize', calc);
  }, []);
  const distribute = (list, n) => {
    const c = Math.max(1, Math.min(n, list.length || 1));
    const out = Array.from({ length: c }, () => []);
    const hs = new Array(c).fill(0);
    list.forEach(note => {
      let mi = 0;
      for (let i = 1; i < c; i++) if (hs[i] < hs[mi]) mi = i;
      out[mi].push(note);
      hs[mi] += brainEstHeight(note, expanded[note.id]) + 18;
    });
    return out;
  };

  const onEdit = (n) => { setEditingBrainNote(n.id); setOpenModal('brain-note'); };
  const onTagClick = (tag, sub) => { setActiveTag(tag); setActiveSub(sub || null); };
  const toggleExpand = (id) => setExpanded(e => ({ ...e, [id]: !e[id] }));

  const renderCard = (n, dragWrap) => (
    <BrainCard key={n.id}
      note={n}
      tags={brainTags}
      query={query} setQuery={setQuery}
      expanded={!!expanded[n.id]}
      onEdit={() => onEdit(n)}
      onTogglePin={() => togglePin(n.id)}
      onToggleDone={() => toggleDone(n.id)}
      onToggleItem={(itemId) => toggleItem(n.id, itemId)}
      onToggleExpand={() => toggleExpand(n.id)}
      onTagClick={onTagClick}
      onHabitAction={handleHabitAction}
      onContextMenu={(e) => {
        e.preventDefault();
        e.stopPropagation();
        setRadial({ x: e.clientX, y: e.clientY, noteId: n.id });
      }}
      onToggleBodyCheck={(charIndex) => toggleBodyCheck(n.id, charIndex)}
      drag={dragWrap} />
  );

  // ─── sections ──────────────────────────────────────
  let sections = [];
  if (groupByTag) {
    const groups = {};
    sorted.forEach(n => {
      const t = (n.tags && n.tags[0] && n.tags[0].tag) || 'Unsorted';
      (groups[t] = groups[t] || []).push(n);
    });
    sections = Object.keys(groups).map(k => ({
      kind: 'group', label: k,
      color: brainTagColor(brainTags, k),
      count: groups[k].length,
      columns: distribute(groups[k], cols),
    }));
  } else {
    if (pinnedOrdered.length) {
      sections.push({ kind: 'pinned', items: pinnedOrdered });
      if (rest.length) sections.push({ kind: 'masonry', columns: distribute(rest, cols), divider: true });
    } else {
      sections.push({ kind: 'masonry', columns: distribute(rest, cols) });
    }
  }

  const isEmpty = sorted.length === 0;
  const emptyHint = query
    ? `No matches for "${query}."`
    : (activeTag || typeFilter !== 'all'
        ? 'Nothing under this filter yet.'
        : showArchived
          ? 'Nothing archived yet.'
          : 'Capture your first thought above.');

  // Parent tag rail counts on the unfiltered visible set so they don't
  // bounce around as the user clicks through filters.
  const tagCounts = (name) => visibleNotes.filter(n => (n.tags || []).some(t => t.tag === name)).length;

  // Subtag rail (shown when a parent tag is active)
  let subRail = [];
  if (activeTag) {
    const notesWithTag = visibleNotes.filter(n => (n.tags || []).some(t => t.tag === activeTag));
    const subs = [];
    notesWithTag.forEach(n => (n.tags || []).forEach(t => {
      if (t.tag === activeTag && t.sub && !subs.includes(t.sub)) subs.push(t.sub);
    }));
    if (subs.length) {
      subRail = [
        { name: `All ${activeTag}`, value: null, count: notesWithTag.length },
        ...subs.sort().map(sb => ({
          name: sb, value: sb,
          count: notesWithTag.filter(n => (n.tags || []).some(t => t.tag === activeTag && t.sub === sb)).length,
        })),
      ];
    }
  }
  const subRailColor = activeTag ? brainTagColor(brainTags, activeTag) : '#a4a4ad';

  return (
    <div className="brain-screen" data-screen-label="Commonplace">
      <div className="brain-meta mono">{metaLine}</div>

      {composerOpen ? (
        <div className="brain-composer">
          <div className="brain-composer-head">
            <span className="mono brain-modal-eyebrow">NEW</span>
            <div className="brain-composer-head-actions">
              <button className="brain-composer-expand"
                onClick={expandToModal}
                title="Open full editor — keep your text">
                <span aria-hidden="true">⤢</span>
                <span>Expand</span>
              </button>
              <button className="brain-modal-close" onClick={closeComposer}>×</button>
            </div>
          </div>
          <BrainNoteFields
            draft={draft}
            setDraft={setDraft}
            brainTags={composerBrainTags}
            commitTag={commitTag}
            pickerParent={pickerParent}
            setPickerParent={setPickerParent}
            bigBody={false}
            pendingTagNames={new Set(pendingTags.map(t => t.name.toLowerCase()))}
            actions={
              <>
                <button className="btn ghost" onClick={closeComposer}>Cancel</button>
                <button className="btn primary" onClick={submitDraft}
                  disabled={!(draft.title.trim() || draft.body.trim() || draft.items.some(i => i.text.trim()))}>
                  Add to dump
                </button>
              </>
            } />
        </div>
      ) : (
        <div className="brain-quick-capture" onClick={openComposer}>
          <span className="brain-quick-plus">+</span>
          <span className="brain-quick-prompt">Jot a note, idea, or to-do…</span>
          <span className="mono brain-quick-label">QUICK CAPTURE</span>
        </div>
      )}

      <div className="brain-toolbar">
        <div className="brain-search">
          <span className="brain-search-icon">⌕</span>
          <input value={query} onChange={e => setQuery(e.target.value)}
            placeholder="Search — text or #hashtag…" />
        </div>
        <div className="brain-type-filters">
          {[['all','All'],['note','Notes'],['idea','Ideas'],['todo','To-dos'],['checklist','Checklists'],['habit','Habits']].map(([k, l]) => (
            <button key={k}
              className={`brain-type-filter${typeFilter === k ? ' active' : ''}`}
              onClick={() => setTypeFilter(k)}>{l}</button>
          ))}
        </div>
        <div className="brain-toolbar-right">
          <select className="brain-sort" value={sort} onChange={e => setSort(e.target.value)}>
            <option value="newest">Newest first</option>
            <option value="oldest">Oldest first</option>
          </select>
          <button className={`brain-group-btn${groupByTag ? ' active' : ''}`}
                  onClick={() => setGroupByTag(g => !g)}>Group by tag</button>
          <button className={`brain-group-btn${showArchived ? ' active' : ''}`}
                  onClick={() => setShowArchived(v => !v)}
                  title={showArchived ? 'Back to active notes' : 'Show archived notes'}>
            {showArchived ? 'Archived' : 'Show archived'}
            {archivedCount > 0 && <span className="brain-archived-count">{archivedCount}</span>}
          </button>
        </div>
      </div>

      <div className="brain-flag-filter">
        <button className={`brain-flag-chip${flagFilter === 'all' ? ' active' : ''}`}
                onClick={() => setFlagFilter('all')}>All flags</button>
        {BRAIN_FLAGS.map(f => (
          <button key={f.id}
            className={`brain-flag-chip brain-flag-${f.id}${flagFilter === f.id ? ' active' : ''}`}
            onClick={() => setFlagFilter(flagFilter === f.id ? 'all' : f.id)}
            title={f.label} aria-label={`Filter · ${f.label} flag`}>
            <svg viewBox="0 0 10 12" width="10" height="12" aria-hidden="true">
              <path d="M1.6 0.9 V11.1 M1.6 1.2 H8.1 L6.6 3.7 L8.1 6.2 H1.6"
                    fill="currentColor" stroke="currentColor" strokeWidth="0.6"
                    strokeLinejoin="round" />
            </svg>
            <span className="brain-rail-count">{visibleNotes.filter(n => (n.flag || '') === f.id).length}</span>
          </button>
        ))}
        <span className="brain-filter-divider" aria-hidden="true" />
        {[
          { id: 'all',     label: 'All' },
          { id: 'family',  label: 'Family',  fillClass: 'is-family' },
          { id: 'private', label: 'Private', fillClass: 'is-private' },
        ].map(p => {
          const count = p.id === 'all'
            ? visibleNotes.length
            : visibleNotes.filter(n => (p.id === 'family' ? n.privacy === 'public' : n.privacy !== 'public')).length;
          return (
            <button key={p.id}
              className={`brain-flag-chip brain-priv-chip${privacyFilter === p.id ? ' active' : ''}${p.id !== 'all' ? ' priv-' + p.id : ''}`}
              onClick={() => setPrivacyFilter(privacyFilter === p.id && p.id !== 'all' ? 'all' : p.id)}
              title={p.id === 'all' ? 'All notes' : `${p.label} notes only`}>
              {p.fillClass && <span className={`brain-priv-fill ${p.fillClass}`} aria-hidden="true" />}
              {p.label}
              <span className="brain-rail-count">{count}</span>
            </button>
          );
        })}
      </div>

      <div className="brain-tag-rail">
        <button className={`brain-rail-chip${!activeTag ? ' active' : ''}`}
                onClick={() => { setActiveTag(null); setActiveSub(null); }}>
          All
          <span className="brain-rail-count">{visibleNotes.length}</span>
        </button>
        {brainTags.length > 0 && (
          <button className="brain-rail-manage"
                  onClick={() => setTagManagerOpen(true)}
                  title="Manage tags">Manage</button>
        )}
        {visibleBrainTags.map(t => (
          <button key={t.name}
                  className={`brain-rail-chip${activeTag === t.name ? ' active' : ''}`}
                  onClick={() => { setActiveTag(activeTag === t.name ? null : t.name); setActiveSub(null); }}>
            <span className="brain-card-tag-dot" style={{ background: t.color }} />
            {t.name}
            <span className="brain-rail-count">{tagCounts(t.name)}</span>
          </button>
        ))}
      </div>

      {subRail.length > 0 && (
        <div className="brain-sub-rail" style={{ '--sub-color': subRailColor }}>
          <span className="brain-sub-rail-label mono">SUBTAG</span>
          {subRail.map(c => (
            <button key={c.name}
              className={`brain-sub-rail-chip${activeSub === c.value ? ' active' : ''}`}
              onClick={() => setActiveSub(activeSub === c.value ? null : c.value)}>
              {c.name}
              <span className="brain-rail-count">{c.count}</span>
            </button>
          ))}
        </div>
      )}

      <div ref={wallRef} className="brain-wall">
        {sections.map((sec, idx) => {
          if (sec.kind === 'pinned') {
            // Pinned uses the same height-balanced masonry layout as the
            // wall so a heavy pin set doesn't leave an orphan trailing
            // row. Drag-reorder still operates on the linear pinOrder
            // array — moving a card to another column rewrites that
            // order top-down through the new column layout.
            const pinnedColumns = distribute(sec.items, cols);
            return (
              <div key={idx} className="brain-section">
                <div className="brain-pinned-head mono">
                  <span className="brain-pin-diamond on" />
                  PINNED
                  <span className="brain-pinned-hint">· drag to reorder</span>
                </div>
                <div className="brain-masonry">
                  {pinnedColumns.map((col, ci) => (
                    <div key={ci} className="brain-masonry-col">
                      {col.map(n => renderCard(n, {
                        dragging: dragId === n.id,
                        onDragStart: (e) => onDragStart(n.id, e),
                        onDragEnter: (e) => { e.preventDefault(); onDragEnter(n.id); },
                        onDragEnd: onDragEnd,
                      }))}
                    </div>
                  ))}
                </div>
              </div>
            );
          }
          if (sec.kind === 'masonry') {
            return (
              <div key={idx} className="brain-section">
                {sec.divider && <div className="brain-divider" />}
                <div className="brain-masonry">
                  {sec.columns.map((col, ci) => (
                    <div key={ci} className="brain-masonry-col">
                      {col.map(n => renderCard(n))}
                    </div>
                  ))}
                </div>
              </div>
            );
          }
          // group
          return (
            <div key={idx} className="brain-section">
              <div className="brain-group-head">
                <span className="brain-card-tag-dot" style={{ background: sec.color }} />
                <span className="brain-group-label">{sec.label}</span>
                <span className="mono brain-group-count">{sec.count} {sec.count === 1 ? 'note' : 'notes'}</span>
              </div>
              <div className="brain-masonry">
                {sec.columns.map((col, ci) => (
                  <div key={ci} className="brain-masonry-col">
                    {col.map(n => renderCard(n))}
                  </div>
                ))}
              </div>
            </div>
          );
        })}
        {isEmpty && (
          <div className="brain-empty">
            <div className="brain-empty-title">Nothing here yet</div>
            <div className="brain-empty-hint">{emptyHint}</div>
          </div>
        )}
      </div>
      {tagManagerOpen && (
        <BrainTagManagerModal state={state} setState={setState}
          onClose={() => setTagManagerOpen(false)} />
      )}
      {radial && (() => {
        const target = (state.brainDump?.notes || []).find(n => n.id === radial.noteId);
        return (
          <BrainCardRadial
            x={radial.x} y={radial.y}
            currentFlag={target?.flag || null}
            onPickFlag={(flag) => { setFlag(radial.noteId, flag); setRadial(null); }}
            onArchive={() => { setArchived(radial.noteId, true); setRadial(null); }}
            onClose={() => setRadial(null)} />
        );
      })()}
    </div>
  );
}

Object.assign(window, { BrainDumpScreen });
