/* global React, Icon, Badge, IconBtn, useLiveQuery, useReceiveFrom, SendToButton, relTime, db */
const { useState, useEffect, useRef, useMemo, useCallback } = React;

// ============================================================
// Diff Viewer (line-based, LCS)
// ============================================================
function lcsDiff(a, b) {
  // Myers-style LCS via dynamic programming, returns ops:
  //   { kind: 'eq' | 'add' | 'del', text }
  const n = a.length, m = b.length;
  if (n === 0) return b.map(t => ({ kind: 'add', text: t }));
  if (m === 0) return a.map(t => ({ kind: 'del', text: t }));

  // Use rolling arrays to keep memory reasonable
  const dp = Array.from({ length: n + 1 }, () => new Uint32Array(m + 1));
  for (let i = 1; i <= n; i++) {
    for (let j = 1; j <= m; j++) {
      if (a[i-1] === b[j-1]) dp[i][j] = dp[i-1][j-1] + 1;
      else dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
    }
  }
  const ops = [];
  let i = n, j = m;
  while (i > 0 && j > 0) {
    if (a[i-1] === b[j-1]) { ops.push({ kind: 'eq',  text: a[i-1] }); i--; j--; }
    else if (dp[i-1][j] >= dp[i][j-1]) { ops.push({ kind: 'del', text: a[i-1] }); i--; }
    else { ops.push({ kind: 'add', text: b[j-1] }); j--; }
  }
  while (i > 0) { ops.push({ kind: 'del', text: a[--i] }); }
  while (j > 0) { ops.push({ kind: 'add', text: b[--j] }); }
  return ops.reverse();
}

