import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import { DocumentPlusIcon } from "@heroicons/react/24/outline";
import classNames from "classnames";
import React, { useCallback, useEffect, useRef, useState } from "react";

import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "../Button";
import Separator, { ESeperatorColor, ESeperatorDirection } from "../Separator";
import classes from "./classes.module.scss";
import DocumentFileElement from "./DocumentFileElement";

/**
 * @description Drag and drop component to upload files
 * @param {string} title - Title of the component
 * @param {string} description - Description of the component
 * @param {IDocumentFileWithUid[]} defaultFiles - Default files to display
 * @param {(fileUid: string) => Promise<any>} onDelete - Function to delete a file  (must be used with defaultFiles)
 * @param {(file: File) => Promise<any>} onAddFile - Function to add a file (must be used with defaultFiles)
 */
type IProps = {
	title: string;
	description?: string;
	defaultFiles?: IDocumentFileWithUid[];
	onDelete?: (fileUid: string) => Promise<any>;
	onAddFile?: (file: File) => Promise<any>;
	onAddToList?: (file: IDocumentFile) => void;
	onRemoveFromList?: (file: IDocumentFile) => void;
} & (
	| { onDelete: (fileUid: string) => Promise<any>; onAddFile?: never; defaultFiles: IDocumentFileWithUid[] }
	| { onDelete?: never; onAddFile: (file: File) => Promise<any>; defaultFiles: IDocumentFileWithUid[] }
	| { onDelete?: (fileUid: string) => Promise<any>; onAddFile?: (file: File) => Promise<any>; defaultFiles: IDocumentFileWithUid[] }
	| { onDelete?: never; onAddFile?: never; defaultFiles?: never }
);

type IMimeTypes = {
	extension: string;
	size: number;
};

const mimeTypesAccepted: { [key: string]: IMimeTypes } = {
	"application/pdf": {
		extension: "pdf",
		size: 41943040,
	},
	"image/jpeg": {
		extension: "jpeg",
		size: 41943040,
	},
	"image/png": {
		extension: "png",
		size: 41943040,
	},
	"image/jpg": {
		extension: "jpg",
		size: 41943040,
	},
};

type IDocumentFileBase = {
	id: string;
	file: File | null;
	uid?: string;
	isLoading?: boolean;
	error?: string;
};

export type IDocumentFileWithUid = IDocumentFileBase & {
	uid: string;
};

type IDocumentFile = IDocumentFileBase | IDocumentFileWithUid;

export default function DragAndDrop(props: IProps) {
	const { title, description, defaultFiles, onDelete, onAddFile, onAddToList, onRemoveFromList } = props;
	const fileInputRef = useRef<HTMLInputElement>(null);

	const [documentFiles, setDocumentFiles] = useState<IDocumentFile[]>([]);

	useEffect(() => {
		if (defaultFiles) {
			setDocumentFiles(defaultFiles);
		}
	}, [defaultFiles]);

	const handleAddFiles = useCallback(
		(files: File[]) => {
			files.forEach((file) => {
				setDocumentFiles((prevDocs) => [...prevDocs, { id: file.name, file: file, isLoading: true }]);
				try {
					if (!mimeTypesAccepted[file.type]) {
						throw new Error("Type de fichier non accepté");
					}
					const newDoc: IDocumentFile = { id: file.name, file, isLoading: false };

					if (onAddFile) {
						// As onAddFile is used along defaultFiles prop we dont need to update the state here but the parent component should update the defaultFiles prop
						return onAddFile(file);
					}

					return setTimeout(async () => {
						if (onAddToList) {
							onAddToList(newDoc);
						}
						setDocumentFiles((prevDocs) => prevDocs.map((doc) => (doc.id === newDoc.id ? newDoc : doc)));
					}, 1000);
				} catch (error: any) {
					const errorDoc: IDocumentFile = { id: file.name, file: null, isLoading: false, error: error.message };
					return setDocumentFiles((prevDocs) => prevDocs.map((doc) => (doc.id === errorDoc.id ? errorDoc : doc)));
				}
			});
		},
		[onAddFile],
	);

	const handleDrop = useCallback(
		(event: React.DragEvent<HTMLDivElement>) => {
			event.preventDefault();
			const files = Array.from(event.dataTransfer.files);
			handleAddFiles(files);
		},
		[handleAddFiles],
	);

	const handleRemove = useCallback(
		(documentFile: IDocumentFile) => {
			if (onRemoveFromList) {
				onRemoveFromList(documentFile);
			}
			const loadingDoc = { ...documentFile, isLoading: true };
			setDocumentFiles((prevDocs) => prevDocs.map((doc) => (doc.id === documentFile.id ? loadingDoc : doc)));

			if (documentFile.uid) {
				return onDelete?.(documentFile.uid);
			}
			return setDocumentFiles((prevDocs) => prevDocs.filter((doc) => doc.id !== documentFile.id));
		},
		[onDelete],
	);

	const handleBrowse = useCallback(
		(event: React.ChangeEvent<HTMLInputElement>) => {
			const files = Array.from(event.target.files || []);
			handleAddFiles(files);
		},
		[handleAddFiles],
	);

	const triggerFileInput = () => {
		if (fileInputRef.current) {
			fileInputRef.current.click();
		}
	};

	return (
		<div
			className={classNames(classes["root"], documentFiles.length > 0 && classes["filled"])}
			onDrop={handleDrop}
			onDragOver={(e) => e.preventDefault()}>
			<div className={classes["content"]}>
				<DocumentPlusIcon />
				<Separator direction={ESeperatorDirection.VERTICAL} color={ESeperatorColor.STRONG} size={64} />
				<div className={classes["browse-document-container"]}>
					<div className={classNames(classes["browse-document"], classes["desktop"])}>
						<Typography typo={ETypo.TEXT_LG_SEMIBOLD} color={ETypoColor.TEXT_PRIMARY}>
							{title}
						</Typography>
						<Button
							variant={EButtonVariant.PRIMARY}
							styletype={EButtonstyletype.TEXT}
							size={EButtonSize.SM}
							onClick={triggerFileInput}>
							{inputFile()}
							parcourir
						</Button>
					</div>

					<div className={classNames(classes["browse-document"], classes["mobile"])}>
						<Button
							variant={EButtonVariant.PRIMARY}
							styletype={EButtonstyletype.TEXT}
							size={EButtonSize.SM}
							onClick={triggerFileInput}>
							{inputFile()}
							Ajouter un document
						</Button>
					</div>

					{description && (
						<Typography typo={ETypo.TEXT_MD_LIGHT} color={ETypoColor.TEXT_SECONDARY}>
							{description}
						</Typography>
					)}
				</div>
			</div>
			{documentFiles.length > 0 && (
				<div className={classes["documents"]}>
					{documentFiles.map((documentFile) => (
						<DocumentFileElement
							key={documentFile.id}
							isLoading={documentFile.isLoading}
							file={documentFile.file}
							onRemove={() => handleRemove(documentFile)}
							error={documentFile.error}
						/>
					))}
				</div>
			)}
		</div>
	);

	function inputFile() {
		return (
			<input
				ref={fileInputRef}
				type="file"
				multiple
				accept={Object.keys(mimeTypesAccepted).join(",")}
				onChange={handleBrowse}
				hidden
			/>
		);
	}
}
