import React, { forwardRef, useImperativeHandle } from 'react';
import { DropzoneOptions, useDropzone } from 'react-dropzone';
import { HiOutlineCloudUpload } from 'react-icons/hi';
import {
	alpha,
	Box,
	BoxProps,
	Button,
	ButtonProps,
	FormControl,
	FormControlProps,
	FormHelperText,
	FormHelperTextProps,
	FormLabel,
	FormLabelProps,
	Stack,
	StackProps
} from '@mui/material';
import { downloadBlob } from '@reevolve/core';
import { isFunction } from 'lodash';

import useDefaultProps from '../../hooks/useDefaultProps';
import { useLocalization } from '../../localization';
import { fileFormat } from '../file-thumbnail';
import Spin from '../spin/Spin';

import DeleteButton, { DeleteButtonProps } from './components/DeleteButton';
import DownloadButton, { DownloadButtonProps } from './components/DownloadButton';
import MultiFilePreview, { MultiFilePreviewProps } from './components/MultiFilePreview';
import RejectionFiles, { RejectionFilesProps } from './components/RejectionFiles';
import SingleFilePreview, { SingleFilePreviewProps } from './components/SingleFilePreview';
import UploadPlaceholder, { UploadPlaceholderProps } from './components/UploadPlaceholder';

export interface UploadPlaceholderSlotProps
	extends Pick<
		UploadProps<boolean>,
		| 'accept'
		| 'disabled'
		| 'error'
		| 'label'
		| 'required'
		| 'maxFiles'
		| 'minSize'
		| 'maxSize'
		| 'multiple'
		| 'noClick'
		| 'noDrag'
		| 'preventDropOnDocument'
	> {}

export interface UploadProps<TMultiple extends boolean | undefined = false>
	extends Omit<DropzoneOptions, 'multiple' | 'disabled'> {
	required?: boolean;
	loading?: boolean;
	/**
	 * single image file preview aspect ratio
	 * @default 16 / 9
	 */
	imagePreviewAspectRatio?: number;
	/**
	 * show singe file preview in list view (multiFilePreview)
	 * @default true
	 */
	singleFileListPreview?: boolean;
	/**
	 * show download button in file preview
	 * @default false
	 */
	enableDownload?: boolean;
	slotProps?: {
		wrap?: BoxProps;
		formLabel?: Omit<FormLabelProps, 'control' | 'ref' | 'label' | 'required'>;
		helperText?: FormHelperTextProps;
		formControl?: Omit<FormControlProps, 'name' | 'disabled' | 'error' | 'children' | 'defaultValue'>;
		deleteButton?: Omit<ButtonProps, 'onClick'>;
		deleteIconButton?: Omit<DeleteButtonProps, 'onClick'>;
		downloadIconButton?: Omit<DownloadButtonProps, 'onClick'>;
		actionButtonsContainer?: Omit<StackProps, 'children'>;
		removeAllButton?: Omit<ButtonProps, 'onClick'>;
		uploadButton?: Omit<ButtonProps, 'onClick'>;
		multiFilePreview?: Omit<MultiFilePreviewProps, 'files' | 'thumbnail' | 'onRemove'>;
		singleFilePreview?: Omit<SingleFilePreviewProps, 'file'>;
		placeholder?: UploadPlaceholderProps;
		rejectionFiles?: Omit<RejectionFilesProps, 'files' | 'maxFiles' | 'maxSize' | 'accept' | 'minSize'>;
	};
	slots?: {
		placeholder?:
			| ((params: UploadPlaceholderSlotProps) => React.ReactNode)
			| React.ReactElement<UploadPlaceholderSlotProps>;
	};
	sx?: BoxProps['sx'];
	multiple?: TMultiple;
	name?: string;
	value?: TMultiple extends true ? File[] : File;
	label?: React.ReactNode;
	error?: boolean;
	disabled?: boolean;
	helperText?: React.ReactNode;
	thumbnail?: boolean;
	onDelete?: () => void;
	onUpload?: () => void;
	onRemove?: (file: File) => void;
	onRemoveAll?: () => void;
	onBlur?: (e: React.FocusEvent<HTMLInputElement, Element>) => void;
}

