// Keyboard layout mapping (RU -> EN and EN -> RU)
const ruToEn: Record<string, string> = {
  'й': 'q', 'ц': 'w', 'у': 'e', 'к': 'r', 'е': 't', 'н': 'y', 'г': 'u', 'ш': 'i', 'щ': 'o', 'з': 'p',
  'ф': 'a', 'ы': 's', 'в': 'd', 'а': 'f', 'п': 'g', 'р': 'h', 'о': 'j', 'л': 'k', 'д': 'l',
  'я': 'z', 'ч': 'x', 'с': 'c', 'м': 'v', 'и': 'b', 'т': 'n', 'ь': 'm',
  'х': '[', 'ъ': ']', 'ж': ';', 'э': "'", 'б': ',', 'ю': '.',
}

const enToRu: Record<string, string> = Object.fromEntries(
  Object.entries(ruToEn).map(([ru, en]) => [en, ru])
)

function convertLayout(text: string): string {
  return text
    .split('')
    .map(char => {
      const lower = char.toLowerCase()
      const converted = ruToEn[lower] || enToRu[lower] || char
      return char === lower ? converted : converted.toUpperCase()
    })
    .join('')
}

function levenshteinDistance(a: string, b: string): number {
  const matrix: number[][] = []

  for (let i = 0; i <= b.length; i++) {
    matrix[i] = [i]
  }
  for (let j = 0; j <= a.length; j++) {
    matrix[0][j] = j
  }

  for (let i = 1; i <= b.length; i++) {
    for (let j = 1; j <= a.length; j++) {
      if (b.charAt(i - 1) === a.charAt(j - 1)) {
        matrix[i][j] = matrix[i - 1][j - 1]
      } else {
        matrix[i][j] = Math.min(
          matrix[i - 1][j - 1] + 1, // substitution
          matrix[i][j - 1] + 1,     // insertion
          matrix[i - 1][j] + 1      // deletion
        )
      }
    }
  }

  return matrix[b.length][a.length]
}

export interface FuzzyMatch<T> {
  item: T
  score: number
}

export function fuzzySearch<T>(
  items: T[],
  query: string,
  getSearchField: (item: T) => string
): FuzzyMatch<T>[] {
  if (!query.trim()) {
    return items.map(item => ({ item, score: 1 }))
  }

  const normalizedQuery = query.toLowerCase().trim()
  const convertedQuery = convertLayout(normalizedQuery)

  const results: FuzzyMatch<T>[] = []

  for (const item of items) {
    const field = getSearchField(item).toLowerCase()

    // Exact substring match - highest score
    if (field.includes(normalizedQuery)) {
      results.push({ item, score: 1 })
      continue
    }

    // Converted layout match
    if (field.includes(convertedQuery)) {
      results.push({ item, score: 0.95 })
      continue
    }

    // Check if query matches start of words
    const words = field.split(/\s+/)
    const queryWords = normalizedQuery.split(/\s+/)
    const startsWithMatch = queryWords.every(qw =>
      words.some(w => w.startsWith(qw))
    )
    if (startsWithMatch) {
      results.push({ item, score: 0.9 })
      continue
    }

    // Levenshtein distance for typo tolerance
    const distance = levenshteinDistance(normalizedQuery, field)
    const maxLen = Math.max(normalizedQuery.length, field.length)
    const similarity = 1 - distance / maxLen

    // Also check against converted query
    const convertedDistance = levenshteinDistance(convertedQuery, field)
    const convertedSimilarity = 1 - convertedDistance / maxLen

    const bestSimilarity = Math.max(similarity, convertedSimilarity)

    // Only include if similarity is reasonable (> 40%)
    if (bestSimilarity > 0.4) {
      results.push({ item, score: bestSimilarity * 0.8 })
    }
  }

  // Sort by score descending
  return results.sort((a, b) => b.score - a.score)
}

export function fuzzyFilter<T>(
  items: T[],
  query: string,
  getSearchField: (item: T) => string
): T[] {
  return fuzzySearch(items, query, getSearchField).map(r => r.item)
}