function DiffTool() {
  const [left, setLeft] = useState('');
  const [right, setRight] = useState('');
  const [activeSide, setActiveSide] = useState(null); // 'left' | 'right'

  useReceiveFrom('diff', (payload) => {
    if (payload?.text) {
      // Fill whichever side is empty, prefer left
      setLeft(prev => {
        if (!prev) return payload.text;
        setRight(payload.text);
        return prev;
      });
    }
  });

  const diff = useMemo(() => {
    if (!left && !right) return [];
    return lcsDiff(left.split('\n'), right.split('\n'));
  }, [left, right]);

  const stats = useMemo(() => {
    let added = 0, removed = 0, equal = 0;
    for (const o of diff) {
      if (o.kind === 'add') added++;
      else if (o.kind === 'del') removed++;
      else equal++;
    }
    return { added, removed, equal };
  }, [diff]);

  // Build aligned rows for side-by-side display
  const rows = useMemo(() => {
    const out = [];
    let lNo = 0, rNo = 0;
    let buf = []; // pending add/del for alignment
    const flush = () => {
      // Pair up dels and adds line by line; leftover go alone
      const dels = buf.filter(o => o.kind === 'del');
      const adds = buf.filter(o => o.kind === 'add');
      const max = Math.max(dels.length, adds.length);
      for (let k = 0; k < max; k++) {
        const d = dels[k], a = adds[k];
        out.push({
          left:  d ? { no: ++lNo, text: d.text, kind: 'del' } : { no: null, text: '', kind: 'pad' },
          right: a ? { no: ++rNo, text: a.text, kind: 'add' } : { no: null, text: '', kind: 'pad' },
        });
      }
      buf = [];
    };
    for (const o of diff) {
      if (o.kind === 'eq') {
        flush();
        out.push({
          left:  { no: ++lNo, text: o.text, kind: 'eq' },
          right: { no: ++rNo, text: o.text, kind: 'eq' },
        });
      } else {
        buf.push(o);
      }
    }
    flush();
    return out;
  }, [diff]);

  const swap = () => { setLeft(right); setRight(left); };
  const clear = () => { setLeft(''); setRight(''); };

  return (
    <div className="tool diff-tool">
      <div className="diff-toolbar">
        <Badge tone="success">+{stats.added}</Badge>
        <Badge tone="high">−{stats.removed}</Badge>
        <Badge tone="neutral">={stats.equal}</Badge>
        <div style={{ flex: 1 }} />
        <button className="lh-btn" onClick={swap}><Icon name="swap_horiz" />Swap</button>
        <button className="lh-btn danger" onClick={clear}><Icon name="delete_sweep" />Clear</button>
      </div>

      <div className="diff-pair">
        <div className="diff-side">
          <div className="diff-side-head">
            <Icon name="text_snippet" />
            <span>Original</span>
            <span className="count">{left.split('\n').filter(Boolean).length} lines</span>
            <div style={{ flex: 1 }} />
            <SendToButton payload={{ text: left, title: 'Diff left' }} compact />
          </div>
          <textarea
            className="diff-input"
            value={left}
            onChange={e => setLeft(e.target.value)}
            placeholder="Paste original text…"
            spellCheck={false}
          />
        </div>
        <div className="diff-side">
          <div className="diff-side-head">
            <Icon name="edit_note" />
            <span>Modified</span>
            <span className="count">{right.split('\n').filter(Boolean).length} lines</span>
            <div style={{ flex: 1 }} />
            <SendToButton payload={{ text: right, title: 'Diff right' }} compact />
          </div>
          <textarea
            className="diff-input"
            value={right}
            onChange={e => setRight(e.target.value)}
            placeholder="Paste modified text…"
            spellCheck={false}
          />
        </div>
      </div>

      <div className="diff-result">
        <div className="diff-result-head">
          <Icon name="difference" />
          <span>Side-by-side</span>
        </div>
        <div className="diff-rows">
          {rows.length === 0 ? (
            <div className="lh-empty"><Icon name="difference" /><div className="title">Paste two texts to compare</div></div>
          ) : rows.map((r, i) => (
            <div className="diff-row" key={i}>
              <div className={`diff-cell ${r.left.kind}`}>
                <span className="diff-no">{r.left.no || ''}</span>
                <span className="diff-mark">{r.left.kind === 'del' ? '−' : ' '}</span>
                <span className="diff-text">{r.left.text}</span>
              </div>
              <div className={`diff-cell ${r.right.kind}`}>
                <span className="diff-no">{r.right.no || ''}</span>
                <span className="diff-mark">{r.right.kind === 'add' ? '+' : ' '}</span>
                <span className="diff-text">{r.right.text}</span>
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

// ============================================================
// Password Generator
// ============================================================
const PASS_SETS = {
  upper: 'ABCDEFGHJKLMNPQRSTUVWXYZ',          // no I, O
  lower: 'abcdefghijkmnopqrstuvwxyz',          // no l
  digits: '23456789',                          // no 0, 1
  symbols: '!@#$%^&*-_=+[]{}<>?/',
};

function genPassword({ length, upper, lower, digits, symbols }) {
  let pool = '';
  if (upper) pool += PASS_SETS.upper;
  if (lower) pool += PASS_SETS.lower;
  if (digits) pool += PASS_SETS.digits;
  if (symbols) pool += PASS_SETS.symbols;
  if (!pool) return '';
  const buf = new Uint32Array(length);
  crypto.getRandomValues(buf);
  let out = '';
  for (let i = 0; i < length; i++) out += pool[buf[i] % pool.length];
  return out;
}

const WORDS = ['atlas','bravo','cinder','delta','ember','flux','glow','harbor','ion','jade','kite','lumen','moss','nexus','orbit','pearl','quark','river','spark','tide','umbra','vista','wave','xenon','yoke','zephyr','amber','quartz','meadow','tundra','grove','cobalt','prism','helix','astral','plasma','beacon','thicket'];

function genPassphrase(words = 4, sep = '-') {
  const buf = new Uint32Array(words);
  crypto.getRandomValues(buf);
  return Array.from({ length: words }, (_, i) => WORDS[buf[i] % WORDS.length]).join(sep);
}

function passwordEntropy(pw, opts) {
  let pool = 0;
  if (opts.upper) pool += PASS_SETS.upper.length;
  if (opts.lower) pool += PASS_SETS.lower.length;
  if (opts.digits) pool += PASS_SETS.digits.length;
  if (opts.symbols) pool += PASS_SETS.symbols.length;
  if (!pool || !pw) return 0;
  return Math.round(pw.length * Math.log2(pool));
}

function strengthLabel(bits) {
  if (bits < 40) return { label: 'Weak', tone: 'high' };
  if (bits < 60) return { label: 'Fair', tone: 'med' };
  if (bits < 80) return { label: 'Strong', tone: 'success' };
  return { label: 'Very strong', tone: 'success' };
}

function PasswordTool() {
  const [length, setLength] = useState(20);
  const [upper, setUpper] = useState(true);
  const [lower, setLower] = useState(true);
  const [digits, setDigits] = useState(true);
  const [symbols, setSymbols] = useState(true);
  const [mode, setMode] = useState('chars'); // chars | phrase
  const [words, setWords] = useState(4);
  const [sep, setSep] = useState('-');
  const [output, setOutput] = useState('');
  const [savedLabel, setSavedLabel] = useState('');

  const opts = { length, upper, lower, digits, symbols };
  const generate = useCallback(() => {
    if (mode === 'chars') setOutput(genPassword(opts));
    else setOutput(genPassphrase(words, sep));
  }, [mode, length, upper, lower, digits, symbols, words, sep]);

  useEffect(() => { generate(); }, [generate]);

  const items = useLiveQuery('passwords', { sort: ['updatedAt', 'desc'] });

  const saveCurrent = () => {
    if (!output) return;
    db.passwords.put({ label: savedLabel || 'Untitled', value: output, mode });
    setSavedLabel('');
  };

  const copy = () => navigator.clipboard?.writeText(output);

  const bits = mode === 'chars' ? passwordEntropy(output, opts) : Math.round(words * Math.log2(WORDS.length));
  const strength = strengthLabel(bits);

  return (
    <div className="tool pwd-tool">
      <div className="pwd-grid">
        <div className="pwd-main">
          <div className="pwd-output">
            <div className="pwd-output-text" data-empty={!output ? 'yes' : 'no'}>
              {output || 'Press Generate'}
            </div>
            <div className="pwd-output-actions">
              <IconBtn name="refresh" title="Generate new" onClick={generate} />
              <IconBtn name="content_copy" title="Copy" onClick={copy} />
            </div>
          </div>
          <div className="pwd-strength">
            <div className="pwd-strength-bar">
              <div
                className="pwd-strength-fill"
                style={{
                  width: `${Math.min(100, (bits / 128) * 100)}%`,
                  background: strength.tone === 'high' ? 'var(--color-error)' :
                             strength.tone === 'med' ? 'var(--color-warning)' : 'var(--color-success)'
                }}
              />
            </div>
            <Badge tone={strength.tone}>{strength.label}</Badge>
            <span className="pwd-bits">{bits} bits of entropy</span>
          </div>

          <div className="pwd-section">
            <div className="pwd-section-head">
              <button
                className={`lh-chip ${mode === 'chars' ? 'is-active' : ''}`}
                onClick={() => setMode('chars')}
              ><Icon name="password" />Random</button>
              <button
                className={`lh-chip ${mode === 'phrase' ? 'is-active' : ''}`}
                onClick={() => setMode('phrase')}
              ><Icon name="format_quote" />Passphrase</button>
            </div>

            {mode === 'chars' ? (
              <>
                <div className="pwd-row">
                  <label>Length</label>
                  <input type="range" min="8" max="64" value={length} onChange={e => setLength(parseInt(e.target.value, 10))} className="pwd-slider" />
                  <span className="pwd-num">{length}</span>
                </div>
                <div className="pwd-toggles">
                  <Toggle label="Uppercase (A-Z)" sample="ABCDE" on={upper} onChange={setUpper} />
                  <Toggle label="Lowercase (a-z)" sample="abcde" on={lower} onChange={setLower} />
                  <Toggle label="Digits (2-9)" sample="23456" on={digits} onChange={setDigits} />
                  <Toggle label="Symbols" sample="!@#$%" on={symbols} onChange={setSymbols} />
                </div>
              </>
            ) : (
              <>
                <div className="pwd-row">
                  <label>Words</label>
                  <input type="range" min="2" max="8" value={words} onChange={e => setWords(parseInt(e.target.value, 10))} className="pwd-slider" />
                  <span className="pwd-num">{words}</span>
                </div>
                <div className="pwd-row">
                  <label>Separator</label>
                  <div className="pwd-seps">
                    {['-', '.', '_', ' ', ''].map(s => (
                      <button
                        key={s || 'none'}
                        className={`lh-chip ${sep === s ? 'is-active' : ''}`}
                        onClick={() => setSep(s)}
                      >{s || 'none'}</button>
                    ))}
                  </div>
                </div>
              </>
            )}
          </div>

          <div className="pwd-save">
            <input
              className="lh-input"
              placeholder="Label for saving (e.g. Gmail, AWS root)"
              value={savedLabel}
              onChange={e => setSavedLabel(e.target.value)}
            />
            <button className="lh-btn primary" onClick={saveCurrent} disabled={!output}>
              <Icon name="save" />Save to vault
            </button>
            <SendToButton payload={{ text: output, title: savedLabel || 'Password' }} compact />
          </div>
        </div>

        <div className="pwd-vault">
          <div className="pwd-vault-head">
            <Icon name="lock" />
            <span>Local vault</span>
            <span className="count">{(items || []).length}</span>
          </div>
          <div className="pwd-vault-note">
            Stored unencrypted in this browser. Use a real password manager for important secrets.
          </div>
          <div className="pwd-vault-list">
            {(items || []).length === 0 ? (
              <div className="lh-empty" style={{ padding: '24px' }}>
                <Icon name="lock_open" />
                <div className="sub">No saved entries yet.</div>
              </div>
            ) : (items || []).map(it => <VaultRow key={it.id} item={it} />)}
          </div>
        </div>
      </div>
    </div>
  );
}

function Toggle({ label, sample, on, onChange }) {
  return (
    <button className={`pwd-toggle ${on ? 'is-on' : ''}`} onClick={() => onChange(!on)}>
      <span className="pwd-toggle-dot" />
      <span className="pwd-toggle-label">{label}</span>
      <span className="pwd-toggle-sample">{sample}</span>
    </button>
  );
}

function VaultRow({ item }) {
  const [shown, setShown] = useState(false);
  return (
    <div className="pwd-vault-row">
      <div className="pwd-vault-meta">
        <div className="pwd-vault-label">{item.label}</div>
        <div className="pwd-vault-time">{relTime(item.updatedAt)}</div>
        <PinButton type="password" id={item.id} className="pwd-vault-pin" />
      </div>
      <div className="pwd-vault-value">
        {shown ? item.value : '•'.repeat(Math.min(20, item.value.length))}
      </div>
      <IconBtn name={shown ? 'visibility_off' : 'visibility'} title="Show" onClick={() => setShown(s => !s)} />
      <IconBtn name="content_copy" title="Copy" onClick={() => navigator.clipboard?.writeText(item.value)} />
      <IconBtn name="delete" tone="danger" title="Delete" onClick={() => db.passwords.delete(item.id)} />
    </div>
  );
}

// ============================================================
// Bookmarks — grid / list layouts, nested folder tree
// ============================================================

// Build a tree from flat folder paths (slash-delimited):
//   "Work/Projects" → Work > Projects
function buildFolderTree(items, extraPaths = []) {
  const root = { name: '', path: '', children: new Map(), count: 0 };
  for (const b of items) {
    root.count++;
    if (!b.folder) continue;
    const parts = b.folder.split('/').map(p => p.trim()).filter(Boolean);
    let node = root;
    let prefix = '';
    for (const part of parts) {
      prefix = prefix ? prefix + '/' + part : part;
      if (!node.children.has(part)) {
        node.children.set(part, { name: part, path: prefix, children: new Map(), count: 0 });
      }
      node = node.children.get(part);
      node.count++;
    }
  }
  // Add empty user-created folders without incrementing counts
  for (const path of extraPaths) {
    const parts = path.split('/').map(p => p.trim()).filter(Boolean);
    let node = root;
    let prefix = '';
    for (const part of parts) {
      prefix = prefix ? prefix + '/' + part : part;
      if (!node.children.has(part)) {
        node.children.set(part, { name: part, path: prefix, children: new Map(), count: 0 });
      }
      node = node.children.get(part);
    }
  }
  return root;
}

function flattenTree(node, depth = 0, list = []) {
  for (const child of node.children.values()) {
    list.push({ ...child, depth });
    flattenTree(child, depth + 1, list);
  }
  return list;
}

function BookmarkTool() {
  const items = useLiveQuery('bookmarks', { sort: ['updatedAt', 'desc'] });
  const [folder, setFolder] = useState(null);     // null = all, '' = uncategorized, or a path
  const [search, setSearch] = useState('');
  const [adding, setAdding] = useState(false);
  const [draft, setDraft] = useState({ title: '', url: '', folder: '', tags: '' });
  const [layout, setLayout] = useState('grid');   // 'grid' | 'list' | 'compact'
  const [expandedFolders, setExpandedFolders] = useState(new Set());
  const [selectedTags, setSelectedTags] = useState(new Set());
  const [extraFolders, setExtraFolders] = useState([]); // user-created empty folders
  const [treeCollapsed, toggleTree] = useToolSidebar('bookmark');

  // Persist layout preference + load extra folders
  useEffect(() => {
    db.kv.get('bookmarkLayout').then(rec => {
      if (rec?.v) setLayout(rec.v);
    });
    db.kv.get('bookmarkFolders').then(rec => {
      if (Array.isArray(rec?.v)) setExtraFolders(rec.v);
    });
  }, []);
  const setLayoutPersist = (v) => {
    setLayout(v);
    db.kv.put({ k: 'bookmarkLayout', v });
  };
  const persistExtraFolders = (list) => {
    setExtraFolders(list);
    db.kv.put({ k: 'bookmarkFolders', v: list });
  };

  // Folder tree (bookmark-derived folders + user-created empty ones)
  const tree = useMemo(() => buildFolderTree(items || [], extraFolders), [items, extraFolders]);
  const treeRows = useMemo(() => flattenTree(tree), [tree]);
  const uncategorizedCount = (items || []).filter(b => !b.folder).length;

  // Selected filter — folder match includes descendants (path startsWith)
  const matchFolder = (b) => {
    if (folder === null) return true;
    if (folder === '') return !b.folder;
    if (!b.folder) return false;
    return b.folder === folder || b.folder.startsWith(folder + '/');
  };

  // Tags with counts (across the currently-folder-scoped items so counts feel right)
  const tagStats = useMemo(() => {
    const map = new Map();
    for (const b of (items || [])) {
      if (!matchFolder(b)) continue;
      for (const t of (b.tags || [])) {
        map.set(t, (map.get(t) || 0) + 1);
      }
    }
    return [...map.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [items, folder]);

  const toggleTag = (tag) => {
    setSelectedTags(prev => {
      const next = new Set(prev);
      if (next.has(tag)) next.delete(tag); else next.add(tag);
      return next;
    });
  };

  const filtered = useMemo(() => {
    if (!items) return [];
    return items.filter(b => {
      if (!matchFolder(b)) return false;
      if (selectedTags.size > 0) {
        const bt = new Set(b.tags || []);
        // AND across selected tags — item must have all selected tags
        for (const t of selectedTags) if (!bt.has(t)) return false;
      }
      if (search) {
        const q = search.toLowerCase();
        if (!b.title.toLowerCase().includes(q) &&
            !b.url.toLowerCase().includes(q) &&
            !(b.tags || []).some(t => t.toLowerCase().includes(q))) return false;
      }
      return true;
    });
  }, [items, folder, search, selectedTags]);

  const save = () => {
    if (!draft.url) return;
    let url = draft.url.trim();
    if (!/^https?:\/\//i.test(url)) url = 'https://' + url;
    db.bookmarks.put({
      title: draft.title || url.replace(/^https?:\/\//, '').replace(/\/$/, ''),
      url,
      folder: draft.folder.trim().replace(/^\/+|\/+$/g, '') || null,
      tags: draft.tags.split(',').map(s => s.trim()).filter(Boolean),
    });
    setDraft({ title: '', url: '', folder: '', tags: '' });
    setAdding(false);
  };

  const removeBookmark = (id) => db.bookmarks.delete(id);

  const faviconFor = (url) => {
    try {
      const u = new URL(url);
      return `https://www.google.com/s2/favicons?domain=${u.hostname}&sz=64`;
    } catch { return ''; }
  };

  // Flat list of existing folder paths (for the datalist)
  const allFolderPaths = useMemo(
    () => [...new Set((items || []).map(b => b.folder).filter(Boolean))].sort(),
    [items]
  );

  const toggleExpand = (path) => {
    setExpandedFolders(prev => {
      const next = new Set(prev);
      if (next.has(path)) next.delete(path); else next.add(path);
      return next;
    });
  };

  // ---- Folder CRUD ----
  const createFolder = async (parentPath) => {
    const name = await window.lhDialog.prompt({
      title: parentPath ? 'New child folder' : 'New folder',
      message: parentPath ? `Created under "${parentPath}"` : 'Top-level folder',
      placeholder: 'Folder name',
      icon: 'create_new_folder',
      confirmLabel: 'Create',
      maxLength: 60,
    });
    if (!name) return;
    const clean = name.trim().replace(/^\/+|\/+$/g, '').replace(/\//g, '-');
    if (!clean) return;
    const fullPath = parentPath ? `${parentPath}/${clean}` : clean;
    const allPaths = new Set([
      ...extraFolders,
      ...(items || []).filter(b => b.folder).map(b => b.folder),
    ]);
    if (allPaths.has(fullPath)) {
      setFolder(fullPath);
      if (parentPath) setExpandedFolders(prev => new Set(prev).add(parentPath));
      return;
    }
    persistExtraFolders([...extraFolders, fullPath]);
    if (parentPath) setExpandedFolders(prev => new Set(prev).add(parentPath));
    setFolder(fullPath);
  };

  const renameFolder = async (path) => {
    const parts = path.split('/');
    const oldName = parts.pop();
    const parent = parts.join('/');
    const newName = await window.lhDialog.prompt({
      title: 'Rename folder',
      message: `Currently "${path}"`,
      placeholder: 'New name',
      initial: oldName,
      icon: 'drive_file_rename_outline',
      confirmLabel: 'Rename',
      maxLength: 60,
    });
    if (!newName) return;
    const cleanName = newName.trim().replace(/^\/+|\/+$/g, '').replace(/\//g, '-');
    if (!cleanName || cleanName === oldName) return;
    const newPath = parent ? `${parent}/${cleanName}` : cleanName;

    (items || []).forEach(b => {
      if (b.folder === path) db.bookmarks.put({ ...b, folder: newPath });
      else if (b.folder && b.folder.startsWith(path + '/')) {
        db.bookmarks.put({ ...b, folder: newPath + b.folder.slice(path.length) });
      }
    });
    persistExtraFolders(extraFolders.map(p => {
      if (p === path) return newPath;
      if (p.startsWith(path + '/')) return newPath + p.slice(path.length);
      return p;
    }));
    if (folder === path) setFolder(newPath);
    else if (folder && folder.startsWith(path + '/')) setFolder(newPath + folder.slice(path.length));
  };

  const deleteFolder = async (path) => {
    const matching = (items || []).filter(b => b.folder === path || (b.folder && b.folder.startsWith(path + '/')));
    const ok = await window.lhDialog.confirm({
      title: matching.length ? `Delete "${path}"?` : `Delete empty folder?`,
      message: matching.length
        ? `This will permanently remove the folder and ${matching.length} bookmark${matching.length === 1 ? '' : 's'} inside.`
        : `"${path}" is empty. Remove it from the tree?`,
      confirmLabel: 'Delete',
      danger: true,
      icon: 'delete_forever',
    });
    if (!ok) return;
    matching.forEach(b => db.bookmarks.delete(b.id));
    persistExtraFolders(extraFolders.filter(p => p !== path && !p.startsWith(path + '/')));
    if (folder === path || folder?.startsWith(path + '/')) setFolder(null);
  };

  // Helper: which top-level folder is the selected one in?
  const isVisible = (row) => {
    // Show this row if all its ancestors are expanded
    const parts = row.path.split('/');
    for (let i = 1; i < parts.length; i++) {
      const ancestor = parts.slice(0, i).join('/');
      if (!expandedFolders.has(ancestor)) return false;
    }
    return true;
  };

  return (
    <div className="tool bm-tool">
      <div className="bm-toolbar">
        <button className="lh-btn primary" onClick={() => setAdding(a => !a)}>
          <Icon name="add" />New bookmark
        </button>
        <div className="bm-search">
          <Icon name="search" />
          <input placeholder="Search bookmarks…" value={search} onChange={e => setSearch(e.target.value)} />
        </div>
        <div style={{ flex: 1 }} />
        <span className="bm-count">{filtered.length} of {(items || []).length}</span>
        <div className="bm-layout-pick">
          <button
            className={`bm-layout-btn ${layout === 'grid' ? 'is-active' : ''}`}
            onClick={() => setLayoutPersist('grid')}
            title="Grid"
          ><Icon name="grid_view" /></button>
          <button
            className={`bm-layout-btn ${layout === 'list' ? 'is-active' : ''}`}
            onClick={() => setLayoutPersist('list')}
            title="List"
          ><Icon name="view_list" /></button>
          <button
            className={`bm-layout-btn ${layout === 'compact' ? 'is-active' : ''}`}
            onClick={() => setLayoutPersist('compact')}
            title="Compact"
          ><Icon name="view_headline" /></button>
        </div>
      </div>

      {adding && (
        <div className="bm-add">
          <input className="lh-input" placeholder="URL — paste or type" value={draft.url} onChange={e => setDraft(d => ({ ...d, url: e.target.value }))} autoFocus />
          <input className="lh-input" placeholder="Title (optional)" value={draft.title} onChange={e => setDraft(d => ({ ...d, title: e.target.value }))} />
          <input
            className="lh-input"
            placeholder="Folder (use / for nested, e.g. Work/AI)"
            value={draft.folder}
            onChange={e => setDraft(d => ({ ...d, folder: e.target.value }))}
            list="bm-folders"
          />
          <datalist id="bm-folders">{allFolderPaths.map(f => <option key={f} value={f} />)}</datalist>
          <input className="lh-input" placeholder="tags, comma" value={draft.tags} onChange={e => setDraft(d => ({ ...d, tags: e.target.value }))} />
          <button className="lh-btn primary" onClick={save} disabled={!draft.url}><Icon name="check" />Save</button>
          <button className="lh-btn ghost" onClick={() => setAdding(false)}><Icon name="close" /></button>
        </div>
      )}

      <div className={`bm-body ${treeCollapsed ? 'is-tree-collapsed' : ''}`}>
        {/* Folder tree pane */}
        <aside className="bm-tree">
          <div className="tool-list-collapsed-rail" onClick={toggleTree} title="Expand folders">
            <Icon name="chevron_right" />
          </div>
          <div className="bm-tree-head">
            <span>Folders</span>
            <button
              className="bm-tree-add"
              title="New folder"
              onClick={() => createFolder(null)}
            >
              <Icon name="create_new_folder" />
            </button>
            <ToolSidebarToggle collapsed={treeCollapsed} onToggle={toggleTree} />
          </div>
          <button
            className={`bm-tree-row ${folder === null ? 'is-active' : ''}`}
            onClick={() => setFolder(null)}
          >
            <span className="bm-tree-indent" style={{ width: 0 }} />
            <span className="bm-tree-toggle bm-tree-toggle-empty" />
            <Icon name="all_inbox" />
            <span className="bm-tree-name">All bookmarks</span>
            <span className="bm-tree-count">{(items || []).length}</span>
          </button>
          {uncategorizedCount > 0 && (
            <button
              className={`bm-tree-row ${folder === '' ? 'is-active' : ''}`}
              onClick={() => setFolder('')}
            >
              <span className="bm-tree-indent" style={{ width: 0 }} />
              <span className="bm-tree-toggle bm-tree-toggle-empty" />
              <Icon name="inbox" />
              <span className="bm-tree-name">Uncategorized</span>
              <span className="bm-tree-count">{uncategorizedCount}</span>
            </button>
          )}
          {treeRows.filter(isVisible).map(row => {
            const hasChildren = row.children.size > 0;
            const isOpen = expandedFolders.has(row.path);
            return (
              <div
                key={row.path}
                className={`bm-tree-row ${folder === row.path ? 'is-active' : ''}`}
                onClick={() => setFolder(row.path)}
                onDoubleClick={() => { if (hasChildren) toggleExpand(row.path); }}
                role="button"
                tabIndex={0}
              >
                <span className="bm-tree-indent" style={{ width: row.depth * 14 }} />
                {hasChildren ? (
                  <span
                    className={`bm-tree-toggle ${isOpen ? 'is-open' : ''}`}
                    onClick={(e) => { e.stopPropagation(); toggleExpand(row.path); }}
                  >
                    <Icon name="chevron_right" />
                  </span>
                ) : (
                  <span className="bm-tree-toggle bm-tree-toggle-empty" />
                )}
                <Icon name={isOpen && hasChildren ? 'folder_open' : 'folder'} />
                <span className="bm-tree-name">{row.name}</span>
                <span className="bm-tree-count">{row.count}</span>
                <span className="bm-tree-actions">
                  <button
                    className="bm-tree-action"
                    title="Add child folder"
                    onClick={(e) => { e.stopPropagation(); createFolder(row.path); }}
                  ><Icon name="create_new_folder" /></button>
                  <button
                    className="bm-tree-action"
                    title="Rename"
                    onClick={(e) => { e.stopPropagation(); renameFolder(row.path); }}
                  ><Icon name="edit" /></button>
                  <button
                    className="bm-tree-action danger"
                    title="Delete folder"
                    onClick={(e) => { e.stopPropagation(); deleteFolder(row.path); }}
                  ><Icon name="delete" /></button>
                </span>
              </div>
            );
          })}

          {/* Tag filter */}
          {tagStats.length > 0 && (
            <>
              <div className="bm-tree-head bm-tree-head-tags">
                <span>Tags</span>
                {selectedTags.size > 0 && (
                  <button className="bm-tag-clear" onClick={() => setSelectedTags(new Set())}>
                    Clear
                  </button>
                )}
              </div>
              <div className="bm-tag-cloud">
                {tagStats.map(([tag, count]) => (
                  <button
                    key={tag}
                    className={`bm-tag-chip ${selectedTags.has(tag) ? 'is-active' : ''}`}
                    onClick={() => toggleTag(tag)}
                  >
                    <span>#{tag}</span>
                    <span className="bm-tag-count">{count}</span>
                  </button>
                ))}
              </div>
            </>
          )}
        </aside>

        {/* Content */}
        <div className={`bm-content layout-${layout}`}>
          {filtered.length === 0 ? (
            <div className="lh-empty">
              <Icon name="bookmark" />
              <div className="title">No bookmarks here</div>
              <div className="sub">Click <kbd>New bookmark</kbd> to add one.</div>
            </div>
          ) : layout === 'grid' ? (
            <div className="bm-grid">
              {filtered.map(b => (
                <BookmarkCardGrid key={b.id} b={b} faviconFor={faviconFor} onRemove={removeBookmark} />
              ))}
            </div>
          ) : layout === 'list' ? (
            <div className="bm-list">
              {filtered.map(b => (
                <BookmarkRowList key={b.id} b={b} faviconFor={faviconFor} onRemove={removeBookmark} />
              ))}
            </div>
          ) : (
            <div className="bm-compact">
              {filtered.map(b => (
                <BookmarkRowCompact key={b.id} b={b} faviconFor={faviconFor} onRemove={removeBookmark} />
              ))}
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

function BookmarkCardGrid({ b, faviconFor, onRemove }) {
  return (
    <a className="bm-card" href={b.url} target="_blank" rel="noopener">
      <div className="bm-favicon">
        <img src={faviconFor(b.url)} alt="" onError={e => { e.target.style.display = 'none'; }} />
        <Icon name="public" />
      </div>
      <div className="bm-card-body">
        <div className="bm-card-title">{b.title}</div>
        <div className="bm-card-url">{b.url.replace(/^https?:\/\//, '')}</div>
        <div className="bm-card-meta">
          {b.folder && <span className="bm-card-folder"><Icon name="folder" />{b.folder}</span>}
          {(b.tags || []).slice(0,3).map(t => <span key={t} className="bm-card-tag">#{t}</span>)}
        </div>
      </div>
      <PinButton type="bookmark" id={b.id} className="bm-card-pin" />
      <IconBtn
        name="delete"
        tone="danger"
        className="bm-card-del"
        onClick={(e) => { e.preventDefault(); e.stopPropagation(); onRemove(b.id); }}
      />
    </a>
  );
}

function BookmarkRowList({ b, faviconFor, onRemove }) {
  return (
    <a className="bm-list-row" href={b.url} target="_blank" rel="noopener">
      <div className="bm-list-favicon">
        <img src={faviconFor(b.url)} alt="" onError={e => { e.target.style.display = 'none'; }} />
        <Icon name="public" />
      </div>
      <div className="bm-list-main">
        <div className="bm-list-title">{b.title}</div>
        <div className="bm-list-url">{b.url.replace(/^https?:\/\//, '')}</div>
      </div>
      <div className="bm-list-tags">
        {b.folder && <span className="bm-card-folder"><Icon name="folder" />{b.folder}</span>}
        {(b.tags || []).slice(0,3).map(t => <span key={t} className="bm-card-tag">#{t}</span>)}
      </div>
      <PinButton type="bookmark" id={b.id} className="bm-row-pin" />
      <IconBtn
        name="delete"
        tone="danger"
        className="bm-row-del"
        onClick={(e) => { e.preventDefault(); e.stopPropagation(); onRemove(b.id); }}
      />
    </a>
  );
}

function BookmarkRowCompact({ b, faviconFor, onRemove }) {
  return (
    <a className="bm-compact-row" href={b.url} target="_blank" rel="noopener">
      <img className="bm-compact-favicon" src={faviconFor(b.url)} alt="" onError={e => { e.target.style.visibility = 'hidden'; }} />
      <span className="bm-compact-title">{b.title}</span>
      <span className="bm-compact-url">{b.url.replace(/^https?:\/\//, '')}</span>
      {b.folder && <span className="bm-compact-folder">{b.folder}</span>}
      <PinButton type="bookmark" id={b.id} className="bm-row-pin" />
      <IconBtn
        name="delete"
        tone="danger"
        className="bm-row-del"
        onClick={(e) => { e.preventDefault(); e.stopPropagation(); onRemove(b.id); }}
      />
    </a>
  );
}

Object.assign(window, { DiffTool, PasswordTool, BookmarkTool });
