import { cva, type VariantProps } from "class-variance-authority";
import {
	CheckCircle2,
	File,
	FileCode,
	FileText,
	Image as ImageIcon,
	Upload,
	X,
} from "lucide-react";
import {
	type ChangeEvent,
	type DragEvent,
	forwardRef,
	useRef,
	useState,
} from "react";
import { Button } from "@/components/ui/button";
import { Progress } from "@/components/ui/progress";
import { cn } from "@/lib/utils";

/**
 * File uploader component for uploading files with drag and drop support
 *
 * @example
 * ```tsx
 * <FileUploader
 *   onFilesSelected={(files) => console.log(files)}
 *   accept=".jpg, .png, .pdf"
 *   maxFiles={3}
 *   maxSize={5 * 1024 * 1024} // 5MB
 * />
 * ```
 */

const fileUploaderVariants = cva(
	"relative border-2 border-dashed rounded-lg p-4 transition-colors",
	{
		variants: {
			variant: {
				default: "border-border bg-background hover:border-primary/50",
				compact: "border-border bg-muted/20 p-2",
				invisible: "border-transparent bg-transparent p-0",
			},
			state: {
				idle: "",
				dragging: "border-primary bg-primary/5",
				uploading: "border-primary/50",
				success: "border-emerald-500/50 bg-emerald-50 dark:bg-emerald-950/10",
				error: "border-destructive/50 bg-destructive/5",
			},
		},
		defaultVariants: {
			variant: "default",
			state: "idle",
		},
	},
);

export interface FileUploaderProps
	extends Omit<
			React.HTMLAttributes<HTMLDivElement>,
			"onDrop" | "onError" | "onChange"
		>,
		VariantProps<typeof fileUploaderVariants> {
	/**
	 * File types to accept
	 */
	accept?: string;
	/**
	 * Maximum number of files allowed
	 */
	maxFiles?: number;
	/**
	 * Maximum file size in bytes
	 */
	maxSize?: number;
	/**
	 * Whether to allow multiple files
	 */
	multiple?: boolean;
	/**
	 * Button text
	 */
	buttonText?: string;
	/**
	 * Instruction text
	 */
	instructionText?: string;
	/**
	 * Whether to automatically upload files
	 */
	autoUpload?: boolean;
	/**
	 * Function to handle file upload
	 */
	uploadFunction?: (files: File[]) => Promise<any>;
	/**
	 * Callback when files are selected
	 */
	onFilesSelected?: (files: File[]) => void;
	/**
	 * Callback when files change
	 */
	onChange?: (files: File[]) => void;
	/**
	 * Callback on upload error
	 */
	onError?: (error: string) => void;
	/**
	 * Callback on upload success
	 */
	onSuccess?: (result: any) => void;
	/**
	 * Whether to show selected files
	 */
	showSelectedFiles?: boolean;
	/**
	 * Whether to show file preview
	 */
	showPreview?: boolean;
	/**
	 * Whether the uploader is disabled
	 */
	disabled?: boolean;
}

// Helper to get file icon based on mime type
const getFileIcon = (file: File) => {
	if (file.type.startsWith("image/")) {
		return <ImageIcon className="h-6 w-6 text-blue-500" />;
	} else if (file.type.startsWith("text/")) {
		return <FileText className="h-6 w-6 text-amber-500" />;
	} else if (file.type.includes("pdf")) {
		return <FileText className="h-6 w-6 text-rose-500" />;
	} else if (
		file.type.includes("code") ||
		file.type.includes("javascript") ||
		file.type.includes("html")
	) {
		return <FileCode className="h-6 w-6 text-emerald-500" />;
	} else {
		return <File className="h-6 w-6 text-muted-foreground" />;
	}
};

