import type React from "react";
import { memo, useRef, useState } from "react";
import { connect } from "react-redux";

import { DragAndDrop, Icon, ProgressBar } from "@ax/components";
import { VALID_DOCUMENT_FORMATS, VALID_FILE_FORMATS } from "@ax/constants";
import { fileDriveActions } from "@ax/containers/FileDrive";
import type { IFile, IRootState } from "@ax/types";

import * as S from "./style";

const FileDragAndDrop = (props: IProps) => {
	const {
		customFormats,
		isUploading,
		isSuccess,
		isError,
		errorMsg,
		folderID = null,
		inverse = false,
		uploadError,
		uploadFile,
		handleMultipleUpload,
		handleUpload,
		replaceData,
		replaceFile,
		siteID,
		isAllowedToUpload = true,
		resetError,
	} = props;

	const validFormats = customFormats ? customFormats : VALID_FILE_FORMATS;
	const validExtensions = validFormats.map((format) => `.${format}`).join(",");
	const validFormatsText = customFormats ? customFormats.join(", ") : `${VALID_DOCUMENT_FORMATS.join(", ")} and videos`;
	const filesInputRef = useRef<any>(null);
	const filesButtonRef = useRef<any>(null);
	const [inDropZone, setInDropZone] = useState(false);
	const [dropDepth, setDropDepth] = useState(0);
	const [uploadingState, setUploadingState] = useState({ total: 0, ready: 0 });
	const [progress, setProgress] = useState(0);

	const uploading = isUploading || uploadingState.total > uploadingState.ready;
	const success = isSuccess && uploadingState.total === uploadingState.ready;

	const handleDragEnter = () => {
		setDropDepth((depth) => depth + 1);
	};

	const handleDragLeave = () => {
		setDropDepth((depth) => depth - 1);
		if (dropDepth > 1) return;
		setInDropZone(false);
	};

	const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
		e.dataTransfer.dropEffect = "copy";
		setInDropZone(true);
	};

	const checkType = (fileName: string) => {
		const fileNameArray = fileName.split(".");
		if (validFormats.includes(fileNameArray[fileNameArray.length - 1])) {
			return true;
		}
		return false;
	};

	const handleDrop = async (e: React.DragEvent<HTMLDivElement>) => {
		const files = Array.from(e.dataTransfer.files);
		if (handleMultipleUpload && files.length > 1) {
			if (!files.every((file) => checkType(file.name))) {
				uploadError(true, "Invalid format");
			} else {
				handleMultipleUpload(files);
			}
		} else {
			await uploadFiles(files);
		}
		setDropDepth(0);
	};

	const handleFilesUpload = async (e: any) => {
		const files: File[] = Array.from(e.currentTarget.files);
		if (handleMultipleUpload && files.length > 1) {
			if (!files.every((file) => checkType(file.name))) {
				uploadError(true, "Invalid format");
			} else {
				handleMultipleUpload(files);
			}
		} else {
			await uploadFiles(files);
		}
	};

	const uploadFiles = async (files: File[]) => {
		try {
			if (!files.every((file) => checkType(file.name))) {
				uploadError(true, "Invalid format");
				return;
			}

			setUploadingState({ total: files.length, ready: 0 });

			const result: IFile[] = [];
			if (replaceData) {
				const fileUploaded =
					files[0] && (await replaceFile(files[0], replaceData.fileID, replaceData.keepURL, siteID, setProgress));
				if (fileUploaded) {
					result.push(fileUploaded);
					setUploadingState((state) => ({ total: state.total, ready: state.ready + 1 }));
				}
			} else {
				while (files.length) {
					const file = files.shift();
					const fileUploaded = file && (await uploadFile(file, folderID, siteID || "global", setProgress));
					if (fileUploaded) {
						result.push(fileUploaded);
						setUploadingState((state) => ({ total: state.total, ready: state.ready + 1 }));
					}
				}
			}

			if (result.length) {
				setInDropZone(false);
				setUploadingState({ total: 0, ready: 0 });
				setTimeout(() => {
					handleUpload(result);
				}, 3000);
			}
		} catch (error) {
			console.log(error);
		}
	};

	const handleTryAgain = () => {
		setInDropZone(false);
		setUploadingState({ total: 0, ready: 0 });
		resetError();
	};

	const handleFileClick = () => {
		if (filesInputRef) {
			filesInputRef.current.click();
		}
	};

	const errorWrapper = errorMsg ? <S.ErrorMsg>{errorMsg}</S.ErrorMsg> : null;

	const renderDragAndDrop = () => (
		<DragAndDrop
			onDrop={handleDrop}
			onDragOver={handleDragOver}
			onDragEnter={handleDragEnter}
			onDragLeave={handleDragLeave}
			validFormats={validFormats}
		>
			<S.StatusWrapper onDragEnter={handleDragEnter} onDragLeave={handleDragLeave}>
				<S.DragStatus onDragEnter={handleDragEnter} onDragLeave={handleDragLeave}>
					<S.DragIcon>
						<Icon name="page" size="48" />
					</S.DragIcon>
					<S.DragTitle>Drag your file here</S.DragTitle>
					<S.DragSubtitle>or</S.DragSubtitle>
					<S.FilesInput type="file" ref={filesInputRef} multiple accept={validExtensions} onInput={handleFilesUpload} />
					<S.FilesButton
						ref={filesButtonRef}
						type="button"
						buttonStyle={inverse ? "lineInverse" : "line"}
						onClick={handleFileClick}
						className={inverse ? "inverse" : ""}
					>
						Select files
					</S.FilesButton>
					<S.DragSubtitle>
						Valid formats: {validFormatsText}.
						<br />
						Max. size: 50MB
					</S.DragSubtitle>
				</S.DragStatus>
				<S.DragOverStatus onDragEnter={handleDragEnter} onDragLeave={handleDragLeave}>
					<S.DropIcon>
						<Icon name="page" size="48" />
					</S.DropIcon>
					<S.DragTitle>Drop your file</S.DragTitle>
					<S.DragSubtitle>
						Valid formats: {validFormatsText}.
						<br />
						Max. size: 50MB
					</S.DragSubtitle>
				</S.DragOverStatus>
			</S.StatusWrapper>
		</DragAndDrop>
	);

	const renderPlaceholder = () => (
		<S.StatusWrapper>
			<S.DragIcon>
				<Icon name="page" size="36" />
			</S.DragIcon>
			<S.DragTitle>Select a file to see details</S.DragTitle>
		</S.StatusWrapper>
	);

	return (
		<S.Wrapper data-testid="file-drag-and-drop-wrapper" inverse={inverse}>
			<S.DragAndDropWrapper
				inDropZone={inDropZone}
				uploading={uploading}
				success={success}
				error={isError}
				inverse={inverse}
			>
				{isAllowedToUpload ? renderDragAndDrop() : renderPlaceholder()}
			</S.DragAndDropWrapper>
			<S.UploadingWrapper
				inDropZone={inDropZone}
				uploading={uploading}
				success={success}
				error={isError}
				inverse={inverse}
			>
				<S.StatusWrapper>
					<S.UploadingStatus>
						<S.DragIcon>
							<Icon name="page" size="48" />
						</S.DragIcon>
						<S.ProgressBar>
							<ProgressBar percentage={progress} inverse={inverse} />
						</S.ProgressBar>
						<S.DragTitle>Uploading...</S.DragTitle>
					</S.UploadingStatus>
					<S.SuccessStatus>
						<S.SuccessIcon>
							<Icon name="success" size="48" />
						</S.SuccessIcon>
						<S.DragTitle>File loaded!</S.DragTitle>
					</S.SuccessStatus>
					<S.ErrorStatus>
						<S.DragIcon>
							<Icon name="alert" size="48" />
						</S.DragIcon>
						<S.DragTitle>Error uploading file</S.DragTitle>
						{errorWrapper}
						<S.StyledButton type="button" buttonStyle="text" onClick={handleTryAgain}>
							TRY AGAIN
						</S.StyledButton>
					</S.ErrorStatus>
				</S.StatusWrapper>
			</S.UploadingWrapper>
		</S.Wrapper>
	);
};

