import React, {useCallback, useEffect, useId, useMemo, useRef, useState} from 'react';
import renderHtmlAttributes from "../../utils/renderHtmlAttributes";
import useFormSubmitSuccess from "../../hooks/useFormSubmitSuccess";
import type {FileRejection} from "react-dropzone";
import {useDropzone} from "react-dropzone";
import convertMBToBytes from "../../utils/convertMBToBytes";
import fetcher from "../../utils/fetcher";
import {filesize} from "filesize";
import uuid from "../../utils/uuid";
import {Icon} from "@wordpress/components";
import {closeSmall} from "@wordpress/icons";
import {__, _n, sprintf} from "@wordpress/i18n";
import getFileExtension from "../../utils/getFileExtension";
import UpgradeOverlay from "../../components/UpgradeOverlay";
import uniqId from '../../utils/uniqid';
import {type AjaxEventDetail, setupAjaxInterceptor} from '../../utils/ajaxComplete';
import type {FreemiusProps} from "../../types/freemius";

type FieldProps = {
	itemId: string
	formId: string
	attrs: Record<string, string | string[]>
	attachmentType: 'link' | 'attach' | 'both'
	fileSizes: number
	fileTypes: { [key: string]: readonly string[] }
	fileTypesRaw: string
	allowMultipleUpload: boolean
	maxFiles: number
	generic: string
	custom_id: string
	text: string
	restUrl: string
	nonce: string
	GROUP: string
} & FreemiusProps

type AcceptedFilesProps = {
	file: File;
	uuid: string;
	progress: string;
	error: string;
	isPaused: boolean;
	preview: any;
}

function isImage(file: any) {
	const isMimeTypeImage = file.type.startsWith('image/');
	const isExtensionImage = /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name);
	return isMimeTypeImage && isExtensionImage;
}