export default forwardRef(function Upload<TMultiple extends boolean | undefined = false>(
	inProps: UploadProps<TMultiple>,
	ref: React.ForwardedRef<HTMLDivElement>
) {
	const { translate } = useLocalization();

	const {
		required,
		slotProps,
		sx,
		value,
		error,
		disabled,
		onDelete,
		onUpload,
		onRemove,
		thumbnail,
		helperText,
		onRemoveAll,
		multiple = false,
		label,
		maxFiles,
		minSize,
		maxSize,
		accept,
		slots,
		onDrop,
		name,
		onBlur,
		noClick,
		noDrag,
		preventDropOnDocument,
		loading = false,
		imagePreviewAspectRatio = 16 / 9,
		singleFileListPreview = true,
		enableDownload = false,
		...other
	} = useDefaultProps({
		name: 'ReevolveUpload',
		props: inProps
	});

	const { getRootProps, getInputProps, isDragActive, isDragReject, fileRejections } = useDropzone({
		multiple,
		disabled,
		maxFiles,
		minSize,
		maxSize,
		accept,
		noClick,
		noDrag,
		preventDropOnDocument,
		onDrop(acceptedFiles, fileRejections, event) {
			if (multiple) {
				if (maxFiles && ((value as File[])?.length || 0) + acceptedFiles.length > maxFiles) {
					return;
				}

				if (
					(value as File[])?.some((file) =>
						acceptedFiles.some((acceptedFile) => acceptedFile.name === file.name && acceptedFile.size === file.size)
					)
				) {
					return;
				}
			}

			onDrop?.(acceptedFiles, fileRejections, event);
		},
		...other
	});

	const rootProps = getRootProps();
	const inputProps = getInputProps();

	// eslint-disable-next-line react-hooks/exhaustive-deps
	useImperativeHandle(ref, () => (inputProps as any)?.ref?.current, []);

	const isArray = Array.isArray(value) && multiple;

	const hasFile = !isArray && !!value;

	const hasFiles = isArray && !!value.length;

	const hasError = isDragReject || !!error;

	const renderPlaceholder = () => {
		if (slots?.placeholder) {
			const placeHolderProps: UploadPlaceholderSlotProps = {
				accept,
				disabled,
				error,
				label,
				maxFiles,
				maxSize,
				minSize,
				multiple,
				noClick,
				noDrag,
				preventDropOnDocument,
				required
			};

			if (isFunction(slots.placeholder)) {
				return slots.placeholder(placeHolderProps);
			}

			if (React.isValidElement(slots.placeholder)) {
				return React.cloneElement(slots.placeholder, placeHolderProps);
			}
		}

		return <UploadPlaceholder {...slotProps?.placeholder} />;
	};

	return (
		<Spin spinning={loading}>
			<FormControl
				fullWidth
				{...slotProps?.formControl}
				required={required}
				error={hasError}
			>
				{label && (
					<FormLabel
						sx={(theme) => ({
							pb: 0.75,
							...(isFunction(slotProps?.formLabel?.sx)
								? (slotProps.formLabel.sx as Function)(theme)
								: slotProps?.formLabel?.sx)
						})}
						{...slotProps?.formLabel}
					>
						{label}
					</FormLabel>
				)}

				<Box
					{...slotProps?.wrap}
					sx={(theme) => ({
						width: 1,
						position: 'relative',
						...(isFunction(slotProps?.wrap?.sx) ? (slotProps.wrap.sx as Function)(theme) : slotProps?.wrap?.sx)
					})}
				>
					<Box
						{...rootProps}
						sx={(theme) => ({
							padding: 5,
							outline: 'none',
							borderRadius: 1,
							cursor: 'pointer',
							overflow: 'hidden',
							position: 'relative',
							border: `1px dashed ${alpha(theme.palette.grey[500], 0.75)}`,
							transition: theme.transitions.create(['opacity', 'padding', 'background-color']),
							'&:hover': { opacity: 0.72 },
							...(hasError && {
								color: 'error.main',
								borderColor: 'error.main',
								bgcolor: alpha(theme.palette.error.main, 0.08)
							}),
							...(isDragActive && {
								borderColor: 'primary.main',
								bgcolor: alpha(theme.palette.primary.main, 0.08)
							}),
							...(disabled && { opacity: 0.48, pointerEvents: 'none' }),
							display: 'flex',
							justifyContent: 'center',
							...(hasFile &&
								!singleFileListPreview &&
								fileFormat((value as File).name) === 'image' && {
									paddingTop: 0,
									paddingBottom: `${100 / imagePreviewAspectRatio}%`
								}),
							...(isFunction(sx) ? (sx as Function)(theme) : sx)
						})}
					>
						<input
							{...inputProps}
							name={name}
							onBlur={(e) => {
								inputProps?.onBlur?.(e);

								onBlur?.(e);
							}}
						/>

						{/* Single file */}
						{!singleFileListPreview && hasFile ? (
							<SingleFilePreview
								{...slotProps?.singleFilePreview}
								file={value as File}
							/>
						) : (
							renderPlaceholder()
						)}
					</Box>

					{/* Single file */}
					{!singleFileListPreview && hasFile && (
						<Stack
							direction="row"
							spacing={1}
							flexWrap="nowrap"
							sx={{ top: 16, right: 16, zIndex: 9, position: 'absolute' }}
						>
							{enableDownload && (
								<DownloadButton
									{...slotProps?.downloadIconButton}
									onClick={(e) => {
										e.stopPropagation();

										downloadBlob(value as File);
									}}
								/>
							)}
							{!disabled && onDelete && (
								<DeleteButton
									{...slotProps?.deleteIconButton}
									onClick={onDelete}
								/>
							)}
						</Stack>
					)}

					{helperText && (
						<FormHelperText
							{...slotProps?.helperText}
							error={hasError}
						>
							{helperText}
						</FormHelperText>
					)}

					<RejectionFiles
						files={fileRejections}
						maxFiles={maxFiles}
						minSize={minSize}
						maxSize={maxSize}
						accept={accept}
					/>

					{hasFile && singleFileListPreview && (
						<MultiFilePreview
							{...slotProps?.multiFilePreview}
							enableDownload={enableDownload}
							files={[value as File]}
							disabled={disabled}
							thumbnail={thumbnail}
							onRemove={onDelete}
							sx={(theme) => ({
								my: 3,
								...(isFunction(slotProps?.multiFilePreview?.sx)
									? (slotProps.multiFilePreview.sx as Function)(theme)
									: slotProps?.multiFilePreview?.sx)
							})}
						/>
					)}

					{/* Multi files */}
					{hasFiles && (
						<MultiFilePreview
							{...slotProps?.multiFilePreview}
							enableDownload={enableDownload}
							files={value}
							disabled={disabled}
							thumbnail={thumbnail}
							onRemove={onRemove}
							sx={(theme) => ({
								my: 3,
								...(isFunction(slotProps?.multiFilePreview?.sx)
									? (slotProps.multiFilePreview.sx as Function)(theme)
									: slotProps?.multiFilePreview?.sx)
							})}
						/>
					)}

					{((hasFiles && onRemoveAll) || (hasFile && onDelete) || ((hasFile || hasFiles) && onUpload)) && (
						<Stack
							direction="row"
							justifyContent="flex-end"
							spacing={1.5}
							{...slotProps?.actionButtonsContainer}
							sx={(theme) => ({
								mt: hasFile ? 3 : undefined,
								...(isFunction(slotProps?.actionButtonsContainer?.sx)
									? (slotProps.actionButtonsContainer.sx as Function)(theme)
									: slotProps?.actionButtonsContainer?.sx)
							})}
						>
							{hasFiles && onRemoveAll && (
								<Button
									color="inherit"
									variant="outlined"
									size="small"
									{...slotProps?.removeAllButton}
									onClick={onRemoveAll}
									disabled={disabled}
								>
									{translate('upload.removeAll')}
								</Button>
							)}

							{hasFile && onUpload && onDelete && (
								<Button
									color="inherit"
									variant="outlined"
									size="small"
									{...slotProps?.deleteButton}
									onClick={onDelete}
									disabled={disabled}
								>
									{translate('upload.remove')}
								</Button>
							)}

							{onUpload && (
								<Button
									size="small"
									variant="contained"
									color="primary"
									startIcon={<HiOutlineCloudUpload />}
									{...slotProps?.uploadButton}
									onClick={onUpload}
								>
									{translate('upload.upload')}
								</Button>
							)}
						</Stack>
					)}
				</Box>
			</FormControl>
		</Spin>
	);
});

declare module '@mui/material' {
	interface Components {
		ReevolveUpload?: {
			defaultProps?: Partial<UploadProps>;
		};
	}
}
