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

import { useState, useEffect, useRef, useCallback, memo } from 'react';
import { createPortal } from 'react-dom';
import type { ImageData } from '../../types/image';

interface ImagePreviewProps {
	images: ImageData[];
	initialIndex: number;
	isOpen: boolean;
	onClose: () => void;
}

/**
 * Format file size for display.
 *
 * @since 3.1.0
 */
function formatFileSize(bytes: number): string {
	if (bytes < 1024) return `${bytes} B`;
	if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
	return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}

/**
 * ImagePreview modal component for full-size image viewing.
 *
 * @since 3.1.0
 */
export const ImagePreview = memo(
	({ images, initialIndex, isOpen, onClose }: ImagePreviewProps) => {
		const [currentIndex, setCurrentIndex] = useState(initialIndex);
		const [zoom, setZoom] = useState(1);
		const [isLoading, setIsLoading] = useState(true);
		const [hasError, setHasError] = useState(false);
		const imageRef = useRef<HTMLImageElement>(null);
		const containerRef = useRef<HTMLDivElement>(null);
		const touchStartRef = useRef<{ x: number; y: number } | null>(null);
		const touchDistanceRef = useRef<number>(0);

		// Reset state when modal opens or index changes.
		useEffect(() => {
			if (isOpen) {
				setCurrentIndex(initialIndex);
				setZoom(1);
				setIsLoading(true);
				setHasError(false);
			}
		}, [isOpen, initialIndex]);

		// Handle keyboard navigation.
		useEffect(() => {
			if (!isOpen) return;

			const handleKeyDown = (e: KeyboardEvent) => {
				switch (e.key) {
					case 'Escape':
						onClose();
						break;
					case 'ArrowLeft':
						e.preventDefault();
						handlePrevious();
						break;
					case 'ArrowRight':
						e.preventDefault();
						handleNext();
						break;
					case '+':
					case '=':
						e.preventDefault();
						handleZoomIn();
						break;
					case '-':
						e.preventDefault();
						handleZoomOut();
						break;
					case '0':
						e.preventDefault();
						setZoom(1);
						break;
				}
			};

			window.addEventListener('keydown', handleKeyDown);
			return () => window.removeEventListener('keydown', handleKeyDown);
			// eslint-disable-next-line react-hooks/exhaustive-deps
		}, [isOpen, currentIndex, images.length, onClose]);

		// Prevent body scroll when modal is open.
		useEffect(() => {
			if (isOpen) {
				document.body.style.overflow = 'hidden';
			} else {
				document.body.style.overflow = '';
			}

			return () => {
				document.body.style.overflow = '';
			};
		}, [isOpen]);

		const handlePrevious = useCallback(() => {
			setCurrentIndex((prev) => {
				const newIndex = prev > 0 ? prev - 1 : images.length - 1;
				setIsLoading(true);
				setHasError(false);
				setZoom(1);
				return newIndex;
			});
		}, [images.length]);

		const handleNext = useCallback(() => {
			setCurrentIndex((prev) => {
				const newIndex = prev < images.length - 1 ? prev + 1 : 0;
				setIsLoading(true);
				setHasError(false);
				setZoom(1);
				return newIndex;
			});
		}, [images.length]);

		const handleZoomIn = useCallback(() => {
			setZoom((prev) => Math.min(prev + 0.25, 3));
		}, []);

		const handleZoomOut = useCallback(() => {
			setZoom((prev) => Math.max(prev - 0.25, 0.5));
		}, []);

		const handleImageLoad = () => {
			setIsLoading(false);
		};

		const handleImageError = () => {
			setIsLoading(false);
			setHasError(true);
		};

		const handleBackdropClick = (e: React.MouseEvent) => {
			if (e.target === e.currentTarget) {
				onClose();
			}
		};

		const handleBackdropKeyDown = (e: React.KeyboardEvent) => {
			if (e.key === 'Escape') {
				onClose();
			}
		};

		/**
		 * Handle touch start for swipe gestures.
		 *
		 * @since 3.1.0
		 */
		const handleTouchStart = useCallback((e: React.TouchEvent) => {
			if (e.touches.length === 1) {
				touchStartRef.current = {
					x: e.touches[0].clientX,
					y: e.touches[0].clientY,
				};
			} else if (e.touches.length === 2) {
				// Calculate initial distance for pinch zoom.
				const dx = e.touches[0].clientX - e.touches[1].clientX;
				const dy = e.touches[0].clientY - e.touches[1].clientY;
				touchDistanceRef.current = Math.sqrt(dx * dx + dy * dy);
			}
		}, []);

		/**
		 * Handle touch move for swipe and pinch gestures.
		 *
		 * @since 3.1.0
		 */
		const handleTouchMove = useCallback((e: React.TouchEvent) => {
			if (e.touches.length === 2) {
				// Handle pinch zoom.
				e.preventDefault();
				const dx = e.touches[0].clientX - e.touches[1].clientX;
				const dy = e.touches[0].clientY - e.touches[1].clientY;
				const distance = Math.sqrt(dx * dx + dy * dy);

				if (touchDistanceRef.current > 0) {
					const scale = distance / touchDistanceRef.current;
					setZoom((prev) => Math.max(0.5, Math.min(3, prev * scale)));
				}

				touchDistanceRef.current = distance;
			}
		}, []);

		/**
		 * Handle touch end for swipe navigation.
		 *
		 * @since 3.1.0
		 */
		const handleTouchEnd = useCallback(
			(e: React.TouchEvent) => {
				if (e.changedTouches.length === 1 && touchStartRef.current) {
					const touchEnd = {
						x: e.changedTouches[0].clientX,
						y: e.changedTouches[0].clientY,
					};

					const dx = touchEnd.x - touchStartRef.current.x;
					const dy = touchEnd.y - touchStartRef.current.y;

					// Only trigger swipe if horizontal movement is significant.
					if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 50) {
						if (dx > 0) {
							handlePrevious();
						} else {
							handleNext();
						}
					}

					touchStartRef.current = null;
				}

				touchDistanceRef.current = 0;
			},
			[handleNext, handlePrevious]
		);

		if (!isOpen) return null;

		const currentImage = images[currentIndex];
		const showNavigation = images.length > 1;

		const modalContent = (
			// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
			<div
				className="fixed inset-0 bg-black/90 flex items-center justify-center"
				style={{ zIndex: 999999 }}
				onClick={handleBackdropClick}
				onKeyDown={handleBackdropKeyDown}
				role="dialog"
				aria-modal="true"
				aria-label="Image preview"
				tabIndex={-1}
			>
				{/* Top controls bar - contains close button and zoom controls */}
				<div className="absolute top-0 left-0 right-0 z-[10] flex items-start justify-between p-2 sm:p-4 pointer-events-none">
					{/* Zoom controls */}
					<div className="flex gap-2 pointer-events-auto">
						<button
							onClick={handleZoomOut}
							disabled={zoom <= 0.5}
							className="w-10 h-10 sm:w-12 sm:h-12 flex items-center justify-center rounded-full bg-black/70 text-white hover:bg-black/80 active:bg-black/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed touch-manipulation text-4xl sm:text-5xl font-bold"
							aria-label="Zoom out"
						>
							−
						</button>
						<button
							onClick={() => setZoom(1)}
							className="px-3 sm:px-4 py-2 rounded-full bg-black/70 text-white hover:bg-black/80 active:bg-black/90 transition-colors text-xs sm:text-sm font-medium touch-manipulation"
							aria-label="Reset zoom"
						>
							{Math.round(zoom * 100)}%
						</button>
						<button
							onClick={handleZoomIn}
							disabled={zoom >= 3}
							className="w-10 h-10 sm:w-12 sm:h-12 flex items-center justify-center rounded-full bg-black/70 text-white hover:bg-black/80 active:bg-black/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed touch-manipulation text-4xl sm:text-5xl font-bold"
							aria-label="Zoom in"
						>
							+
						</button>
					</div>

					{/* Close button */}
					<button
						onClick={onClose}
						className="pointer-events-auto w-10 h-10 sm:w-12 sm:h-12 flex items-center justify-center rounded-full bg-black/70 text-white hover:bg-black/80 active:bg-black/90 transition-colors touch-manipulation text-4xl sm:text-5xl font-bold"
						aria-label="Close preview"
					>
						×
					</button>
				</div>

				{/* Navigation buttons */}
				{showNavigation && (
					<>
						<button
							onClick={handlePrevious}
							className="absolute left-2 sm:left-4 top-1/2 -translate-y-1/2 z-[5] w-10 h-10 sm:w-12 sm:h-12 flex items-center justify-center rounded-full bg-black/70 text-white hover:bg-black/80 active:bg-black/90 transition-colors touch-manipulation text-4xl sm:text-5xl font-bold"
							aria-label="Previous image"
						>
							‹
						</button>
						<button
							onClick={handleNext}
							className="absolute right-2 sm:right-4 top-1/2 -translate-y-1/2 z-[5] w-10 h-10 sm:w-12 sm:h-12 flex items-center justify-center rounded-full bg-black/70 text-white hover:bg-black/80 active:bg-black/90 transition-colors touch-manipulation text-4xl sm:text-5xl font-bold"
							aria-label="Next image"
						>
							›
						</button>
					</>
				)}

				{/* Bottom info bar - contains counter and metadata */}
				<div className="absolute bottom-0 left-0 right-0 z-[10] flex items-end justify-between p-2 sm:p-4 pointer-events-none">
					{/* Image counter */}
					{showNavigation && (
						<div className="pointer-events-auto px-3 sm:px-4 py-1.5 sm:py-2 rounded-full bg-black/50 text-white text-xs sm:text-sm">
							{currentIndex + 1} / {images.length}
						</div>
					)}

					{/* Spacer for centering when no navigation */}
					{!showNavigation && <div />}

					{/* Image metadata */}
					{currentImage.fileSize > 0 && (
						<div className="pointer-events-auto px-2 sm:px-4 py-1.5 sm:py-2 rounded-md bg-black/50 text-white text-xs space-y-0.5 sm:space-y-1 max-w-[200px] sm:max-w-none">
							<div className="truncate">{currentImage.alt}</div>
							{currentImage.width && currentImage.height && (
								<div className="text-white/70">
									{currentImage.width} × {currentImage.height}
								</div>
							)}
							<div className="text-white/70">
								{formatFileSize(currentImage.fileSize)}
							</div>
						</div>
					)}
				</div>

				{/* Image container */}
				<div
					ref={containerRef}
					className="relative w-full h-full flex items-center justify-center p-4 sm:p-16 overflow-auto z-[1]"
					onTouchStart={handleTouchStart}
					onTouchMove={handleTouchMove}
					onTouchEnd={handleTouchEnd}
				>
					{/* Loading state */}
					{isLoading && !hasError && (
						<div className="absolute inset-0 flex items-center justify-center">
							<div className="w-12 h-12 border-4 border-white/30 border-t-white rounded-full animate-spin" />
						</div>
					)}

					{/* Error state */}
					{hasError && (
						<div className="flex flex-col items-center justify-center text-white">
							<svg
								className="w-16 h-16 mb-4"
								fill="none"
								stroke="currentColor"
								viewBox="0 0 24 24"
								aria-hidden="true"
							>
								<path
									strokeLinecap="round"
									strokeLinejoin="round"
									strokeWidth={2}
									d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
								/>
							</svg>
							<p className="text-lg mb-4">Failed to load image</p>
							<button
								onClick={() => {
									setHasError(false);
									setIsLoading(true);
								}}
								className="px-4 py-2 rounded-md bg-white/20 hover:bg-white/30 transition-colors"
							>
								Retry
							</button>
						</div>
					)}

					{/* Image */}
					{!hasError && (
						<img
							ref={imageRef}
							src={currentImage.url}
							alt={currentImage.alt}
							className={`max-w-full max-h-full object-contain transition-all duration-200 ${
								isLoading ? 'opacity-0' : 'opacity-100'
							}`}
							style={{
								transform: `scale(${zoom})`,
								cursor: zoom > 1 ? 'move' : 'default',
							}}
							onLoad={handleImageLoad}
							onError={handleImageError}
							draggable={false}
						/>
					)}
				</div>
			</div>
		);

		// Render modal using portal to escape parent container constraints.
		return createPortal(modalContent, document.body);
	}
);

ImagePreview.displayName = 'ImagePreview';
