// ─── Search ─── one box that finds anything in Laminar Flow.
//
// Ctrl+K (or "/") from anywhere, or the 🔍 button in the top bar. Searches the
// tree already in memory — no server call — and groups hits by what they are.
// Clicking one jumps to it and flashes it.
//
// Documents live on the Evidence screen and are deliberately NOT searched here:
// this is about his own items, so a search for "1406" returns his tasks, not
// every drawing that mentions the room.
//
// Matching, grouping and snippets are in lib-search.js (pure, unit-tested).
(function () {
  const { useState, useEffect, useRef, useMemo, useCallback } = React;
  const S = window.LFSearch;

  const KIND_ICON = { project: '◆', milestone: '▬', task: '•', subtask: '·', capture: '⊡' };

  // The matched span, marked. Everything else is plain text, so a note full of
  // pasted HTML can't inject anything into the overlay.
  function Highlighted({ text, ranges }) {
    if (!ranges || !ranges.length) return <span>{text}</span>;
    const out = [];
    let at = 0;
    ranges.forEach(([s, e], i) => {
      if (s > at) out.push(<span key={'p' + i}>{text.slice(at, s)}</span>);
      out.push(<mark key={'m' + i} style={{ background: 'rgba(59,130,246,0.32)', color: '#fff', borderRadius: 2, padding: '0 1px' }}>{text.slice(s, e)}</mark>);
      at = e;
    });
    if (at < text.length) out.push(<span key="tail">{text.slice(at)}</span>);
    return <span>{out}</span>;
  }

  function SearchOverlay({ open, onClose }) {
    const [q, setQ] = useState('');
    const [typed, setTyped] = useState('');     // what's in the box right now
    const debounceRef = useRef(0);
    const [status, setStatus] = useState('open');      // open | done | all
    const [projectId, setProjectId] = useState('');
    const [cursor, setCursor] = useState(0);
    const inputRef = useRef(null);
    const listRef = useRef(null);

    // ~60 ms: long enough to skip the keystrokes in the middle of a word,
    // short enough that it still feels instant.
    useEffect(() => {
      clearTimeout(debounceRef.current);
      debounceRef.current = setTimeout(() => setQ(typed), 60);
      return () => clearTimeout(debounceRef.current);
    }, [typed]);

    const projects = window.useLFState ? window.useLFState('projects', [])[0] : [];
    const captures = window.useLFState ? window.useLFState('inboxItems', [])[0] : [];

    // Rebuilt when the tree changes, not on every keystroke.
    const index = useMemo(() => S.buildIndex(projects, captures), [projects, captures]);
    const result = useMemo(
      () => S.search(index, q, { status, projectId: projectId || undefined, limitPerGroup: 40 }),
      [index, q, status, projectId]);
    const flat = useMemo(() => S.flatten(result), [result]);

    useEffect(() => { setCursor(0); }, [q, status, projectId]);
    // The tree can change while the box is open (a sync, an agent write). Keep
    // the cursor on a row that exists, or Enter would quietly do nothing.
    useEffect(() => {
      setCursor((c) => (flat.length === 0 ? 0 : Math.min(c, flat.length - 1)));
    }, [flat.length]);
    useEffect(() => {
      if (!open) return;
      // Start clean every time. A project filter left on from an earlier search
      // is invisible until you look for it, and silently hides half the app —
      // captures have no project at all, so they'd never appear again.
      setStatus('open');
      setProjectId('');
      setCursor(0);
      setQ(typed);
      setTimeout(() => { try { inputRef.current && inputRef.current.focus(); inputRef.current.select(); } catch (e) {} }, 10);
    }, [open]);

    // Keep the highlighted row in view while arrowing through.
    useEffect(() => {
      const el = listRef.current && listRef.current.querySelector('[data-cursor="1"]');
      if (el && el.scrollIntoView) el.scrollIntoView({ block: 'nearest' });
    }, [cursor, q, status, projectId]);

    const go = useCallback((hit) => {
      if (!hit) return;
      onClose();
      try { window.LFNav.go(hit); } catch (e) {}
    }, [onClose]);

    // Escape closes from anywhere in the panel; the list keys are bound to the
    // query box and the results only, so the project dropdown and the
    // Open/Done/All buttons keep their normal keyboard behaviour.
    const onPanelKeyDown = (e) => {
      if (e.key === 'Escape') { e.preventDefault(); onClose(); }
    };
    const onListKeyDown = (e) => {
      if (e.key === 'ArrowDown') { e.preventDefault(); setCursor((c) => (flat.length ? Math.min(c + 1, flat.length - 1) : 0)); return; }
      if (e.key === 'ArrowUp') { e.preventDefault(); setCursor((c) => Math.max(0, c - 1)); return; }
      if (e.key === 'Enter') { e.preventDefault(); if (flat.length) go(flat[Math.min(cursor, flat.length - 1)]); }
    };

    // If the matcher failed to load, stay out of the way rather than throwing
    // inside the app shell.
    if (!open || !S) return null;

    let running = -1;   // index within the flattened list, for the cursor
    const pill = (active) => ({
      background: active ? '#1d283a' : 'transparent', color: active ? '#cbd5e1' : '#555',
      border: 'none', borderRadius: 6, padding: '4px 9px', fontSize: 10, fontWeight: 700,
      letterSpacing: '0.06em', textTransform: 'uppercase', cursor: 'pointer', fontFamily: 'inherit',
    });

    return (
      <div
        onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }}
        style={{
          position: 'fixed', inset: 0, zIndex: 400, background: 'rgba(0,0,0,0.55)',
          backdropFilter: 'blur(2px)', display: 'flex', justifyContent: 'center',
          alignItems: 'flex-start', padding: '8vh 16px 16px',
        }}>
        <div onKeyDown={onPanelKeyDown}
          style={{
            width: 'min(760px, 100%)', maxHeight: '80vh', display: 'flex', flexDirection: 'column',
            background: '#0b0b0d', border: '1px solid #232838', borderRadius: 12, overflow: 'hidden',
            boxShadow: '0 24px 60px rgba(0,0,0,0.6)',
          }}>
          {/* Query */}
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: 12, borderBottom: '1px solid #1a1a1a' }}>
            <span style={{ color: '#475569', fontSize: 14 }}>🔍</span>
            <input
              ref={inputRef}
              value={typed}
              onChange={(e) => setTyped(e.target.value)}
              onKeyDown={onListKeyDown}
              placeholder="Search projects, milestones, tasks, subtasks, captures…"
              style={{ flex: 1, minWidth: 0, background: 'transparent', border: 'none', outline: 'none', color: '#e7e7e7', fontSize: 15, fontFamily: 'inherit' }}
            />
            <span style={{ fontSize: 9, color: '#3a3a3a', letterSpacing: '0.08em', whiteSpace: 'nowrap' }}>ESC</span>
          </div>

          {/* Filters */}
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '8px 12px', borderBottom: '1px solid #141414', flexWrap: 'wrap' }}>
            <div style={{ display: 'flex', gap: 2, background: '#0f0f12', borderRadius: 8, padding: 2 }}>
              {[['open', 'Open'], ['done', 'Done'], ['all', 'All']].map(([id, label]) => (
                <button key={id} onClick={() => setStatus(id)} style={pill(status === id)}>{label}</button>
              ))}
            </div>
            <select
              value={projectId}
              onChange={(e) => setProjectId(e.target.value)}
              style={{ background: '#0f0f12', border: '1px solid #1f1f1f', borderRadius: 8, padding: '5px 8px', color: '#9aa4b2', fontSize: 11, fontFamily: 'inherit', maxWidth: 220 }}>
              <option value="">All projects</option>
              {(projects || []).map((p) => <option key={p.id} value={p.id}>{p.name}</option>)}
            </select>
            <span style={{ marginLeft: 'auto', fontSize: 10, color: '#3a3a3a', letterSpacing: '0.06em' }}>
              {q.trim()
                ? `${result.total} result${result.total === 1 ? '' : 's'}${result.truncated ? ` · showing ${result.shown}` : ''}`
                : '↑↓ move · ↵ open'}
            </span>
          </div>

          {/* Results */}
          <div ref={listRef} tabIndex={-1} onKeyDown={onListKeyDown} style={{ overflowY: 'auto', padding: '4px 0 10px' }}>
            {!typed.trim() && (
              <div style={{ padding: '26px 16px', textAlign: 'center', color: '#333', fontSize: 11, letterSpacing: '0.1em', textTransform: 'uppercase' }}>
                Type to search · documents live on the Evidence screen
              </div>
            )}
            {typed.trim() && q === typed && result.total === 0 && (
              <div style={{ padding: '26px 16px', textAlign: 'center', color: '#444', fontSize: 12 }}>
                Nothing matches “{typed.trim()}”{status === 'open' ? ' in open items — try Done or All' : ''}
              </div>
            )}
            {S.KINDS.map((kind) => {
              const rows = result.groups[kind] || [];
              if (!rows.length) return null;
              return (
                <div key={kind}>
                  <div style={{ padding: '10px 14px 4px', fontSize: 9, fontWeight: 800, letterSpacing: '0.14em', textTransform: 'uppercase', color: '#475569' }}>
                    {S.GROUP_LABEL[kind]}{' '}
                    <span style={{ color: '#2f3947' }}>
                      · {(result.counts && result.counts[kind] > rows.length) ? `${rows.length} of ${result.counts[kind]}` : rows.length}
                    </span>
                  </div>
                  {rows.map((hit) => {
                    running += 1;
                    const isCursor = running === cursor;
                    const mine = running;
                    return (
                      <div
                        key={kind + ':' + hit.id}
                        data-cursor={isCursor ? '1' : '0'}
                        onMouseEnter={() => setCursor(mine)}
                        onClick={() => go(hit)}
                        style={{
                          display: 'flex', alignItems: 'baseline', gap: 8, padding: '7px 14px', cursor: 'pointer',
                          background: isCursor ? 'rgba(59,130,246,0.10)' : 'transparent',
                          borderLeft: `2px solid ${isCursor ? (hit.color || '#3B82F6') : 'transparent'}`,
                        }}>
                        <span style={{ color: hit.color || '#475569', fontSize: 11, width: 10, flexShrink: 0 }}>{KIND_ICON[hit.kind]}</span>
                        <div style={{ minWidth: 0, flex: 1 }}>
                          <div style={{ fontSize: 12.5, color: hit.done ? '#5b6472' : '#dfe5ec', textDecoration: hit.done ? 'line-through' : 'none', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                            {hit.snippet.field === 'label'
                              ? <Highlighted text={hit.snippet.text} ranges={hit.snippet.ranges} />
                              : hit.label}
                          </div>
                          {hit.snippet.field === 'notes' && (
                            <div style={{ fontSize: 11, color: '#8a93a0', marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                              <Highlighted text={hit.snippet.text} ranges={hit.snippet.ranges} />
                            </div>
                          )}
                          {hit.breadcrumb.length > 0 && (
                            <div style={{ fontSize: 9.5, color: '#3f4a5a', marginTop: 2, letterSpacing: '0.04em', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                              {hit.breadcrumb.join(' › ')}
                            </div>
                          )}
                        </div>
                        <div style={{ display: 'flex', alignItems: 'center', gap: 6, flexShrink: 0 }}>
                          {hit.done && <span style={{ fontSize: 9, color: '#22C55E' }}>done</span>}
                          {hit.dueDate && !hit.done && (
                            <span style={{ fontSize: 9.5, color: '#64748B', fontVariantNumeric: 'tabular-nums' }}>
                              {window.formatDueLabel ? window.formatDueLabel(hit.dueDate) : hit.dueDate}
                            </span>
                          )}
                        </div>
                      </div>
                    );
                  })}
                </div>
              );
            })}
          </div>
        </div>
      </div>
    );
  }

  window.SearchOverlay = SearchOverlay;
})();
