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

    Handles file upload, import process, error handling, and displays import summary and data table for the user.

    Returns Element

    export function ImportPage() {
    const navigate = useNavigate();
    const { recordEvent } = useDebugActions();
    const { completeStep } = useOnboarding();
    const [importData, setImportData] = useState<KenmeiData | null>(null);
    const [error, setError] = useState<AppError | null>(null);
    const [isLoading, setIsLoading] = useState(false);
    const [importSuccess, setImportSuccess] = useState(false);
    const [previousMatchCount, setPreviousMatchCount] = useState(0);
    const [progress, setProgress] = useState(0);

    const statusCountsSnapshot = useMemo(
    () => (importData ? getStatusCounts(importData.manga) : null),
    [importData],
    );

    const handleFileLoaded = (data: KenmeiData) => {
    console.info(
    `[Import] 📁 File loaded successfully: ${data.manga.length} manga entries`,
    );
    console.debug(`[Import] 🔍 Previous match count: ${previousMatchCount}`);

    recordEvent({
    type: "import.file-loaded",
    message: `File loaded with ${data.manga.length} manga entries`,
    level: "info",
    metadata: {
    entryCount: data.manga.length,
    hasPreviousMatches: previousMatchCount > 0,
    previousMatchCount,
    },
    });

    setImportData(data);
    setError(null);
    setImportSuccess(false);
    toast.success("File loaded", {
    description: truncateToastMessage(
    `${data.manga.length} entries queued for review.` +
    (previousMatchCount > 0
    ? " Prior matches will be reapplied automatically."
    : " We'll keep your Kenmei metadata intact."),
    200,
    ).component,
    });
    };

    /**
    * Handles import errors by logging, recording debug event, and displaying enhanced error notification.
    * @param error - The application error object to handle.
    * @param toastId - Optional ID of an existing toast to replace with error message.
    * @source
    */
    const getRetryCallback = (
    error: AppError,
    ): (() => void | Promise<void>) | undefined => {
    switch (error.type) {
    case ErrorType.VALIDATION:
    // Validation errors cannot be retried - user must fix the file
    return undefined;
    case ErrorType.STORAGE:
    case ErrorType.NETWORK:
    // Network and storage errors can be retried
    return () => handleImport();
    default:
    return undefined;
    }
    };

    /**
    * Handles import errors by logging, recording debug event, and displaying enhanced error notification.
    * @param error - The application error object to handle.
    * @param toastId - Optional ID of an existing toast to replace with error message.
    * @source
    */
    const handleError = (error: AppError, toastId?: string) => {
    console.error(`[Import] ❌ Import error (${error.type}):`, error.message);

    recordEvent({
    type: "import.error",
    message: `Import error: ${error.message}`,
    level: "error",
    metadata: {
    errorType: error.type,
    errorMessage: error.message,
    },
    });

    setError(error);
    setImportData(null);
    setImportSuccess(false);

    // Use enhanced error notification with recovery actions
    showErrorNotification(error, {
    toastId,
    onRetry: getRetryCallback(error),
    duration: 8000,
    });
    };

    /**
    * Processes the loaded manga import data and saves it to storage.
    * Merges with previous data, normalizes IDs, validates, and redirects to review page on success.
    * @source
    */
    const handleImport = async () => {
    if (!importData) {
    console.warn("[Import] ⚠️ Cannot import - no data loaded");
    return;
    }

    console.info(
    `[Import] 🚀 Starting import process for ${importData.manga.length} entries`,
    );
    recordEvent({
    type: "import.start",
    message: `Import started with ${importData.manga.length} entries`,
    level: "info",
    metadata: { entryCount: importData.manga.length },
    });
    setIsLoading(true);

    const loadingToastId = toast.loading("Processing your library", {
    description: "Merging entries and preserving previous matches...",
    }) as string;

    // Start progress animation
    setProgress(10);
    const progressInterval = setInterval(() => {
    setProgress((prev) => {
    if (prev >= 90) {
    clearInterval(progressInterval);
    return 90;
    }
    return prev + 10;
    });
    }, 200);

    try {
    // Normalize the imported manga with proper ID assignment
    const normalizedManga = normalizeMangaItems(importData.manga);

    // Get previously imported manga to compare against
    const previousManga = getPreviousMangaData();

    // Merge manga data and get results
    const { mergedManga, results } = mergeMangaData(
    previousManga,
    normalizedManga,
    );

    console.info(
    `[Import] Import results: Previous: ${previousManga.length}, New file: ${normalizedManga.length}, Final merged: ${mergedManga.length}`,
    );
    console.info(
    `[Import] Changes: ${results.newMangaCount} new manga, ${results.updatedMangaCount} updated manga`,
    );

    // Ensure all manga have proper IDs and required fields
    const validMergedManga = validateMangaData(mergedManga);
    saveKenmeiData({ manga: validMergedManga });

    // Update existing match results with new data if any exist
    updateMatchResults(validMergedManga);

    // Clear pending manga storage after import to force recalculation
    clearPendingMangaStorage();

    // Show success state briefly before redirecting
    console.info("[Import] ✅ Import completed successfully");
    console.debug("[Import] 🔍 Redirecting to review page...");

    recordEvent({
    type: "import.complete",
    message: `Import completed: ${results.newMangaCount} new, ${results.updatedMangaCount} updated, ${validMergedManga.length} total`,
    level: "success",
    metadata: {
    newMangaCount: results.newMangaCount,
    updatedMangaCount: results.updatedMangaCount,
    totalMangaCount: validMergedManga.length,
    previousMangaCount: previousManga.length,
    normalizedMangaCount: normalizedManga.length,
    },
    });

    setImportSuccess(true);
    setProgress(100);

    toast.success("Import ready for review", {
    id: loadingToastId,
    duration: 2800,
    description: "We'll take you to the match review in just a sec.",
    });

    // Mark import step as complete
    completeStep("import");

    // Start warming up title normalization caches in background
    console.info(
    "[Import] 🔥 Starting cache warmup for normalization algorithms",
    );
    const cacheWarmer = getCacheWarmer();
    const titles = validMergedManga.map((m) => m.title);
    cacheWarmer
    .warmupCachesInBackground(
    titles,
    ["normalizeForMatching", "processTitle"],
    (algorithm, current, total) => {
    console.debug(
    `[Import] 📊 Cache warmup progress - ${algorithm}: ${current}/${total}`,
    );
    },
    )
    .catch((error) => {
    console.warn(
    "[Import] ⚠️ Cache warmup failed, but continuing:",
    error,
    );
    });

    // Redirect to the review page after a short delay
    setTimeout(() => {
    navigate({ to: "/review" });
    }, 1500);
    } catch (err) {
    // Handle any errors that might occur during storage
    console.error("[Import] ❌ Storage error:", err);
    handleError(
    createError(
    ErrorType.STORAGE,
    "Failed to save import data. Please try again.",
    err,
    "STORAGE_WRITE_FAILED",
    ErrorRecoveryAction.RETRY,
    "Click retry to attempt saving again.",
    ),
    loadingToastId,
    );
    } finally {
    clearInterval(progressInterval);
    setIsLoading(false);
    }
    };

    /**
    * Clears the error message from display.
    * @source
    */
    const dismissError = () => {
    console.debug("[Import] 🔍 Dismissing error message");
    setError(null);
    };

    /**
    * Resets the import form to its initial state and notifies the user.
    * @source
    */
    const resetForm = () => {
    console.info("[Import] 🔄 Resetting import form");
    recordEvent({
    type: "import.reset",
    message: "Import form reset",
    level: "info",
    });
    setImportData(null);
    setError(null);
    setImportSuccess(false);
    toast("Import reset", {
    description:
    "Upload a fresh Kenmei export whenever you're ready to try again.",
    });
    };

    useEffect(() => {
    // Check if we have previous match results
    console.debug("[Import] 🔍 Checking for previous match results...");
    const savedResults = getSavedMatchResults();
    if (savedResults && Array.isArray(savedResults)) {
    const reviewedCount = savedResults.filter(
    (m) =>
    m.status === "matched" ||
    m.status === "manual" ||
    m.status === "skipped",
    ).length;

    console.info(
    `[Import] ✅ Found ${reviewedCount} previously reviewed matches`,
    );
    setPreviousMatchCount(reviewedCount);
    } else {
    console.debug("[Import] 🔍 No previous match results found");
    }
    }, []);

    return (
    <motion.div
    className="container mx-auto space-y-10 px-4 py-8 md:px-6"
    initial={{ opacity: 0 }}
    animate={{ opacity: 1 }}
    transition={{ duration: 0.4 }}
    >
    <motion.header
    className="max-w-3xl space-y-3"
    initial={{ opacity: 0, y: -12 }}
    animate={{ opacity: 1, y: 0 }}
    transition={{ duration: 0.4 }}
    >
    <h1 className="text-3xl font-semibold tracking-tight sm:text-4xl">
    Import your Kenmei library
    </h1>
    <p className="text-muted-foreground text-base">
    Upload your Kenmei export, review each match, and sync the results to
    AniList. We preserve previous match decisions so you can pick up right
    where you left off.
    </p>
    </motion.header>

    {error && (
    <motion.div
    className="mb-6"
    initial={{ opacity: 0, scale: 0.95 }}
    animate={{ opacity: 1, scale: 1 }}
    transition={{ duration: 0.3 }}
    >
    <ErrorMessage
    message={error.message}
    type={error.type}
    onDismiss={dismissError}
    onRetry={importData ? handleImport : undefined}
    />
    </motion.div>
    )}

    {(() => {
    if (importSuccess && importData) {
    return (
    <ImportSuccessContent importData={importData} progress={progress} />
    );
    }

    if (!importData) {
    return (
    <FileUploadContent
    onFileLoaded={handleFileLoaded}
    onError={handleError}
    />
    );
    }

    const statusCounts =
    statusCountsSnapshot ?? getStatusCounts(importData.manga);
    return (
    <FileReadyContent
    importData={importData}
    statusCounts={statusCounts}
    previousMatchCount={previousMatchCount}
    isLoading={isLoading}
    onImport={handleImport}
    onReset={resetForm}
    />
    );
    })()}
    </motion.div>
    );
    }