• 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.

    Parameters

    • manga: AniListManga

      The manga to calculate match score for

    • searchTitle: string

      The search title to match against

    • options: MatchScoreOptions = {}

      Options to customize matching behavior (e.g., disable overlap heuristics)

    Returns MatchScoreDetails

    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 },
    };
    }