smartSearchDynamicDetailedExtension method

Future<List<T>> smartSearchDynamicDetailedExtension({
  1. required String query,
  2. required Map<String, List<String>> searchFieldsMap,
  3. required Map<String, dynamic> toJson(
    1. T
    ),
  4. Map<String, double> fieldWeights = const {},
  5. double minScore = 0.3,
  6. int limit = 10,
  7. bool useDamerauLevenshtein = true,
  8. bool useNGram = true,
  9. bool useIsolate = true,
  10. 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 string
  • searchFieldsMap: Map of field names to their sub-fields to search
  • toJson: Function to convert items to JSON for searching
  • fieldWeights: 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();
}