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

import { DragAndDrop, Icon, ProgressBar } from "@ax/components";
import { VALID_IMAGE_FORMATS } from "@ax/constants";
import { galleryActions } from "@ax/containers/Gallery";
import { getCroppedImg } from "@ax/helpers";
import type { IGetFolderParams, IImage, IRootState } from "@ax/types";

import CropStep from "./CropStep";

import * as S from "./style";

const ImageDragAndDrop = (props: IProps) => {
	const {
		isUploading,
		isSuccess,
		isError,
		errorMsg,
		folderID = null,
		inverse = false,
		uploadError,
		handleMultipleUpload,
		handleUpload,
		replaceData,
		siteID,
		isAllowedToUpload = true,
		resetError,
		uploadImage,
		replaceImage,
		visible = true,
		getParams,
		setUploadSuccess,
		withCrop = false,
		maxImages,
	} = props;

	const validExtensions = VALID_IMAGE_FORMATS.map((format) => `.${format}`).join(",");
	const filesInputRef = useRef<HTMLInputElement | null>(null);
	const filesButtonRef = useRef<HTMLButtonElement | null>(null);
	const dropDepthRef = useRef(0);
	const [inDropZone, setInDropZone] = useState(false);
	const [uploadingState, setUploadingState] = useState({ total: 0, ready: 0 });
	const [progress, setProgress] = useState(0);
	const [cropImageSrc, setCropImageSrc] = useState<string | null>(null);

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

	useEffect(() => {
		setUploadSuccess(false);
	}, [setUploadSuccess]);

	const handleDragEnter = () => {
		dropDepthRef.current++;
		setInDropZone(true);
	};

	const handleDragLeave = () => {
		dropDepthRef.current--;
		if (dropDepthRef.current === 0) {
			setInDropZone(false);
		}
	};

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

	const isValidFileType = (fileName: string): boolean => {
		const extension = fileName.split(".").pop()?.toLowerCase();
		return extension ? VALID_IMAGE_FORMATS.includes(extension) : false;
	};

	const validateFiles = (files: File[]): boolean => {
		return files.every((file) => isValidFileType(file.name));
	};

	const readFileAsDataUrl = (file: File): Promise<string> =>
		new Promise((resolve, reject) => {
			const reader = new FileReader();
			reader.onload = (e) => {
				const result = e.target?.result;
				if (typeof result === "string") resolve(result);
				else reject(new Error("Failed to read file"));
			};
			reader.onerror = () => reject(new Error("Failed to read file"));
			reader.readAsDataURL(file);
		});

	const handleUploadAction = async (files: File[]) => {
		if (maxImages && files.length > maxImages) {
			uploadError(true, `Maximum ${maxImages} image${maxImages !== 1 ? "s" : ""} allowed`);
			return;
		}

		if (handleMultipleUpload && files.length > 1) {
			if (!validateFiles(files)) {
				uploadError(true, "Invalid format");
			} else {
				handleMultipleUpload(files);
			}
		} else if (withCrop && files.length === 1) {
			if (!validateFiles(files)) {
				uploadError(true, "Invalid format");
				return;
			}
			try {
				const dataUrl = await readFileAsDataUrl(files[0]);
				setCropImageSrc(dataUrl);
			} catch (_error) {
				uploadError(true, "Failed to load image");
			}
		} else {
			uploadFiles(files);
		}
	};

	const handleDrop = async (e: React.DragEvent<HTMLDivElement>) => {
		const files = Array.from(e.dataTransfer.files);
		handleUploadAction(files);
		dropDepthRef.current = 0;
		setInDropZone(false);
	};

	const handleFilesUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
		const files: File[] = Array.from(e.currentTarget.files || []);
		handleUploadAction(files);
	};

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

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

			const result: IImage[] = [];
			if (replaceData) {
				const params = getParams ? getParams() : { siteID, folderID };
				const fileUploaded = files[0] && (await replaceImage(files[0], replaceData.fileID, params, 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 uploadImage(file, siteID || "global", setProgress, visible, folderID));
					if (fileUploaded) {
						result.push(fileUploaded);
						setUploadingState((state) => ({ total: state.total, ready: state.ready + 1 }));
					}
				}
			}

			if (result.length) {
				setInDropZone(false);
				setUploadingState({ total: 0, ready: 0 });
				const delay = replaceData ? 0 : 3000;
				setTimeout(() => {
					handleUpload(result);
				}, delay);
			}
		} catch (error) {
			uploadError(true, "Upload failed. Please try again.");
			console.error("Upload error:", error);
		}
	};

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

	const handleCropConfirm = async (croppedAreaPixels: Area) => {
		if (!cropImageSrc) return;
		try {
			const croppedFile = await getCroppedImg(cropImageSrc, croppedAreaPixels);
			setCropImageSrc(null);
			uploadFiles([croppedFile]);
		} catch (_error) {
			uploadError(true, "Failed to crop image");
		}
	};

	const handleCropCancel = () => {
		setCropImageSrc(null);
	};

	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={VALID_IMAGE_FORMATS}
		>
			<S.StatusWrapper onDragEnter={handleDragEnter} onDragLeave={handleDragLeave}>
				<S.DragStatus onDragEnter={handleDragEnter} onDragLeave={handleDragLeave}>
					<S.DragIcon>
						<Icon name="image" size="48" />
					</S.DragIcon>
					<S.DragTitle>Drag your image here</S.DragTitle>
					<S.DragSubtitle>or</S.DragSubtitle>
					<S.FilesInput
						type="file"
						ref={filesInputRef}
						multiple={!maxImages || maxImages > 1}
						accept={validExtensions}
						onInput={handleFilesUpload}
					/>
					<S.FilesButton
						ref={filesButtonRef}
						type="button"
						buttonStyle={inverse ? "lineInverse" : "line"}
						onClick={handleFileClick}
						className={inverse ? "inverse" : ""}
					>
						Select images
					</S.FilesButton>
					<S.DragSubtitle>
						Valid formats: {VALID_IMAGE_FORMATS.join(", ")}.
						<br />
						Max. size: 50MB
					</S.DragSubtitle>
				</S.DragStatus>
				<S.DragOverStatus onDragEnter={handleDragEnter} onDragLeave={handleDragLeave}>
					<S.DropIcon>
						<Icon name="image" size="48" />
					</S.DropIcon>
					<S.DragTitle>Drop your image</S.DragTitle>
					<S.DragSubtitle>
						Valid formats: {VALID_IMAGE_FORMATS.join(", ")}.
						<br />
						Max. size: 50MB
					</S.DragSubtitle>
				</S.DragOverStatus>
			</S.StatusWrapper>
		</DragAndDrop>
	);

	const renderPlaceholder = () => (
		<S.StatusWrapper>
			<S.DragIcon>
				<Icon name="image" size="36" />
			</S.DragIcon>
			<S.DragTitle>Select an image 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}
				cropping={!!cropImageSrc}
			>
				{isAllowedToUpload ? renderDragAndDrop() : renderPlaceholder()}
			</S.DragAndDropWrapper>
			<S.CropWrapper cropping={!!cropImageSrc}>
				{cropImageSrc && <CropStep imageSrc={cropImageSrc} onConfirm={handleCropConfirm} onCancel={handleCropCancel} />}
			</S.CropWrapper>
			<S.UploadingWrapper
				inDropZone={inDropZone}
				uploading={uploading}
				success={success}
				error={isError}
				inverse={inverse}
			>
				<S.StatusWrapper>
					<S.UploadingStatus>
						<S.DragIcon>
							<Icon name="image" 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>Image loaded!</S.DragTitle>
					</S.SuccessStatus>
					<S.ErrorStatus>
						<S.DragIcon>
							<Icon name="alert" size="48" />
						</S.DragIcon>
						<S.DragTitle>Error uploading image</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 {
	isUploading: boolean;
	isSuccess: boolean;
	isError: boolean;
	errorMsg: string;
	folderID?: number | null;
	inverse?: boolean;
	siteID: number | "global";
	isAllowedToUpload?: boolean;
	replaceData?: { fileID: number };
	visible?: boolean;
	withCrop?: boolean;
	maxImages?: number;
	handleUpload: (result: IImage[]) => void;
	handleMultipleUpload?: (files: File[]) => void;
	uploadError: (error: boolean, msg?: string) => Promise<void>;
	resetError: () => void;
	uploadImage: (
		imageFiles: File | File[],
		site: number | string,
		setProgress?: (progress: number) => void,
		visible?: boolean,
		folderID?: number | null,
	) => Promise<IImage | null>;
	replaceImage: (
		imageFile: File,
		imageID: number,
		params: IGetFolderParams,
		setProgress?: (progress: number) => void,
	) => Promise<IImage | null>;
	getParams?(): IGetFolderParams;
	setUploadSuccess: (isSuccess: boolean) => void;
}

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

const mapDispatchToProps = {
	uploadError: galleryActions.uploadError,
	resetError: galleryActions.resetError,
	uploadImage: galleryActions.uploadImage,
	replaceImage: galleryActions.replaceImage,
	setUploadSuccess: galleryActions.setUploadSuccess,
};

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