• Calculate word matching score between title and search words. Counts exact word matches and partial matches (prefix/suffix) of length >= 4. Requires minimum 75% match ratio to return a score.

    Parameters

    • titleWords: string[]

      Array of words from the manga title

    • searchWords: string[]

      Array of words from the search query

    Returns number

    Word match score (0.75-1) or -1 if below threshold

    export function calculateWordMatchScore(
    titleWords: string[],
    searchWords: string[],
    ): number {
    const matchableTitleWords = filterMatchableWords(titleWords);
    const matchableSearchWords = filterMatchableWords(searchWords);

    if (matchableTitleWords.length === 0 || matchableSearchWords.length === 0) {
    return -1;
    }

    let matchingWords = 0;
    const matchedSearchWords = new Set<string>();

    for (const word of matchableTitleWords) {
    for (const searchWord of matchableSearchWords) {
    if (matchedSearchWords.has(searchWord)) continue;

    if (word === searchWord) {
    matchingWords += 1;
    matchedSearchWords.add(searchWord);
    break;
    }

    if (isPartialWordMatch(word, searchWord)) {
    matchingWords += 0.5;
    matchedSearchWords.add(searchWord);
    break;
    }
    }
    }

    const denominator = Math.max(
    2,
    Math.min(matchableTitleWords.length, matchableSearchWords.length),
    );
    const matchRatio = matchingWords / denominator;
    const adjustedMatchRatio = applyDensityPenalty(
    matchRatio,
    matchingWords,
    matchableTitleWords.length,
    matchableSearchWords.length,
    );

    if (adjustedMatchRatio >= WORD_MATCH_BASE_RATIO) {
    return (
    WORD_MATCH_BASE_RATIO + (adjustedMatchRatio - WORD_MATCH_BASE_RATIO) * 0.6
    );
    }

    const fallbackScore = getWordMatchFallbackScore(
    adjustedMatchRatio,
    matchableTitleWords.length,
    matchableSearchWords.length,
    matchingWords,
    );

    if (fallbackScore !== null) {
    return fallbackScore;
    }

    return -1;
    }