// Phase 2D — Library Search overlay.
//
// AlphaSense-inspired faceted Cmd+K across the entire brain. Type a query,
// see ranked results from every layer with snippet previews and highlighted
// match terms. Click a facet chip ("Frameworks", "Stocks", "Documents") to
// scope. Click a result to open the markdown in the Brain tab editor.
//
// Backed by /api/research/library-search which reuses IdeasVault.search()
// (already returns hit-scored results with snippets + facet counts).

const {
  useState: lsUseState, useEffect: lsUseEffect, useRef: lsUseRef,
  useCallback: lsUseCallback, useMemo: lsUseMemo,
} = React;

// Friendly facet labels for the chip strip. Order matters — this is the
// order the chips render in. `documents` is a synthetic facet that the
// backend computes from any 4-Stocks/Documents/* hit.
const FACETS = [
  { key: '',                           label: 'All',         hint: 'Everything in the brain' },
  { key: '1-Frameworks',               label: 'Frameworks',  hint: 'Investing frameworks + mental models' },
  { key: '2-Science & Tech Sectors',   label: 'Sectors',     hint: 'Per-sector deep dives' },
  { key: '3-Hedging & Macro',          label: 'Macro',       hint: 'Hedging frameworks + current regime' },
  { key: '4-Stocks',                   label: 'Stocks',      hint: 'Per-ticker deep dives + watchlist + open positions' },
  { key: 'documents',                  label: 'Documents',   hint: 'SEC filings + earnings + shareholder letters (corpus)' },
  { key: '5-Newsletters',              label: 'Newsletters', hint: 'External research, morning briefs' },
  { key: '7-Jarvis Captures',          label: 'Captures',    hint: 'Tibeb chat captures + ad-hoc notes' },
  { key: 'agents',                     label: 'Agents',      hint: 'Custom + built-in analyst prompts' },
];

