import { FileIcon, UploadCloud, X } from 'lucide-react';
import type { FC, ChangeEvent, DragEvent, MouseEvent } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Button } from '../../../../../../packages/ui-shadcn/components/ui/button';
import { cn } from '../../../../../../packages/ui-shadcn/lib/utils';

export interface UploadedFile {
    id: string;
    file: File;
    preview: string | null;
}

interface FileUploadProps {
    label?: string;
    description?: string;
    multiple?: boolean;
    maxSize?: number; // in bytes
    allowedExtensions?: string[];
    value?: File[];
    onChange?: (files: File[]) => void;
    error?: string;
    disabled?: boolean;
    className?: string;
}

/**
 * File Upload Component
 * 
 * A drag-and-drop file upload component with preview functionality.
 * 
 * Features:
 * - Drag and drop support
 * - Multiple file upload
 * - File validation (size, extension)
 * - Image preview
 * - File info display
 * - Memory leak prevention
 * 
 * @example
 * ```tsx
 * <FileUpload
 *   label="Upload Documents"
 *   description="Upload your documents here"
 *   multiple
 *   maxSize={5 * 1024 * 1024} // 5MB
 *   allowedExtensions={['jpg', 'png', 'pdf']}
 *   value={files}
 *   onChange={setFiles}
 * />
 * ```
 */
