import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../models/document.dart'; import '../models/note.dart'; import 'note_provider.dart'; 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.isEmpty) return []; // Obtain the DB through the provider graph so this participates in // initialization and disposal like every other consumer. final db = await ref.watch(databaseServiceProvider.future); // Run the note and document searches concurrently. final searches = await Future.wait([ db.searchNotes(query), db.searchDocuments(query), ]); final noteHits = searches[0] as List; final docHits = searches[1] as List>; final results = []; // Add note results. for (final note in noteHits) { results.add(NoteSearchHit(note: note, snippet: note.title)); } // Resolve document metadata without an N+1 loop: collect the distinct // document ids referenced by the hits, look each up exactly once, then // build the result list from the cached lookups. final docIds = { for (final hit in docHits) if (hit['document_id'] is String) hit['document_id'] as String, }; final docEntries = await Future.wait( docIds.map((id) async => MapEntry(id, await db.getDocument(id))), ); final docsById = { for (final entry in docEntries) if (entry.value != null) entry.key: entry.value!, }; for (final hit in docHits) { final documentId = hit['document_id']; if (documentId is! String) continue; final doc = docsById[documentId]; if (doc == null) continue; final pageNumber = hit['page_number']; final content = hit['content']; results.add( DocumentSearchHit( documentId: documentId, filename: doc.filename, filePath: doc.filePath, pageNumber: pageNumber is int ? pageNumber : 0, snippet: content is String ? content : '', ), ); } return results; });