• Scores a match between a Kenmei manga and an AniList manga entry. Returns a score between 0-100 and information about the match.

    Parameters

    Returns { confidence: number; isExactMatch: boolean; matchedField: string }

    An object containing confidence, isExactMatch, and matchedField.

    export function scoreMatch(
    kenmeiManga: KenmeiManga,
    anilistManga: AniListManga,
    config: Partial<MatchEngineConfig> = {},
    ): { confidence: number; isExactMatch: boolean; matchedField: string } {
    const matchConfig = { ...DEFAULT_MATCH_CONFIG, ...config };
    const {
    caseSensitive,
    preferEnglishTitles,
    preferRomajiTitles,
    useAlternativeTitles,
    } = matchConfig;

    // Normalize the Kenmei title
    const kenmeiTitle = normalizeString(kenmeiManga.title, caseSensitive);

    // Skip extremely short titles (likely errors)
    if (kenmeiTitle.length < matchConfig.minTitleLength) {
    return { confidence: 0, isExactMatch: false, matchedField: "none" };
    }

    // Array to store all similarity scores with their sources
    const scores: Array<{ field: string; score: number }> = [];

    // Check primary titles based on preferences
    // We'll calculate similarity for all titles but weight them differently later

    if (anilistManga.title.english) {
    const englishScore = calculateSimilarity(
    kenmeiTitle,
    anilistManga.title.english,
    matchConfig,
    );
    scores.push({ field: "english", score: englishScore });

    // Exact match check
    if (englishScore === 100) {
    return { confidence: 100, isExactMatch: true, matchedField: "english" };
    }
    }

    if (anilistManga.title.romaji) {
    const romajiScore = calculateSimilarity(
    kenmeiTitle,
    anilistManga.title.romaji,
    matchConfig,
    );
    scores.push({ field: "romaji", score: romajiScore });

    // Exact match check
    if (romajiScore === 100) {
    return { confidence: 100, isExactMatch: true, matchedField: "romaji" };
    }
    }

    if (anilistManga.title.native) {
    const nativeScore = calculateSimilarity(
    kenmeiTitle,
    anilistManga.title.native,
    matchConfig,
    );
    scores.push({ field: "native", score: nativeScore });

    // Exact match check
    if (nativeScore === 100) {
    return { confidence: 100, isExactMatch: true, matchedField: "native" };
    }
    }

    // Check alternative titles if enabled
    if (
    useAlternativeTitles &&
    anilistManga.synonyms &&
    anilistManga.synonyms.length > 0
    ) {
    for (const synonym of anilistManga.synonyms) {
    if (!synonym) continue;

    const synonymScore = calculateSimilarity(
    kenmeiTitle,
    synonym,
    matchConfig,
    );
    scores.push({ field: "synonym", score: synonymScore });

    // Exact match check
    if (synonymScore === 100) {
    return { confidence: 100, isExactMatch: true, matchedField: "synonym" };
    }
    }
    }

    // Check Kenmei alternative titles against AniList titles
    if (
    useAlternativeTitles &&
    kenmeiManga.alternative_titles &&
    kenmeiManga.alternative_titles.length > 0
    ) {
    for (const altTitle of kenmeiManga.alternative_titles) {
    if (!altTitle) continue;

    const normalizedAltTitle = normalizeString(altTitle, caseSensitive);

    // Skip very short alternative titles
    if (normalizedAltTitle.length < matchConfig.minTitleLength) continue;

    // Check against each AniList title field
    if (anilistManga.title.english) {
    const altEnglishScore = calculateSimilarity(
    normalizedAltTitle,
    anilistManga.title.english,
    matchConfig,
    );
    scores.push({ field: "alt_to_english", score: altEnglishScore });

    if (altEnglishScore === 100) {
    return {
    confidence: 95,
    isExactMatch: true,
    matchedField: "alt_to_english",
    };
    }
    }

    if (anilistManga.title.romaji) {
    const altRomajiScore = calculateSimilarity(
    normalizedAltTitle,
    anilistManga.title.romaji,
    matchConfig,
    );
    scores.push({ field: "alt_to_romaji", score: altRomajiScore });

    if (altRomajiScore === 100) {
    return {
    confidence: 95,
    isExactMatch: true,
    matchedField: "alt_to_romaji",
    };
    }
    }
    }
    }

    // If we have no scores, return zero confidence
    if (scores.length === 0) {
    return { confidence: 0, isExactMatch: false, matchedField: "none" };
    }

    // Get the highest score and its field
    scores.sort((a, b) => b.score - a.score);
    const topScore = scores[0];

    // Apply title preference weighting
    let adjustedScore = topScore.score;
    if (topScore.field === "english" && preferEnglishTitles) {
    adjustedScore = Math.min(100, adjustedScore * 1.05);
    } else if (topScore.field === "romaji" && preferRomajiTitles) {
    adjustedScore = Math.min(100, adjustedScore * 1.05);
    }

    // Consider an "exact match" if the confidence is very high
    const isExactMatch = adjustedScore >= 95;

    return {
    confidence: Math.round(adjustedScore),
    isExactMatch,
    matchedField: topScore.field,
    };
    }