export interface FuzzyMatchResult {
  value: string;
  score: number;
}

export function simplifyForMatch(text: string) {
  return text.toLowerCase().replace(/[^a-z0-9%]+/g, "");
}

export function fuzzyFindKnown(text: string, values: string[], minimumScore = 0.72): FuzzyMatchResult | null {
  const haystack = simplifyForMatch(text);
  if (!haystack) return null;

  let best: FuzzyMatchResult | null = null;
  for (const value of values) {
    const needle = simplifyForMatch(value);
    if (!needle) continue;

    const score = scoreCandidate(haystack, needle);
    if (!best || score > best.score) best = { value, score };
  }

  return best && best.score >= minimumScore ? best : null;
}

function scoreCandidate(haystack: string, needle: string) {
  if (haystack.includes(needle)) return 1;
  if (needle.includes(haystack) && haystack.length >= Math.min(8, needle.length)) return haystack.length / needle.length;

  const windows = slidingWindows(haystack, needle.length);
  const bestDistance = Math.min(...windows.map((window) => levenshtein(window, needle)));
  const normalized = 1 - bestDistance / Math.max(needle.length, 1);

  const prefixBonus = needle.startsWith(haystack.slice(0, Math.min(haystack.length, needle.length))) ? 0.08 : 0;
  return Math.max(0, Math.min(1, normalized + prefixBonus));
}

function slidingWindows(text: string, size: number) {
  if (text.length <= size) return [text];
  const windows: string[] = [];
  const minSize = Math.max(4, Math.floor(size * 0.7));
  for (let start = 0; start <= text.length - minSize; start++) {
    windows.push(text.slice(start, Math.min(text.length, start + size)));
  }
  return windows;
}

function levenshtein(a: string, b: string) {
  const dp = Array.from({ length: a.length + 1 }, () => Array<number>(b.length + 1).fill(0));
  for (let i = 0; i <= a.length; i++) dp[i][0] = i;
  for (let j = 0; j <= b.length; j++) dp[0][j] = j;

  for (let i = 1; i <= a.length; i++) {
    for (let j = 1; j <= b.length; j++) {
      const cost = a[i - 1] === b[j - 1] ? 0 : 1;
      dp[i][j] = Math.min(
        dp[i - 1][j] + 1,
        dp[i][j - 1] + 1,
        dp[i - 1][j - 1] + cost,
      );
    }
  }

  return dp[a.length][b.length];
}
