• Process a batch of manga updates with rate limiting and progress tracking.

    Parameters

    • mediaEntries: AniListMediaEntry[]

      Array of AniList media entries to sync.

    • token: string

      The user's authentication token.

    • OptionalonProgress: (progress: SyncProgress) => void

      Optional callback for progress updates.

    • OptionalabortSignal: AbortSignal

      Optional abort signal to cancel the sync.

    • OptionaldisplayOrderMediaIds: number[]

      Optional array of media IDs to control sync order.

    • OptionalonBatchComplete: (
          progress: SyncProgress,
          batchResult: { mediaId: number; success: boolean; error?: string },
      ) => void

      Optional callback fired after each media ID completes (batch boundary). Called with current progress state and last batch result to enable checkpoint persistence.

    Returns Promise<SyncReport>

    A promise resolving to a SyncReport object.

    export async function syncMangaBatch(
    mediaEntries: AniListMediaEntry[],
    token: string,
    onProgress?: (progress: SyncProgress) => void,
    abortSignal?: AbortSignal,
    displayOrderMediaIds?: number[],
    onBatchComplete?: (
    progress: SyncProgress,
    batchResult: { mediaId: number; success: boolean; error?: string },
    ) => void,
    ): Promise<SyncReport> {
    return withGroupAsync(
    `[AniListSync] Batch Sync (${mediaEntries.length} entries)`,
    async () => {
    const errors: { mediaId: number; error: string }[] = [];

    // Initialize progress
    const initialProgress: SyncProgress = {
    total: mediaEntries.length,
    completed: 0,
    successful: 0,
    failed: 0,
    skipped: 0,
    currentEntry: null,
    currentStep: null,
    totalSteps: null,
    rateLimited: false,
    retryAfter: null,
    };

    if (onProgress) onProgress({ ...initialProgress });

    // Use worker pool for pre-processing
    const pool = getBatchSyncWorkerPool();
    await pool.initialize();

    try {
    await pool.executeBatchSyncPreprocessing(
    mediaEntries,
    (
    phase: string,
    processed: number,
    total: number,
    currentMediaId?: number,
    ) => {
    // Update progress during pre-processing phase
    const progress: SyncProgress = {
    ...initialProgress,
    completed: processed,
    total: total,
    };
    if (currentMediaId) {
    const mediaEntry = mediaEntries.find(
    (e) => e.mediaId === currentMediaId,
    );
    if (mediaEntry) {
    progress.currentEntry = {
    mediaId: currentMediaId,
    title: mediaEntry.title || "Unknown",
    coverImage: mediaEntry.coverImage || "",
    };
    }
    }
    if (onProgress) onProgress(progress);
    },
    );
    } catch (error) {
    console.error(
    "[AniListSync] ⚠️ Pre-processing failed, continuing with direct sync:",
    error,
    );
    // Pre-processing failure is not fatal - continue with direct sync
    }

    // Organize entries by media ID for handling incremental sync properly
    const entriesGroupedByMediaId = organizeEntriesByMediaId(mediaEntries);

    // Determine processing order and unique entry count
    const userOrderMediaIds = determineProcessingOrder(
    displayOrderMediaIds,
    entriesGroupedByMediaId,
    );
    const uniqueEntryCount = userOrderMediaIds.length;

    // Track progress against unique media IDs rather than incremental steps
    const progress: SyncProgress = {
    total: uniqueEntryCount,
    completed: 0,
    successful: 0,
    failed: 0,
    skipped: 0,
    currentEntry: null,
    currentStep: null,
    totalSteps: null,
    rateLimited: false,
    retryAfter: null,
    };

    if (onProgress) onProgress({ ...progress });

    const apiCallsCompleted = { count: 0 };

    // Process each media ID in order
    for (const mediaId of userOrderMediaIds) {
    await processMediaIdInBatch(mediaId, {
    entriesGroupedByMediaId,
    token,
    apiCallsCompleted,
    progress,
    onProgress,
    abortSignal,
    onBatchComplete,
    errors,
    });

    // If the caller aborted during processing, exit early
    if (abortSignal?.aborted) break;
    }

    return generateSyncReport(mediaEntries, progress, errors);
    },
    );
    }