import { Injectable } from '@nestjs/common';

export type ProductMatchCandidate = {
  id: number;
  name: string;
  canonicalName: string | null;
  normalizedName: string;
};

export type IngredientSuggestion = {
  productId: number;
  productName: string;
  score: number;
};

@Injectable()
export class RecipeMatchingService {
  private normalize(value: string): string {
    return value
      .toLowerCase()
      .trim()
      .replace(/[^a-zåäö0-9\s]/gi, '')
      .replace(/\s+/g, ' ');
  }

  private levenshtein(a: string, b: string): number {
    const m = a.length;
    const n = b.length;
    const dp: number[][] = Array.from({ length: m + 1 }, (_, i) =>
      Array.from({ length: n + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0)),
    );

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

    return dp[m][n];
  }

  private scoreProducts(query: string, products: ProductMatchCandidate[]) {
    const normalizedQuery = this.normalize(query);

    return products
      .map((product) => {
        const targetName = this.normalize(product.canonicalName || product.name);
        const targetNormalized = this.normalize(product.normalizedName);

        if (targetNormalized === normalizedQuery || targetName === normalizedQuery) {
          return { product, score: 100 };
        }

        if (targetName.includes(normalizedQuery) || normalizedQuery.includes(targetName)) {
          return { product, score: 70 };
        }

        const dist = this.levenshtein(normalizedQuery, targetName);
        const maxLen = Math.max(normalizedQuery.length, targetName.length);
        const similarity = maxLen === 0 ? 100 : Math.round((1 - dist / maxLen) * 100);

        return { product, score: similarity };
      })
      .filter((s) => s.score >= 40)
      .sort((a, b) => b.score - a.score)
      .slice(0, 5);
  }

  buildIngredientSuggestions(
    rawName: string,
    alternatives: string[] | undefined,
    products: ProductMatchCandidate[],
  ): IngredientSuggestion[] {
    const variants = alternatives && alternatives.length > 1 ? alternatives : [rawName];

    const seenIds = new Set<number>();

    return variants
      .flatMap((variant) => this.scoreProducts(variant, products))
      .filter((s) => {
        if (seenIds.has(s.product.id)) return false;
        seenIds.add(s.product.id);
        return true;
      })
      .sort((a, b) => b.score - a.score)
      .slice(0, 5)
      .map((s) => ({
        productId: s.product.id,
        productName: s.product.canonicalName || s.product.name,
        score: s.score,
      }));
  }
}
