• Matching page component for the Kenmei to AniList sync tool.

    Handles manga matching, review, rematch, and sync preparation for the user.

    Returns Element

    export function MatchingPage() {
    const navigate = useNavigate();
    const { authState } = useAuth();
    const { rateLimitState } = useRateLimit();

    // State for manga data
    const [manga, setManga] = useState<KenmeiManga[]>([]);
    const [matchResults, setMatchResults] = useState<MangaMatchResult[]>([]);

    // State for manual search
    const [isSearchOpen, setIsSearchOpen] = useState(false);
    const [searchTarget, setSearchTarget] = useState<KenmeiManga | undefined>(
    undefined,
    );

    // Add state for status filtering and rematching
    const [selectedStatuses, setSelectedStatuses] = useState<StatusFilterOptions>(
    {
    pending: true,
    skipped: true,
    matched: false,
    manual: false,
    unmatched: true,
    },
    );
    const [showRematchOptions, setShowRematchOptions] = useState(false);
    const [rematchWarning, setRematchWarning] = useState<string | null>(null);

    // Get matching process hooks
    const matchingProcess = useMatchingProcess({
    accessToken: authState.accessToken || null,
    });
    const pendingMangaState = usePendingManga();

    // Use match handlers
    const matchHandlers = useMatchHandlers(
    matchResults,
    setMatchResults,
    setSearchTarget,
    setIsSearchOpen,
    matchingProcess.setBypassCache,
    );

    // Add a ref to track if we've already done initialization
    const hasInitialized = useRef(false);

    // Add debug effect for matching results
    useEffect(() => {
    if (matchResults.length > 0) {
    console.log("matchResults updated - Current status counts:");
    const statusCounts = {
    matched: matchResults.filter((m) => m.status === "matched").length,
    pending: matchResults.filter((m) => m.status === "pending").length,
    manual: matchResults.filter((m) => m.status === "manual").length,
    skipped: matchResults.filter((m) => m.status === "skipped").length,
    };
    console.log("Status counts:", statusCounts);
    }
    }, [matchResults]);

    // Initial data loading
    useEffect(() => {
    // Strong initialization guard to prevent multiple runs
    if (hasInitialized.current) {
    console.log("Already initialized, skipping initialization");
    return;
    }

    // Mark as initialized immediately to prevent any possibility of re-runs
    hasInitialized.current = true;

    // Check if user is authenticated but don't redirect
    if (!authState.isAuthenticated || !authState.accessToken) {
    console.log("User not authenticated, showing auth error");
    matchingProcess.setError(
    "Authentication Required. You need to connect your AniList account to match manga.",
    );
    matchingProcess.setDetailMessage(
    "Please go to Settings to authenticate with AniList.",
    );
    return;
    }

    console.log("*** INITIALIZATION START ***");
    console.log("Initial states:", {
    isLoading: matchingProcess.isLoading,
    hasError: !!matchingProcess.error,
    matchResultsLength: matchResults.length,
    pendingMangaLength: pendingMangaState.pendingManga.length,
    isMatchingInitialized: matchingProcess.matchingInitialized.current,
    });

    // Check if there's an ongoing matching process
    if (window.matchingProcessState?.isRunning) {
    console.log("Detected running matching process, restoring state");

    // Restore the matching process state
    matchingProcess.setIsLoading(true);
    matchingProcess.setProgress({
    current: window.matchingProcessState.progress.current,
    total: window.matchingProcessState.progress.total,
    currentTitle: window.matchingProcessState.progress.currentTitle,
    });
    matchingProcess.setStatusMessage(
    window.matchingProcessState.statusMessage,
    );
    matchingProcess.setDetailMessage(
    window.matchingProcessState.detailMessage,
    );

    // Mark as initialized to prevent auto-starting
    matchingProcess.matchingInitialized.current = true;
    matchingProcess.setIsInitializing(false);
    return;
    }

    // Skip if this effect has already been run
    if (matchingProcess.matchingInitialized.current) {
    console.log(
    "Matching already initialized, skipping duplicate initialization",
    );
    matchingProcess.setIsInitializing(false);
    return;
    }

    console.log("Initializing MatchingPage component...");

    // Get imported data from storage to have it available for calculations
    const importedData = getKenmeiData();
    const importedManga = importedData?.manga || [];

    if (importedManga.length > 0) {
    console.log(`Found ${importedManga.length} imported manga from storage`);
    // Store the imported manga data for later use
    setManga(importedManga as KenmeiManga[]);
    } else {
    console.log("No imported manga found in storage");
    }

    // First check for pending manga from a previously interrupted operation
    const pendingMangaData = pendingMangaState.loadPendingManga();

    if (pendingMangaData && pendingMangaData.length > 0) {
    // Clear any error message since we're showing the resume notification instead
    matchingProcess.setError(null);
    // End initialization when we've found pending manga
    matchingProcess.setIsInitializing(false);
    }

    // Preload the cache service to ensure it's initialized
    import("../api/matching/manga-search-service").then((module) => {
    console.log("Preloaded manga search service");
    // Force cache sync
    if (module.cacheDebugger) {
    module.cacheDebugger.forceSyncCaches();
    }

    // Check if we have saved match results before starting a new matching process
    console.log("Checking for saved match results...");
    const savedResults = getSavedMatchResults();
    if (
    savedResults &&
    Array.isArray(savedResults) &&
    savedResults.length > 0
    ) {
    console.log(
    `Found ${savedResults.length} existing match results - loading from cache`,
    );

    // Check how many matches have already been reviewed
    const reviewedCount = savedResults.filter(
    (m) =>
    m.status === "matched" ||
    m.status === "manual" ||
    m.status === "skipped",
    ).length;

    console.log(
    `${reviewedCount} manga have already been reviewed (${Math.round((reviewedCount / savedResults.length) * 100)}% complete)`,
    );

    // If we don't have pending manga from storage but do have imported manga, calculate what might still need processing
    if (!pendingMangaData && importedManga.length > 0) {
    console.log(
    "No pending manga in storage but we have imported manga - calculating unmatched manga",
    );
    const calculatedPendingManga =
    pendingMangaState.calculatePendingManga(
    savedResults as MangaMatchResult[],
    importedManga as KenmeiManga[],
    );
    if (calculatedPendingManga.length > 0) {
    console.log(
    `Calculated ${calculatedPendingManga.length} manga that still need to be processed`,
    );
    pendingMangaState.savePendingManga(calculatedPendingManga);
    console.log(
    `Saved ${calculatedPendingManga.length} pending manga to storage for resume`,
    );
    } else {
    console.log("No pending manga found in calculation");

    // If there's a clear discrepancy between total manga and processed manga,
    // force a calculation of pending manga based on the numerical difference
    if (importedManga.length > savedResults.length) {
    console.log(
    `⚠️ Discrepancy detected! Total manga: ${importedManga.length}, Processed: ${savedResults.length}`,
    );
    console.log(
    "Forcing pending manga calculation based on numerical difference",
    );

    // Calculate how many manga are missing from the results
    const pendingCount = importedManga.length - savedResults.length;
    // Get manga that aren't in savedResults
    const pendingManga = importedManga.slice(0, pendingCount);

    if (pendingManga.length > 0) {
    console.log(
    `Forced calculation found ${pendingManga.length} pending manga`,
    );
    pendingMangaState.savePendingManga(
    pendingManga as KenmeiManga[],
    );
    }
    }
    }
    }

    // Mark as initialized
    matchingProcess.matchingInitialized.current = true;
    // Set the saved results directly
    setMatchResults(savedResults as MangaMatchResult[]);
    console.log("*** INITIALIZATION COMPLETE - Using saved results ***");
    return; // Skip further initialization
    } else {
    console.log("No saved match results found");
    }

    if (
    importedManga.length &&
    !matchingProcess.matchingInitialized.current
    ) {
    console.log("Starting initial matching process with imported manga");
    matchingProcess.matchingInitialized.current = true;

    // Start matching process automatically
    matchingProcess.startMatching(
    importedManga as KenmeiManga[],
    false,
    setMatchResults,
    );
    } else if (!importedManga.length) {
    console.log("No imported manga found, redirecting to import page");
    matchingProcess.setError(
    "No manga data found. Please import your data first.",
    );
    }

    // Make sure we mark initialization as complete
    matchingProcess.setIsInitializing(false);
    console.log("*** INITIALIZATION COMPLETE ***");
    });

    // Cleanup function to ensure initialization state is reset
    return () => {
    matchingProcess.setIsInitializing(false);
    };
    }, [navigate, matchingProcess, pendingMangaState]); // Remove matchResults from dependencies

    // Add an effect to sync with the global process state while the page is mounted
    useEffect(() => {
    // Skip if we're not in the middle of a process
    if (!window.matchingProcessState?.isRunning) return;

    // Create a function to sync the UI with the global state
    const syncUIWithGlobalState = () => {
    if (window.matchingProcessState?.isRunning) {
    const currentState = window.matchingProcessState;

    console.log("Syncing UI with global process state:", {
    current: currentState.progress.current,
    total: currentState.progress.total,
    statusMessage: currentState.statusMessage,
    });

    // Update our local state with the global state
    matchingProcess.setIsLoading(true);
    matchingProcess.setProgress({
    current: currentState.progress.current,
    total: currentState.progress.total,
    currentTitle: currentState.progress.currentTitle,
    });
    matchingProcess.setStatusMessage(currentState.statusMessage);
    matchingProcess.setDetailMessage(currentState.detailMessage);
    } else {
    // If the process is no longer running, update our loading state
    console.log("Global process complete, syncing final state");
    matchingProcess.setIsLoading(false);
    }
    };

    // Create a visibility change listener to ensure UI updates when page becomes visible
    const handleVisibilityChange = () => {
    if (document.visibilityState === "visible") {
    console.log("Page became visible, syncing state immediately");
    syncUIWithGlobalState();
    }
    };

    // Add visibility change listener
    document.addEventListener("visibilitychange", handleVisibilityChange);

    // Create an interval to check for updates to the global state (less frequently since we also have visibility events)
    const syncInterval = setInterval(() => {
    if (document.visibilityState === "visible") {
    syncUIWithGlobalState();
    }
    }, 2000); // Check every 2 seconds when visible

    // Clean up the interval and event listener when the component unmounts
    return () => {
    clearInterval(syncInterval);
    document.removeEventListener("visibilitychange", handleVisibilityChange);
    };
    }, []); // Only run once when component mounts

    // Add an effect to listen for re-search empty matches events
    useEffect(() => {
    // Handler for the reSearchEmptyMatches custom event
    const handleReSearchEmptyMatches = (
    event: CustomEvent<{ mangaToResearch: KenmeiManga[] }>,
    ) => {
    const { mangaToResearch } = event.detail;

    console.log(
    `Received request to re-search ${mangaToResearch.length} manga without matches`,
    );

    // Reset any previous warnings or cancel state
    setRematchWarning(null);
    matchingProcess.cancelMatchingRef.current = false;
    matchingProcess.setDetailMessage(null);

    if (mangaToResearch.length === 0) {
    console.log("No manga to re-search, ignoring request");
    return;
    }

    // Show cache clearing notification with count
    matchingProcess.setIsCacheClearing(true);
    matchingProcess.setCacheClearingCount(mangaToResearch.length);
    matchingProcess.setStatusMessage(
    "Preparing to clear cache for manga without matches...",
    );

    // Use a Promise to handle the async operations
    (async () => {
    try {
    // Small delay to ensure UI updates
    await new Promise((resolve) => setTimeout(resolve, 100));

    // Get the cache service to clear specific entries
    await import("../api/matching/manga-search-service");

    // Clear cache entries for each manga being re-searched
    const mangaTitles = mangaToResearch.map((manga) => manga.title);
    console.log(
    `🔄 Clearing cache for ${mangaTitles.length} manga titles`,
    );
    matchingProcess.setStatusMessage(
    `Clearing cache for ${mangaTitles.length} manga titles...`,
    );

    // Use the clearCacheForTitles function to clear entries efficiently
    const clearResult = clearCacheForTitles(mangaTitles);

    // Log results
    console.log(
    `🧹 Cleared ${clearResult.clearedCount} cache entries for re-search`,
    );

    // Before starting fresh search, preserve existing results but reset re-searched manga to pending
    if (matchResults.length > 0) {
    // Create a set of titles being re-searched (for quick lookup)
    const reSearchTitles = new Set(
    mangaTitles.map((title) => title.toLowerCase()),
    );

    // Update the match results to set re-searched items back to pending
    const updatedResults = matchResults.map((match) => {
    // If this manga is being re-searched, reset its status to pending
    if (reSearchTitles.has(match.kenmeiManga.title.toLowerCase())) {
    return {
    ...match,
    status: "pending" as const,
    selectedMatch: undefined, // Clear any previously selected match
    matchDate: new Date(),
    };
    }
    // Otherwise, keep it as is
    return match;
    });

    // Update the results state
    setMatchResults(updatedResults);
    console.log(
    `Reset status to pending for ${reSearchTitles.size} manga before re-searching`,
    );
    }

    // Hide cache clearing notification
    matchingProcess.setIsCacheClearing(false);
    matchingProcess.setStatusMessage(
    `Cleared cache entries - starting fresh searches...`,
    );

    // Start fresh search for the manga
    matchingProcess.startMatching(mangaToResearch, true, setMatchResults);
    } catch (error) {
    console.error("Failed to clear manga cache entries:", error);
    matchingProcess.setIsCacheClearing(false);

    // Continue with re-search even if cache clearing fails
    matchingProcess.startMatching(mangaToResearch, true, setMatchResults);
    }
    })();
    };

    // Add event listener for the custom event
    window.addEventListener(
    "reSearchEmptyMatches",
    handleReSearchEmptyMatches as EventListener,
    );

    // Clean up the event listener when the component unmounts
    return () => {
    window.removeEventListener(
    "reSearchEmptyMatches",
    handleReSearchEmptyMatches as EventListener,
    );
    };
    }, [matchingProcess, setMatchResults]);

    /**
    * Handle retry button click
    */
    const handleRetry = () => {
    // Clear any pending manga data
    pendingMangaState.savePendingManga([]);

    if (manga.length > 0) {
    matchingProcess.startMatching(manga, false, setMatchResults);
    }
    };

    /**
    * Format future sync path
    */
    const getSyncPath = () => {
    // When we have a sync route, return that instead
    return "/sync";
    };

    /**
    * Proceed to synchronization
    */
    const handleProceedToSync = () => {
    // Count how many matches we have
    const matchedCount = matchResults.filter(
    (m) => m.status === "matched" || m.status === "manual",
    ).length;

    if (matchedCount === 0) {
    matchingProcess.setError(
    "No matches have been approved. Please review and accept matches before proceeding.",
    );
    return;
    }

    navigate({ to: getSyncPath() });
    };

    /**
    * Handle rematch by status
    */
    const handleRematchByStatus = async () => {
    // Reset any previous warnings
    setRematchWarning(null);

    // Reset any previous cancel state
    matchingProcess.cancelMatchingRef.current = false;
    matchingProcess.setDetailMessage(null);

    console.log("=== REMATCH DEBUG INFO ===");
    console.log(`Total manga in state: ${manga.length}`);
    console.log(`Total match results: ${matchResults.length}`);
    console.log(
    `Displayed unmatched count: ${manga.length - matchResults.length}`,
    );

    // Get manga that have been processed but match selected statuses
    const filteredManga = matchResults.filter(
    (manga) =>
    selectedStatuses[manga.status as keyof typeof selectedStatuses] ===
    true,
    );
    console.log(`Filtered manga from results: ${filteredManga.length}`);

    // Find unmatched manga that aren't in matchResults yet
    let unmatchedManga: KenmeiManga[] = [];
    if (selectedStatuses.unmatched) {
    console.log("Finding unmatched manga using title-based matching first");

    // Create a set of processed manga titles (lowercase for case-insensitive comparison)
    const processedTitles = new Set(
    matchResults.map((r) => r.kenmeiManga.title.toLowerCase()),
    );
    console.log(
    `Created set with ${processedTitles.size} processed manga titles`,
    );

    // Find manga that don't have matching titles in the processed set
    unmatchedManga = manga.filter(
    (m) => !processedTitles.has(m.title.toLowerCase()),
    );
    console.log(
    `Title-based approach found ${unmatchedManga.length} unmatched manga to process`,
    );

    // If we didn't find any unmatched manga with title matching, try ID-based matching as fallback
    if (unmatchedManga.length === 0) {
    console.log(
    "Title matching found no manga, trying ID-based matching as fallback",
    );

    // Create a set of processed manga IDs for faster lookup
    const processedIds = new Set(matchResults.map((r) => r.kenmeiManga.id));
    console.log(`Found ${processedIds.size} processed manga IDs`);

    // Find manga that haven't been processed at all
    unmatchedManga = manga.filter((m) => !processedIds.has(m.id));
    console.log(
    `ID-based approach found ${unmatchedManga.length} unmatched manga to process`,
    );
    }

    // Debug info to help diagnose the issue
    console.log(
    `Total manga titles in state: ${manga.map((m) => m.title).length}`,
    );
    console.log(
    `Unique manga titles in state: ${new Set(manga.map((m) => m.title.toLowerCase())).size}`,
    );
    console.log(
    `Total results titles: ${matchResults.map((r) => r.kenmeiManga.title).length}`,
    );
    console.log(
    `Unique results titles: ${new Set(matchResults.map((r) => r.kenmeiManga.title.toLowerCase())).size}`,
    );

    // Log any duplicate manga titles in the state
    const titleOccurrences = manga.reduce(
    (acc, m) => {
    const title = m.title.toLowerCase();
    acc[title] = (acc[title] || 0) + 1;
    return acc;
    },
    {} as Record<string, number>,
    );

    const duplicates = Object.entries(titleOccurrences)
    .filter(([, count]) => count > 1)
    .map(([title, count]) => ({ title, count }));

    if (duplicates.length > 0) {
    console.log(
    `Found ${duplicates.length} duplicate titles in the manga list:`,
    );
    console.log(duplicates);
    }

    // If we still didn't find any unmatched manga but there's a clear numerical difference,
    // use a fallback approach to ensure we're processing something
    if (unmatchedManga.length === 0 && manga.length > matchResults.length) {
    console.log("Using fallback numerical difference approach");

    // Calculate how many manga should be unmatched
    const expectedUnmatched = manga.length - matchResults.length;
    console.log(
    `Expected unmatched count based on total difference: ${expectedUnmatched}`,
    );

    // Last resort - just take the first N manga
    unmatchedManga = manga.slice(0, expectedUnmatched);
    console.log(
    `Last resort: using first ${unmatchedManga.length} manga as unmatched`,
    );
    }
    }

    // Combine the filtered manga with unmatched manga
    const pendingMangaToProcess = [
    ...filteredManga.map((item) => item.kenmeiManga),
    ...unmatchedManga,
    ];

    console.log(`Total manga to process: ${pendingMangaToProcess.length}`);
    console.log(
    "IMPORTANT: Will clear cache entries for selected manga to ensure fresh searches",
    );
    console.log("=== END DEBUG INFO ===");

    // Show more specific error message depending on what's selected
    if (pendingMangaToProcess.length === 0) {
    if (selectedStatuses.unmatched && unmatchedManga.length === 0) {
    // If unmatched is selected but there are no unmatched manga
    setRematchWarning(
    "There are no unmatched manga to process. All manga have been processed previously.",
    );
    } else {
    // Generic message for other cases
    setRematchWarning(
    "No manga found with the selected statuses. Please select different statuses.",
    );
    }
    return;
    }

    console.log(
    `Rematching ${filteredManga.length} status-filtered manga and ${unmatchedManga.length} unmatched manga`,
    );

    try {
    // Show cache clearing notification with count
    matchingProcess.setIsCacheClearing(true);
    matchingProcess.setCacheClearingCount(pendingMangaToProcess.length);
    matchingProcess.setStatusMessage(
    "Preparing to clear cache for selected manga...",
    );

    // Small delay to ensure UI updates before potentially intensive operation
    await new Promise((resolve) => setTimeout(resolve, 100));

    // Get the cache service to clear specific entries
    const { cacheDebugger } = await import(
    "../api/matching/manga-search-service"
    );

    // Get initial cache status for comparison
    const initialCacheStatus = cacheDebugger.getCacheStatus();
    console.log(
    `📊 Initial cache status: ${initialCacheStatus.inMemoryCache} entries in memory, ${initialCacheStatus.localStorage.mangaCache} in localStorage`,
    );

    // Clear cache entries for each manga being rematched - use our new dedicated function
    const mangaTitles = pendingMangaToProcess.map((manga) => manga.title);
    console.log(
    `🔄 Clearing cache for ${mangaTitles.length} manga titles at once`,
    );
    matchingProcess.setStatusMessage(
    `Clearing cache for ${mangaTitles.length} manga titles...`,
    );

    // Use our new function to clear cache entries efficiently
    const clearResult = clearCacheForTitles(mangaTitles);

    // Log the results
    console.log(
    `🧹 Cleared ${clearResult.clearedCount} cache entries for selected manga`,
    );
    if (clearResult.clearedCount > 0 && mangaTitles.length > 0) {
    console.log(
    "Cleared titles:",
    mangaTitles.slice(0, 5).join(", ") +
    (mangaTitles.length > 5
    ? ` and ${mangaTitles.length - 5} more...`
    : ""),
    );
    }

    // Log final cache status
    console.log(
    `📊 Final cache status: ${clearResult.remainingCacheSize} entries in memory (removed ${clearResult.clearedCount})`,
    );

    // Hide cache clearing notification
    matchingProcess.setIsCacheClearing(false);
    matchingProcess.setStatusMessage(
    `Cleared ${clearResult.clearedCount} cache entries - preparing fresh searches from AniList...`,
    );

    // Reset the options panel and start matching
    setShowRematchOptions(false);

    // Before starting the matching process, save the existing matchResults
    // Filter out the ones we're about to rematch to avoid duplicates
    const mangaIdsToRematch = new Set(pendingMangaToProcess.map((m) => m.id));
    const existingResults = matchResults.filter(
    (m) => !mangaIdsToRematch.has(m.kenmeiManga.id),
    );

    console.log(
    `Preserved ${existingResults.length} existing match results that aren't being rematched`,
    );

    // Start fresh search
    matchingProcess.startMatching(
    pendingMangaToProcess,
    true,
    setMatchResults,
    );
    } catch (error) {
    console.error("Failed to clear manga cache entries:", error);
    matchingProcess.setIsCacheClearing(false);
    // Continue with rematch even if cache clearing fails
    matchingProcess.startMatching(
    pendingMangaToProcess,
    true,
    setMatchResults,
    );
    }
    };

    // Loading state
    if (matchingProcess.isLoading) {
    return (
    <motion.div
    className="container mx-auto max-w-5xl space-y-6 px-4 py-8 md:px-6"
    variants={pageVariants}
    initial="hidden"
    animate="visible"
    >
    <motion.div className="space-y-2" variants={headerVariants}>
    <h1 className="bg-gradient-to-r from-blue-600 to-indigo-600 bg-clip-text text-3xl font-bold text-transparent">
    Match Your Manga
    </h1>
    <p className="text-muted-foreground max-w-2xl">
    Automatically match your imported manga with AniList entries
    </p>
    </motion.div>

    {/* Loading State with Progress and Cancel Button */}
    <motion.div variants={contentVariants}>
    <MatchingProgressPanel
    isCancelling={matchingProcess.isCancelling}
    progress={matchingProcess.progress}
    statusMessage={matchingProcess.statusMessage}
    detailMessage={matchingProcess.detailMessage}
    timeEstimate={matchingProcess.timeEstimate}
    onCancelProcess={matchingProcess.handleCancelProcess}
    bypassCache={matchingProcess.bypassCache}
    freshSearch={matchingProcess.freshSearch}
    disableControls={rateLimitState.isRateLimited}
    />
    </motion.div>

    {/* Error Display */}
    {matchingProcess.error && !matchResults.length && (
    <motion.div
    initial={{ opacity: 0, scale: 0.95 }}
    animate={{ opacity: 1, scale: 1 }}
    transition={{ delay: 0.25, duration: 0.3 }}
    >
    {matchingProcess.error.includes("Authentication Required") ? (
    <Card className="mx-auto w-full max-w-lg overflow-hidden border-amber-200 bg-amber-50/30 text-center dark:border-amber-800/30 dark:bg-amber-900/10">
    <CardContent className="pt-6 pb-4">
    <div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-amber-100 dark:bg-amber-900/30">
    <AlertCircle className="h-6 w-6 text-amber-600 dark:text-amber-400" />
    </div>
    <h3 className="text-lg font-medium">
    Authentication Required
    </h3>
    <p className="mt-2 text-sm text-slate-600 dark:text-slate-400">
    You need to be authenticated with AniList to match your
    manga.
    </p>
    <Button
    onClick={() => navigate({ to: "/settings" })}
    className="mt-4 bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700"
    >
    Go to Settings
    </Button>
    </CardContent>
    </Card>
    ) : (
    <ErrorDisplay
    error={matchingProcess.error}
    detailedError={matchingProcess.detailedError}
    onRetry={handleRetry}
    onClearPendingManga={() =>
    pendingMangaState.savePendingManga([])
    }
    />
    )}
    </motion.div>
    )}
    </motion.div>
    );
    }

    return (
    <motion.div
    className="container mx-auto flex h-full max-w-full flex-col px-4 py-6 md:px-6"
    variants={pageVariants}
    initial="hidden"
    animate="visible"
    >
    {/* Authentication Error - Display regardless of loading state */}
    {matchingProcess.error &&
    matchingProcess.error.includes("Authentication Required") ? (
    <motion.div
    initial={{ opacity: 0, scale: 0.95 }}
    animate={{ opacity: 1, scale: 1 }}
    transition={{ delay: 0.25, duration: 0.3 }}
    >
    <Card className="mx-auto w-full max-w-lg overflow-hidden border-amber-200 bg-amber-50/30 text-center dark:border-amber-800/30 dark:bg-amber-900/10">
    <CardContent className="pt-6 pb-4">
    <div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-amber-100 dark:bg-amber-900/30">
    <AlertCircle className="h-6 w-6 text-amber-600 dark:text-amber-400" />
    </div>
    <h3 className="text-lg font-medium">Authentication Required</h3>
    <p className="mt-2 text-sm text-slate-600 dark:text-slate-400">
    You need to be authenticated with AniList to match your manga.
    </p>
    <Button
    onClick={() => navigate({ to: "/settings" })}
    className="mt-4 bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700"
    >
    Go to Settings
    </Button>
    </CardContent>
    </Card>
    </motion.div>
    ) : (
    <>
    <motion.header className="mb-6 space-y-2" variants={headerVariants}>
    <div className="flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
    <h1 className="bg-gradient-to-r from-blue-600 to-indigo-600 bg-clip-text text-3xl font-bold text-transparent">
    Review Your Manga
    </h1>
    {matchResults.length > 0 && !matchingProcess.isLoading && (
    <motion.div
    initial={{ opacity: 0, scale: 0.9 }}
    animate={{ opacity: 1, scale: 1 }}
    transition={{ delay: 0.2, duration: 0.25 }}
    >
    <Button
    onClick={() => setShowRematchOptions(!showRematchOptions)}
    variant="default"
    className="bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700"
    >
    {showRematchOptions
    ? "Hide Rematch Options"
    : "Fresh Search (Clear Cache)"}
    </Button>
    </motion.div>
    )}
    </div>
    <p className="text-muted-foreground max-w-2xl">
    Review the matches found between your Kenmei manga and AniList.
    </p>
    </motion.header>

    {/* Rematch by status options */}
    <AnimatePresence>
    {showRematchOptions &&
    !matchingProcess.isLoading &&
    matchResults.length > 0 && (
    <RematchOptions
    selectedStatuses={selectedStatuses}
    onChangeSelectedStatuses={setSelectedStatuses}
    matchResults={matchResults}
    rematchWarning={rematchWarning}
    onRematchByStatus={handleRematchByStatus}
    onCloseOptions={() => setShowRematchOptions(false)}
    />
    )}
    </AnimatePresence>

    {/* Initialization state - only show if not already loading and we have pending manga */}
    {matchingProcess.isInitializing &&
    !matchingProcess.isLoading &&
    pendingMangaState.pendingManga.length > 0 && (
    <motion.div
    initial={{ opacity: 0, y: 20 }}
    animate={{ opacity: 1, y: 0 }}
    transition={{ delay: 0.2, duration: 0.3 }}
    >
    <Card className="mb-6 border-blue-100 bg-blue-50/50 shadow-md dark:border-blue-900/50 dark:bg-blue-900/20">
    <CardContent className="flex items-center justify-between p-4">
    <div className="flex items-center space-x-3">
    <div className="relative flex h-8 w-8 items-center justify-center">
    <div className="absolute h-full w-full animate-ping rounded-full bg-blue-400 opacity-20"></div>
    <Loader2 className="h-5 w-5 animate-spin text-blue-600 dark:text-blue-400" />
    </div>
    <div>
    <p className="font-medium text-blue-700 dark:text-blue-300">
    Checking for pending manga to resume...
    </p>
    <p className="text-sm text-blue-600/80 dark:text-blue-400/80">
    Please wait while we analyze your previous session
    </p>
    </div>
    </div>
    </CardContent>
    </Card>
    </motion.div>
    )}

    {/* Resume message when we have pending manga but aren't already in the loading state */}
    {(pendingMangaState.pendingManga.length > 0 ||
    manga.length > matchResults.length) &&
    !matchingProcess.isLoading &&
    !matchingProcess.isInitializing &&
    (() => {
    console.log(
    `Checking if ${pendingMangaState.pendingManga.length || manga.length - matchResults.length} pending manga need processing...`,
    );

    let needsProcessing = false;
    let unprocessedCount = 0;

    // First check: Pending manga from storage
    if (pendingMangaState.pendingManga.length > 0) {
    // Since IDs are undefined, use title-based matching instead
    const processedTitles = new Set(
    matchResults.map((r) => r.kenmeiManga.title.toLowerCase()),
    );

    // Check if any of the pending manga aren't already processed by title
    const pendingTitles = pendingMangaState.pendingManga.map((m) =>
    m.title.toLowerCase(),
    );
    needsProcessing = pendingTitles.some(
    (title) => !processedTitles.has(title),
    );

    // Calculate how many manga still need processing
    unprocessedCount = pendingMangaState.pendingManga.filter(
    (manga) => !processedTitles.has(manga.title.toLowerCase()),
    ).length;

    console.log(
    `Title-based check on stored pending manga: ${needsProcessing ? "Pending manga need processing" : "All pending manga are already processed"}`,
    );
    console.log(
    `${unprocessedCount} manga from stored pending manga need processing`,
    );

    if (
    !needsProcessing &&
    pendingMangaState.pendingManga.length > 0
    ) {
    console.log(
    "All pending manga titles are already in matchResults - clearing pending manga",
    );
    pendingMangaState.savePendingManga([]);
    }
    }

    // Second check: Count difference between all manga and processed results
    if (manga.length > matchResults.length) {
    console.log(
    `${manga.length - matchResults.length} manga still need processing based on count difference`,
    );

    // Use title-based matching to find unprocessed manga
    const processedTitles = new Set(
    matchResults.map((r) => r.kenmeiManga.title.toLowerCase()),
    );

    const stillNeedProcessing = manga.filter(
    (m) => !processedTitles.has(m.title.toLowerCase()),
    ).length;

    console.log(
    `${stillNeedProcessing} manga actually need processing based on title comparison`,
    );

    if (stillNeedProcessing > 0) {
    needsProcessing = true;
    unprocessedCount = Math.max(
    unprocessedCount,
    stillNeedProcessing,
    );
    }
    }

    return needsProcessing;
    })() && (
    <motion.div
    initial={{ opacity: 0, y: 20 }}
    animate={{ opacity: 1, y: 0 }}
    transition={{ delay: 0.4, duration: 0.3 }}
    >
    <ResumeNotification
    // Only count manga that actually need processing
    pendingMangaCount={(() => {
    const processedTitles = new Set(
    matchResults.map((r) =>
    r.kenmeiManga.title.toLowerCase(),
    ),
    );

    // Check stored pending manga
    const unprocessedFromPending =
    pendingMangaState.pendingManga.filter(
    (manga) =>
    !processedTitles.has(manga.title.toLowerCase()),
    ).length;

    // Check all manga
    const unprocessedFromAll = manga.filter(
    (m) => !processedTitles.has(m.title.toLowerCase()),
    ).length;

    // Return the larger of the two counts
    return Math.max(unprocessedFromPending, unprocessedFromAll);
    })()}
    onResumeMatching={() => {
    console.log(
    "Resume matching clicked - ensuring unprocessed manga are processed",
    );

    // Get ALL unprocessed manga by comparing the full manga list with processed titles
    const processedTitles = new Set(
    matchResults.map((r) =>
    r.kenmeiManga.title.toLowerCase(),
    ),
    );

    // Find unprocessed manga by title comparison from the ENTIRE manga list
    const unprocessedManga = manga.filter(
    (m) => !processedTitles.has(m.title.toLowerCase()),
    );

    if (unprocessedManga.length > 0) {
    console.log(
    `Found ${unprocessedManga.length} unprocessed manga from the full manga list`,
    );

    // ALWAYS update the pendingManga with ALL unprocessed manga before resuming
    // This ensures we're processing all 870 manga not just the 21
    console.log(
    `Setting pendingManga to ${unprocessedManga.length} unprocessed manga before resuming`,
    );
    pendingMangaState.savePendingManga(unprocessedManga);

    // Small delay to ensure state is updated before continuing
    setTimeout(() => {
    // Now call the resume function, which will use the newly updated pendingManga
    matchingProcess.handleResumeMatching(
    matchResults,
    setMatchResults,
    );
    }, 100);
    } else {
    console.log(
    "No unprocessed manga found, trying to resume anyway",
    );
    matchingProcess.handleResumeMatching(
    matchResults,
    setMatchResults,
    );
    }
    }}
    onCancelResume={matchingProcess.handleCancelResume}
    />
    </motion.div>
    )}

    {/* Main content */}
    <motion.div className="relative flex-1" variants={contentVariants}>
    {matchResults.length > 0 ? (
    <>
    {/* The main matching panel */}
    {
    <motion.div
    className="mb-6 flex h-full flex-col"
    initial={{ opacity: 0 }}
    animate={{ opacity: 1 }}
    transition={{ delay: 0.25, duration: 0.3 }}
    >
    <MangaMatchingPanel
    matches={matchResults}
    onManualSearch={matchHandlers.handleManualSearch}
    onAcceptMatch={matchHandlers.handleAcceptMatch}
    onRejectMatch={matchHandlers.handleRejectMatch}
    onSelectAlternative={
    matchHandlers.handleSelectAlternative
    }
    onResetToPending={matchHandlers.handleResetToPending}
    />

    {/* Action buttons */}
    <motion.div
    className="mt-6 flex flex-col-reverse justify-end space-y-4 space-y-reverse sm:flex-row sm:space-y-0 sm:space-x-4"
    initial={{ opacity: 0, y: 20 }}
    animate={{ opacity: 1, y: 0 }}
    transition={{ delay: 0.35, duration: 0.3 }}
    >
    <Button
    variant="outline"
    onClick={() => {
    // Clear any pending manga data
    pendingMangaState.savePendingManga([]);
    navigate({ to: "/import" });
    }}
    >
    Back to Import
    </Button>
    <Button
    className="bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700"
    onClick={handleProceedToSync}
    >
    Proceed to Sync
    </Button>
    </motion.div>
    </motion.div>
    }
    </>
    ) : (
    // No results state
    <motion.div
    className="bg-background/50 flex h-full min-h-[60vh] flex-col items-center justify-center rounded-lg border border-dashed border-gray-300 p-12 text-center backdrop-blur-sm dark:border-gray-700"
    initial={{ opacity: 0 }}
    animate={{ opacity: 1 }}
    transition={{ delay: 0.2, duration: 0.3 }}
    >
    <motion.div
    className="mx-auto mb-6 flex h-20 w-20 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900/30"
    initial={{ scale: 0, opacity: 0 }}
    animate={{ scale: 1, opacity: 1 }}
    transition={{
    delay: 0.3,
    type: "spring",
    stiffness: 200,
    damping: 20,
    }}
    >
    <svg
    xmlns="http://www.w3.org/2000/svg"
    className="h-10 w-10 text-blue-600 dark:text-blue-400"
    fill="none"
    viewBox="0 0 24 24"
    stroke="currentColor"
    >
    <path
    strokeLinecap="round"
    strokeLinejoin="round"
    strokeWidth={2}
    d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"
    />
    </svg>
    </motion.div>
    <motion.div
    initial={{ opacity: 0, y: 20 }}
    animate={{ opacity: 1, y: 0 }}
    transition={{ delay: 0.4, duration: 0.3 }}
    >
    <h3 className="mb-2 text-xl font-semibold">
    No Manga To Match
    </h3>
    <p className="text-muted-foreground mb-6 text-sm">
    No manga data to match. Return to the import page to load
    your data.
    </p>
    <Button
    className="bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700"
    onClick={() => {
    // Clear any pending manga data
    pendingMangaState.savePendingManga([]);
    navigate({ to: "/import" });
    }}
    >
    Go to Import Page
    </Button>
    </motion.div>
    </motion.div>
    )}
    </motion.div>

    {/* Search Modal */}
    <SearchModal
    isOpen={isSearchOpen}
    searchTarget={searchTarget}
    accessToken={authState.accessToken || ""}
    bypassCache={true}
    onClose={() => {
    setIsSearchOpen(false);
    setSearchTarget(undefined);
    matchingProcess.setBypassCache(false);
    }}
    onSelectMatch={matchHandlers.handleSelectSearchMatch}
    />

    {/* Cache Clearing Notification */}
    {matchingProcess.isCacheClearing && (
    <CacheClearingNotification
    cacheClearingCount={matchingProcess.cacheClearingCount}
    />
    )}

    {/* Error display when we have an error but also have results */}
    <AnimatePresence>
    {matchingProcess.error &&
    !matchingProcess.error.includes("Authentication Required") &&
    matchResults.length > 0 &&
    !rateLimitState.isRateLimited && (
    <motion.div
    className="fixed right-4 bottom-4 max-w-sm rounded-md bg-red-50 p-4 shadow-lg dark:bg-red-900/30"
    initial={{ opacity: 0, x: 20, y: 20 }}
    animate={{ opacity: 1, x: 0, y: 0 }}
    exit={{ opacity: 0, x: 20 }}
    transition={{ duration: 0.2 }}
    >
    <div className="flex">
    <div className="flex-shrink-0">
    <svg
    className="h-5 w-5 text-red-400"
    xmlns="http://www.w3.org/2000/svg"
    viewBox="0 0 20 20"
    fill="currentColor"
    >
    <path
    fillRule="evenodd"
    d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
    clipRule="evenodd"
    />
    </svg>
    </div>
    <div className="ml-3">
    <p className="text-sm font-medium text-red-800 dark:text-red-200">
    {matchingProcess.error}
    </p>
    <div className="mt-2 flex space-x-2">
    <button
    onClick={() => matchingProcess.setError(null)}
    className="rounded-md bg-red-100 px-2 py-1 text-xs font-medium text-red-800 hover:bg-red-200 dark:bg-red-800 dark:text-red-100 dark:hover:bg-red-700"
    >
    Dismiss
    </button>
    </div>
    </div>
    </div>
    </motion.div>
    )}
    </AnimatePresence>
    </>
    )}
    </motion.div>
    );
    }