// Format file size in a human-readable way
const 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]}`;
};

const FileUploader = forwardRef<HTMLDivElement, FileUploaderProps>(
	(
		{
			className,
			variant,
			state: externalState,
			accept,
			maxFiles = 1,
			maxSize,
			multiple = false,
			buttonText = "Select file",
			instructionText = "or drag and drop",
			autoUpload = false,
			uploadFunction,
			onFilesSelected,
			onChange,
			onError,
			onSuccess,
			showSelectedFiles = true,
			showPreview = true,
			disabled = false,
			...props
		},
		ref,
	) => {
		const [files, setFiles] = useState<File[]>([]);
		const [dragging, setDragging] = useState(false);
		const [uploading, setUploading] = useState(false);
		const [uploadProgress, setUploadProgress] = useState(0);
		const [error, setError] = useState<string | null>(null);
		const [success, setSuccess] = useState(false);
		const fileInputRef = useRef<HTMLInputElement>(null);

		// Determine component state based on props and internal state
		const state =
			externalState ||
			(error
				? "error"
				: success
					? "success"
					: uploading
						? "uploading"
						: dragging
							? "dragging"
							: "idle");

		// Handle file selection from input
		const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
			const selectedFiles = Array.from(e.target.files || []);
			processFiles(selectedFiles);
		};

		// Handle file drop
		const handleDrop = (e: DragEvent<HTMLDivElement>) => {
			e.preventDefault();
			e.stopPropagation();
			setDragging(false);

			if (disabled) return;

			if (e.dataTransfer.files.length > 0) {
				const droppedFiles = Array.from(e.dataTransfer.files);
				processFiles(droppedFiles);
			}
		};

		// Handle file validation and processing
		const processFiles = (selectedFiles: File[]) => {
			setError(null);

			// Check if too many files
			if (selectedFiles.length > maxFiles) {
				const errorMessage = `Maximum ${maxFiles} file${maxFiles === 1 ? "" : "s"} allowed`;
				setError(errorMessage);
				onError?.(errorMessage);
				return;
			}

			// Check file sizes if maxSize is provided
			if (maxSize) {
				const oversizedFiles = selectedFiles.filter(
					(file) => file.size > maxSize,
				);
				if (oversizedFiles.length > 0) {
					const errorMessage = `File${oversizedFiles.length > 1 ? "s" : ""} exceed${oversizedFiles.length === 1 ? "s" : ""} maximum size of ${formatFileSize(maxSize)}`;
					setError(errorMessage);
					onError?.(errorMessage);
					return;
				}
			}

			// Update files state
			const newFiles = multiple ? [...files, ...selectedFiles] : selectedFiles;
			setFiles(newFiles);

			// Call callbacks
			onFilesSelected?.(newFiles);
			onChange?.(newFiles);

			// Auto upload if enabled
			if (autoUpload && uploadFunction) {
				uploadFiles(newFiles);
			}
		};

		// Handle file upload
		const uploadFiles = async (filesToUpload: File[]) => {
			if (!uploadFunction) return;

			try {
				setUploading(true);
				setUploadProgress(0);

				// Fake progress updates for demo
				const progressInterval = setInterval(() => {
					setUploadProgress((prev) => {
						const increment = Math.random() * 10;
						return Math.min(prev + increment, 95);
					});
				}, 300);

				// Actual upload
				const result = await uploadFunction(filesToUpload);

				// Complete progress and show success
				clearInterval(progressInterval);
				setUploadProgress(100);
				setSuccess(true);
				setUploading(false);
				onSuccess?.(result);
			} catch (err) {
				setUploading(false);
				const errorMessage =
					err instanceof Error ? err.message : "Upload failed";
				setError(errorMessage);
				onError?.(errorMessage);
			}
		};

		// Handle drag events
		const handleDragOver = (e: DragEvent<HTMLDivElement>) => {
			e.preventDefault();
			e.stopPropagation();
			if (!disabled) setDragging(true);
		};

		const handleDragLeave = (e: DragEvent<HTMLDivElement>) => {
			e.preventDefault();
			e.stopPropagation();
			setDragging(false);
		};

		// Trigger file input click
		const handleButtonClick = () => {
			if (!disabled && fileInputRef.current) {
				fileInputRef.current.click();
			}
		};

		// Remove file from selection
		const handleRemoveFile = (indexToRemove: number) => {
			const newFiles = files.filter((_, index) => index !== indexToRemove);
			setFiles(newFiles);
			onChange?.(newFiles);
		};

		// Create preview for image files
		const renderPreview = (file: File) => {
			if (!showPreview) return null;

			if (file.type.startsWith("image/")) {
				return (
					<div className="relative h-16 w-16 rounded overflow-hidden bg-muted">
						<img
							src={URL.createObjectURL(file)}
							alt={file.name}
							className="h-full w-full object-cover"
							onLoad={() => URL.revokeObjectURL(URL.createObjectURL(file))}
						/>
					</div>
				);
			}

			return (
				<div className="flex items-center justify-center h-16 w-16 rounded bg-muted/50">
					{getFileIcon(file)}
				</div>
			);
		};

		return (
			<div className="w-full space-y-2" ref={ref} {...props}>
				{/* File Input (hidden) */}
				<input
					ref={fileInputRef}
					type="file"
					accept={accept}
					multiple={multiple && maxFiles > 1}
					className="hidden"
					onChange={handleFileChange}
					disabled={disabled}
				/>

				{/* Drag & Drop Area */}
				<div
					className={cn(
						fileUploaderVariants({ variant, state, className }),
						disabled && "opacity-60 cursor-not-allowed",
					)}
					onDragOver={handleDragOver}
					onDragLeave={handleDragLeave}
					onDrop={handleDrop}
				>
					<div className="flex flex-col items-center justify-center py-4 text-center">
						<Upload
							className={cn(
								"h-10 w-10 mb-2",
								state === "error"
									? "text-destructive"
									: state === "success"
										? "text-emerald-500"
										: "text-muted-foreground",
							)}
						/>

						<div className="space-y-1">
							{state === "error" ? (
								<>
									<p className="text-sm font-medium text-destructive">
										Upload failed
									</p>
									<p className="text-xs text-muted-foreground">{error}</p>
								</>
							) : state === "success" ? (
								<>
									<p className="text-sm font-medium text-emerald-600 dark:text-emerald-400">
										Upload complete
									</p>
									<p className="text-xs text-muted-foreground">
										Files uploaded successfully
									</p>
								</>
							) : (
								<>
									<Button
										type="button"
										variant="outline"
										size="sm"
										onClick={handleButtonClick}
										disabled={disabled || uploading}
									>
										{buttonText}
									</Button>
									<p className="text-xs text-muted-foreground pt-1">
										{instructionText}
										{accept && (
											<span className="block">
												{accept.replaceAll(",", ", ")}
											</span>
										)}
										{maxSize && (
											<span className="block">
												Max size: {formatFileSize(maxSize)}
											</span>
										)}
									</p>
								</>
							)}
						</div>
					</div>
				</div>

				{/* Upload Progress */}
				{uploading && (
					<div className="space-y-1">
						<div className="flex justify-between text-xs text-muted-foreground">
							<span>Uploading...</span>
							<span>{Math.round(uploadProgress)}%</span>
						</div>
						<Progress value={uploadProgress} />
					</div>
				)}

				{/* File List */}
				{showSelectedFiles && files.length > 0 && (
					<ul className="mt-2 space-y-2">
						{files.map((file, index) => (
							<li
								key={`${file.name}-${index}`}
								className="flex items-center gap-3 bg-muted/20 rounded p-2 text-sm"
							>
								{renderPreview(file)}

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

								{state === "success" ? (
									<CheckCircle2 className="h-5 w-5 text-emerald-500 flex-shrink-0" />
								) : (
									<Button
										type="button"
										variant="ghost"
										size="icon"
										className="h-6 w-6 rounded-full"
										onClick={() => handleRemoveFile(index)}
										disabled={uploading || disabled}
									>
										<X className="h-4 w-4" />
										<span className="sr-only">Remove {file.name}</span>
									</Button>
								)}
							</li>
						))}
					</ul>
				)}

				{/* Upload Button (when not auto-uploading) */}
				{!autoUpload && files.length > 0 && uploadFunction && !success && (
					<Button
						type="button"
						onClick={() => uploadFiles(files)}
						disabled={uploading || disabled || files.length === 0}
						className="mt-2"
					>
						{uploading ? "Uploading..." : "Upload"}
						{uploading && <span className="loading ml-2"></span>}
					</Button>
				)}
			</div>
		);
	},
);

FileUploader.displayName = "FileUploader";

export { FileUploader, fileUploaderVariants };
