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

    Handles synchronization preview, configuration, execution, and results display for the user.

    Returns Element

    export function SyncPage() {
    const navigate = useNavigate();
    const { authState } = useAuth();
    const token = authState.accessToken || "";
    const [state, actions] = useSynchronization();
    const [viewMode, setViewMode] = useState<ViewMode>("preview");
    const { rateLimitState, setRateLimit } = useRateLimit();

    // Authentication and validation states
    const [authError, setAuthError] = useState(false);
    const [matchDataError, setMatchDataError] = useState(false);
    const [validMatchesError, setValidMatchesError] = useState(false);

    // Authentication and data validation check
    useEffect(() => {
    // Check if user is authenticated
    if (!authState.isAuthenticated || !token) {
    console.log("User not authenticated, showing auth error");
    setAuthError(true);
    return;
    } else {
    setAuthError(false);
    }

    // Check if there are match results to sync
    const savedResults = getSavedMatchResults();
    if (
    !savedResults ||
    !Array.isArray(savedResults) ||
    savedResults.length === 0
    ) {
    console.log("No match results found, showing match data error");
    setMatchDataError(true);
    return;
    } else {
    setMatchDataError(false);
    }

    // Validate that there are actual matches (not just skipped entries)
    const validMatches = savedResults.filter(
    (match) => match.status === "matched" || match.status === "manual",
    );

    if (validMatches.length === 0) {
    console.log("No valid matches found, showing valid matches error");
    setValidMatchesError(true);
    return;
    } else {
    setValidMatchesError(false);
    }

    console.log(
    `Found ${validMatches.length} valid matches for synchronization`,
    );
    }, [authState.isAuthenticated, token]);

    // Sync configuration options
    const [syncConfig, setSyncConfig] = useState<SyncConfig>(getSyncConfig());
    // Track if we're using a custom threshold
    const [useCustomThreshold, setUseCustomThreshold] = useState<boolean>(
    ![1, 7, 14, 30, 60, 90, 180, 365].includes(syncConfig.autoPauseThreshold),
    );

    // Toggle handler for sync options
    const handleToggleOption = (option: keyof SyncConfig) => {
    setSyncConfig((prev) => {
    const newConfig = {
    ...prev,
    [option]: !prev[option],
    };

    // Save the updated config to storage
    saveSyncConfig(newConfig);

    return newConfig;
    });
    };

    // Handler for refreshing user library (shared between Try Again and Refresh buttons)
    const handleLibraryRefresh = () => {
    handleLibraryRefreshUtil({
    token,
    setLibraryLoading,
    setLibraryError,
    setRetryCount,
    setRateLimit,
    setUserLibrary,
    });
    };

    // View mode for displaying manga entries
    const [displayMode, setDisplayMode] = useState<DisplayMode>("cards");

    // State to hold manga matches
    const [mangaMatches, setMangaMatches] = useState<MangaMatchResult[]>([]);

    // Pagination and loading state
    const [visibleItems, setVisibleItems] = useState(20);
    const [isLoadingMore, setIsLoadingMore] = useState(false);
    // State to hold user's AniList library
    const [userLibrary, setUserLibrary] = useState<UserMediaList>({});
    const [libraryLoading, setLibraryLoading] = useState(false);
    const [libraryError, setLibraryError] = useState<string | null>(null);
    const [retryCount, setRetryCount] = useState(0);
    const maxRetries = 3;

    // Sorting and filtering options
    const [sortOption, setSortOption] = useState<SortOption>({
    field: "title",
    direction: "asc",
    });

    const [filters, setFilters] = useState<FilterOptions>({
    status: "all", // 'all', 'reading', 'completed', 'planned', 'paused', 'dropped'
    changes: "with-changes", // 'all', 'with-changes', 'no-changes'
    library: "all", // 'all', 'new', 'existing'
    });

    // Load manga matches from the app's storage system
    useEffect(() => {
    const savedResults = getSavedMatchResults();
    if (savedResults && Array.isArray(savedResults)) {
    console.log(`Loaded ${savedResults.length} match results from storage`);
    setMangaMatches(savedResults as MangaMatchResult[]);
    } else {
    console.error("No match results found in storage");
    }
    }, []);

    // Handle rate limit errors
    type ApiError = {
    isRateLimited?: boolean;
    status?: number;
    retryAfter?: number;
    message?: string;
    name?: string;
    };

    const handleRateLimitError = (
    error: ApiError,
    controller: AbortController,
    fetchLibrary: (attempt: number) => void,
    ) => {
    const err = error;

    console.warn("📛 DETECTED RATE LIMIT in SyncPage:", {
    isRateLimited: err.isRateLimited,
    status: err.status,
    retryAfter: err.retryAfter,
    });

    const retryDelay = err.retryAfter ? err.retryAfter : 60;

    setRateLimit(
    true,
    retryDelay,
    "AniList API rate limit reached. Waiting to retry...",
    );
    setLibraryLoading(false);
    setLibraryError("AniList API rate limit reached. Waiting to retry...");

    const timer = setTimeout(() => {
    if (!controller.signal.aborted) {
    console.log("Rate limit timeout complete, retrying...");
    setLibraryLoading(true);
    setLibraryError(null);
    fetchLibrary(0);
    }
    }, retryDelay * 1000);

    return () => clearTimeout(timer);
    };

    // Handle server errors with retry logic
    const handleServerError = (
    error: ApiError,
    attempt: number,
    controller: AbortController,
    fetchLibrary: (attempt: number) => void,
    ) => {
    const err = error;

    const message = err.message || "";
    const isServerError =
    message.includes("500") ||
    message.includes("502") ||
    message.includes("503") ||
    message.includes("504") ||
    message.toLowerCase().includes("network error");

    if (!isServerError || attempt >= maxRetries) {
    return false;
    }

    const backoffDelay = Math.pow(2, attempt) * 1000;
    setLibraryError(
    `AniList server error. Retrying in ${backoffDelay / 1000} seconds (${attempt + 1}/${maxRetries})...`,
    );

    const timer = setTimeout(() => {
    if (!controller.signal.aborted) {
    fetchLibrary(attempt + 1);
    }
    }, backoffDelay);

    return () => clearTimeout(timer);
    };

    // Handle fetch library success
    const handleFetchSuccess = (library: UserMediaList) => {
    console.log(
    `Loaded ${Object.keys(library).length} entries from user's AniList library`,
    );
    setUserLibrary(library);
    setLibraryLoading(false);
    setLibraryError(null);
    setRetryCount(0);
    };

    // Handle fetch library error
    const handleFetchError = (
    error: ApiError,
    attempt: number,
    controller: AbortController,
    fetchLibrary: (attempt: number) => void,
    ) => {
    const err = error;
    if (err.name === "AbortError") return;

    console.error("Failed to load user library:", err);
    console.log("Error object structure:", JSON.stringify(err, null, 2));

    // Check for rate limiting
    if (err.isRateLimited || err.status === 429) {
    return handleRateLimitError(err, controller, fetchLibrary);
    }

    // Check for server error
    const serverErrorResult = handleServerError(
    err,
    attempt,
    controller,
    fetchLibrary,
    );
    if (serverErrorResult) {
    return serverErrorResult;
    }

    // Default error handling
    setLibraryError(
    err.message ||
    "Failed to load your AniList library. Synchronization can still proceed, but comparison data will not be shown.",
    );
    setUserLibrary({});
    setLibraryLoading(false);
    };

    // Fetch the user's AniList library for comparison
    useEffect(() => {
    if (token && mangaMatches.length > 0) {
    setLibraryLoading(true);
    setLibraryError(null);

    const controller = new AbortController();

    const fetchLibrary = (attempt = 0) => {
    console.log(
    `Fetching AniList library (attempt ${attempt + 1}/${maxRetries + 1})`,
    );
    setRetryCount(attempt);

    getUserMangaList(token, controller.signal)
    .then(handleFetchSuccess)
    .catch((error) => {
    handleFetchError(error, attempt, controller, fetchLibrary);
    });
    };

    fetchLibrary(0);

    return () => controller.abort();
    }
    }, [token, mangaMatches, maxRetries, setRateLimit]);

    // Reset visible items when changing display mode
    useEffect(() => {
    setVisibleItems(20);
    }, [displayMode]);

    // Apply filters to manga matches
    const filteredMangaMatches = useMemo(() => {
    return filterMangaMatches(mangaMatches, filters, userLibrary, syncConfig);
    }, [mangaMatches, filters, userLibrary, syncConfig]);

    // Apply sorting to filtered manga matches
    const sortedMangaMatches = useMemo(() => {
    return sortMangaMatches(
    filteredMangaMatches,
    sortOption,
    userLibrary,
    syncConfig,
    );
    }, [filteredMangaMatches, sortOption, userLibrary, syncConfig]);

    // Compute all entries to sync (unfiltered, all with changes)
    const allEntriesToSync = useMemo(() => {
    return prepareAllEntriesToSync(mangaMatches, userLibrary, syncConfig);
    }, [mangaMatches, userLibrary, syncConfig]);

    // Only sync entries with actual changes
    const entriesWithChanges = useMemo(
    () => allEntriesToSync.filter((entry) => hasChanges(entry, syncConfig)),
    [allEntriesToSync, syncConfig],
    );

    const totalMatchedManga = useMemo(
    () => mangaMatches.filter((match) => match.status !== "skipped").length,
    [mangaMatches],
    );

    const newEntriesCount = useMemo(
    () =>
    mangaMatches.filter(
    (match) => match.selectedMatch && !userLibrary[match.selectedMatch.id],
    ).length,
    [mangaMatches, userLibrary],
    );

    const manualMatchesCount = useMemo(
    () => mangaMatches.filter((match) => match.status === "manual").length,
    [mangaMatches],
    );

    const queuedPercentage = useMemo(() => {
    if (totalMatchedManga === 0) {
    return 0;
    }

    return Math.round((entriesWithChanges.length / totalMatchedManga) * 100);
    }, [entriesWithChanges.length, totalMatchedManga]);

    const heroStats = useMemo(
    () => [
    {
    label: "Ready to sync",
    value: entriesWithChanges.length,
    helper: "entries queued",
    icon: CheckCircle2,
    accent:
    "from-emerald-400/80 via-emerald-400/10 to-transparent dark:from-emerald-500/60 dark:via-emerald-500/5",
    },
    {
    label: "Total matches",
    value: totalMatchedManga,
    helper: `${queuedPercentage}% prepared`,
    icon: Layers,
    accent:
    "from-sky-400/70 via-sky-400/10 to-transparent dark:from-sky-500/50 dark:via-sky-500/5",
    },
    {
    label: "New additions",
    value: newEntriesCount,
    helper: "not yet in AniList",
    icon: UserPlus,
    accent:
    "from-purple-400/80 via-purple-400/10 to-transparent dark:from-purple-500/60 dark:via-purple-500/5",
    },
    ],
    [
    entriesWithChanges.length,
    totalMatchedManga,
    newEntriesCount,
    manualMatchesCount,
    queuedPercentage,
    ],
    );

    // Modify handleStartSync to only change the view, not start synchronization
    const handleStartSync = () => {
    if (entriesWithChanges.length === 0) {
    return;
    }
    setViewMode("sync");
    };

    // Handle sync completion
    const handleSyncComplete = () => {
    setViewMode("results");
    };

    // Handle sync cancellation
    const [wasCancelled, setWasCancelled] = useState(false);
    const handleCancel = () => {
    if (viewMode === "sync") {
    // If sync has not started, go back to preview
    if (!state.isActive && !state.report) {
    setViewMode("preview");
    return;
    }
    actions.cancelSync();
    setWasCancelled(true);
    setViewMode("results");
    return;
    }
    // Navigate back to the matching page
    navigate({ to: "/review" });
    };

    // Handle final completion (after viewing results)
    const handleGoHome = () => {
    actions.reset();
    setWasCancelled(false);
    navigate({ to: "/" });
    };
    // Helper to refresh AniList library
    const refreshUserLibrary = () => {
    refreshUserLibraryUtil({
    token,
    setLibraryLoading,
    setLibraryError,
    setRetryCount,
    setRateLimit,
    setUserLibrary,
    });
    };

    const handleBackToReview = () => {
    actions.reset();
    setWasCancelled(false);
    refreshUserLibrary();
    setViewMode("preview");
    };

    const renderStatusBadge = (
    statusWillChange: boolean,
    userEntry: UserMediaEntry | undefined,
    kenmei: KenmeiManga,
    syncConfig: SyncConfig,
    ) => {
    if (!statusWillChange) return null;

    const fromStatus = userEntry?.status || "None";
    const toStatus = getEffectiveStatus(kenmei, syncConfig);

    if (fromStatus === toStatus) return null;

    return (
    <Badge
    variant="outline"
    className="border-blue-400/70 bg-blue-50/60 px-2 py-0 text-[10px] text-blue-600 shadow-sm dark:border-blue-500/40 dark:bg-blue-900/40 dark:text-blue-300"
    >
    {fromStatus} → {toStatus}
    </Badge>
    );
    };

    const renderProgressBadge = (
    progressWillChange: boolean,
    userEntry: UserMediaEntry | undefined,
    kenmei: KenmeiManga,
    syncConfig: SyncConfig,
    ) => {
    if (!progressWillChange) return null;

    const fromProgress = userEntry?.progress || 0;
    let toProgress: number;

    if (syncConfig.prioritizeAniListProgress) {
    if (userEntry?.progress && userEntry.progress > 0) {
    toProgress = Math.max(
    kenmei.chapters_read || 0,
    userEntry.progress || 0,
    );
    } else {
    toProgress = kenmei.chapters_read || 0;
    }
    } else {
    toProgress = kenmei.chapters_read || 0;
    }

    if (fromProgress === toProgress) return null;

    return (
    <Badge
    variant="outline"
    className="border-green-400/70 bg-green-50/60 px-2 py-0 text-[10px] text-green-600 shadow-sm dark:border-green-500/40 dark:bg-green-900/30 dark:text-green-300"
    >
    {fromProgress} → {toProgress} ch
    </Badge>
    );
    };

    const renderScoreBadge = (
    scoreWillChange: boolean,
    userEntry: UserMediaEntry | undefined,
    kenmei: KenmeiManga,
    ) => {
    if (!scoreWillChange) return null;

    const fromScore = userEntry?.score || 0;
    const toScore = kenmei.score || 0;

    if (fromScore === toScore) return null;

    return (
    <Badge
    variant="outline"
    className="border-amber-400/70 bg-amber-50/60 px-2 py-0 text-[10px] text-amber-600 shadow-sm dark:border-amber-500/40 dark:bg-amber-900/30 dark:text-amber-300"
    >
    {fromScore} → {toScore}/10
    </Badge>
    );
    };

    const renderPrivacyBadge = (
    userEntry: UserMediaEntry | undefined,
    syncConfig: SyncConfig,
    ) => {
    const shouldShowBadge = userEntry
    ? syncConfig.setPrivate && !userEntry.private
    : syncConfig.setPrivate;

    if (!shouldShowBadge) return null;

    return (
    <Badge
    variant="outline"
    className="border-purple-400/70 bg-purple-50/60 px-2 py-0 text-[10px] text-purple-600 shadow-sm dark:border-purple-500/40 dark:bg-purple-900/30 dark:text-purple-300"
    >
    {userEntry ? "Yes" : "No"}
    </Badge>
    );
    };

    const renderPrivacyDisplay = (
    userEntry: UserMediaEntry | undefined,
    syncConfig: SyncConfig,
    isCurrentAniList: boolean,
    ) => {
    let privacyClass = "text-xs font-medium";
    const willChange = userEntry
    ? syncConfig.setPrivate && !userEntry.private
    : syncConfig.setPrivate;

    if (isCurrentAniList) {
    if (willChange) {
    privacyClass += " text-muted-foreground line-through";
    }
    return (
    <span className={privacyClass}>
    {userEntry?.private ? "Yes" : "No"}
    </span>
    );
    } else {
    if (willChange) {
    privacyClass += " text-blue-700 dark:text-blue-300";
    }
    const privacyDisplay =
    syncConfig.setPrivate || userEntry?.private ? "Yes" : "No";
    return <span className={privacyClass}>{privacyDisplay}</span>;
    }
    };

    const renderAfterSyncProgress = (
    userEntry: UserMediaEntry | undefined,
    kenmei: KenmeiManga,
    anilist: AniListManga | undefined,
    syncConfig: SyncConfig,
    progressWillChange: boolean,
    ) => {
    let afterSyncProgress: number;
    if (syncConfig.prioritizeAniListProgress) {
    if (userEntry?.progress && userEntry.progress > 0) {
    afterSyncProgress = Math.max(
    kenmei.chapters_read || 0,
    userEntry.progress || 0,
    );
    } else {
    afterSyncProgress = kenmei.chapters_read || 0;
    }
    } else {
    afterSyncProgress = kenmei.chapters_read || 0;
    }

    return (
    <span
    className={`text-xs font-medium ${
    progressWillChange ? "text-blue-700 dark:text-blue-300" : ""
    }`}
    >
    {afterSyncProgress} ch
    {anilist?.chapters ? ` / ${anilist.chapters}` : ""}
    </span>
    );
    };

    const renderAfterSyncScore = (
    userEntry: UserMediaEntry | undefined,
    kenmei: KenmeiManga,
    scoreWillChange: boolean,
    ) => {
    let scoreDisplay: string;
    if (scoreWillChange) {
    scoreDisplay = kenmei.score ? `${kenmei.score}/10` : "None";
    } else {
    scoreDisplay = userEntry?.score ? `${userEntry.score}/10` : "None";
    }

    return (
    <span
    className={`text-xs font-medium ${
    scoreWillChange ? "text-blue-700 dark:text-blue-300" : ""
    }`}
    >
    {scoreDisplay}
    </span>
    );
    };

    // Helper function to render manga cover image
    const renderMangaCover = (
    anilist: AniListManga | undefined,
    isNewEntry: boolean,
    isCompleted: boolean,
    ) => {
    return (
    <div className="relative flex h-[200px] flex-shrink-0 items-center justify-center pl-3">
    {anilist?.coverImage?.large || anilist?.coverImage?.medium ? (
    <motion.div
    layout="position"
    animate={{
    transition: { type: false },
    }}
    >
    <img
    src={anilist?.coverImage?.large || anilist?.coverImage?.medium}
    alt={anilist?.title?.romaji || ""}
    className="h-full w-[145px] rounded-sm object-cover"
    />
    </motion.div>
    ) : (
    <div className="flex h-[200px] items-center justify-center rounded-sm bg-slate-200 dark:bg-slate-800">
    <span className="text-muted-foreground text-xs">No Cover</span>
    </div>
    )}

    {/* Status Badges */}
    <div className="absolute top-2 left-4 flex flex-col gap-1">
    {isNewEntry && <Badge className="bg-emerald-500">New</Badge>}
    {isCompleted && (
    <Badge
    variant="outline"
    className="border-amber-500 text-amber-700 dark:text-amber-400"
    >
    Completed
    </Badge>
    )}
    </div>
    </div>
    );
    };

    // Helper function to render change badges
    const renderChangeBadges = (
    statusWillChange: boolean,
    progressWillChange: boolean,
    scoreWillChange: boolean,
    userEntry: UserMediaEntry | undefined,
    syncConfig: SyncConfig,
    ) => {
    return (
    <div className="mt-1 flex flex-wrap gap-1">
    {statusWillChange && (
    <Badge
    variant="outline"
    className="border-blue-400/70 bg-blue-50/60 px-1.5 py-0 text-xs text-blue-600 shadow-sm dark:border-blue-500/50 dark:bg-blue-900/40 dark:text-blue-300"
    >
    Status
    </Badge>
    )}
    {progressWillChange && (
    <Badge
    variant="outline"
    className="border-green-400/70 bg-green-50/60 px-1.5 py-0 text-xs text-green-600 shadow-sm dark:border-green-500/50 dark:bg-green-900/30 dark:text-green-300"
    >
    Progress
    </Badge>
    )}
    {scoreWillChange && (
    <Badge
    variant="outline"
    className="border-amber-400/70 bg-amber-50/60 px-1.5 py-0 text-xs text-amber-600 shadow-sm dark:border-amber-500/40 dark:bg-amber-900/30 dark:text-amber-300"
    >
    Score
    </Badge>
    )}
    {userEntry
    ? syncConfig.setPrivate &&
    !userEntry.private && (
    <Badge
    variant="outline"
    className="border-purple-400/70 bg-purple-50/60 px-1.5 py-0 text-xs text-purple-600 shadow-sm dark:border-purple-500/50 dark:bg-purple-900/30 dark:text-purple-300"
    >
    Privacy
    </Badge>
    )
    : syncConfig.setPrivate && (
    <Badge
    variant="outline"
    className="border-purple-400/70 bg-purple-50/60 px-1.5 py-0 text-xs text-purple-600 shadow-sm dark:border-purple-500/50 dark:bg-purple-900/30 dark:text-purple-300"
    >
    Privacy
    </Badge>
    )}
    </div>
    );
    };

    // Helper function to render current AniList data
    const renderCurrentAniListData = (
    userEntry: UserMediaEntry | undefined,
    anilist: AniListManga | undefined,
    statusWillChange: boolean,
    progressWillChange: boolean,
    scoreWillChange: boolean,
    syncConfig: SyncConfig,
    ) => {
    return (
    <div className="space-y-2">
    <div className="flex items-center justify-between">
    <span className="text-muted-foreground text-xs">Status:</span>
    <span
    className={`text-xs font-medium ${statusWillChange ? "text-muted-foreground line-through" : ""}`}
    >
    {userEntry?.status || "None"}
    </span>
    </div>
    <div className="flex items-center justify-between">
    <span className="text-muted-foreground text-xs">Progress:</span>
    <span
    className={`text-xs font-medium ${progressWillChange ? "text-muted-foreground line-through" : ""}`}
    >
    {userEntry?.progress || 0} ch
    {anilist?.chapters ? ` / ${anilist.chapters}` : ""}
    </span>
    </div>
    <div className="flex items-center justify-between">
    <span className="text-muted-foreground text-xs">Score:</span>
    <span
    className={`text-xs font-medium ${scoreWillChange ? "text-muted-foreground line-through" : ""}`}
    >
    {userEntry?.score ? `${userEntry.score}/10` : "None"}
    </span>
    </div>
    <div className="flex items-center justify-between">
    <span className="text-muted-foreground text-xs">Private:</span>
    {renderPrivacyDisplay(userEntry, syncConfig, true)}
    </div>
    </div>
    );
    };

    // If any error condition is true, show the appropriate error message
    if (authError || matchDataError || validMatchesError) {
    return (
    <ErrorStateDisplay
    authError={authError}
    matchDataError={matchDataError}
    validMatchesError={validMatchesError}
    />
    );
    }

    // If no manga matches are loaded yet, show loading state
    if (mangaMatches.length === 0) {
    return <LoadingStateDisplay type="manga" />;
    }

    // If library is loading, show loading state
    if (libraryLoading) {
    return (
    <LoadingStateDisplay
    type="library"
    isRateLimited={rateLimitState.isRateLimited}
    retryCount={retryCount}
    maxRetries={maxRetries}
    />
    );
    }

    // Render the appropriate view based on state
    const renderContent = () => {
    switch (viewMode) {
    case "preview":
    return (
    <motion.div
    className="space-y-6"
    key="preview"
    initial="hidden"
    animate="visible"
    exit="exit"
    variants={staggerContainerVariants}
    >
    <motion.div variants={cardVariants} className="space-y-6">
    <motion.section
    initial={{ opacity: 0, y: 20 }}
    animate={{ opacity: 1, y: 0 }}
    transition={{ duration: 0.45 }}
    className="relative overflow-hidden rounded-3xl border border-slate-200/60 bg-white/80 p-6 shadow-xl backdrop-blur-xl dark:border-slate-800/70 dark:bg-slate-950/60"
    >
    <div className="pointer-events-none absolute inset-0 opacity-80">
    <div className="absolute top-[-6rem] left-1/2 h-56 w-96 -translate-x-1/2 rounded-full bg-gradient-to-r from-blue-400/50 to-indigo-400/50 blur-3xl dark:from-blue-500/25 dark:to-indigo-500/25" />
    <div className="absolute right-[-3rem] bottom-[-4rem] h-40 w-40 rounded-full bg-gradient-to-br from-slate-200/70 to-transparent blur-3xl dark:from-slate-800/40" />
    </div>
    <div className="relative flex flex-col gap-6 md:flex-row md:items-center md:justify-between">
    <div className="max-w-xl space-y-3">
    <h1 className="text-3xl font-semibold tracking-tight text-slate-900 dark:text-slate-100">
    Finalize your AniList library updates
    </h1>
    <p className="text-sm text-slate-600 dark:text-slate-400">
    Review detected changes, tune your syncing preferences,
    and push a polished update to AniList.
    </p>
    </div>
    <div className="grid w-full gap-3 sm:grid-cols-3 md:w-auto">
    {heroStats.map((stat) => {
    const Icon = stat.icon;
    return (
    <div
    key={stat.label}
    className="relative overflow-hidden rounded-2xl border border-white/70 bg-white/80 p-4 shadow-sm backdrop-blur-lg dark:border-slate-800/70 dark:bg-slate-950/70"
    >
    <div
    className={`pointer-events-none absolute inset-0 bg-gradient-to-br ${stat.accent} opacity-80`}
    />
    <div className="relative flex flex-col gap-2">
    <div className="flex items-center justify-between">
    <span className="pr-2 text-xs font-medium tracking-wide text-slate-500 uppercase dark:text-slate-400">
    {stat.label}
    </span>
    <Icon className="h-4 w-4 text-slate-400 dark:text-slate-500" />
    </div>
    <span className="text-2xl font-semibold text-slate-900 dark:text-slate-100">
    {stat.value}
    </span>
    <span className="text-xs text-slate-500 dark:text-slate-400">
    {stat.helper}
    </span>
    </div>
    </div>
    );
    })}
    </div>
    </div>
    </motion.section>

    <Card className="border border-slate-200/70 bg-white/80 shadow-2xl backdrop-blur-xl dark:border-slate-800/70 dark:bg-slate-950/60">
    <CardHeader className="space-y-2">
    <CardTitle className="bg-gradient-to-r from-slate-900 via-slate-700 to-slate-900 bg-clip-text text-2xl font-semibold text-transparent dark:from-slate-100 dark:via-slate-300 dark:to-slate-100">
    Sync Preview
    </CardTitle>
    <CardDescription className="text-sm text-slate-600 dark:text-slate-400">
    Review the changes that will be applied to your AniList
    account
    </CardDescription>
    </CardHeader>
    <CardContent>
    <div className="space-y-6">
    <div className="rounded-2xl border border-slate-200/70 bg-slate-50/80 p-4 shadow-sm dark:border-slate-800/70 dark:bg-slate-900/40">
    <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
    <div>
    <span className="text-xs font-semibold tracking-wide text-slate-500 uppercase dark:text-slate-400">
    Sync readiness
    </span>
    <div className="mt-1 text-lg font-semibold text-slate-900 dark:text-slate-100">
    {entriesWithChanges.length} of {totalMatchedManga}{" "}
    entries prepared
    </div>
    <p className="text-xs text-slate-500 dark:text-slate-400">
    Automatically excludes skipped matches and completed
    entries preserved per your settings.
    </p>
    </div>
    <div className="flex w-full items-center gap-3 sm:w-auto">
    <div className="flex h-14 w-14 items-center justify-center rounded-full border-2 border-blue-200/80 bg-white/80 text-sm font-semibold text-blue-600 dark:border-blue-700/80 dark:bg-blue-900/20 dark:text-blue-300">
    {queuedPercentage}%
    </div>
    <div className="min-w-[140px] flex-1">
    <div className="h-2 w-full overflow-hidden rounded-full bg-slate-200/80 dark:bg-slate-800/60">
    <div
    className="h-full rounded-full bg-gradient-to-r from-blue-500 via-indigo-500 to-purple-500 transition-all duration-300"
    style={{
    width: `${Math.min(queuedPercentage, 100)}%`,
    }}
    />
    </div>
    </div>
    </div>
    </div>
    </div>

    <SyncConfigurationPanel
    syncConfig={syncConfig}
    setSyncConfig={setSyncConfig}
    useCustomThreshold={useCustomThreshold}
    setUseCustomThreshold={setUseCustomThreshold}
    handleToggleOption={handleToggleOption}
    />

    <ChangesSummary
    entriesWithChanges={entriesWithChanges.length}
    libraryLoading={libraryLoading}
    libraryError={libraryError}
    isRateLimited={rateLimitState.isRateLimited}
    onLibraryRefresh={handleLibraryRefresh}
    userLibrary={userLibrary}
    mangaMatches={mangaMatches}
    syncConfig={syncConfig}
    />

    <ViewControls
    displayMode={displayMode}
    setDisplayMode={setDisplayMode}
    sortOption={sortOption}
    setSortOption={setSortOption}
    filters={filters}
    setFilters={setFilters}
    />

    {sortedMangaMatches.length !== mangaMatches.length && (
    <div className="flex items-center justify-between rounded-xl border border-slate-200/70 bg-slate-50/70 px-3 py-2 text-sm shadow-sm dark:border-slate-800/60 dark:bg-slate-900/50">
    <div>
    Showing{" "}
    <span className="font-medium">
    {sortedMangaMatches.length}
    </span>{" "}
    of{" "}
    <span className="font-medium">
    {
    mangaMatches.filter(
    (match) => match.status !== "skipped",
    ).length
    }
    </span>{" "}
    manga
    {Object.values(filters).some((v) => v !== "all") && (
    <span className="text-muted-foreground ml-1">
    (filtered)
    </span>
    )}
    </div>
    <Button
    variant="ghost"
    size="sm"
    className="h-7 rounded-full border border-transparent px-3 text-xs hover:border-slate-300 hover:bg-slate-100 dark:hover:border-slate-700 dark:hover:bg-slate-800/60"
    onClick={() => {
    setSortOption({ field: "title", direction: "asc" });
    setFilters({
    status: "all",
    changes: "with-changes",
    library: "all",
    });
    }}
    >
    Clear Filters
    </Button>
    </div>
    )}

    <div className="max-h-[60vh] overflow-y-auto">
    <AnimatePresence
    initial={false}
    mode="wait"
    key={displayMode}
    >
    {displayMode === "cards" ? (
    <motion.div
    key="cards-view"
    variants={fadeVariants}
    initial="hidden"
    animate="visible"
    exit="exit"
    transition={viewModeTransition}
    className="grid grid-cols-1 gap-4"
    >
    <AnimatePresence initial={false}>
    {sortedMangaMatches
    .slice(0, visibleItems)
    .map((match, index) => {
    const kenmei = match.kenmeiManga;
    const anilist = match.selectedMatch!;

    // Get the user's existing data for this manga if it exists
    const userEntry = userLibrary[anilist.id];

    // Calculate sync changes using helper function
    const {
    statusWillChange,
    progressWillChange,
    scoreWillChange,
    isNewEntry,
    isCompleted,
    changeCount,
    } = calculateSyncChanges(
    kenmei,
    userEntry,
    syncConfig,
    );

    return (
    <motion.div
    key={`${anilist.id}-${index}`}
    layout="position"
    initial={{ opacity: 0, y: 20 }}
    animate={{ opacity: 1, y: 0 }}
    exit={{ opacity: 0, scale: 0.9 }}
    transition={{ duration: 0.3 }}
    layoutId={undefined}
    >
    <Card className="group overflow-hidden border border-slate-200/70 bg-white/80 shadow-sm backdrop-blur-sm transition-all duration-300 hover:-translate-y-1 hover:border-blue-200/70 hover:shadow-xl dark:border-slate-800/60 dark:bg-slate-950/60">
    <div className="flex">
    {/* Manga Cover Image */}
    {renderMangaCover(
    anilist,
    isNewEntry,
    isCompleted,
    )}

    {/* Content */}
    <div className="flex-1 p-4">
    <div className="flex items-start justify-between">
    <div>
    <h3 className="line-clamp-2 max-w-[580px] text-base font-semibold">
    {anilist.title.romaji ||
    kenmei.title}
    </h3>
    {changeCount > 0 &&
    !isCompleted ? (
    renderChangeBadges(
    statusWillChange,
    progressWillChange,
    scoreWillChange,
    userEntry,
    syncConfig,
    )
    ) : (
    <div className="mt-1">
    <Badge
    variant="outline"
    className="border-slate-200/70 bg-slate-100/70 px-1.5 py-0 text-xs text-slate-500 dark:border-slate-700/60 dark:bg-slate-800/50 dark:text-slate-400"
    >
    {isCompleted
    ? "Preserving Completed"
    : "No Changes"}
    </Badge>
    </div>
    )}
    </div>

    {/* Change Indicator */}
    {changeCount > 0 &&
    !isCompleted && (
    <div className="rounded-full bg-blue-100/80 px-2 py-1 text-xs font-medium text-blue-600 shadow-sm dark:bg-blue-900/30 dark:text-blue-300">
    {changeCount} change
    {changeCount === 1
    ? ""
    : "s"}
    </div>
    )}
    </div>

    {/* Comparison Table */}
    <div className="mt-4 grid grid-cols-2 gap-2 text-sm">
    <div className="rounded-xl border border-slate-200/60 bg-white/70 p-3 shadow-sm dark:border-slate-800/60 dark:bg-slate-900/40">
    <h4 className="text-muted-foreground mb-2 text-xs font-medium">
    {isNewEntry
    ? "Not in Library"
    : "Current AniList"}
    </h4>

    {isNewEntry ? (
    <div className="text-muted-foreground py-4 text-center text-xs">
    New addition to your library
    </div>
    ) : (
    renderCurrentAniListData(
    userEntry,
    anilist,
    statusWillChange,
    progressWillChange,
    scoreWillChange,
    syncConfig,
    )
    )}
    </div>

    <div className="rounded-xl border border-blue-100/60 bg-blue-50/70 p-3 shadow-sm dark:border-blue-900/40 dark:bg-blue-900/20">
    <h4 className="mb-2 text-xs font-medium text-blue-600 dark:text-blue-300">
    After Sync
    </h4>

    <div className="space-y-2">
    <div className="flex items-center justify-between">
    <span className="text-xs text-blue-500 dark:text-blue-400">
    Status:
    </span>
    <span
    className={`text-xs font-medium ${statusWillChange ? "text-blue-700 dark:text-blue-300" : ""}`}
    >
    {getEffectiveStatus(
    kenmei,
    syncConfig,
    )}
    </span>
    </div>
    <div className="flex items-center justify-between">
    <span className="text-xs text-blue-500 dark:text-blue-400">
    Progress:
    </span>
    {renderAfterSyncProgress(
    userEntry,
    kenmei,
    anilist,
    syncConfig,
    progressWillChange,
    )}
    </div>
    <div className="flex items-center justify-between">
    <span className="text-xs text-blue-500 dark:text-blue-400">
    Score:
    </span>
    {renderAfterSyncScore(
    userEntry,
    kenmei,
    scoreWillChange,
    )}
    </div>
    <div className="flex items-center justify-between">
    <span className="text-xs text-blue-500 dark:text-blue-400">
    Private:
    </span>
    {renderPrivacyDisplay(
    userEntry,
    syncConfig,
    false,
    )}
    </div>
    </div>
    </div>
    </div>
    </div>
    </div>
    </Card>
    </motion.div>
    );
    })}
    </AnimatePresence>

    {/* Load more button instead of automatic loading */}
    {sortedMangaMatches.length > visibleItems ? (
    <div className="py-4 text-center">
    <Button
    onClick={() => {
    setIsLoadingMore(true);
    const newValue = Math.min(
    visibleItems + 20,
    sortedMangaMatches.length,
    );
    console.log(
    `Loading more items: ${visibleItems} → ${newValue}`,
    );

    // Add a small delay to show the loading spinner
    setTimeout(() => {
    setVisibleItems(newValue);
    setIsLoadingMore(false);
    }, 300);
    }}
    variant="outline"
    className="gap-2"
    disabled={isLoadingMore}
    >
    {isLoadingMore && (
    <div className="text-primary inline-block h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent"></div>
    )}
    {isLoadingMore
    ? "Loading..."
    : `Load More (${visibleItems} of ${sortedMangaMatches.length})`}
    </Button>
    </div>
    ) : null}
    {sortedMangaMatches.length > 0 &&
    sortedMangaMatches.length <= visibleItems && (
    <div className="py-4 text-center">
    <span className="text-muted-foreground text-xs">
    All items loaded
    </span>
    </div>
    )}
    </motion.div>
    ) : (
    <motion.div
    key="compact-view"
    variants={fadeVariants}
    initial="hidden"
    animate="visible"
    exit="exit"
    transition={viewModeTransition}
    className="space-y-1 overflow-hidden rounded-2xl border border-slate-200/70 bg-white/70 shadow-sm backdrop-blur-sm dark:border-slate-800/60 dark:bg-slate-950/50"
    >
    <AnimatePresence initial={false}>
    {sortedMangaMatches
    .slice(0, visibleItems)
    .map((match, index) => {
    const kenmei = match.kenmeiManga;
    const anilist = match.selectedMatch!;

    // Get the user's existing data for this manga if it exists
    const userEntry = userLibrary[anilist.id];

    // Calculate sync changes using helper function
    const {
    statusWillChange,
    progressWillChange,
    scoreWillChange,
    isNewEntry,
    isCompleted,
    changeCount,
    } = calculateSyncChanges(
    kenmei,
    userEntry,
    syncConfig,
    );

    const baseRowClasses =
    "group flex items-center rounded-xl px-3 py-2 transition-colors duration-200";
    let backgroundClass = "";
    if (isCompleted) {
    backgroundClass =
    "bg-amber-50/70 dark:bg-amber-950/20";
    } else if (isNewEntry) {
    backgroundClass =
    "bg-emerald-50/70 dark:bg-emerald-950/20";
    } else if (index % 2 === 0) {
    backgroundClass =
    "bg-white/70 dark:bg-slate-900/40";
    } else {
    backgroundClass =
    "bg-white/60 dark:bg-slate-900/30";
    }

    return (
    <motion.div
    key={`${anilist.id}-${index}`}
    layout="position"
    initial={{ opacity: 0, y: 5 }}
    animate={{ opacity: 1, y: 0 }}
    exit={{ opacity: 0 }}
    transition={{ duration: 0.2 }}
    layoutId={undefined}
    >
    <div
    className={`${baseRowClasses} ${backgroundClass} hover:bg-blue-50/70 dark:hover:bg-slate-900/60`}
    >
    {/* Thumbnail - Updated styling */}
    <div className="mr-3 flex flex-shrink-0 items-center pl-2">
    {anilist.coverImage?.large ||
    anilist.coverImage?.medium ? (
    <motion.div
    layout="position"
    animate={{
    transition: { type: false },
    }}
    >
    <img
    src={
    anilist.coverImage?.large ||
    anilist.coverImage?.medium
    }
    alt={anilist.title.romaji || ""}
    className="h-12 w-8 rounded-sm object-cover"
    />
    </motion.div>
    ) : (
    <div className="flex h-12 w-8 items-center justify-center rounded-sm bg-slate-200 dark:bg-slate-800">
    <span className="text-muted-foreground text-[8px]">
    No Cover
    </span>
    </div>
    )}
    </div>

    {/* Title and status */}
    <div className="mr-2 min-w-0 flex-1">
    <div className="truncate text-sm font-medium">
    {anilist.title.romaji ||
    kenmei.title}
    </div>
    <div className="mt-0.5 flex items-center gap-1">
    {isNewEntry && (
    <Badge className="bg-emerald-500/80 px-2 py-0 text-[10px] text-white shadow-sm">
    New
    </Badge>
    )}
    {isCompleted && (
    <Badge
    variant="outline"
    className="border-amber-400/70 bg-amber-50/60 px-2 py-0 text-[10px] text-amber-600 shadow-sm dark:border-amber-500/40 dark:bg-amber-900/30 dark:text-amber-300"
    >
    Completed
    </Badge>
    )}
    </div>
    </div>

    {/* Changes */}
    <div className="flex flex-shrink-0 items-center gap-1">
    {!isNewEntry && !isCompleted && (
    <>
    {renderStatusBadge(
    statusWillChange,
    userEntry,
    kenmei,
    syncConfig,
    )}
    {renderProgressBadge(
    progressWillChange,
    userEntry,
    kenmei,
    syncConfig,
    )}
    {renderScoreBadge(
    scoreWillChange,
    userEntry,
    kenmei,
    )}
    {renderPrivacyBadge(
    userEntry,
    syncConfig,
    )}

    {changeCount === 0 && (
    <span className="px-1 text-[10px] text-slate-500 dark:text-slate-400">
    No Changes
    </span>
    )}
    </>
    )}

    {isNewEntry && (
    <span className="text-[10px] font-medium text-emerald-600 dark:text-emerald-400">
    Adding to Library
    </span>
    )}

    {isCompleted && (
    <span className="text-[10px] font-medium text-amber-600 dark:text-amber-400">
    Preserving Completed
    </span>
    )}
    </div>
    </div>
    </motion.div>
    );
    })}
    </AnimatePresence>

    {/* Load more button for compact view */}
    {sortedMangaMatches.length > 0 && (
    <div className="bg-muted/20 py-3 text-center">
    {sortedMangaMatches.length > visibleItems ? (
    <Button
    onClick={() => {
    setIsLoadingMore(true);
    const newValue = Math.min(
    visibleItems + 20,
    sortedMangaMatches.length,
    );
    console.log(
    `Loading more items: ${visibleItems} → ${newValue}`,
    );

    // Add a small delay to show the loading spinner
    setTimeout(() => {
    setVisibleItems(newValue);
    setIsLoadingMore(false);
    }, 300);
    }}
    variant="outline"
    size="sm"
    className="gap-2"
    disabled={isLoadingMore}
    >
    {isLoadingMore && (
    <div className="text-primary inline-block h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent"></div>
    )}
    {isLoadingMore
    ? "Loading..."
    : `Load More (${visibleItems} of ${sortedMangaMatches.length})`}
    </Button>
    ) : (
    <span className="text-muted-foreground text-xs">
    All items loaded
    </span>
    )}
    </div>
    )}
    </motion.div>
    )}
    </AnimatePresence>
    </div>
    </div>
    </CardContent>
    <CardFooter className="flex flex-col gap-3 border-t border-slate-200/60 pt-6 sm:flex-row sm:items-center sm:justify-between dark:border-slate-800/80">
    <div className="flex items-center gap-2 text-xs text-slate-500 dark:text-slate-400">
    <span
    className={`inline-flex h-2 w-2 rounded-full ${entriesWithChanges.length > 0 ? "bg-emerald-500" : "bg-amber-500"}`}
    ></span>
    {entriesWithChanges.length > 0
    ? `${entriesWithChanges.length} entries ready to sync`
    : "No actionable changes detected yet"}
    </div>
    <div className="flex w-full flex-col gap-2 sm:w-auto sm:flex-row">
    <Button
    variant="outline"
    onClick={handleCancel}
    className="w-full border-slate-300/70 text-slate-700 hover:border-slate-400 hover:bg-slate-100 sm:w-auto dark:border-slate-700/70 dark:text-slate-200 dark:hover:border-slate-600 dark:hover:bg-slate-900"
    >
    Cancel
    </Button>
    <Button
    onClick={handleStartSync}
    disabled={
    entriesWithChanges.length === 0 || libraryLoading
    }
    className="group relative w-full overflow-hidden rounded-md bg-gradient-to-r from-blue-500 via-indigo-500 to-purple-500 px-6 py-2 font-semibold text-white shadow-lg transition hover:shadow-xl disabled:cursor-not-allowed disabled:opacity-70 sm:w-auto"
    >
    <span className="absolute inset-0 bg-white/20 opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
    <span className="relative flex items-center justify-center gap-2">
    <Sparkles className="h-4 w-4" />
    Launch Sync
    </span>
    </Button>
    </div>
    </CardFooter>
    </Card>
    </motion.div>
    </motion.div>
    );

    case "sync":
    return (
    <motion.div
    variants={pageVariants}
    initial="hidden"
    animate="visible"
    exit="exit"
    >
    <SyncManager
    entries={entriesWithChanges}
    token={token || ""}
    onComplete={handleSyncComplete}
    onCancel={handleCancel}
    autoStart={false}
    syncState={state}
    syncActions={{
    ...actions,
    startSync: (entries, token, _unused, displayOrderMediaIds) =>
    actions.startSync(
    entries,
    token,
    _unused,
    displayOrderMediaIds,
    ),
    }}
    incrementalSync={syncConfig.incrementalSync}
    onIncrementalSyncChange={(value) => {
    const newConfig = { ...syncConfig, incrementalSync: value };
    setSyncConfig(newConfig);
    saveSyncConfig(newConfig);
    }}
    displayOrderMediaIds={entriesWithChanges
    .filter(Boolean)
    .map((e) => e.mediaId)}
    />
    </motion.div>
    );

    case "results": {
    if (state.report || wasCancelled) {
    return (
    <motion.div
    variants={pageVariants}
    initial="hidden"
    animate="visible"
    exit="exit"
    >
    {state.report ? (
    <SyncResultsView
    report={state.report}
    onClose={handleGoHome}
    onExportErrors={() =>
    state.report && exportSyncErrorLog(state.report)
    }
    />
    ) : (
    <div className="flex min-h-[300px] flex-col items-center justify-center">
    <div className="mb-4">
    <Loader2 className="h-10 w-10 animate-spin text-blue-500" />
    </div>
    <div className="text-lg font-medium text-blue-700 dark:text-blue-300">
    Loading synchronization results...
    </div>
    </div>
    )}
    <div className="mt-6 flex justify-center gap-4">
    <Button onClick={handleGoHome} variant="default">
    Go Home
    </Button>
    <Button onClick={handleBackToReview} variant="outline">
    Back to Sync Review
    </Button>
    </div>
    {wasCancelled && (
    <div className="mt-4 text-center text-amber-600 dark:text-amber-400">
    Synchronization was cancelled. No further entries will be
    processed.
    </div>
    )}
    </motion.div>
    );
    } else {
    return (
    <motion.div
    variants={pageVariants}
    initial="hidden"
    animate="visible"
    exit="exit"
    >
    <Card className="mx-auto w-full max-w-md p-6 text-center">
    <CardContent>
    <AlertCircle className="mx-auto mb-4 h-12 w-12 text-red-500" />
    <h3 className="text-lg font-medium">Synchronization Error</h3>
    <p className="mt-2 text-sm text-slate-500">
    {state.error ||
    "An unknown error occurred during synchronization."}
    </p>
    </CardContent>
    <CardFooter className="justify-center gap-4">
    <Button onClick={handleGoHome}>Go Home</Button>
    <Button onClick={handleBackToReview} variant="outline">
    Back to Sync Review
    </Button>
    </CardFooter>
    </Card>
    </motion.div>
    );
    }
    }
    }
    };

    return (
    <div className="relative min-h-0 overflow-hidden">
    <div className="pointer-events-none absolute inset-0 -z-10">
    <div className="absolute top-[-10%] left-[8%] h-64 w-64 rounded-full bg-blue-200/50 blur-3xl dark:bg-blue-500/20" />
    <div className="absolute top-1/3 right-[-12%] h-80 w-80 rounded-full bg-indigo-200/40 blur-3xl dark:bg-indigo-500/15" />
    <div className="absolute bottom-[-20%] left-1/2 h-[26rem] w-[40rem] -translate-x-1/2 bg-gradient-to-t from-slate-100 via-transparent to-transparent opacity-80 dark:from-slate-900/40" />
    </div>
    <motion.div
    className="relative z-10 container py-10"
    initial="hidden"
    animate="visible"
    variants={pageVariants}
    >
    <AnimatePresence mode="wait">{renderContent()}</AnimatePresence>
    </motion.div>
    </div>
    );
    }