import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../models/document.dart';
import '../models/note.dart';
import 'note_provider.dart';

final searchQueryProvider = StateProvider<String>((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<List<SearchResult>>((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<Note>;
  final docHits = searches[1] as List<Map<String, dynamic>>;

  final results = <SearchResult>[];

  // 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 = <String>{
    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 = <String, Document>{
    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;
});
