The manga to calculate match score for
The search title to match against
Options to customize matching behavior (e.g., disable overlap heuristics)
Match score between 0 and 1, or -1 if no match found
export function calculateMatchScoreDetails(
manga: AniListManga,
searchTitle: string,
options: MatchScoreOptions = {},
): MatchScoreDetails {
// Handle empty search title
if (!searchTitle || searchTitle.trim() === "") {
console.warn(
`[MangaSearchService] ⚠️ Empty search title provided for manga ID ${manga.id}`,
);
return {
score: -1,
matchType: "none",
components: { directMatch: 0, wordMatch: 0, legacyMatch: 0 },
};
}
// Log for debugging
console.debug(
`[MangaSearchService] 🔍 Calculating match score for "${searchTitle}" against manga ID ${manga.id}, titles:`,
{
english: manga.title.english,
romaji: manga.title.romaji,
native: manga.title.native,
synonyms: manga.synonyms?.slice(0, 3), // Limit to first 3 for cleaner logs
},
);
// If we have synonyms, log them explicitly for better debugging
if (manga.synonyms && manga.synonyms.length > 0) {
console.debug(
`[MangaSearchService] 📚 Synonyms for manga ID ${manga.id}:`,
manga.synonyms,
);
}
// Collect all manga titles
const titles = collectMangaTitles(manga);
// Create normalized titles for matching
const normalizedTitles = createNormalizedTitles(manga);
// Normalize the search title for better matching
const normalizedSearchTitle = normalizeForMatching(searchTitle);
const searchWords = normalizedSearchTitle.split(/\s+/);
const importantWords = searchWords.filter((word) => word.length > 2);
// Check for direct matches first (highest confidence)
const directMatch = checkDirectMatches(
normalizedTitles,
normalizedSearchTitle,
searchTitle,
manga,
);
if (directMatch > 0) {
return {
score: directMatch,
matchType: "direct",
components: { directMatch, wordMatch: 0, legacyMatch: 0 },
};
}
// Try word-based matching approaches
const wordMatch = checkWordMatching(
normalizedTitles,
normalizedSearchTitle,
searchTitle,
options,
);
if (wordMatch > 0) {
return {
score: wordMatch,
matchType: "word",
components: { directMatch: 0, wordMatch, legacyMatch: 0 },
};
}
// Finally try legacy matching approaches for comprehensive coverage
const legacyMatch = checkLegacyMatching(
titles,
normalizedSearchTitle,
searchTitle,
importantWords,
);
console.debug(
`[MangaSearchService] 🔍 Final match score for "${searchTitle}": ${legacyMatch.toFixed(2)}`,
);
return {
score: legacyMatch,
matchType: "legacy",
components: { directMatch: 0, wordMatch: 0, legacyMatch },
};
}
Calculate match score between a manga title and search query. Uses multiple matching strategies in order: direct matches, word-based matching, then legacy approaches. Returns normalized score between 0 and 1, or -1 if no match found.