function LibrarySearch({ open, onClose, onOpenNote }) {
  if (!open) return null;
  const [query, setQuery] = lsUseState('');
  const [activeFacet, setActiveFacet] = lsUseState('');
  const [results, setResults] = lsUseState([]);
  const [facets, setFacets] = lsUseState({});
  const [totalHits, setTotalHits] = lsUseState(0);
  const [loading, setLoading] = lsUseState(false);
  const [err, setErr] = lsUseState('');
  const [activeIdx, setActiveIdx] = lsUseState(0);
  // Phase 3.C — saved searches: shown in the empty state for quick re-run
  const [savedSearches, setSavedSearches] = lsUseState([]);
  const [savingNow, setSavingNow] = lsUseState(false);
  const isPersistedUser = !!(window.TIBEB_USER && window.TIBEB_USER.auth_configured && !window.TIBEB_USER.is_local);
  const inputRef = lsUseRef(null);

  // Autofocus + reset when (re)opened. Also fetch saved searches once
  // per open so they appear in the empty state.
  lsUseEffect(() => {
    if (open) {
      setActiveIdx(0);
      setTimeout(() => inputRef.current && inputRef.current.focus(), 30);
      if (isPersistedUser) {
        fetch('/api/saved-searches').then(r => r.ok ? r.json() : null).then(j => {
          if (j && Array.isArray(j.saved)) setSavedSearches(j.saved);
        }).catch(() => {});
      }
    }
  }, [open, isPersistedUser]);

  // Save current query as a named search. Auto-name uses the query text.
  const saveCurrent = lsUseCallback(async () => {
    const q = query.trim();
    if (!q || savingNow) return;
    const name = q.length > 40 ? q.slice(0, 40) + '…' : q;
    setSavingNow(true);
    try {
      const r = await fetch('/api/saved-searches', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name, query: q, layer: activeFacet || null }),
      });
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      const j = await r.json();
      setSavedSearches(prev => [{ id: j.id, name, query: q, layer: activeFacet || null, created: new Date().toISOString(), last_run: null }, ...prev]);
    } catch (e) {
      setErr(`Save failed — ${e.message || e}`);
    } finally {
      setSavingNow(false);
    }
  }, [query, activeFacet, savingNow]);

  // Run a saved search: load its query/layer + stamp last_run server-side.
  const runSaved = lsUseCallback((s) => {
    if (!s) return;
    setQuery(s.query || '');
    setActiveFacet(s.layer || '');
    if (s.id) {
      fetch(`/api/saved-searches/${s.id}/touch`, { method: 'POST' }).catch(() => {});
    }
  }, []);

  const deleteSaved = lsUseCallback(async (s) => {
    if (!s || !s.id) return;
    try {
      const r = await fetch(`/api/saved-searches/${s.id}`, { method: 'DELETE' });
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      setSavedSearches(prev => prev.filter(x => x.id !== s.id));
    } catch (e) {
      setErr(`Delete failed — ${e.message || e}`);
    }
  }, []);

  // Currently-saved? (so the Save button can flip to a checkmark)
  const isAlreadySaved = lsUseMemo(() => {
    const q = query.trim();
    if (!q) return false;
    return savedSearches.some(s => s.query === q && (s.layer || '') === (activeFacet || ''));
  }, [query, activeFacet, savedSearches]);

  // Debounced search. Empty query → clear results.
  lsUseEffect(() => {
    if (!query.trim()) {
      setResults([]); setTotalHits(0); setLoading(false); setErr('');
      return;
    }
    setLoading(true);
    setErr('');
    const handle = setTimeout(async () => {
      try {
        const params = new URLSearchParams({ q: query, limit: '40' });
        if (activeFacet) params.set('layer', activeFacet);
        const r = await fetch(`/api/research/library-search?${params}`);
        if (!r.ok) {
          const t = await r.text().catch(() => '');
          throw new Error(`HTTP ${r.status} ${t.slice(0, 80)}`);
        }
        const j = await r.json();
        setResults(j.results || []);
        setFacets(j.facets || {});
        setTotalHits(j.total_hits || 0);
        setActiveIdx(0);
      } catch (e) {
        setErr(e.message || String(e));
        setResults([]);
      } finally {
        setLoading(false);
      }
    }, 180);
    return () => clearTimeout(handle);
  }, [query, activeFacet]);

  // Keyboard navigation: ↑/↓ move selection, Enter opens, Esc closes.
  lsUseEffect(() => {
    if (!open) return;
    const onKey = (e) => {
      if (e.key === 'Escape') { e.preventDefault(); onClose(); return; }
      if (e.key === 'ArrowDown') {
        e.preventDefault();
        setActiveIdx(i => Math.min(i + 1, Math.max(0, results.length - 1)));
      } else if (e.key === 'ArrowUp') {
        e.preventDefault();
        setActiveIdx(i => Math.max(i - 1, 0));
      } else if (e.key === 'Enter' && results[activeIdx]) {
        e.preventDefault();
        openResult(results[activeIdx]);
      }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [open, results, activeIdx, onClose]);

  const openResult = lsUseCallback((r) => {
    if (!r) return;
    if (typeof onOpenNote === 'function') onOpenNote(r.path);
    onClose();
  }, [onOpenNote, onClose]);

  // Render a snippet with the query terms highlighted. The backend's
  // _make_snippet returns plain text — we match here on the same terms.
  const highlightSnippet = lsUseCallback((snippet) => {
    if (!snippet || !query.trim()) return snippet;
    const terms = query.trim().split(/\s+/).filter(t => t.length > 1).map(t => t.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'));
    if (!terms.length) return snippet;
    const re = new RegExp(`(${terms.join('|')})`, 'gi');
    const parts = snippet.split(re);
    return parts.map((p, i) =>
      i % 2 === 1 ? <mark key={i} className="ls-mark">{p}</mark> : <span key={i}>{p}</span>
    );
  }, [query]);

  return (
    <div className="ls-overlay" onClick={(e) => { if (e.target.classList.contains('ls-overlay')) onClose(); }}>
      <div className="ls-shell">
        <div className="ls-head">
          <div className="ls-eyebrow">Library Search · brain/</div>
          <input
            ref={inputRef}
            className="ls-input"
            type="text"
            value={query}
            onChange={(e) => setQuery(e.target.value)}
            placeholder="Search across frameworks, sectors, stocks, captures, agents, SEC filings…"
            autoComplete="off"
            spellCheck="false"
          />
          <div className="ls-meta">
            {loading && <span className="ls-loading">searching…</span>}
            {!loading && query.trim() && (
              <span className="ls-count">
                {totalHits === 0 ? 'no matches' : `${totalHits} match${totalHits === 1 ? '' : 'es'}`}
                {totalHits > results.length && ` · showing top ${results.length}`}
              </span>
            )}
            {/* Phase 3.C — Save button. Persisted users only. Flips to ✓ saved
                when the current (query, layer) tuple matches a stored entry. */}
            {isPersistedUser && query.trim() && (
              <button
                className={`ls-save ${isAlreadySaved ? 'saved' : ''}`}
                onClick={saveCurrent}
                disabled={savingNow || isAlreadySaved}
                title={isAlreadySaved ? 'Already saved' : 'Pin this query for one-click reuse'}
              >
                {isAlreadySaved ? '✓ saved' : (savingNow ? 'saving…' : '💾 save')}
              </button>
            )}
            <button className="ls-close" onClick={onClose} title="Close (Esc)">✕</button>
          </div>
        </div>

        {/* Facet chip strip — counts come from the full unscoped result so
            users see how relevant each layer is at a glance. */}
        {query.trim() && (
          <div className="ls-facets">
            {FACETS.map(f => {
              const count = f.key ? (facets[f.key] || 0) : Object.values(facets).reduce((a, b) => a + b, 0);
              if (f.key && count === 0) return null; // hide empty layers
              return (
                <button
                  key={f.key || 'all'}
                  className={`ls-facet ${activeFacet === f.key ? 'active' : ''}`}
                  onClick={() => setActiveFacet(f.key)}
                  title={f.hint}
                >
                  {f.label}
                  <span className="ls-facet-count">{count}</span>
                </button>
              );
            })}
          </div>
        )}

        {/* Result list */}
        <div className="ls-results">
          {!query.trim() && (
            <div className="ls-empty">
              <div className="ls-empty-title">Search the entire brain</div>
              <div className="ls-empty-body">
                Type any keyword — companies, themes, framework names, agent personas, SEC form numbers.
                Results rank by hit count + show a snippet with the match highlighted. Use <kbd>↑</kbd> / <kbd>↓</kbd> to navigate, <kbd>Enter</kbd> to open, <kbd>Esc</kbd> to close.
              </div>
              {/* Phase 3.C — Saved searches as one-click chips. Persisted users
                  only. Clicking a chip pre-fills query + facet and re-runs.
                  Hover the chip for an ✕ button to delete the saved search. */}
              {isPersistedUser && savedSearches.length > 0 && (
                <div className="ls-saved-section">
                  <div className="ls-saved-label">Your saved searches ({savedSearches.length})</div>
                  <div className="ls-saved-list">
                    {savedSearches.map(s => (
                      <div key={s.id} className="ls-saved-chip-wrap">
                        <button
                          className="ls-saved-chip"
                          onClick={() => runSaved(s)}
                          title={`Re-run: ${s.query}${s.layer ? ' · ' + s.layer : ''}`}
                        >
                          <span className="ls-saved-name">{s.name}</span>
                          {s.layer && <span className="ls-saved-layer">{s.layer}</span>}
                        </button>
                        <button
                          className="ls-saved-del"
                          onClick={() => deleteSaved(s)}
                          title="Delete saved search"
                        >✕</button>
                      </div>
                    ))}
                  </div>
                </div>
              )}
              <div className="ls-empty-tips">
                <div>Try: <code>capex</code>, <code>10-K</code>, <code>NVDA</code>, <code>position sizing</code>, <code>regime</code></div>
                {isPersistedUser && savedSearches.length === 0 && (
                  <div style={{ marginTop: 8, opacity: 0.75 }}>
                    Pro tip: type a query, then click <b>💾 save</b> to pin it. Saved queries appear here for one-click reuse.
                  </div>
                )}
              </div>
            </div>
          )}
          {err && <div className="ls-err">⚠ {err}</div>}
          {query.trim() && !loading && results.length === 0 && !err && (
            <div className="ls-empty">
              <div className="ls-empty-title">No matches in {activeFacet || 'the brain'}.</div>
              <div className="ls-empty-body">
                {activeFacet
                  ? <>Try removing the <b>{FACETS.find(f => f.key === activeFacet)?.label || activeFacet}</b> filter to widen the search.</>
                  : 'Try a shorter or different keyword. The search is keyword-based (no fuzzy matching yet).'}
              </div>
            </div>
          )}
          {results.map((r, i) => (
            <button
              key={r.path}
              className={`ls-result ${i === activeIdx ? 'active' : ''}`}
              onClick={() => openResult(r)}
              onMouseEnter={() => setActiveIdx(i)}
            >
              <div className="ls-result-head">
                <span className="ls-result-layer">{(FACETS.find(f => f.key === r.layer)?.label) || r.layer}</span>
                <span className="ls-result-title">{r.title}</span>
                <span className="ls-result-score">{r.score} hit{r.score === 1 ? '' : 's'}</span>
              </div>
              <div className="ls-result-snippet">{highlightSnippet(r.snippet)}</div>
              <div className="ls-result-path">{r.path}</div>
            </button>
          ))}
        </div>
      </div>
    </div>
  );
}

window.LibrarySearch = LibrarySearch;
