"use client";

import { AlertCircle, File, FileText, Image, Upload, X } from "lucide-react";
import React, { useCallback, useRef, useState } from "react";
import { toast } from "sonner";
import { cx } from "../lib/utils";
import { OnboardingButton } from "../primitives/onboarding-button";
import { OnboardingLabel } from "../primitives/onboarding-label";

export interface UploadedFile {
	id: string;
	file: File;
	preview?: string;
	progress: number;
	status: "uploading" | "success" | "error";
	error?: string;
}

export interface FileUploadField {
	/** Unique identifier for the field */
	id: string;
	/** Label displayed above the upload area */
	label: string;
	/** Optional description/helper text */
	description?: string;
	/** Accepted file types (e.g., 'image/*', '.pdf,.doc,.docx') */
	accept?: string;
	/** Maximum file size in bytes */
	maxSize?: number;
	/** Maximum number of files allowed */
	maxFiles?: number;
	/** Whether at least one file is required */
	required?: boolean;
	/** Default uploaded files (for restoring state) */
	defaultValue?: File[];
	/** Custom drop zone title text */
	dropZoneTitle?: string;
	/** Custom drop zone subtitle text */
	dropZoneSubtitle?: string;
}

export interface FileUploadStepProps {
	/** Title displayed at the top of the step */
	title?: string;
	/** Description text below the title */
	description?: string;
	/** Array of file upload fields */
	fields: FileUploadField[];
	/** Simulated upload delay in ms (for demo purposes). Set to 0 for instant. */
	uploadDelay?: number;
	/** Custom upload handler. If provided, replaces the simulated upload. Should return a promise that resolves on success or rejects with error message. */
	onUpload?: (file: File, fieldId: string) => Promise<void>;
	/** Called when files change */
	onFilesChange?: (files: Record<string, File[]>) => void;
	/** Called when the user submits the form */
	onSubmit: (files: Record<string, File[]>) => void | Promise<void>;
	/** Text for the submit button */
	submitText?: string;
	/** Text shown while submitting */
	loadingText?: string;
	/** Text shown while uploading files */
	uploadingText?: string;
	/** Optional skip button config (for optional upload steps) */
	skipButton?: {
		text: string;
		onClick: () => void;
	};
	/** Optional back button config */
	backButton?: {
		text: string;
		onClick: () => void;
	};
	/** Whether to show file size in the file list */
	showFileSize?: boolean;
	/** Whether to show success toast on upload */
	showSuccessToast?: boolean;
}

function formatFileSize(bytes: number): string {
	if (bytes === 0) return "0 Bytes";
	const k = 1024;
	const sizes = ["Bytes", "KB", "MB", "GB"];
	const i = Math.floor(Math.log(bytes) / Math.log(k));
	return parseFloat((bytes / k ** i).toFixed(2)) + " " + sizes[i];
}

function generateId(): string {
	return Math.random().toString(36).substring(2, 9);
}

function getFileIcon(file: File) {
	if (file.type.startsWith("image/")) {
		return Image;
	}
	if (file.type.includes("pdf") || file.type.includes("document")) {
		return FileText;
	}
	return File;
}