export const FileUpload: FC<FileUploadProps> = ({
    label,
    description,
    multiple = true,
    maxSize = 5 * 1024 * 1024, // 5MB default
    allowedExtensions = ['jpg', 'jpeg', 'png', 'pdf'],
    value = [],
    onChange,
    error,
    disabled = false,
    className,
}) => {
    // Convert File[] to UploadedFile[] for internal state
    const [uploadedFiles, setUploadedFiles] = useState<UploadedFile[]>(() => {
        return value.map((file) => ({
            id: `${file.name}-${file.size}-${Date.now()}-${Math.random()}`,
            file,
            preview: file.type.startsWith('image/')
                ? URL.createObjectURL(file)
                : null,
        }));
    });

    const [isDragging, setIsDragging] = useState(false);
    const [validationError, setValidationError] = useState<string | null>(null);
    const fileInputRef = useRef<HTMLInputElement>(null);

    // Sync internal state with external value prop
    useEffect(() => {
        if (value.length === 0 && uploadedFiles.length > 0) {
            // Parent cleared the files
            uploadedFiles.forEach((file) => {
                if (file.preview) {
                    URL.revokeObjectURL(file.preview);
                }
            });
            setUploadedFiles([]);
        }
    }, [value, uploadedFiles]);

    // Cleanup on unmount
    useEffect(() => {
        return () => {
            uploadedFiles.forEach((file) => {
                if (file.preview) {
                    URL.revokeObjectURL(file.preview);
                }
            });
        };
    }, [uploadedFiles]);

    const validateFile = useCallback(
        (file: File): string | null => {
            // Check file size
            if (file.size > maxSize) {
                return `${file.name} is too large (max ${(maxSize / 1024 / 1024).toFixed(0)}MB)`;
            }

            // Check file extension
            const extension = file.name.split('.').pop()?.toLowerCase();

            if (!extension || !allowedExtensions.includes(extension)) {
                return `${file.name} has an invalid file extension`;
            }

            return null;
        },
        [maxSize, allowedExtensions],
    );

    const processFiles = useCallback(
        (fileList: FileList | null) => {
            if (!fileList || disabled) {
                return;
            }

            setValidationError(null);
            const newFiles: UploadedFile[] = [];

            Array.from(fileList).forEach((file) => {
                const error = validateFile(file);

                if (error) {
                    setValidationError(error);

                    return;
                }

                const uploadedFile: UploadedFile = {
                    id: `${file.name}-${file.size}-${Date.now()}-${Math.random()}`,
                    file,
                    preview: file.type.startsWith('image/')
                        ? URL.createObjectURL(file)
                        : null,
                };

                newFiles.push(uploadedFile);
            });

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

            const updatedFiles = multiple
                ? [...uploadedFiles, ...newFiles]
                : newFiles;

            setUploadedFiles(updatedFiles);
            onChange?.(updatedFiles.map((f) => f.file));
        },
        [disabled, multiple, uploadedFiles, validateFile, onChange],
    );

    const handleDrop = useCallback(
        (e: DragEvent<HTMLDivElement>) => {
            e.preventDefault();
            e.stopPropagation();
            setIsDragging(false);
            processFiles(e.dataTransfer.files);
        },
        [processFiles],
    );

    const handleDragOver = useCallback(
        (e: DragEvent<HTMLDivElement>) => {
            e.preventDefault();
            e.stopPropagation();
            setIsDragging(true);
        },
        [],
    );

    const handleDragLeave = useCallback(
        (e: DragEvent<HTMLDivElement>) => {
            e.preventDefault();
            e.stopPropagation();
            setIsDragging(false);
        },
        [],
    );

    const handleRemove = useCallback(
        (id: string) => {
            const updatedFiles = uploadedFiles.filter((f) => f.id !== id);

            // Revoke object URL to prevent memory leaks
            const fileToRemove = uploadedFiles.find((f) => f.id === id);

            if (fileToRemove?.preview) {
                URL.revokeObjectURL(fileToRemove.preview);
            }

            setUploadedFiles(updatedFiles);
            onChange?.(updatedFiles.map((f) => f.file));
        },
        [uploadedFiles, onChange],
    );

    const handleClick = () => {
        if (!disabled) {
            fileInputRef.current?.click();
        }
    };

    const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
        processFiles(e.target.files);
        // Reset input value to allow uploading the same file again
        e.target.value = '';
    };

    const displayError = error || validationError;

    return (
        <div className={cn('space-y-3', className)}>
            {/* Label */}
            {label && (
                <div className="space-y-1">
                    <p className="text-sm font-medium text-foreground">
                        {label}
                    </p>
                    {description && (
                        <p className="text-xs text-muted-foreground">
                            {description}
                        </p>
                    )}
                </div>
            )}

            {/* Drop Zone */}
            <div
                onDrop={handleDrop}
                onDragOver={handleDragOver}
                onDragLeave={handleDragLeave}
                onClick={handleClick}
                className={cn(
                    'flex cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed p-6 text-center transition-all duration-200',
                    isDragging
                        ? 'border-primary bg-primary/5'
                        : 'border-muted-foreground/25 bg-muted/40 hover:bg-muted/60',
                    disabled && 'cursor-not-allowed opacity-50',
                    displayError && 'border-destructive',
                )}
            >
                <UploadCloud
                    className={cn(
                        'mb-2 h-8 w-8',
                        isDragging ? 'text-primary' : 'text-muted-foreground',
                    )}
                />
                <p className="text-sm text-foreground">
                    Drag & drop files here, or{' '}
                    <span className="font-medium text-primary underline">
                        browse
                    </span>
                </p>
                <p className="mt-1 text-xs text-muted-foreground">
                    {allowedExtensions
                        .map((ext) => ext.toUpperCase())
                        .join(', ')}{' '}
                    (max {(maxSize / 1024 / 1024).toFixed(0)}MB)
                </p>

                <input
                    ref={fileInputRef}
                    type="file"
                    multiple={multiple}
                    accept={allowedExtensions.map((ext) => `.${ext}`).join(',')}
                    className="hidden"
                    onChange={handleFileChange}
                    disabled={disabled}
                />
            </div>

            {/* Error Message */}
            {displayError && (
                <p className="text-sm text-destructive" role="alert">
                    {displayError}
                </p>
            )}

            {/* Preview Grid */}
            {uploadedFiles.length > 0 && (
                <div className="grid grid-cols-2 gap-3 md:grid-cols-3 lg:grid-cols-4">
                    {uploadedFiles.map((uploadedFile) => (
                        <div
                            key={uploadedFile.id}
                            className="group relative overflow-hidden rounded-lg border border-border bg-card p-2 transition-shadow hover:shadow-md"
                        >
                            {/* Preview Content */}
                            {uploadedFile.preview ? (
                                <img
                                    src={uploadedFile.preview}
                                    alt={uploadedFile.file.name}
                                    className="h-32 w-full rounded object-cover"
                                />
                            ) : (
                                <div className="flex h-32 flex-col items-center justify-center gap-2 text-muted-foreground">
                                    <FileIcon className="h-8 w-8" />
                                    <span className="text-xs">
                                        {uploadedFile.file.name
                                            .split('.')
                                            .pop()
                                            ?.toUpperCase()}
                                    </span>
                                </div>
                            )}

                            {/* Remove Button */}
                            <Button
                                type="button"
                                variant="destructive"
                                size="icon"
                                className="absolute top-2 right-2 h-6 w-6 opacity-0 transition-opacity group-hover:opacity-100"
                                onClick={(e: MouseEvent<HTMLButtonElement>) => {
                                    e.stopPropagation();
                                    handleRemove(uploadedFile.id);
                                }}
                            >
                                <X className="h-3 w-3" />
                            </Button>

                            {/* File Info */}
                            <div className="mt-2 space-y-0.5">
                                <p className="truncate text-xs font-medium text-foreground">
                                    {uploadedFile.file.name}
                                </p>
                                <p className="text-xs text-muted-foreground">
                                    {(uploadedFile.file.size / 1024).toFixed(1)}{' '}
                                    KB
                                </p>
                            </div>
                        </div>
                    ))}
                </div>
            )}
        </div>
    );
};
