smartSearchDynamicDetailedExtension method
Future<List<T> >
smartSearchDynamicDetailedExtension({
- required String query,
- required Map<
String, List< searchFieldsMap,String> > - required Map<
String, dynamic> toJson(- T
- Map<
String, double> fieldWeights = const {}, - double minScore = 0.3,
- int limit = 10,
- bool useDamerauLevenshtein = true,
- bool useNGram = true,
- bool useIsolate = true,
- bool debug = false,
Performs smart search and returns matching items.
This is the main search method with full feature support including debug output, field scoring, and performance optimizations.
Parameters:
query: The search query stringsearchFieldsMap: Map of field names to their sub-fields to searchtoJson: Function to convert items to JSON for searchingfieldWeights: Optional weights for each field (default: 1.0)minScore: Minimum score threshold (0.0-1.0, default: 0.3)limit: Maximum number of results (default: 10)useDamerauLevenshtein: Use Damerau-Levenshtein distance (default: true)useNGram: Include N-gram similarity (default: true)useIsolate: Use isolate for lists greater than 1000 items (default: true)debug: Enable debug output (default: false)
Returns a list of matching items sorted by relevance score.
Implementation
Future<List<T>> smartSearchDynamicDetailedExtension({
required String query,
required Map<String, List<String>> searchFieldsMap,
required Map<String, dynamic> Function(T) toJson,
Map<String, double> fieldWeights = const {},
double minScore = 0.3,
int limit = 10,
bool useDamerauLevenshtein = true,
bool useNGram = true,
bool useIsolate = true,
bool debug = false,
}) async {
// Validate parameters
_validateSearchParameters(
query: query,
searchFieldsMap: searchFieldsMap,
minScore: minScore,
limit: limit,
);
// Validate field weights
for (final entry in fieldWeights.entries) {
if (entry.value < 0) {
throw SearchParameterException(
'Field weight for "${entry.key}" must be non-negative, got ${entry.value}',
);
}
if (!searchFieldsMap.containsKey(entry.key)) {
throw SearchParameterException(
'Field weight specified for "${entry.key}" but field not in searchFieldsMap',
);
}
}
final stopwatch = Stopwatch()..start();
// Convert to JSON
final itemsJson = map((item) => toJson(item)).toList();
if (debug) {
debugPrint('\n${'=' * 70}');
debugPrint('🔍 SMART SEARCH DEBUG - Detailed Analysis');
debugPrint('${'=' * 70}');
debugPrint('📝 Query: "$query"');
debugPrint('📊 Total Items: ${itemsJson.length}');
debugPrint('⚙️ Parameters:');
debugPrint(' - minScore: $minScore');
debugPrint(' - limit: $limit');
debugPrint(' - useDamerauLevenshtein: $useDamerauLevenshtein');
debugPrint(' - useNGram: $useNGram');
debugPrint(' - useIsolate: $useIsolate');
debugPrint(' - Field Weights: $fieldWeights');
debugPrint(' - Search Fields Map: $searchFieldsMap');
debugPrint('');
}
final params = DynamicSearchParams(
items: itemsJson,
query: query,
searchFieldsMap: searchFieldsMap,
fieldWeights: fieldWeights,
minScore: minScore,
limit: limit,
useDamerauLevenshtein: useDamerauLevenshtein,
useNGram: useNGram,
);
// Show query word groups in debug
if (debug) {
final queryWordGroups = EnhancedSmartSearch.groupQueryWordsByOriginal(query);
debugPrint('🔤 Query Word Groups:');
queryWordGroups.forEach((original, variants) {
debugPrint(' "$original" → ${variants.length} variants: ${variants.take(5).join(", ")}${variants.length > 5 ? "..." : ""}');
});
debugPrint('');
}
List<Map<String, dynamic>> results;
try {
if (useIsolate && length > SearchConstants.isolateThreshold) {
if (debug) debugPrint('⚡ Using isolate (items > ${SearchConstants.isolateThreshold})');
// Note: compute() only accepts single-parameter top-level functions
results = await compute(_isolateSearchWorker, params);
} else {
if (debug) debugPrint('⚡ Using main thread (items ≤ ${SearchConstants.isolateThreshold})');
results = _dynamicSearchWorker(params, debug: debug, itemsJson: itemsJson, searchFieldsMap: searchFieldsMap);
}
} catch (e) {
if (debug) {
debugPrint('❌ Error during search execution: $e');
}
rethrow;
}
// If using isolate, we need to get field scores separately for debug
if (debug && useIsolate && length > 1000) {
// Recalculate field scores for top results in debug mode
final queryWordGroups = EnhancedSmartSearch.groupQueryWordsByOriginal(query);
for (final result in results) {
final index = result['_index'] as int;
final item = itemsJson[index];
final fieldScores = <String, double>{};
searchFieldsMap.forEach((fieldName, subFields) {
final fieldWords = <String>[];
for (final subField in subFields) {
if (!item.containsKey(subField)) continue;
final value = item[subField];
if (value is List) {
for (final v in value) {
fieldWords.addAll(EnhancedSmartSearch.splitWords(v.toString()));
}
} else if (value != null) {
fieldWords.addAll(EnhancedSmartSearch.splitWords(value.toString()));
}
}
final fieldScore = EnhancedSmartSearch.itemScore(
fieldWords,
queryWordGroups,
useDamerauLevenshtein,
useNGram,
);
fieldScores[fieldName] = fieldScore;
});
result['_fieldScores'] = fieldScores;
}
}
stopwatch.stop();
if (debug) {
debugPrint('');
debugPrint('📈 Results Summary:');
debugPrint(' - Found: ${results.length} results');
debugPrint(' - Processing Time: ${stopwatch.elapsedMilliseconds}ms');
final elapsedMs = stopwatch.elapsedMilliseconds;
final throughput = elapsedMs > 0
? (itemsJson.length / elapsedMs * 1000).toStringAsFixed(0)
: 'N/A';
debugPrint(' - Throughput: $throughput items/sec');
debugPrint('');
if (results.isEmpty) {
debugPrint('⚠️ No results found (all scores below minScore: $minScore)');
} else {
debugPrint('🏆 Top Results:');
for (int i = 0; i < results.length && i < limit; i++) {
final result = results[i];
final score = result['_score'] as double;
final index = result['_index'] as int;
final item = itemsJson[index];
// Extract display name
String displayName = '';
for (final fieldList in searchFieldsMap.values) {
for (final field in fieldList) {
if (item.containsKey(field)) {
final value = item[field];
if (value is String && value.isNotEmpty) {
displayName = value;
break;
}
}
}
if (displayName.isNotEmpty) break;
}
if (displayName.isEmpty) {
displayName = item.toString();
}
debugPrint(' ${i + 1}. Score: ${score.toStringAsFixed(4)} | $displayName');
// Show field scores if available
if (result.containsKey('_fieldScores')) {
final fieldScores = result['_fieldScores'] as Map<String, double>;
fieldScores.forEach((field, score) {
debugPrint(' └─ $field: ${score.toStringAsFixed(4)}');
});
}
}
}
// Cache statistics
final cacheStats = EnhancedSmartSearch.getCacheStats();
debugPrint('');
debugPrint('💾 Cache Statistics:');
cacheStats.forEach((key, value) {
debugPrint(' - $key: $value entries');
});
debugPrint('${'=' * 70}\n');
}
// Convert results back to items (List<T>)
return results.map((result) {
final index = result['_index'] as int;
return this[index];
}).toList();
}