• Matches multiple Kenmei manga entries efficiently using batch operations.

    Uses cached results, batch ID fetching, and sequential searches for uncached entries. Supports cancellation with partial results return and progress tracking.

    Parameters

    • mangaList: KenmeiManga[]

      Kenmei manga to match.

    • Optionaltoken: string

      Optional authentication token.

    • config: Partial<SearchServiceConfig> = {}

      Optional search service configuration overrides.

    • OptionalprogressCallback: (current: number, total: number, currentTitle?: string) => void

      Optional callback for progress updates (current, total, currentTitle).

    • OptionalshouldCancel: () => boolean

      Optional function to check for cancellation request.

    • OptionalabortSignal: AbortSignal

      Optional abort signal to cancel the operation.

    Returns Promise<MangaMatchResult[]>

    Promise resolving to array of MangaMatchResult objects.

    If operation is cancelled or aborted.

    export async function batchMatchManga(
    mangaList: KenmeiManga[],
    token?: string,
    config: Partial<SearchServiceConfig> = {},
    progressCallback?: (
    current: number,
    total: number,
    currentTitle?: string,
    ) => void,
    shouldCancel?: () => boolean,
    abortSignal?: AbortSignal,
    ): Promise<MangaMatchResult[]> {
    return withGroupAsync(
    `[MangaSearchService] Batch Match (${mangaList.length} manga)`,
    async () => {
    // Ensure we have the latest cache data
    syncWithClientCache();

    const searchConfig = { ...DEFAULT_SEARCH_CONFIG, ...config };

    // Create a set to track which manga have been reported in the progress
    const reportedIndices = new Set<number>();

    // Declare cache variables in outer scope for access in catch block

    // Update progress with deduplication
    const updateProgress = (index: number, title?: string) => {
    if (progressCallback && !reportedIndices.has(index)) {
    reportedIndices.add(index);
    progressCallback(reportedIndices.size, mangaList.length, title);
    }
    };

    // Simplified cancellation check
    const checkCancellation = () => {
    checkCancellationState(abortSignal, shouldCancel, "Batch matching");
    };

    let storage: CachedResultsStorage | undefined;

    try {
    console.info(
    `[MangaSearchService] 🚀 Starting batch matching for ${mangaList.length} manga entries`,
    );

    storage = {
    cachedResults: {},
    cachedComickSources: {},
    cachedMangaDexSources: {},
    cachedFallbackIndices: new Set<number>(),
    };

    // Categorize manga based on cache status
    const result = categorizeMangaForBatching(
    mangaList,
    searchConfig,
    updateProgress,
    );
    storage.cachedResults = result.cachedResults;
    storage.cachedComickSources = result.cachedComickSources;
    storage.cachedMangaDexSources = result.cachedMangaDexSources;
    const { uncachedManga, knownMangaIds } = result;

    console.debug(
    `[MangaSearchService] 🔍 Categorization: ${Object.keys(storage.cachedResults).length} cached, ${uncachedManga.length} uncached, ${knownMangaIds.length} known IDs`,
    );

    // Check for cancellation
    checkCancellation();

    // Process manga with known IDs first
    await processKnownMangaIds(
    { knownMangaIds, mangaList, uncachedManga },
    { searchConfig, token },
    { shouldCancel, abortSignal },
    { updateProgress },
    storage,
    );

    // Check for cancellation
    checkCancellation();

    // Process uncached manga using batched GraphQL queries with automatic rate limit handling
    await processMangaWithRateLimit(
    { uncachedManga, mangaList, reportedIndices },
    { token, searchConfig },
    { abortSignal, checkCancellation },
    { updateProgress },
    storage,
    );

    // Check for cancellation after the batch completes
    checkCancellation();

    console.debug(
    "[MangaSearchService] 🔍 Compiling final match results...",
    );

    // Compile final results
    const finalResults = await compileMatchResults(
    mangaList,
    storage,
    checkCancellation,
    updateProgress,
    searchConfig.shouldUseWorkers,
    );

    console.info(
    `[MangaSearchService] ✅ Batch matching complete: ${finalResults.length} results`,
    );

    return finalResults;
    } catch (error) {
    console.error(
    "[MangaSearchService] ❌ Error in batch matching process:",
    error,
    );

    // If we got here due to cancellation, return whatever partial results we have
    if (error instanceof CancelledError) {
    console.info(
    `[MangaSearchService] Cancellation detected, returning partial results`,
    );
    // Log cancellation as info message, not an exception
    import("@sentry/electron/renderer")
    .then((Sentry) => {
    Sentry.captureMessage("Batch matching cancelled by user", "info");
    })
    .catch(() => {
    // Silently ignore Sentry import errors
    });
    // Return partial results we've gathered so far
    return handleCancellationResults(
    mangaList,
    storage?.cachedResults ?? {},
    );
    }

    // For non-cancellation errors, capture to Sentry
    captureError(ErrorType.UNKNOWN, "Batch manga matching failed", error, {
    mangaListLength: mangaList.length,
    searchConfig: {
    confidenceThreshold: config.matchConfig?.confidenceThreshold,
    },
    stage: "batch_match",
    });

    // Otherwise rethrow the error
    throw error;
    }
    },
    );
    }