import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../models/note.dart'; import '../services/vault_search_index.dart'; import 'document_provider.dart' show vaultServiceProvider; /// The search index, rebuilt by SCANNING the vault sidecars (the source of /// truth) — NOT the demoted SQLite cache (Phase 6, §B/§F). Bumping /// [searchIndexEpochProvider] (e.g. after an import or note edit) invalidates /// this provider so the next read re-scans the vault from disk. final vaultSearchIndexProvider = FutureProvider((ref) async { ref.watch(searchIndexEpochProvider); final vault = await ref.watch(vaultServiceProvider.future); final index = VaultSearchIndex(vault); await index.rebuild(); return index; }); /// Bump to force the search index to rebuild from disk (e.g. after an import). final searchIndexEpochProvider = StateProvider((ref) => 0); final searchQueryProvider = StateProvider((ref) => ''); /// A search result that can be either a note hit or a document hit. sealed class SearchResult { const SearchResult(); } class NoteSearchHit extends SearchResult { final Note note; final String snippet; const NoteSearchHit({required this.note, this.snippet = ''}); } class DocumentSearchHit extends SearchResult { final String documentId; final String filename; final String filePath; final int pageNumber; final String snippet; const DocumentSearchHit({ required this.documentId, required this.filename, required this.filePath, required this.pageNumber, this.snippet = '', }); } final searchResultsProvider = FutureProvider>((ref) async { final query = ref.watch(searchQueryProvider); if (query.trim().isEmpty) return []; final index = await ref.watch(vaultSearchIndexProvider.future); final hits = await index.search(query); final results = []; for (final hit in hits) { final entry = hit.entry; final snippet = hit.snippet.text; if (entry.isNote) { // Construct a lightweight Note whose id is the synthetic note path so // PenNoteScreen re-keys the right sidecar on open. Strokes are hydrated // lazily by the editor; the search list only needs id/title. final now = DateTime.now(); results.add( NoteSearchHit( note: Note( id: entry.openPath, title: entry.title, createdAt: now, updatedAt: now, ), snippet: snippet, ), ); } else { results.add( DocumentSearchHit( documentId: entry.id, filename: entry.title, filePath: entry.openPath, // The scan-based index matches whole-notebook text, not per-page, so // the document opens at its first page. pageNumber: 0, snippet: snippet, ), ); } } return results; });