• Process cached results with filtering and confidence recalculation.

    When kenmeiManga is provided, applies custom rules in the following order:

    1. System filters: one-shots, adult content
    2. Custom skip rules - removes matches entirely (highest precedence)
    3. Confidence recalculation based on cached results
    4. Custom accept rule boost - applies confidence floor immediately (85% exact / 75% other)

    Without kenmeiManga, only system filters are applied (backward-compatible mode).

    Parameters

    • title: string

      Manga title to process cached results for

    • cacheKey: string

      Cache key to retrieve results from

    • OptionalkenmeiManga: KenmeiManga

      Optional Kenmei manga for custom rule evaluation

    Returns null | MangaSearchResponse

    Manga search response with cached results or null if cache invalid

    Custom rules are only evaluated when kenmeiManga is provided to enable proper filtering. For general caching without kenmeiManga, custom rules are not evaluated. Skip rules take precedence over accept rules - if a match satisfies both, it is skipped. Accept rule confidence boost is applied immediately within this function for cached results, ensuring consistent confidence values before returning to the caller.

    export function processCachedResults(
    title: string,
    cacheKey: string,
    kenmeiManga?: KenmeiManga,
    ): MangaSearchResponse | null {
    if (!isCacheValid(cacheKey)) return null;

    console.debug(`[MangaSearchService] Using cache for ${title}`);
    let filteredResults = filterOutBlacklistedManga(
    mangaCache[cacheKey].manga.filter(
    (manga) => manga.format !== "NOVEL" && manga.format !== "LIGHT_NOVEL",
    ),
    );

    const matchConfig = getMatchConfig();

    // Apply system content filters (one-shots, adult content, custom skip rules)
    filteredResults = applySystemContentFilters(
    filteredResults,
    matchConfig,
    kenmeiManga,
    title,
    );

    if (filteredResults.length === 0) {
    console.warn(
    `[MangaSearchService] ⚠️ Cached results for "${title}" were fully filtered out (blacklisted/filtered). Invalidating cache entry and re-running search`,
    );
    delete mangaCache[cacheKey];
    saveCache();
    return null;
    }

    console.debug(
    `[MangaSearchService] ⚖️ Calculating fresh confidence scores for ${filteredResults.length} cached matches`,
    );

    const cacheEntry = mangaCache[cacheKey];
    const comickSourceMap = recordToSourceMap(
    cacheEntry?.sourceMetadata?.comickSources,
    );
    const mangaDexSourceMap = recordToSourceMap(
    cacheEntry?.sourceMetadata?.mangaDexSources,
    );

    const matches = filteredResults.map((manga) => {
    let confidence = calculateConfidence(title, manga);
    const titleTypePriority = calculateTitleTypePriority(manga, title);

    // Apply custom accept rule boost if kenmeiManga provided
    if (kenmeiManga) {
    const { shouldAccept, matchedRule } = shouldAcceptByCustomRules(
    manga,
    kenmeiManga,
    );
    if (shouldAccept && matchedRule) {
    // Apply confidence floor boost immediately for cached results
    const isExactMatch =
    title.toLowerCase() === manga.title?.romaji?.toLowerCase() ||
    title.toLowerCase() === manga.title?.english?.toLowerCase();
    const minConfidence = isExactMatch
    ? ACCEPT_RULE_CONFIDENCE_FLOOR_EXACT
    : ACCEPT_RULE_CONFIDENCE_FLOOR_REGULAR;

    if (confidence < minConfidence) {
    console.debug(
    `[MangaSearchService] ⭐ Boosting cached result confidence from ${(confidence * 100).toFixed(0)}% to ${(minConfidence * 100).toFixed(0)}% for "${manga.title?.romaji || manga.title?.english}" (custom accept rule: "${matchedRule.description}")`,
    );
    confidence = minConfidence;
    }
    }
    }

    console.debug(
    `[MangaSearchService] ⚖️ Cached match confidence for "${manga.title?.english || manga.title?.romaji}": ${(confidence * 100).toFixed(0)}% (priority: ${titleTypePriority})`,
    );

    return { manga, confidence, titleTypePriority };
    });

    // Sort by confidence and priority
    matches.sort((a, b) => {
    if (a.confidence !== b.confidence) {
    return b.confidence - a.confidence;
    }
    return b.titleTypePriority - a.titleTypePriority;
    });

    const finalMatches = matches.map(({ manga, confidence }) => ({
    manga,
    confidence,
    comickSource: comickSourceMap.get(manga.id),
    mangaDexSource: mangaDexSourceMap.get(manga.id),
    sourceInfo: getSourceInfo(manga.id, comickSourceMap, mangaDexSourceMap),
    }));

    return {
    matches: finalMatches,
    pageInfo: undefined,
    };
    }