{"version":3,"file":"matchers.cjs","sources":["../../../src/core/matchers.ts"],"sourcesContent":["/**\n * Matching functions for different search strategies\n * Extracted from index.ts for better modularity\n */\n\nimport type { FuzzyIndex, SearchMatch, FuzzyConfig } from \"./types.js\";\nimport type { LanguageProcessor } from \"./types.js\";\nimport { calculateLevenshteinDistance, calculateDamerauLevenshteinDistance, generateNgrams } from \"../algorithms/levenshtein.js\";\n\n/**\n * Find exact matches\n */\nexport function findExactMatches(query: string, index: FuzzyIndex, matches: Map<string, SearchMatch>, language: string): void {\n  const wordBoundaries = index.config.wordBoundaries || false;\n\n  // Check for wildcard pattern\n  if (query.includes(\"*\")) {\n    // Wildcard search\n    for (const baseWord of index.base) {\n      if (matchesWildcard(baseWord, query)) {\n        if (!matches.has(baseWord)) {\n          matches.set(baseWord, {\n            word: baseWord,\n            normalized: query,\n            matchType: \"exact\",\n            editDistance: 0,\n            language,\n          });\n        }\n      }\n    }\n    return;\n  }\n\n  // Check for exact matches in the variant map (normalize to lowercase)\n  const exactMatches = index.variantToBase.get(query.toLowerCase());\n  if (exactMatches) {\n    exactMatches.forEach((word) => {\n      // With word boundaries, verify the match\n      if (wordBoundaries && !matchesWord(word, query, wordBoundaries)) {\n        return;\n      }\n\n      // Always add exact matches, even if already found with lower score\n      const existing = matches.get(word);\n      if (!existing || existing.matchType !== \"exact\") {\n        matches.set(word, {\n          word,\n          normalized: query,\n          matchType: \"exact\",\n          editDistance: 0,\n          language,\n        });\n      }\n    });\n  }\n\n  // Also check if the query exactly matches any base word (case-insensitive)\n  const queryLower = query.toLowerCase();\n  for (const baseWord of index.base) {\n    if (baseWord.toLowerCase() === queryLower) {\n      if (!matches.has(baseWord)) {\n        matches.set(baseWord, {\n          word: baseWord,\n          normalized: query,\n          matchType: \"exact\",\n          editDistance: 0,\n          language,\n        });\n      }\n    }\n  }\n}\n\n/**\n * Find prefix matches\n */\nexport function findPrefixMatches(query: string, index: FuzzyIndex, matches: Map<string, SearchMatch>, language: string): void {\n  const wordBoundaries = index.config.wordBoundaries || false;\n  const queryLower = query.toLowerCase();\n\n  for (const [variant, words] of index.variantToBase.entries()) {\n    if (variant.startsWith(queryLower) && variant !== queryLower) {\n      words.forEach((word) => {\n        // With word boundaries, verify the match\n        if (wordBoundaries && !matchesWord(word, query, wordBoundaries)) {\n          return;\n        }\n\n        if (!matches.has(word)) {\n          matches.set(word, {\n            word,\n            normalized: variant,\n            matchType: \"prefix\",\n            language,\n          });\n        }\n      });\n    }\n  }\n}\n\n/**\n * Find substring matches (exact substring within the word)\n */\nexport function findSubstringMatches(query: string, index: FuzzyIndex, matches: Map<string, SearchMatch>, language: string): void {\n  const queryLower = query.toLowerCase();\n  \n  // Skip very short queries to avoid too many matches\n  if (queryLower.length < 2) return;\n\n  for (const [variant, words] of index.variantToBase.entries()) {\n    // Check if query is a substring (but not prefix or exact match)\n    if (variant.includes(queryLower) && !variant.startsWith(queryLower) && variant !== queryLower) {\n      words.forEach((word) => {\n        const existingMatch = matches.get(word);\n        // Don't replace exact or prefix matches with substring matches\n        if (!existingMatch || (existingMatch.matchType !== \"exact\" && existingMatch.matchType !== \"prefix\")) {\n          matches.set(word, {\n            word,\n            normalized: variant,\n            matchType: \"substring\",\n            language,\n          });\n        }\n      });\n    }\n  }\n}\n\n/**\n * Find phonetic matches\n */\nexport function findPhoneticMatches(query: string, processor: LanguageProcessor, index: FuzzyIndex, matches: Map<string, SearchMatch>): void {\n  if (!processor.supportedFeatures.includes(\"phonetic\")) return;\n\n  const phoneticCode = processor.getPhoneticCode(query);\n  if (phoneticCode) {\n    const phoneticMatches = index.phoneticToBase.get(phoneticCode);\n    if (phoneticMatches) {\n      phoneticMatches.forEach((word) => {\n        if (!matches.has(word)) {\n          matches.set(word, {\n            word,\n            normalized: query,\n            matchType: \"phonetic\",\n            phoneticCode,\n            language: processor.language,\n          });\n        }\n      });\n    }\n  }\n}\n\n/**\n * Find synonym matches\n */\nexport function findSynonymMatches(query: string, index: FuzzyIndex, matches: Map<string, SearchMatch>): void {\n  const synonymMatches = index.synonymMap.get(query.toLowerCase());\n  if (synonymMatches) {\n    synonymMatches.forEach((word) => {\n      if (!matches.has(word)) {\n        matches.set(word, {\n          word,\n          normalized: query,\n          matchType: \"synonym\",\n          language: \"synonym\",\n        });\n      }\n    });\n  }\n}\n\n/**\n * Find n-gram matches\n */\nexport function findNgramMatches(query: string, index: FuzzyIndex, matches: Map<string, SearchMatch>, language: string, ngramSize: number): void {\n  if (query.length < ngramSize) return;\n\n  const queryNgrams = generateNgrams(query, ngramSize);\n  const candidateWords = new Set<string>();\n\n  queryNgrams.forEach((ngram: string) => {\n    const ngramMatches = index.ngramIndex.get(ngram);\n    if (ngramMatches) {\n      ngramMatches.forEach((word) => candidateWords.add(word));\n    }\n  });\n\n  candidateWords.forEach((word) => {\n    if (!matches.has(word)) {\n      matches.set(word, {\n        word,\n        normalized: query,\n        matchType: \"ngram\",\n        language,\n      });\n    }\n  });\n}\n\n/**\n * Find fuzzy matches using edit distance\n */\nexport function findFuzzyMatches(query: string, index: FuzzyIndex, matches: Map<string, SearchMatch>, processor: LanguageProcessor, config: FuzzyConfig): void {\n  // Adaptive max distance based on query length\n  let maxDistance = config.maxEditDistance;\n  \n  // Scale max distance with query length for better fuzzy matching\n  if (query.length <= 3) {\n    maxDistance = Math.max(maxDistance, 2);\n  } else if (query.length <= 4) {\n    maxDistance = Math.max(maxDistance, 2);\n  } else if (query.length >= 10) {\n    // For longer queries (10+ chars), allow more edits\n    maxDistance = Math.max(maxDistance, 3);\n  }\n\n  for (const [variant, words] of index.variantToBase.entries()) {\n    // More lenient length check - scale with query length\n    const lengthDiff = Math.abs(variant.length - query.length);\n    \n    // Calculate max allowed length difference based on query length\n    // Short queries (<=4): strict (maxDistance)\n    // Medium queries (5-9): maxDistance + 1\n    // Long queries (10+): maxDistance + 2\n    let maxLengthDiff;\n    if (query.length <= 4) {\n      maxLengthDiff = query.length <= 3 ? 5 : 4;\n    } else if (query.length <= 9) {\n      maxLengthDiff = maxDistance + 1;\n    } else {\n      maxLengthDiff = maxDistance + 2;\n    }\n    \n    if (lengthDiff <= maxLengthDiff) {\n      // Use Damerau-Levenshtein if transpositions feature is enabled\n      const useTranspositions = index.config.features?.includes(\"transpositions\");\n      const distance = useTranspositions ? calculateDamerauLevenshteinDistance(query, variant, maxDistance) : calculateLevenshteinDistance(query, variant, maxDistance);\n\n      // Adaptive distance threshold\n      const distanceThreshold = query.length <= 3 ? 2 : maxDistance;\n      \n      if (distance <= distanceThreshold) {\n        words.forEach((word) => {\n          const existingMatch = matches.get(word);\n          // Don't replace exact or prefix matches with fuzzy matches\n          if (!existingMatch || (existingMatch.matchType !== \"exact\" && existingMatch.matchType !== \"prefix\" && (existingMatch.editDistance || Infinity) > distance)) {\n            matches.set(word, {\n              word,\n              normalized: variant,\n              matchType: \"fuzzy\",\n              editDistance: distance,\n              language: processor.language,\n            });\n          }\n        });\n      }\n    }\n  }\n}\n\n/**\n * Helper: Check if a word matches with word boundaries\n */\nfunction matchesWord(word: string, query: string, wordBoundaries: boolean): boolean {\n  if (!wordBoundaries) return true;\n  \n  const wordLower = word.toLowerCase();\n  const queryLower = query.toLowerCase();\n  \n  // Check if query matches at word boundaries\n  const regex = new RegExp(`\\\\b${escapeRegex(queryLower)}`, 'i');\n  return regex.test(wordLower);\n}\n\n/**\n * Helper: Check if a word matches a wildcard pattern\n */\nfunction matchesWildcard(word: string, pattern: string): boolean {\n  const regexPattern = pattern\n    .split('*')\n    .map(escapeRegex)\n    .join('.*');\n  \n  const regex = new RegExp(`^${regexPattern}$`, 'i');\n  return regex.test(word);\n}\n\n/**\n * Helper: Escape special regex characters\n */\nfunction escapeRegex(str: string): string {\n  return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n"],"names":["generateNgrams","calculateDamerauLevenshteinDistance","calculateLevenshteinDistance"],"mappings":"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"}