const Field = (props: FieldProps) => {

	const containerRef = useRef<HTMLDivElement>(null);

	const [acceptedFiles, setAcceptedFiles] = useState<AcceptedFilesProps[]>([]);
	const [rejectedFiles, setRejectedFiles] = useState<FileRejection[]>([]);

	const attrs = useMemo(() => renderHtmlAttributes(props.attrs), [props.attrs]);

	async function onDropAccepted<T extends File>(files: T[]) {
		if (!props.freemius.can_use_premium_code) {
			return;
		}
		const newFiles = files.map((file) => ({
			file,
			uuid: uuid(),
			progress: '0',
			error: '',
			isPaused: false,
			preview: isImage(file) ? URL.createObjectURL(file) : null,
		}));
		if (acceptedFiles.length >= props.maxFiles) {
			const container = containerRef.current as HTMLDivElement;
			const messageElement = container.parentElement?.querySelector('.elementor-message');
			/* translators: %d: number of files */
			const errorMessage = sprintf(_n('You can upload only %d file.', 'You can upload up to %d files.', props.maxFiles, 'advanced-fields-for-elementor-forms'), props.maxFiles);
			if (messageElement) {
				messageElement.textContent = errorMessage;
			} else {
				const messageCreate = document.createElement('span');
				messageCreate.classList.add('elementor-message', 'elementor-message-danger');
				messageCreate.textContent = errorMessage;
				container.parentElement?.appendChild(messageCreate);
			}
		} else {
			setAcceptedFiles(prevState => {
				return [...prevState, ...newFiles.slice(0, props.maxFiles)];
			});
		}
	}

	function onDropRejected(fileRejections: FileRejection[]) {
		const filteredFiles = fileRejections.filter(file =>
			file.errors.length === 1 && file.errors.some(error => error.code === "too-many-files")
		);
		if (filteredFiles.length > props.maxFiles) {
			const container = containerRef.current as HTMLDivElement;
			const messageElement = container.parentElement?.querySelector('.elementor-message');
			const errorMessage = `${__('The maximum number of files is', 'advanced-fields-for-elementor-forms')} ${props.maxFiles}`;
			if (messageElement) {
				messageElement.textContent = errorMessage;
			} else {
				const messageCreate = document.createElement('span');
				messageCreate.classList.add('elementor-message', 'elementor-message-danger');
				messageCreate.textContent = errorMessage;
				container.parentElement?.appendChild(messageCreate);
			}
		}
		// Now filter out the filteredFiles from fileRejections
		const remainingFiles = fileRejections.filter(file => !filteredFiles.includes(file));
		setRejectedFiles(prevState => [...prevState, ...remainingFiles]);
	}

	const maxSize = useMemo(() => convertMBToBytes(props.fileSizes), [props.fileSizes]);

	const {getRootProps, getInputProps} = useDropzone({
		...(props.fileTypes && {accept: props.fileTypes}),
		...(props.maxFiles && {maxFiles: props.maxFiles}),
		...(props.fileSizes && {maxSize}),
		multiple: props.allowMultipleUpload,
		onDropAccepted,
		onDropRejected,
	});

	const genericIcon = useMemo(() => decodeURIComponent(props.generic), [props.generic]);

	const handleRemove = useCallback((e: React.MouseEvent<HTMLButtonElement>, uuid3: string) => {
		e.preventDefault();
		setAcceptedFiles(prevState => {
			return prevState.filter((file) => file.uuid !== uuid3);
		});
	}, []);

	const handleProgress = useCallback((val: string, uuid1: string) => {
		setAcceptedFiles(prevState => prevState.map(value => {
			return value.uuid === uuid1 ? {
				...value,
				progress: val
			} : value;
		}));
	}, []);

	const getName = useMemo(() => {
		if (props.allowMultipleUpload) {
			return `${attrs.name}[]`;
		}
		return attrs.name;
	}, [attrs.name, props.allowMultipleUpload]);

	const newAttrs = useMemo(() => {
		const {name, ...rest} = attrs;
		return rest;
	}, [attrs]);

	const mainId = useId();

	function handleRemoveRejectedFiles(e: React.MouseEvent<HTMLButtonElement>, index: number) {
		e.preventDefault();
		setRejectedFiles(prevState => {
			return prevState.filter((_, index1) => index1 !== index);
		});
	}

	// Reset after form submit
	useFormSubmitSuccess(containerRef, () => {
		setAcceptedFiles([]);
		setRejectedFiles([]);
	}, []);

	// On ajax complete
	useEffect(() => {
		const handleAjaxComplete = (detail: AjaxEventDetail) => {
			const data = JSON.parse(detail.response);
			// Remove uploaded item if file not exit
			if (data?.data?.data?.notExistFiles.length > 0) {
				const wrapperElement = containerRef.current;
				if (!wrapperElement) {
					return;
				}
				const notExistFiles: string[] = data?.data?.data?.notExistFiles;
				notExistFiles.forEach(tempName => {
					const element: HTMLButtonElement | null = wrapperElement.querySelector(`[data-temp-name="${tempName}"]`);
					element?.click();
				});
			}
		};
		// Set up the interceptor
		setupAjaxInterceptor(handleAjaxComplete);
	}, []);

	return (
		<>
			{!props.freemius.can_use_premium_code && <UpgradeOverlay url={props.freemius.get_upgrade_url}/>}
			<section className="hulk-field-container" ref={containerRef}>
				<div {...getRootProps({className: 'hulk-dropzone'})}>
					<input
						{...newAttrs}
						{...getInputProps()}
						id={`${attrs.id}-dropzone`}
					/>
					<p>{props.text}</p>
				</div>
				{acceptedFiles.length > 0 && (
					<ul className="hulk-files">
						{acceptedFiles.map((item) => (
							<li key={item.uuid}>
								<FileUpload
									{...item}
									genericIcon={genericIcon}
									onProgress={(val: string) => handleProgress(val, item.uuid)}
									onRemove={(e: any) => handleRemove(e, item.uuid)}
									getName={getName}
									restUrl={props.restUrl}
									GROUP={props.GROUP}
								/>
							</li>
						))}
					</ul>
				)}
				{rejectedFiles.length > 0 && (
					<ul className="hulk-files">
						{rejectedFiles.map((item, index) => (
							<li key={`${mainId}-${props.formId}-${props.itemId}-${index}`}>
								<FileRejected
									{...item}
									genericIcon={genericIcon}
									getName={getName}
									onRemove={(e: any) => handleRemoveRejectedFiles(e, index)}
									maxSize={maxSize}
								/>
							</li>
						))}
					</ul>
				)}
			</section>
			<div id={attrs.id}/>
		</>
	);
};

