• 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 [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) => {
    setImportData(data);
    setError(null);
    setImportSuccess(false);
    toast.success("File loaded", {
    description:
    `${data.manga.length} entries queued for review.` +
    (previousMatchCount > 0
    ? " Prior matches will be reapplied automatically."
    : " We'll keep your Kenmei metadata intact."),
    });
    };

    const handleError = (error: AppError, toastId?: string) => {
    setError(error);
    setImportData(null);
    setImportSuccess(false);
    const descriptions: Partial<Record<ErrorType, string>> = {
    [ErrorType.VALIDATION]:
    "The file format looks off. Please double-check the export steps and try again.",
    [ErrorType.STORAGE]:
    "We couldn't persist the import locally. Make sure storage permissions are available and retry.",
    [ErrorType.NETWORK]:
    "A network hiccup interrupted the import. Check your connection and try again.",
    };

    const description =
    descriptions[error.type] ||
    "Please try again in a moment. If the issue persists, export a fresh file from Kenmei.";

    toast.error(error.message, {
    id: toastId,
    description,
    });
    };

    const handleImport = async () => {
    if (!importData) {
    return;
    }

    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.log(
    `Import results: Previous: ${previousManga.length}, New file: ${normalizedManga.length}, Final merged: ${mergedManga.length}`,
    );
    console.log(
    `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
    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.",
    });

    // 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("Storage error:", err);
    handleError(
    createError(
    ErrorType.STORAGE,
    "Failed to save import data. Please try again.",
    ),
    loadingToastId,
    );
    } finally {
    clearInterval(progressInterval);
    setIsLoading(false);
    }
    };

    const dismissError = () => {
    setError(null);
    };

    const resetForm = () => {
    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
    const savedResults = getSavedMatchResults();
    if (savedResults && Array.isArray(savedResults)) {
    const reviewedCount = savedResults.filter(
    (m) =>
    m.status === "matched" ||
    m.status === "manual" ||
    m.status === "skipped",
    ).length;

    setPreviousMatchCount(reviewedCount);
    }
    }, []);

    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}
    dismiss={dismissError}
    retry={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>
    );
    }