/**
 * JTZL_WebIRC_Chat - Image Upload Component
 *
 * @package   JTZL_WebIRC_Chat
 * @copyright Copyright (c) 2025, JT. G.
 * @license   GPL-3.0+
 * @since     3.1.0
 */

import {
	useState,
	useRef,
	useCallback,
	useMemo,
	type ChangeEvent,
	type DragEvent,
} from 'react';

import { Button } from '../ui/button';
import {
	type UploadState,
	type UploadedFile,
	type ImageValidationConfig,
	DEFAULT_IMAGE_VALIDATION_CONFIG,
} from '../../types/image';
import { validateImageFiles } from '../../lib/image-validation';
import {
	generateImageId,
	formatFileSize,
	isMobileDevice,
	compressImageForMobile,
} from '../../lib/image-processing';

/**
 * Props for ImageUpload component.
 *
 * @since 3.1.0
 */
interface ImageUploadProps {
	onImagesUploaded: (imageUrls: string[], caption?: string) => void;
	onUploadError: (error: string) => void;
	maxFiles?: number;
	maxFileSize?: number;
	disabled?: boolean;
	className?: string;
}

/**
 * ImageUpload component for handling file selection and upload.
 *
 * @since 3.1.0
 */
export function ImageUpload({
	onImagesUploaded,
	onUploadError,
	maxFiles = 10,
	maxFileSize = 10 * 1024 * 1024,
	disabled = false,
	className = '',
}: ImageUploadProps) {
	const [uploadState, setUploadState] = useState<UploadState>({
		isUploading: false,
		progress: 0,
		uploadedFiles: [],
		errors: [],
	});
	const [isDragging, setIsDragging] = useState(false);

	const fileInputRef = useRef<HTMLInputElement>(null);
	const dragCounterRef = useRef(0);
	const xhrRefs = useRef<Map<string, XMLHttpRequest>>(new Map());

	/**
	 * Validation configuration.
	 *
	 * @since 3.1.0
	 */
	const validationConfig: ImageValidationConfig = useMemo(
		() => ({
			...DEFAULT_IMAGE_VALIDATION_CONFIG,
			maxFiles,
			maxFileSize,
		}),
		[maxFiles, maxFileSize]
	);

	/**
	 * Upload a single file.
	 *
	 * @since 3.1.0
	 */
	const uploadSingleFile = useCallback(
		async (uploadFile: UploadedFile): Promise<string | undefined> => {
			// Update file status to uploading.
			setUploadState((prev) => ({
				...prev,
				uploadedFiles: prev.uploadedFiles.map((f) =>
					f.id === uploadFile.id
						? { ...f, status: 'uploading' as const }
						: f
				),
			}));

			try {
				// Create FormData for upload.
				const formData = new FormData();
				formData.append('file', uploadFile.file);
				formData.append('action', 'chat_webirc_upload_image');
				formData.append('nonce', window.webircChatConfig?.nonce ?? '');

				// Upload with XMLHttpRequest for progress tracking.
				const url = await new Promise<string>((resolve, reject) => {
					const xhr = new XMLHttpRequest();
					xhrRefs.current.set(uploadFile.id, xhr);

					xhr.upload.addEventListener('progress', (e) => {
						if (e.lengthComputable) {
							const progress = Math.round(
								(e.loaded / e.total) * 100
							);

							setUploadState((prev) => ({
								...prev,
								uploadedFiles: prev.uploadedFiles.map((f) =>
									f.id === uploadFile.id
										? { ...f, progress }
										: f
								),
								progress: Math.round(
									prev.uploadedFiles.reduce(
										(sum, f) =>
											sum +
											(f.id === uploadFile.id
												? progress
												: f.progress),
										0
									) / prev.uploadedFiles.length
								),
							}));
						}
					});

					xhr.addEventListener('load', () => {
						xhrRefs.current.delete(uploadFile.id);
						if (xhr.status === 200) {
							try {
								const response = JSON.parse(xhr.responseText);
								if (response.success && response.data?.url) {
									resolve(response.data.url);
								} else {
									reject(
										new Error(
											response.data?.message ||
												'Upload failed'
										)
									);
								}
							} catch {
								reject(new Error('Invalid server response'));
							}
						} else {
							reject(
								new Error(`Upload failed: ${xhr.statusText}`)
							);
						}
					});

					xhr.addEventListener('error', () => {
						xhrRefs.current.delete(uploadFile.id);
						reject(new Error('Network error during upload'));
					});

					xhr.addEventListener('abort', () => {
						xhrRefs.current.delete(uploadFile.id);
						reject(new Error('Upload cancelled'));
					});

					xhr.open(
						'POST',
						window.webircChatConfig?.ajaxUrl ??
							'/wp-admin/admin-ajax.php'
					);
					xhr.send(formData);
				});

				// Update file status to success.
				setUploadState((prev) => ({
					...prev,
					uploadedFiles: prev.uploadedFiles.map((f) =>
						f.id === uploadFile.id
							? {
									...f,
									status: 'success' as const,
									progress: 100,
									url,
								}
							: f
					),
				}));

				return url;
			} catch (error) {
				const errorMessage =
					error instanceof Error ? error.message : 'Upload failed';

				// Update file status to error.
				setUploadState((prev) => ({
					...prev,
					uploadedFiles: prev.uploadedFiles.map((f) =>
						f.id === uploadFile.id
							? {
									...f,
									status: 'error' as const,
									error: errorMessage,
								}
							: f
					),
				}));

				throw error;
			}
		},
		[]
	);

	/**
	 * Upload files to server.
	 *
	 * @since 3.1.0
	 */
	const uploadFilesToServer = useCallback(
		async (files: UploadedFile[]) => {
			const uploadPromises = files.map((uploadFile) =>
				uploadSingleFile(uploadFile)
			);

			try {
				const results = await Promise.allSettled(uploadPromises);

				// Check for any failures.
				const failures = results.filter(
					(result) => result.status === 'rejected'
				);

				if (failures.length > 0) {
					const errorMessage = `${failures.length} file(s) failed to upload`;
					setUploadState((prev) => ({
						...prev,
						isUploading: false,
						errors: [errorMessage],
					}));
					onUploadError(errorMessage);
					return;
				}

				// Extract successful URLs.
				const successfulUrls = results
					.filter(
						(
							result
						): result is PromiseFulfilledResult<
							string | undefined
						> => result.status === 'fulfilled'
					)
					.map((result) => result.value)
					.filter((url): url is string => url !== undefined);

				setUploadState((prev) => ({
					...prev,
					isUploading: false,
					progress: 100,
				}));

				onImagesUploaded(successfulUrls);

				// Reset state after successful upload.
				setTimeout(() => {
					setUploadState({
						isUploading: false,
						progress: 0,
						uploadedFiles: [],
						errors: [],
					});
				}, 1000);
			} catch (error) {
				const errorMessage =
					error instanceof Error ? error.message : 'Upload failed';
				setUploadState((prev) => ({
					...prev,
					isUploading: false,
					errors: [errorMessage],
				}));
				onUploadError(errorMessage);
			}
		},
		[onImagesUploaded, onUploadError, uploadSingleFile]
	);

	/**
	 * Handle file selection from input.
	 *
	 * @since 3.1.0
	 */
	const handleFileSelect = useCallback(
		async (files: FileList | null) => {
			if (!files || files.length === 0) {
				return;
			}

			let fileArray = Array.from(files);

			// Compress images on mobile devices.
			if (isMobileDevice()) {
				try {
					fileArray = await Promise.all(
						fileArray.map(async (file) => {
							// Only compress large images.
							if (file.size > 1024 * 1024) {
								// > 1MB.
								return await compressImageForMobile(file);
							}
							return file;
						})
					);
				} catch {
					// Continue with original files if compression fails.
				}
			}

			// Validate files.
			const validationResults = await validateImageFiles(
				fileArray,
				validationConfig
			);

			// Check for validation errors.
			const hasErrors = validationResults.some(
				(result) => !result.isValid
			);
			if (hasErrors) {
				const errorMessages = validationResults
					.filter((result) => !result.isValid)
					.map((result) => result.message || 'Invalid file');

				setUploadState((prev) => ({
					...prev,
					errors: errorMessages,
				}));

				onUploadError(errorMessages[0]);
				return;
			}

			// Create upload file objects.
			const uploadFiles: UploadedFile[] = fileArray.map((file) => ({
				file,
				id: generateImageId(),
				progress: 0,
				status: 'pending',
			}));

			setUploadState({
				isUploading: true,
				progress: 0,
				uploadedFiles: uploadFiles,
				errors: [],
			});

			// Start upload process.
			await uploadFilesToServer(uploadFiles);
		},
		[validationConfig, onUploadError, uploadFilesToServer]
	);

	/**
	 * Handle file input change.
	 *
	 * @since 3.1.0
	 */
	const handleInputChange = useCallback(
		(e: ChangeEvent<HTMLInputElement>) => {
			handleFileSelect(e.target.files);
			// Reset input value to allow selecting the same file again.
			e.target.value = '';
		},
		[handleFileSelect]
	);

	/**
	 * Handle drag enter event.
	 *
	 * @since 3.1.0
	 */
	const handleDragEnter = useCallback((e: DragEvent<HTMLDivElement>) => {
		e.preventDefault();
		e.stopPropagation();

		dragCounterRef.current += 1;

		if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
			setIsDragging(true);
		}
	}, []);

	/**
	 * Handle drag leave event.
	 *
	 * @since 3.1.0
	 */
	const handleDragLeave = useCallback((e: DragEvent<HTMLDivElement>) => {
		e.preventDefault();
		e.stopPropagation();

		dragCounterRef.current -= 1;

		if (dragCounterRef.current === 0) {
			setIsDragging(false);
		}
	}, []);

	/**
	 * Handle drag over event.
	 *
	 * @since 3.1.0
	 */
	const handleDragOver = useCallback((e: DragEvent<HTMLDivElement>) => {
		e.preventDefault();
		e.stopPropagation();
	}, []);

	/**
	 * Handle drop event.
	 *
	 * @since 3.1.0
	 */
	const handleDrop = useCallback(
		(e: DragEvent<HTMLDivElement>) => {
			e.preventDefault();
			e.stopPropagation();

			setIsDragging(false);
			dragCounterRef.current = 0;

			const files = e.dataTransfer.files;
			handleFileSelect(files);
		},
		[handleFileSelect]
	);

	/**
	 * Trigger file input click.
	 *
	 * @since 3.1.0
	 */
	const handleButtonClick = useCallback(() => {
		// Debug: Log config availability.
		if (!window.webircChatConfig?.nonce) {
			// eslint-disable-next-line no-console
			console.error(
				'WebIRC Chat: Configuration not available. Nonce:',
				window.webircChatConfig?.nonce
			);
		}
		fileInputRef.current?.click();
	}, []);

	/**
	 * Cancel upload for a specific file.
	 *
	 * @since 3.1.0
	 */
	const cancelUpload = useCallback((fileId: string) => {
		const xhr = xhrRefs.current.get(fileId);
		if (xhr) {
			xhr.abort();
			xhrRefs.current.delete(fileId);
		}

		setUploadState((prev) => ({
			...prev,
			uploadedFiles: prev.uploadedFiles.filter((f) => f.id !== fileId),
		}));
	}, []);

	/**
	 * Cancel all uploads.
	 *
	 * @since 3.1.0
	 */
	const cancelAllUploads = useCallback(() => {
		xhrRefs.current.forEach((xhr) => xhr.abort());
		xhrRefs.current.clear();

		setUploadState({
			isUploading: false,
			progress: 0,
			uploadedFiles: [],
			errors: [],
		});
	}, []);

	/**
	 * Retry failed upload.
	 *
	 * @since 3.1.0
	 */
	const retryUpload = useCallback(
		async (fileId: string) => {
			const uploadFile = uploadState.uploadedFiles.find(
				(f) => f.id === fileId
			);
			if (!uploadFile) {
				return;
			}

			// Reset file status.
			setUploadState((prev) => ({
				...prev,
				uploadedFiles: prev.uploadedFiles.map((f) =>
					f.id === fileId
						? { ...f, status: 'pending' as const, error: undefined }
						: f
				),
			}));

			// Retry upload.
			try {
				await uploadSingleFile(uploadFile);
			} catch {
				// Error already handled in uploadSingleFile.
			}
		},
		[uploadState.uploadedFiles, uploadSingleFile]
	);

	return (
		<div className={className}>
			<input
				ref={fileInputRef}
				type="file"
				accept="image/*"
				multiple
				onChange={handleInputChange}
				disabled={disabled || uploadState.isUploading}
				className="sr-only"
				aria-label="Select images to upload"
			/>

			<div
				onDragEnter={handleDragEnter}
				onDragLeave={handleDragLeave}
				onDragOver={handleDragOver}
				onDrop={handleDrop}
				className={`relative ${isDragging ? 'pointer-events-none' : ''}`}
			>
				<Button
					type="button"
					variant="ghost"
					onClick={handleButtonClick}
					disabled={disabled || uploadState.isUploading}
					className="h-10 w-10 p-0 flex-shrink-0"
					aria-label="Upload images"
					title="Upload images"
				>
					<svg
						width="16"
						height="16"
						viewBox="0 0 24 24"
						fill="none"
						stroke="currentColor"
						strokeWidth="2"
						strokeLinecap="round"
						strokeLinejoin="round"
					>
						<rect
							x="3"
							y="3"
							width="18"
							height="18"
							rx="2"
							ry="2"
						/>
						<circle cx="8.5" cy="8.5" r="1.5" />
						<polyline points="21 15 16 10 5 21" />
					</svg>
				</Button>

				{isDragging && (
					<div className="fixed inset-0 z-[999999] bg-primary/10 backdrop-blur-sm flex items-center justify-center pointer-events-none">
						<div className="bg-background border-2 border-dashed border-primary rounded-lg p-8 text-center">
							<svg
								className="mx-auto h-12 w-12 text-primary mb-4"
								fill="none"
								stroke="currentColor"
								viewBox="0 0 24 24"
							>
								<path
									strokeLinecap="round"
									strokeLinejoin="round"
									strokeWidth={2}
									d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
								/>
							</svg>
							<p className="text-lg font-medium">
								Drop images here
							</p>
						</div>
					</div>
				)}
			</div>

			{uploadState.uploadedFiles.length > 0 && (
				<div className="fixed bottom-4 right-4 z-[999999] bg-background border border-border rounded-lg shadow-lg p-4 max-w-md w-full">
					<div className="flex items-center justify-between mb-3">
						<div className="text-sm font-medium">
							Uploading {uploadState.uploadedFiles.length} file(s)
						</div>
						<div className="flex items-center gap-1">
							{uploadState.isUploading && (
								<Button
									type="button"
									size="sm"
									variant="ghost"
									onClick={cancelAllUploads}
									className="h-6 px-2 text-xs"
								>
									Cancel All
								</Button>
							)}
							<Button
								type="button"
								size="icon"
								variant="ghost"
								onClick={() =>
									setUploadState({
										isUploading: false,
										progress: 0,
										uploadedFiles: [],
										errors: [],
									})
								}
								className="h-6 w-6"
								aria-label="Close"
							>
								<svg
									width="12"
									height="12"
									viewBox="0 0 24 24"
									fill="none"
									stroke="currentColor"
									strokeWidth="2"
								>
									<line x1="18" y1="6" x2="6" y2="18" />
									<line x1="6" y1="6" x2="18" y2="18" />
								</svg>
							</Button>
						</div>
					</div>

					<div className="space-y-2 max-h-64 overflow-y-auto">
						{uploadState.uploadedFiles.map((file) => (
							<div
								key={file.id}
								className="flex items-center gap-2 p-2 bg-secondary/50 rounded"
							>
								<div className="flex-1 min-w-0">
									<div className="text-xs font-medium truncate">
										{file.file.name}
									</div>
									<div className="text-xs text-muted-foreground">
										{formatFileSize(file.file.size)}
									</div>

									{file.status === 'uploading' && (
										<div className="mt-1">
											<div className="w-full bg-background rounded-full h-1.5 overflow-hidden">
												<div
													className="bg-primary h-full transition-all duration-300"
													style={{
														width: `${file.progress}%`,
													}}
													role="progressbar"
													aria-valuenow={
														file.progress
													}
													aria-valuemin={0}
													aria-valuemax={100}
													aria-label={`Upload progress: ${file.progress}%`}
												/>
											</div>
										</div>
									)}

									{file.status === 'error' && file.error && (
										<div className="mt-1 text-xs text-destructive">
											{file.error}
										</div>
									)}
								</div>

								<div className="flex-shrink-0">
									{file.status === 'uploading' && (
										<Button
											type="button"
											size="icon"
											variant="ghost"
											onClick={() =>
												cancelUpload(file.id)
											}
											className="h-6 w-6"
											aria-label="Cancel upload"
										>
											<svg
												width="12"
												height="12"
												viewBox="0 0 24 24"
												fill="none"
												stroke="currentColor"
												strokeWidth="2"
											>
												<line
													x1="18"
													y1="6"
													x2="6"
													y2="18"
												/>
												<line
													x1="6"
													y1="6"
													x2="18"
													y2="18"
												/>
											</svg>
										</Button>
									)}

									{file.status === 'success' && (
										<div className="text-green-500">
											<svg
												width="16"
												height="16"
												viewBox="0 0 24 24"
												fill="none"
												stroke="currentColor"
												strokeWidth="2"
											>
												<polyline points="20 6 9 17 4 12" />
											</svg>
										</div>
									)}

									{file.status === 'error' && (
										<Button
											type="button"
											size="icon"
											variant="ghost"
											onClick={() => retryUpload(file.id)}
											className="h-6 w-6"
											aria-label="Retry upload"
										>
											<svg
												width="12"
												height="12"
												viewBox="0 0 24 24"
												fill="none"
												stroke="currentColor"
												strokeWidth="2"
											>
												<polyline points="23 4 23 10 17 10" />
												<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10" />
											</svg>
										</Button>
									)}
								</div>
							</div>
						))}
					</div>

					{uploadState.errors.length > 0 && (
						<div className="mt-3 pt-3 border-t border-border">
							<div className="text-xs text-destructive">
								{uploadState.errors[0]}
							</div>
						</div>
					)}
				</div>
			)}
		</div>
	);
}