export function FileUploadStep({
	title = "Upload your files",
	description = "Add the files you want to upload.",
	fields,
	uploadDelay = 1500,
	onUpload,
	onFilesChange,
	onSubmit,
	submitText = "Continue",
	loadingText = "Submitting...",
	uploadingText = "Uploading...",
	skipButton,
	backButton,
	showFileSize = true,
	showSuccessToast = true,
}: FileUploadStepProps) {
	const [uploadedFiles, setUploadedFiles] = useState<
		Record<string, UploadedFile[]>
	>(() => {
		const initial: Record<string, UploadedFile[]> = {};
		fields.forEach((field) => {
			initial[field.id] = (field.defaultValue || []).map((file) => ({
				id: generateId(),
				file,
				preview: file.type.startsWith("image/")
					? URL.createObjectURL(file)
					: undefined,
				progress: 100,
				status: "success" as const,
			}));
		});
		return initial;
	});
	const [errors, setErrors] = useState<Record<string, string>>({});
	const [dragOver, setDragOver] = useState<Record<string, boolean>>({});
	const [loading, setLoading] = useState(false);
	const fileInputRefs = useRef<Record<string, HTMLInputElement | null>>({});

	const validateFile = useCallback(
		(
			field: FileUploadField,
			file: File,
			currentFiles: UploadedFile[],
		): string | null => {
			// Check max files
			if (field.maxFiles && currentFiles.length >= field.maxFiles) {
				return `Maximum ${field.maxFiles} file${field.maxFiles > 1 ? "s" : ""} allowed`;
			}
			// Check file size
			if (field.maxSize && file.size > field.maxSize) {
				return `File size must be less than ${formatFileSize(field.maxSize)}`;
			}
			// Check file type
			if (field.accept) {
				const acceptedTypes = field.accept.split(",").map((t) => t.trim());
				const fileType = file.type;
				const fileExtension = "." + file.name.split(".").pop()?.toLowerCase();

				const isAccepted = acceptedTypes.some((type) => {
					if (type.startsWith(".")) {
						return fileExtension === type.toLowerCase();
					}
					if (type.endsWith("/*")) {
						return fileType.startsWith(type.slice(0, -1));
					}
					return fileType === type;
				});

				if (!isAccepted) {
					return `File type not accepted. Allowed: ${field.accept}`;
				}
			}
			return null;
		},
		[],
	);

	const simulateUpload = useCallback(
		(fieldId: string, fileId: string): Promise<void> => {
			return new Promise((resolve) => {
				if (uploadDelay === 0) {
					setUploadedFiles((prev) => ({
						...prev,
						[fieldId]: prev[fieldId].map((f) =>
							f.id === fileId
								? { ...f, status: "success" as const, progress: 100 }
								: f,
						),
					}));
					resolve();
					return;
				}

				const steps = 10;
				const stepTime = uploadDelay / steps;
				let currentStep = 0;

				const interval = setInterval(() => {
					currentStep++;
					const progress = Math.min((currentStep / steps) * 100, 100);

					setUploadedFiles((prev) => ({
						...prev,
						[fieldId]: prev[fieldId].map((f) =>
							f.id === fileId ? { ...f, progress } : f,
						),
					}));

					if (currentStep >= steps) {
						clearInterval(interval);
						setUploadedFiles((prev) => ({
							...prev,
							[fieldId]: prev[fieldId].map((f) =>
								f.id === fileId
									? { ...f, status: "success" as const, progress: 100 }
									: f,
							),
						}));
						resolve();
					}
				}, stepTime);
			});
		},
		[uploadDelay],
	);

	const performUpload = useCallback(
		async (fieldId: string, fileId: string, file: File): Promise<void> => {
			if (onUpload) {
				try {
					await onUpload(file, fieldId);
					setUploadedFiles((prev) => ({
						...prev,
						[fieldId]: prev[fieldId].map((f) =>
							f.id === fileId
								? { ...f, status: "success" as const, progress: 100 }
								: f,
						),
					}));
				} catch (error) {
					const errorMessage =
						error instanceof Error ? error.message : "Upload failed";
					setUploadedFiles((prev) => ({
						...prev,
						[fieldId]: prev[fieldId].map((f) =>
							f.id === fileId
								? { ...f, status: "error" as const, error: errorMessage }
								: f,
						),
					}));
					throw error;
				}
			} else {
				await simulateUpload(fieldId, fileId);
			}
		},
		[onUpload, simulateUpload],
	);

	const handleFiles = useCallback(
		async (fieldId: string, files: FileList | null) => {
			if (!files || files.length === 0) return;

			const field = fields.find((f) => f.id === fieldId);
			if (!field) return;

			const currentFiles = uploadedFiles[fieldId] || [];
			const newFiles: UploadedFile[] = [];

			for (const file of Array.from(files)) {
				const error = validateFile(field, file, [...currentFiles, ...newFiles]);
				if (error) {
					toast.error(error, { description: file.name });
					continue;
				}

				const uploadedFile: UploadedFile = {
					id: generateId(),
					file,
					preview: file.type.startsWith("image/")
						? URL.createObjectURL(file)
						: undefined,
					progress: 0,
					status: "uploading",
				};
				newFiles.push(uploadedFile);
			}

			if (newFiles.length === 0) return;

			// Add files to state
			setUploadedFiles((prev) => ({
				...prev,
				[fieldId]: [...prev[fieldId], ...newFiles],
			}));

			// Clear field error
			setErrors((prev) => ({ ...prev, [fieldId]: "" }));

			// Upload each file
			for (const uploadedFile of newFiles) {
				try {
					await performUpload(fieldId, uploadedFile.id, uploadedFile.file);
					if (showSuccessToast) {
						toast.success("File uploaded", {
							description: (
								<div className="flex items-center gap-2">
									{uploadedFile.preview ? (
										<img
											src={uploadedFile.preview}
											alt={uploadedFile.file.name}
											className="size-8 rounded object-cover"
										/>
									) : (
										<div className="flex size-8 items-center justify-center rounded bg-muted">
											{React.createElement(getFileIcon(uploadedFile.file), {
												className: "size-4 text-muted-foreground",
											})}
										</div>
									)}
									<span className="truncate text-sm">
										{uploadedFile.file.name}
									</span>
								</div>
							),
						});
					}
				} catch {
					toast.error("Upload failed", { description: uploadedFile.file.name });
				}
			}

			// Notify parent of changes
			const allFiles: Record<string, File[]> = {};
			fields.forEach((f) => {
				const files =
					f.id === fieldId
						? [...currentFiles, ...newFiles]
						: uploadedFiles[f.id] || [];
				allFiles[f.id] = files
					.filter((uf) => uf.status === "success")
					.map((uf) => uf.file);
			});
			onFilesChange?.(allFiles);
		},
		[
			fields,
			uploadedFiles,
			validateFile,
			performUpload,
			showSuccessToast,
			onFilesChange,
		],
	);

	const removeFile = useCallback(
		(fieldId: string, fileId: string) => {
			const file = uploadedFiles[fieldId]?.find((f) => f.id === fileId);
			if (file?.preview) {
				URL.revokeObjectURL(file.preview);
			}

			setUploadedFiles((prev) => ({
				...prev,
				[fieldId]: prev[fieldId].filter((f) => f.id !== fileId),
			}));

			// Notify parent of changes
			const allFiles: Record<string, File[]> = {};
			fields.forEach((f) => {
				const files = uploadedFiles[f.id] || [];
				allFiles[f.id] = files
					.filter((uf) => uf.id !== fileId && uf.status === "success")
					.map((uf) => uf.file);
			});
			onFilesChange?.(allFiles);
		},
		[fields, uploadedFiles, onFilesChange],
	);

	const handleDragOver = useCallback((e: React.DragEvent, fieldId: string) => {
		e.preventDefault();
		e.stopPropagation();
		setDragOver((prev) => ({ ...prev, [fieldId]: true }));
	}, []);

	const handleDragLeave = useCallback((e: React.DragEvent, fieldId: string) => {
		e.preventDefault();
		e.stopPropagation();
		setDragOver((prev) => ({ ...prev, [fieldId]: false }));
	}, []);

	const handleDrop = useCallback(
		(e: React.DragEvent, fieldId: string) => {
			e.preventDefault();
			e.stopPropagation();
			setDragOver((prev) => ({ ...prev, [fieldId]: false }));
			handleFiles(fieldId, e.dataTransfer.files);
		},
		[handleFiles],
	);

	const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
		e.preventDefault();

		// Validate required fields
		const newErrors: Record<string, string> = {};
		let hasErrors = false;

		fields.forEach((field) => {
			const files = uploadedFiles[field.id] || [];
			const successFiles = files.filter((f) => f.status === "success");
			if (field.required && successFiles.length === 0) {
				newErrors[field.id] = `At least one file is required`;
				hasErrors = true;
			}
		});

		setErrors(newErrors);
		if (hasErrors) return;

		// Check if any files are still uploading
		const hasUploading = Object.values(uploadedFiles).some((files) =>
			files.some((f) => f.status === "uploading"),
		);
		if (hasUploading) {
			toast.error("Please wait for all files to finish uploading");
			return;
		}

		setLoading(true);
		try {
			const allFiles: Record<string, File[]> = {};
			fields.forEach((f) => {
				allFiles[f.id] = (uploadedFiles[f.id] || [])
					.filter((uf) => uf.status === "success")
					.map((uf) => uf.file);
			});
			await onSubmit(allFiles);
		} finally {
			setLoading(false);
		}
	};

	const isValid = fields.every((field) => {
		if (field.required) {
			const files = uploadedFiles[field.id] || [];
			return files.some((f) => f.status === "success");
		}
		return true;
	});

	const hasUploading = Object.values(uploadedFiles).some((files) =>
		files.some((f) => f.status === "uploading"),
	);

	return (
		<main className="mx-auto p-4">
			<div
				style={{ animationDuration: "500ms" }}
				className="motion-safe:animate-in motion-safe:fade-in motion-safe:slide-in-from-bottom-4"
			>
				<h1 className="text-2xl font-semibold text-foreground sm:text-xl">
					{title}
				</h1>
				<p className="mt-6 text-muted-foreground sm:text-sm">{description}</p>
			</div>

			<form onSubmit={handleSubmit} className="mt-8">
				<div className="flex flex-col gap-6">
					{fields.map((field, index) => {
						const fieldFiles = uploadedFiles[field.id] || [];
						const canAddMore =
							!field.maxFiles || fieldFiles.length < field.maxFiles;

						return (
							<div
								key={field.id}
								className="motion-safe:animate-in motion-safe:fade-in motion-safe:slide-in-from-bottom-4"
								style={{
									animationDuration: "500ms",
									animationDelay: `${100 + index * 100}ms`,
									animationFillMode: "backwards",
								}}
							>
								<OnboardingLabel className="mb-2 block text-sm font-medium text-foreground">
									{field.label}
									{field.required && (
										<span className="ml-1 text-destructive">*</span>
									)}
								</OnboardingLabel>

								{field.description && (
									<p className="mb-2 text-sm text-muted-foreground">
										{field.description}
									</p>
								)}

								{/* Drop zone */}
								{canAddMore && (
									<div
										onDragOver={(e) => handleDragOver(e, field.id)}
										onDragLeave={(e) => handleDragLeave(e, field.id)}
										onDrop={(e) => handleDrop(e, field.id)}
										onClick={() => fileInputRefs.current[field.id]?.click()}
										className={cx(
											"relative cursor-pointer rounded-lg border-2 border-dashed p-6 transition-colors",
											"hover:border-primary/50 hover:bg-muted/50",
											dragOver[field.id]
												? "border-primary bg-primary/5"
												: "border-border bg-background",
											errors[field.id] && "border-destructive",
										)}
									>
										<input
											ref={(el) => {
												fileInputRefs.current[field.id] = el;
											}}
											type="file"
											accept={field.accept}
											multiple={!field.maxFiles || field.maxFiles > 1}
											onChange={(e) => handleFiles(field.id, e.target.files)}
											className="sr-only"
										/>

										<div className="flex flex-col items-center gap-2 text-center">
											<div className="rounded-full bg-muted p-3">
												<Upload className="size-5 text-muted-foreground" />
											</div>
											<div>
												<p className="text-sm font-medium text-foreground">
													{field.dropZoneTitle ||
														"Drop files here or click to upload"}
												</p>
												<p className="mt-1 text-xs text-muted-foreground">
													{field.dropZoneSubtitle || (
														<>
															{field.accept && `Accepted: ${field.accept}`}
															{field.accept && field.maxSize && " · "}
															{field.maxSize &&
																`Max size: ${formatFileSize(field.maxSize)}`}
															{(field.accept || field.maxSize) &&
																field.maxFiles &&
																" · "}
															{field.maxFiles && `Max files: ${field.maxFiles}`}
														</>
													)}
												</p>
											</div>
										</div>
									</div>
								)}

								{/* Uploaded files list */}
								{fieldFiles.length > 0 && (
									<div className="mt-3 space-y-2">
										{fieldFiles.map((uploadedFile) => {
											const FileIcon = getFileIcon(uploadedFile.file);
											return (
												<div
													key={uploadedFile.id}
													className={cx(
														"flex items-center gap-3 rounded-lg border p-3",
														"bg-card",
														uploadedFile.status === "error" &&
															"border-destructive bg-destructive/5",
													)}
												>
													{/* Thumbnail / Icon */}
													<div className="flex-shrink-0">
														{uploadedFile.preview ? (
															<img
																src={uploadedFile.preview}
																alt={uploadedFile.file.name}
																className="size-10 rounded object-cover"
															/>
														) : (
															<div className="flex size-10 items-center justify-center rounded bg-muted">
																<FileIcon className="size-5 text-muted-foreground" />
															</div>
														)}
													</div>

													{/* File info */}
													<div className="min-w-0 flex-1">
														<p className="truncate text-sm font-medium text-foreground">
															{uploadedFile.file.name}
														</p>
														{showFileSize && (
															<p className="text-xs text-muted-foreground">
																{formatFileSize(uploadedFile.file.size)}
															</p>
														)}

														{/* Progress bar */}
														{uploadedFile.status === "uploading" && (
															<div className="mt-1.5 h-1 overflow-hidden rounded-full bg-muted">
																<div
																	className="h-full bg-primary transition-all duration-200"
																	style={{ width: `${uploadedFile.progress}%` }}
																/>
															</div>
														)}

														{/* Error message */}
														{uploadedFile.status === "error" &&
															uploadedFile.error && (
																<p className="mt-1 flex items-center gap-1 text-xs text-destructive">
																	<AlertCircle className="size-3" />
																	{uploadedFile.error}
																</p>
															)}
													</div>

													{/* Remove button */}
													<button
														type="button"
														onClick={() =>
															removeFile(field.id, uploadedFile.id)
														}
														className={cx(
															"flex-shrink-0 rounded-full p-1 transition-colors",
															"hover:bg-muted",
															"focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
														)}
														aria-label={`Remove ${uploadedFile.file.name}`}
													>
														<X className="size-4 text-muted-foreground" />
													</button>
												</div>
											);
										})}
									</div>
								)}

								{errors[field.id] && (
									<p className="mt-1.5 text-sm text-destructive">
										{errors[field.id]}
									</p>
								)}
							</div>
						);
					})}
				</div>

				<div
					className={cx(
						"mt-8 flex",
						backButton || skipButton ? "justify-between" : "justify-end",
					)}
				>
					<div className="flex gap-2">
						{backButton && (
							<OnboardingButton
								type="button"
								variant="ghost"
								onClick={backButton.onClick}
							>
								{backButton.text}
							</OnboardingButton>
						)}
					</div>
					<div className="flex gap-2">
						{skipButton && (
							<OnboardingButton
								type="button"
								variant="ghost"
								onClick={skipButton.onClick}
							>
								{skipButton.text}
							</OnboardingButton>
						)}
						<OnboardingButton
							className="disabled:bg-muted disabled:text-muted-foreground"
							type="submit"
							disabled={!isValid || loading || hasUploading}
							aria-disabled={!isValid || loading || hasUploading}
							isLoading={loading || hasUploading}
						>
							{loading
								? loadingText
								: hasUploading
									? uploadingText
									: submitText}
						</OnboardingButton>
					</div>
				</div>
			</form>
		</main>
	);
}