interface IProps {
	customFormats?: string[];
	isUploading: boolean;
	isSuccess: boolean;
	isError: boolean;
	errorMsg: string;
	folderID?: number | null;
	inverse?: boolean;
	siteID: number | "global";
	isAllowedToUpload?: boolean;
	replaceData?: { fileID: number; keepURL: boolean };
	handleUpload: (result: IFile[]) => void;
	handleMultipleUpload?: (files: File[]) => void;
	uploadError: (error: boolean, msg?: string) => Promise<void>;
	uploadFile: (
		docFiles: File | File[],
		folderID: number | null,
		siteID: number | "global",
		setProgress?: (progress: number) => void,
	) => Promise<IFile | null>;
	replaceFile: (
		docFile: File,
		fileID: number,
		keepUrl: boolean,
		siteID: number | "global",
		setProgress?: (progress: number) => void,
	) => Promise<IFile | null>;
	resetError: () => void;
}

const mapStateToProps = (state: IRootState) => ({
	isUploading: state.fileDrive.isUploading,
	isSuccess: state.fileDrive.isSuccess,
	isError: state.fileDrive.isError,
	errorMsg: state.fileDrive.errorMsg,
});

const mapDispatchToProps = {
	uploadError: fileDriveActions.uploadError,
	uploadFile: fileDriveActions.uploadFile,
	replaceFile: fileDriveActions.replaceFile,
	resetError: fileDriveActions.resetError,
};

export default connect(mapStateToProps, mapDispatchToProps)(memo(FileDragAndDrop));