function FileUpload(item: any) {

	const getFileName = useCallback((name: string) => getFileExtension(name), []);

	const [pausedCallback, setPausedCallback] = useState<() => void>(() => {
	});

	const [removeCallback, setRemoveCallback] = useState<() => void>(() => {
	});

	const [error, setError] = useState('');
	const [value, setValue] = useState('');
	const [tempName, setTempName] = useState('');

	const uploadRef = useRef<HTMLButtonElement | null>(null);

	async function handleUpload(e: React.MouseEvent<HTMLButtonElement>) {
		e.preventDefault();
		setError('');
		try {
			const chunkSize = 2 * 1024 * 1024; // 2MB chunks
			const totalChunks = Math.ceil(item.file.size / chunkSize);
			let chunkIndex = 0;
			let isPaused = false;
			let controller: AbortController;
			const _uniqId = uniqId();

			async function uploadNextChunk() {
				const start = chunkIndex * chunkSize;
				const end = Math.min(start + chunkSize, item.file.size);
				const chunk = item.file.slice(start, end);

				const formData = new FormData();
				formData.append('file_name', item.file.name);
				formData.append('uniq_id', _uniqId);
				formData.append('chunk_index', chunkIndex.toString());
				formData.append('total_chunks', totalChunks.toString());
				formData.append('file_chunk', chunk);

				// Create a new controller for this request
				controller = new AbortController();

				const res: {
					message: string
					tempName: string
					name: string
					value: string
				} = await fetcher(`${item.restUrl}${item.GROUP}/v1/dropzone/upload`, {
					method: "POST",
					headers: {},
					body: formData,
					signal: controller.signal,
				});

				setRemoveCallback(() => () => {
					controller.abort();
					item.onRemove(e);
				});

				chunkIndex++;
				item.onProgress(((chunkIndex / totalChunks) * 100).toFixed(0));

				if (chunkIndex < totalChunks) {
					setPausedCallback(() => () => isPaused = !isPaused);
					if (isPaused) {
						await new Promise<void>(resolve => {
							const checkInterval = setInterval(() => {
								if (!isPaused) {
									clearInterval(checkInterval);
									resolve(); // Resume once isPaused becomes false
								}
							}, 400); // Check every 400ms
						});
					}
					await uploadNextChunk();
				} else {
					setValue(res.value);
					setTempName(res.tempName);
				}
			}

			await uploadNextChunk();
		} catch (err: any) {
			setError(err?.message || JSON.stringify(e));
			setRemoveCallback(() => () => {
				item.onRemove(e);
			});
		}
	}

	const isMount = useRef(false);

	useEffect(() => {
		if (!uploadRef.current) {
			return;
		}
		if (isMount.current) {
			return;
		}
		isMount.current = true;
		uploadRef.current.click();
	}, []);

	return (
		<>
			<div className="hulk-drop-wrap">
				<div className="hulk-icon-wrap">
					{item.preview ? (
						<div className="hulk-drop-type">
							<img src={item.preview} alt={item.file.name}
								// Revoke data uri after image is loaded
								 onLoad={() => {
									 URL.revokeObjectURL(item.preview)
								 }}
							/>
						</div>
					) : (
						<>
							<div
								className="hulk-drop-type"
								dangerouslySetInnerHTML={{__html: item.genericIcon}}
							/>
							<div className="hulk-drop-type-file">{getFileName(item.file.name)}</div>
						</>
					)}
				</div>
				<div className="hulk-info-wrap">
					<div className="hulk-file-info">
						<div className="hulk-drop-filename">{item.file.name}</div>
						<div className="hulk-drop-filesize"
						>({filesize(item.file.size, {standard: "jedec"})})
						</div>
					</div>
					<div className="hulk-drop-progressBar"
						 style={{width: `${item.progress}%`}}
					>{item.progress}%
					</div>
				</div>
				<button
					type="button"
					className="hulk-drop-remove"
					onClick={removeCallback}
					data-temp-name={tempName}
				><Icon icon={closeSmall} size={20}/></button>
				<button
					type="button"
					className="hulk-drop-pause"
					onClick={pausedCallback}
				>Pause
				</button>
				<button
					type="button"
					className="hulk-drop-upload"
					onClick={handleUpload}
					ref={uploadRef}
				>
					{__('Upload', 'dropzone-field-for-elementor-form')}
				</button>
			</div>
			{error && <div
				className="hulk-drop-text-error"
				role={'alert'}
			>{error}</div>}
			<input type="hidden" name={item.getName} defaultValue={value}/>
		</>
	)
}

function FileRejected(item: any) {

	const getFileName = useCallback((name: string) => getFileExtension(name), []);

	const mainId = useId();

	const getErrorMessage = useCallback((message: string) => {
		return message
			.replace(item.maxSize, filesize(item.maxSize, {standard: "jedec"}))
			.replace('bytes', '');
	}, [item.maxSize]);

	const preview = useMemo(() => {
		return isImage(item.file) ? URL.createObjectURL(item.file) : null
	}, [item.file]);

	// const isTooManyFiles = useMemo(() => {
	// 	return !!(item.errors.length === 1 && item.errors.some((error: any) => error.code === "too-many-files"));
	// }, [item.errors]);
	//
	// if (isTooManyFiles) {
	// 	return __('The maximum number of files is 3', 'dropzone-field-for-elementor-form')
	// }

	return (
		<>
			<div className="hulk-drop-wrap">
				<div className="hulk-icon-wrap">
					{preview ? (
						<div className="hulk-drop-type">
							<img src={preview} alt={item.file.name}
								// Revoke data uri after image is loaded
								 onLoad={() => {
									 URL.revokeObjectURL(preview)
								 }}
							/>
						</div>
					) : (
						<>
							<div
								className="hulk-drop-type"
								dangerouslySetInnerHTML={{__html: item.genericIcon}}
							/>
							<div className="hulk-drop-type-file">{getFileName(item.file.name)}</div>
						</>
					)}
				</div>
				<div className="hulk-info-wrap">
					<div className="hulk-file-info">
						<div className="hulk-drop-filename">{item.file.name}</div>
						<div className="hulk-drop-filesize"
						>({filesize(item.file.size, {standard: "jedec"})})
						</div>
					</div>
				</div>
				<button
					type="button"
					className="hulk-drop-remove"
					onClick={item.onRemove}
				><Icon icon={closeSmall} size={20}/></button>
			</div>
			{item.errors.map((data: any, index: number) => (
				<div
					key={`${mainId}-${index}`}
					className="hulk-drop-text-error"
					role={'alert'}
				>{getErrorMessage(data.message)}</div>
			))}
		</>
	)
}

export default Field;
