<script>
	import { m } from '$lib/paraglide/messages';
	import { resolve } from '$app/paths';
	import { getPhotoFileUrl } from '$lib/api/client';
	import { goto } from '$app/navigation';

	let { data } = $props();

	/** @type {import('$lib/api/types').Photo|null} */
	let photo = $derived(data.photo);
	let currentIndex = $derived(data.currentIndex ?? 0);
	let totalPhotos = $derived(data.totalPhotos ?? 0);
	let hasPrev = $derived(data.hasPrev ?? false);
	let hasNext = $derived(data.hasNext ?? false);
	let prevPhotoId = $derived(data.prevPhotoId);
	let nextPhotoId = $derived(data.nextPhotoId);

	// 导航加载状态
	let isNavigating = $state(false);
	let loadError = $state(false);

	// 渐进式加载状态
	/** @type {'thumbnail' | 'preview' | 'full'} */
	let loadStage = $state('thumbnail');
	let isFullQualityLoaded = $state(false);
	let loadProgress = $state(0);
	let isFirstLoad = true;

	// 图片尺寸信息
	let imageNaturalWidth = $state(0);
	let imageNaturalHeight = $state(0);
	let containerWidth = $state(0);
	let containerHeight = $state(0);

	// 触摸滑动支持
	let touchStartX = 0;
	let touchEndX = 0;
	const SWIPE_THRESHOLD = 50;

	// ========== 缩放相关状态 ==========
	let scale = $state(1);
	let minScale = $state(1);
	let maxScale = 4;
	let translateX = $state(0);
	let translateY = $state(0);
	let isDragging = $state(false);
	let dragStartX = 0;
	let dragStartY = 0;
	let isPinching = $state(false);
	let pinchStartDistance = 0;
	let pinchStartScale = 0;

	// 双击缩放
	let lastClickTime = 0;
	let lastClickX = 0;
	let lastClickY = 0;

	// 动画帧 ID
	let animationFrameId = null;
	let initialScale = 1;

	// 监听容器尺寸变化
	function updateContainerSize() {
		const container = document.querySelector('.viewer-content');
		if (container) {
			containerWidth = container.clientWidth;
			containerHeight = container.clientHeight;
			updateMinScale();
		}
	}

	$effect(() => {
		updateContainerSize();
		window.addEventListener('resize', updateContainerSize);
		return () => window.removeEventListener('resize', updateContainerSize);
	});

	// 更新最小缩放比例
	function updateMinScale() {
		if (!imageNaturalWidth || !imageNaturalHeight || !containerWidth || !containerHeight) {
			minScale = 0.25;
			initialScale = 1;
			return;
		}

		const fitWidth = containerWidth / imageNaturalWidth;
		const fitHeight = containerHeight / imageNaturalHeight;
		// 最小缩放：允许缩小到25%
		minScale = 0.25;

		// 只在首次加载时设置初始缩放
		if (isFirstLoad) {
			isFirstLoad = false;
			// 初始缩放：适应容器但不超过100%（不放大）
			initialScale = Math.min(Math.max(fitWidth, fitHeight), 1);
			scale = initialScale;
			targetScaleRef = initialScale;
		}
	}

	// 平滑缩放的动画循环
	function startAnimation() {
		if (animationFrameId) return;

		// 使用局部变量跟踪当前值，避免响应式更新延迟问题
		let currentScale = scale;
		let currentTranslateX = translateX;
		let currentTranslateY = translateY;

		function animate() {
			const ease = 0.2;
			let needsContinue = false;

			if (Math.abs(currentScale - targetScaleRef) > 0.001) {
				currentScale = currentScale + (targetScaleRef - currentScale) * ease;
				scale = currentScale;
				needsContinue = true;
			} else {
				// 动画完成，精确设置为目标值
				scale = targetScaleRef;
			}

			if (Math.abs(currentTranslateX - targetTranslateXRef) > 0.5) {
				currentTranslateX = currentTranslateX + (targetTranslateXRef - currentTranslateX) * ease;
				translateX = currentTranslateX;
				needsContinue = true;
			} else {
				translateX = targetTranslateXRef;
			}

			if (Math.abs(currentTranslateY - targetTranslateYRef) > 0.5) {
				currentTranslateY = currentTranslateY + (targetTranslateYRef - currentTranslateY) * ease;
				translateY = currentTranslateY;
				needsContinue = true;
			} else {
				translateY = targetTranslateYRef;
			}

			clampTranslate();

			if (needsContinue) {
				animationFrameId = requestAnimationFrame(animate);
			} else {
				animationFrameId = null;
			}
		}

		animationFrameId = requestAnimationFrame(animate);
	}

	// 使用普通变量存储目标值（避免 $state 的复杂响应式）
	let targetScaleRef = 1;
	let targetTranslateXRef = 0;
	let targetTranslateYRef = 0;

	function goToPrevious() {
		if (!hasPrev || isNavigating) return;
		navigateToPhoto(prevPhotoId);
	}

	function goToNext() {
		if (!hasNext || isNavigating) return;
		navigateToPhoto(nextPhotoId);
	}

	async function navigateToPhoto(photoId) {
		if (isNavigating) return;

		isNavigating = true;
		loadError = false;
		loadStage = 'thumbnail';
		isFullQualityLoaded = false;
		loadProgress = 0;
		imageNaturalWidth = 0;
		imageNaturalHeight = 0;
		isFirstLoad = true;

		try {
			await goto(resolve(`/photo/${photoId}`), { keepFocus: true });
		} catch (error) {
			console.error('Failed to navigate:', error);
			loadError = true;
		} finally {
			isNavigating = false;
		}
	}

	function handleClose() {
		if (photo?.albumId) {
			goto(resolve(`/album/${photo.albumId}`));
		} else {
			history.back();
		}
	}

	function handleTouchStart(event) {
		if (event.touches.length === 1) {
			touchStartX = event.touches[0].clientX;
			dragStartX = event.touches[0].clientX - translateX;
			dragStartY = event.touches[0].clientY - translateY;
		} else if (event.touches.length === 2) {
			isPinching = true;
			pinchStartDistance = getPinchDistance(event.touches);
			pinchStartScale = scale;
		}
	}

	function handleTouchMove(event) {
		if (event.touches.length === 1 && !isPinching && scale > minScale + 0.1) {
			event.preventDefault();
			touchEndX = event.touches[0].clientX;
			translateX = event.touches[0].clientX - dragStartX;
			translateY = event.touches[0].clientY - dragStartY;
			targetTranslateXRef = translateX;
			targetTranslateYRef = translateY;
		} else if (event.touches.length === 2 && isPinching) {
			event.preventDefault();
			const currentDistance = getPinchDistance(event.touches);
			const newScale = pinchStartScale * (currentDistance / pinchStartDistance);
			targetScaleRef = Math.max(minScale, Math.min(newScale, maxScale));
			startAnimation();
		}
	}

	function handleTouchEnd() {
		isPinching = false;

		if (touchStartX && touchEndX && scale <= minScale + 0.1) {
			const diff = touchStartX - touchEndX;
			if (Math.abs(diff) > SWIPE_THRESHOLD) {
				if (diff > 0) {
					goToNext();
				} else {
					goToPrevious();
				}
			}
		}

		touchStartX = 0;
		touchEndX = 0;
	}

	function getPinchDistance(touches) {
		const dx = touches[0].clientX - touches[1].clientX;
		const dy = touches[0].clientY - touches[1].clientY;
		return Math.sqrt(dx * dx + dy * dy);
	}

	// 键盘导航
	$effect(() => {
		function handleKeydown(event) {
			if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement) {
				return;
			}

			switch (event.key) {
				case 'ArrowLeft':
					if (scale <= minScale + 0.1) {
						event.preventDefault();
						goToPrevious();
					}
					break;
				case 'ArrowRight':
					if (scale <= minScale + 0.1) {
						event.preventDefault();
						goToNext();
					}
					break;
				case 'Escape':
					event.preventDefault();
					if (scale > minScale) {
						resetZoom();
					} else {
						handleClose();
					}
					break;
				case ' ':
					event.preventDefault();
					if (loadStage !== 'full' && photo?.id) {
						upgradeToFullQuality();
					}
					break;
				case '+':
				case '=':
					event.preventDefault();
					zoomIn();
					break;
				case '-':
					event.preventDefault();
					zoomOut();
					break;
				case '0':
					event.preventDefault();
					resetZoom();
					break;
			}
		}

		const container = document.querySelector('.viewer-container');
		if (container) {
			container.addEventListener('keydown', handleKeydown);
			container.focus();
		}

		return () => {
			if (container) {
				container.removeEventListener('keydown', handleKeydown);
			}
		};
	});

	// 智能预加载相邻照片
	$effect(() => {
		let cancelled = false;
		let preloadTimeout;

		preloadTimeout = setTimeout(() => {
			if (cancelled) return;

			if (hasNext && nextPhotoId) {
				const nextImg = new Image();
				nextImg.src = resolve(`/api/v1/photo/${nextPhotoId}/preview?w=800`);
				nextImg.fetchPriority = 'low';
			}

			if (hasPrev && prevPhotoId) {
				const prevImg = new Image();
				prevImg.src = resolve(`/api/v1/photo/${prevPhotoId}/preview?w=800`);
				prevImg.fetchPriority = 'low';
			}
		}, 300);

		return () => {
			cancelled = true;
			clearTimeout(preloadTimeout);
		};
	});

	// 渐进式加载
	$effect(() => {
		if (!photo || photo.mimeType?.startsWith('video/')) return;

		let cancelled = false;

		const previewTimeout = setTimeout(() => {
			if (cancelled || loadStage !== 'thumbnail') return;
			loadStage = 'preview';
		}, 100);

		const fullTimeout = setTimeout(() => {
			if (cancelled || !isFullQualityLoaded) return;
			upgradeToFullQuality();
		}, 800);

		return () => {
			cancelled = true;
			clearTimeout(previewTimeout);
			clearTimeout(fullTimeout);
		};
	});

	function upgradeToFullQuality() {
		if (!photo || loadStage === 'full' || photo.mimeType?.startsWith('video/')) return;

		loadStage = 'full';
		isFullQualityLoaded = true;

		const fullResImg = new Image();
		fullResImg.src = resolve(getPhotoFileUrl(photo.id));
		fullResImg.fetchPriority = 'high';

		fullResImg.onprogress = (event) => {
			if (event.lengthComputable) {
				loadProgress = Math.round((event.loaded / event.total) * 100);
			}
		};

		fullResImg.onload = () => {
			loadProgress = 100;
			const imgElement = document.querySelector('.viewer-content img');
			if (imgElement) {
				imgElement.src = resolve(getPhotoFileUrl(photo.id));
				imgElement.srcset = '';
				imgElement.sizes = '100vw';
			}
			setTimeout(updateMinScale, 50);
		};

		fullResImg.onerror = () => {
			console.error('Failed to load full resolution image');
			loadProgress = 0;
			loadStage = 'preview';
		};
	}

	function handleImageLoad(event) {
		isNavigating = false;
		const img = event.target;
		imageNaturalWidth = img.naturalWidth;
		imageNaturalHeight = img.naturalHeight;
		setTimeout(() => {
			updateContainerSize();
			// 只在首次加载时重置缩放
			if (isFirstLoad) {
				resetZoom();
			}
		}, 10);
	}

	function handleImageError() {
		isNavigating = false;
		loadError = true;
	}

	function getImageSrc() {
		if (!photo || photo.mimeType?.startsWith('video/')) return '';

		if (loadStage === 'full') {
			return resolve(getPhotoFileUrl(photo.id));
		}

		const previewSrc = resolve(`/api/v1/photo/${photo.id}/preview`);
		if (loadStage === 'preview') {
			return `${previewSrc}?w=1200`;
		}

		return `${previewSrc}?w=400`;
	}

	function getImageSrcSet() {
		if (!photo || loadStage === 'full' || photo.mimeType?.startsWith('video/')) return '';

		const previewSrc = resolve(`/api/v1/photo/${photo.id}/preview`);
		if (loadStage === 'preview') {
			return `${previewSrc}?w=800 800w, ${previewSrc}?w=1200 1200w, ${previewSrc}?w=1600 1600w`;
		}

		return `${previewSrc}?w=400 400w, ${previewSrc}?w=800 800w`;
	}

	function getImageSizes() {
		if (!photo || photo.mimeType?.startsWith('video/')) return '';
		return '100vw';
	}

	// ========== 缩放控制函数 ==========

	function zoomIn() {
		const currentPercent = Math.round(targetScaleRef * 100);
		// 找到下一个更高的规整百分比
		const steps = [25, 50, 75, 100, 125, 150, 175, 200, 250, 300, 400];
		let nextPercent = currentPercent * 1.5;
		for (const step of steps) {
			if (step > currentPercent + 5) {
				nextPercent = step;
				break;
			}
		}
		targetScaleRef = Math.min(nextPercent / 100, maxScale);
		startAnimation();
	}

	function zoomOut() {
		const currentPercent = Math.round(targetScaleRef * 100);
		// 找到下一个更低的规整百分比
		const steps = [400, 300, 250, 200, 175, 150, 125, 100, 75, 50, 25];
		let nextPercent = currentPercent / 1.5;
		for (const step of steps) {
			if (step < currentPercent - 5) {
				nextPercent = step;
				break;
			}
		}
		targetScaleRef = Math.max(nextPercent / 100, minScale);
		startAnimation();
	}

	function resetZoom() {
		isFirstLoad = false;
		targetScaleRef = initialScale;
		targetTranslateXRef = 0;
		targetTranslateYRef = 0;
		startAnimation();
	}

	// 双击缩放
	function handleImageClick(event) {
		const now = Date.now();

		// 使用容器作为参考系计算坐标
		const container = document.querySelector('.viewer-content');
		if (!container) return;

		const rect = container.getBoundingClientRect();
		const x = event.clientX - rect.left;
		const y = event.clientY - rect.top;

		if (
			now - lastClickTime < 300 &&
			Math.abs(event.clientX - lastClickX) < 50 &&
			Math.abs(event.clientY - lastClickY) < 50
		) {
			if (targetScaleRef > initialScale + 0.01) {
				resetZoom();
			} else {
				const zoomLevel = Math.min(maxScale, 2.5);
				zoomToPoint(x, y, zoomLevel);
			}
		} else {
			lastClickTime = now;
			lastClickX = event.clientX;
			lastClickY = event.clientY;
		}
	}

	function zoomToPoint(pointX, pointY, zoomLevel) {
		const currentScale = targetScaleRef;
		const currentTranslateX = targetTranslateXRef;
		const currentTranslateY = targetTranslateYRef;

		// 点击点相对于容器中心的偏移
		const dx = pointX - containerWidth / 2;
		const dy = pointY - containerHeight / 2;

		// 缩放比例变化
		const scaleRatio = zoomLevel / currentScale;

		// 计算新的平移，使点击点保持在容器中的相同位置
		targetTranslateXRef = dx * (1 - scaleRatio) + currentTranslateX * scaleRatio;
		targetTranslateYRef = dy * (1 - scaleRatio) + currentTranslateY * scaleRatio;
		targetScaleRef = zoomLevel;

		startAnimation();
	}

	// 鼠标滚轮缩放
	function handleWheel(event) {
		if (event.ctrlKey || event.metaKey) {
			event.preventDefault();
			const delta = event.deltaY > 0 ? 0.9 : 1.1;
			const currentScale = targetScaleRef;
			const newScale = Math.max(minScale, Math.min(currentScale * delta, maxScale));

			if (Math.abs(newScale - currentScale) > 0.001) {
				// 使用容器作为参考系计算坐标
				const container = document.querySelector('.viewer-content');
				if (container) {
					const rect = container.getBoundingClientRect();
					const x = event.clientX - rect.left;
					const y = event.clientY - rect.top;
					zoomToPoint(x, y, newScale);
				}
			}
		}
	}

	// 鼠标拖拽
	function handleMouseDown(event) {
		if (scale > minScale + 0.1 && event.button === 0) {
			isDragging = true;
			dragStartX = event.clientX - translateX;
			dragStartY = event.clientY - translateY;
		}
	}

	function handleMouseMove(event) {
		if (isDragging) {
			translateX = event.clientX - dragStartX;
			translateY = event.clientY - dragStartY;
			targetTranslateXRef = translateX;
			targetTranslateYRef = translateY;
		}
	}

	function handleMouseUp() {
		isDragging = false;
	}

	function handleMouseLeave() {
		isDragging = false;
	}

	// 限制平移范围
	function clampTranslate() {
		if (!imageNaturalWidth || !imageNaturalHeight) return;

		const scaledWidth = imageNaturalWidth * targetScaleRef;
		const scaledHeight = imageNaturalHeight * targetScaleRef;

		const maxX = Math.max(0, (scaledWidth - containerWidth) / 2);
		const maxY = Math.max(0, (scaledHeight - containerHeight) / 2);

		targetTranslateXRef = Math.max(-maxX, Math.min(maxX, targetTranslateXRef));
		targetTranslateYRef = Math.max(-maxY, Math.min(maxY, targetTranslateYRef));
	}

	// 获取当前缩放百分比显示
	function getZoomPercent() {
		return Math.round(scale * 100);
	}

	// 图片样式
	function getImageStyle() {
		const styles = {
			transform: `translate(${translateX}px, ${translateY}px) scale(${scale})`,
			transformOrigin: 'center center',
			transition: isDragging || isPinching ? 'none' : 'transform 0.1s ease-out',
			cursor: scale > minScale + 0.1 ? 'grab' : 'default'
		};
		return Object.entries(styles)
			.map(([key, value]) => `${key}: ${value}`)
			.join('; ');
	}

	function getImageWrapperStyle() {
		const styles = {
			cursor: isDragging ? 'grabbing' : scale > minScale + 0.1 ? 'grab' : 'default',
			overflow: 'hidden'
		};
		return Object.entries(styles)
			.map(([key, value]) => `${key}: ${value}`)
			.join('; ');
	}
</script>

<svelte:head>
	<title>{photo ? photo.fileName : m.loading()}</title>
	<meta
		name="viewport"
		content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
	/>
</svelte:head>

<div
	class="viewer-container"
	role="application"
	tabindex="0"
	ontouchstart={handleTouchStart}
	ontouchmove={handleTouchMove}
	ontouchend={handleTouchEnd}
>
	{#if !photo}
		<div class="loading">
			<div class="loading-spinner"></div>
			<p>{m.loading()}</p>
		</div>
	{:else}
		{#if isNavigating}
			<div class="loading-indicator">
				<div class="loading-spinner small"></div>
			</div>
		{/if}

		{#if loadError}
			<div class="error-toast">
				<span>{m.load_failed()}</span>
				<button
					onclick={() => {
						loadError = false;
						loadStage = 'thumbnail';
					}}>{m.retry()}</button
				>
			</div>
		{/if}

		{#if loadStage === 'full' && loadProgress < 100}
			<div class="progress-indicator">
				<div class="progress-bar">
					<div class="progress-fill" style="width: {loadProgress}%"></div>
				</div>
				<span class="progress-text">{loadProgress}%</span>
			</div>
		{/if}

		{#if loadStage !== 'full' && !isNavigating && photo && !photo.mimeType?.startsWith('video/')}
			<button class="quality-upgrade-btn" onclick={upgradeToFullQuality} type="button">
				<svg
					viewBox="0 0 24 24"
					fill="none"
					stroke="currentColor"
					stroke-width="2"
					width="16"
					height="16"
				>
					<circle cx="11" cy="11" r="8" />
					<path d="M21 21l-4.35-4.35" />
					<path d="M11 8v6" />
					<path d="M8 11h6" />
				</svg>
				<span>{m.view_original()}</span>
			</button>
		{/if}

		<div class="viewer-header">
			<a href={resolve(`/album/${photo.albumId}`)} class="back-link">
				<span class="back-icon">←</span>
				<span class="back-text">{m.back()}</span>
			</a>
			<span class="photo-index">
				{currentIndex + 1} / {totalPhotos}
			</span>
			{#if photo && !photo.mimeType?.startsWith('video/')}
				<span class="quality-indicator">
					{#if loadStage === 'full'}
						{m.original()}
					{:else if loadStage === 'preview'}
						{m.high_quality()}
					{:else}
						{m.thumbnail()}
					{/if}
				</span>
			{/if}
		</div>

		<div class="viewer-content">
			<button
				class="nav-button prev"
				onclick={goToPrevious}
				disabled={!hasPrev || isNavigating}
				aria-label={m.previous_photo()}
				type="button"
			>
				<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
					<path d="M15 18l-6-6 6-6" />
				</svg>
			</button>

			<div
				class="image-wrapper"
				style={getImageWrapperStyle()}
				onmousedown={handleMouseDown}
				onmousemove={handleMouseMove}
				onmouseup={handleMouseUp}
				onmouseleave={handleMouseLeave}
			>
				{#if photo.mimeType?.startsWith('video/')}
					<video
						controls
						playsinline
						class="fill-container"
						onloadeddata={handleImageLoad}
						onerror={handleImageError}
					>
						<source src={resolve(getPhotoFileUrl(photo.id))} type={photo.mimeType} />
					</video>
				{:else}
					<img
						src={getImageSrc()}
						srcset={getImageSrcSet()}
						sizes={getImageSizes()}
						alt={photo.fileName}
						style={getImageStyle()}
						onload={handleImageLoad}
						onerror={handleImageError}
						decoding="async"
						fetchpriority={loadStage === 'full' ? 'high' : 'auto'}
						onclick={handleImageClick}
						onwheel={handleWheel}
						class="zoomable-image"
					/>
				{/if}

				{#if targetScaleRef > initialScale + 0.01 || targetScaleRef < initialScale - 0.01}
					<button class="reset-zoom-btn" onclick={resetZoom} type="button" aria-label="重置缩放">
						<svg
							viewBox="0 0 24 24"
							fill="none"
							stroke="currentColor"
							stroke-width="2"
							width="20"
							height="20"
						>
							<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
							<path d="M3 3v5h5" />
						</svg>
					</button>
				{/if}
			</div>

			<button
				class="nav-button next"
				onclick={goToNext}
				disabled={!hasNext || isNavigating}
				aria-label={m.next_photo()}
				type="button"
			>
				<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
					<path d="M9 18l6-6-6-6" />
				</svg>
			</button>
		</div>

		<!-- 缩放控制栏 -->
		{#if photo && !photo.mimeType?.startsWith('video/')}
			<div class="zoom-controls">
				<button
					onclick={zoomOut}
					disabled={targetScaleRef <= minScale + 0.01}
					type="button"
					aria-label={m.zoom_out()}
				>
					<svg
						viewBox="0 0 24 24"
						fill="none"
						stroke="currentColor"
						stroke-width="2"
						width="20"
						height="20"
					>
						<circle cx="11" cy="11" r="8" />
						<path d="M21 21l-4.35-4.35" />
						<path d="M8 11h6" />
					</svg>
				</button>
				<span class="zoom-level">{getZoomPercent()}%</span>
				<button
					onclick={zoomIn}
					disabled={targetScaleRef >= maxScale - 0.01}
					type="button"
					aria-label={m.zoom_in()}
				>
					<svg
						viewBox="0 0 24 24"
						fill="none"
						stroke="currentColor"
						stroke-width="2"
						width="20"
						height="20"
					>
						<circle cx="11" cy="11" r="8" />
						<path d="M21 21l-4.35-4.35" />
						<path d="M11 8v6" />
						<path d="M8 11h6" />
					</svg>
				</button>
				<button onclick={resetZoom} type="button" aria-label={m.reset()}>
					<svg
						viewBox="0 0 24 24"
						fill="none"
						stroke="currentColor"
						stroke-width="2"
						width="20"
						height="20"
					>
						<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
						<path d="M3 3v5h5" />
					</svg>
				</button>
			</div>
		{/if}

		<div class="photo-info">
			<div class="photo-details">
				<h2>{photo.fileName}</h2>
				<p class="meta">
					{#if photo.fileSize}
						<span>{Math.round(photo.fileSize / 1024)} KB</span>
					{/if}
					{#if photo.width && photo.height}
						<span>{photo.width}×{photo.height}</span>
					{/if}
					{#if photo.createdAt}
						<span>{new Date(photo.createdAt).toLocaleDateString()}</span>
					{/if}
				</p>
			</div>
		</div>

		<!-- 快捷键提示 -->
		<div class="shortcut-hints">
			<span>{m.double_click_zoom()}</span>
			<span>{m.pinch_zoom()}</span>
			<span>{m.keyboard_zoom()}</span>
			<span>{m.reset_zoom()}</span>
		</div>
	{/if}
</div>

<style>
	.viewer-container {
		position: fixed;
		inset: 0;
		background: #000;
		color: #fff;
		display: flex;
		flex-direction: column;
		z-index: var(--z-modal);
		outline: none;
		overflow: hidden;
		touch-action: pan-y;
	}

	.viewer-container:focus {
		outline: none;
	}

	.loading {
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
		height: 100%;
		gap: var(--space-md);
	}

	.loading-spinner {
		width: 48px;
		height: 48px;
		border: 3px solid var(--color-overlay-light);
		border-top-color: var(--color-text);
		border-radius: var(--radius-full);
		animation: spin 1s linear infinite;
	}

	.loading-spinner.small {
		width: 24px;
		height: 24px;
		border-width: 2px;
	}

	@keyframes spin {
		to {
			transform: rotate(360deg);
		}
	}

	.loading p {
		color: var(--color-text-tertiary);
		font-size: var(--font-size-base);
		margin: 0;
	}

	.loading-indicator {
		position: absolute;
		top: 50%;
		left: 50%;
		transform: translate(-50%, -50%);
		z-index: 100;
		pointer-events: none;
	}

	.error-toast {
		position: absolute;
		top: 80px;
		left: 50%;
		transform: translateX(-50%);
		background: var(--color-error);
		color: var(--color-text-inverse);
		padding: var(--space-md) var(--space-lg);
		border-radius: var(--radius-md);
		display: flex;
		align-items: center;
		gap: var(--space-md);
		z-index: 101;
		box-shadow: var(--shadow-lg);
	}

	.error-toast button {
		background: var(--color-overlay-light);
		border: none;
		color: var(--color-text-inverse);
		padding: var(--space-xs) var(--space-md);
		border-radius: var(--radius-sm);
		cursor: pointer;
		font-size: var(--font-size-sm);
	}

	.error-toast button:hover {
		background: var(--color-overlay);
	}

	.progress-indicator {
		position: absolute;
		top: 50%;
		left: 50%;
		transform: translate(-50%, -50%);
		background: var(--color-overlay-dark);
		padding: var(--space-md) var(--space-lg);
		border-radius: var(--radius-lg);
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: var(--space-md);
		z-index: 102;
		min-width: 200px;
	}

	.progress-bar {
		width: 100%;
		height: 6px;
		background: var(--color-overlay-light);
		border-radius: var(--radius-sm);
		overflow: hidden;
	}

	.progress-fill {
		height: 100%;
		background: linear-gradient(90deg, var(--color-primary), #8b5cf6);
		transition: width var(--transition-normal);
		border-radius: var(--radius-sm);
	}

	.progress-text {
		color: var(--color-text-inverse);
		font-size: var(--font-size-sm);
		font-weight: var(--font-weight-semibold);
	}

	.quality-upgrade-btn {
		position: absolute;
		bottom: 120px;
		left: 50%;
		transform: translateX(-50%);
		background: var(--color-primary);
		color: var(--color-text-inverse);
		border: none;
		padding: var(--space-sm) var(--space-lg);
		border-radius: var(--radius-md);
		display: flex;
		align-items: center;
		gap: var(--space-sm);
		cursor: pointer;
		transition: all var(--transition-normal);
		z-index: 101;
		font-size: var(--font-size-sm);
		font-weight: var(--font-weight-medium);
		box-shadow: var(--shadow-lg);
	}

	.quality-upgrade-btn:hover {
		background: var(--color-primary-hover);
		transform: translateX(-50%) scale(1.05);
	}

	.quality-upgrade-btn:active {
		transform: translateX(-50%) scale(0.98);
	}

	.quality-upgrade-btn svg {
		flex-shrink: 0;
	}

	.viewer-header {
		display: flex;
		justify-content: space-between;
		align-items: center;
		padding: var(--space-md) var(--space-md);
		background: linear-gradient(to bottom, var(--color-overlay-dark), transparent);
		position: absolute;
		top: 0;
		left: 0;
		right: 0;
		z-index: 10;
	}

	.back-link {
		display: flex;
		align-items: center;
		gap: var(--space-sm);
		color: var(--color-text-inverse);
		text-decoration: none;
		font-size: var(--font-size-base);
		padding: var(--space-sm) var(--space-md);
		background: var(--color-overlay-light);
		border-radius: var(--radius-md);
		transition: background var(--transition-normal);
	}

	.back-link:hover {
		background: var(--color-overlay);
	}

	.back-icon {
		font-size: var(--font-size-xl);
		line-height: 1;
	}

	@media (max-width: 640px) {
		.back-text {
			display: none;
		}
	}

	.photo-index {
		color: var(--color-text-secondary);
		font-size: var(--font-size-sm);
		background: var(--color-overlay-light);
		padding: 0.35rem var(--space-md);
		border-radius: var(--radius-md);
	}

	.quality-indicator {
		color: var(--color-primary);
		font-size: var(--font-size-xs);
		background: var(--color-primary-light);
		padding: var(--space-xs) 0.625rem;
		border-radius: var(--radius-sm);
		font-weight: var(--font-weight-semibold);
	}

	.viewer-content {
		flex: 1;
		display: flex;
		align-items: center;
		justify-content: center;
		position: relative;
		overflow: hidden;
	}

	.image-wrapper {
		width: 100%;
		height: 100%;
		display: flex;
		align-items: center;
		justify-content: center;
		position: relative;
		overflow: hidden;
	}

	.image-wrapper img,
	.image-wrapper video {
		max-width: 100%;
		max-height: 100%;
		transition: opacity var(--transition-slow);
		user-select: none;
		-webkit-user-drag: none;
	}

	.zoomable-image {
		will-change: transform;
	}

	.fill-container {
		width: 100%;
		height: 100%;
		object-fit: contain;
	}

	.reset-zoom-btn {
		position: absolute;
		top: var(--space-md);
		right: var(--space-md);
		background: var(--color-overlay-dark);
		color: var(--color-text-inverse);
		border: none;
		padding: var(--space-sm);
		border-radius: var(--radius-md);
		cursor: pointer;
		transition: all var(--transition-normal);
		z-index: 20;
		display: flex;
		align-items: center;
		justify-content: center;
	}

	.reset-zoom-btn:hover {
		background: var(--color-primary);
		transform: scale(1.1);
	}

	.reset-zoom-btn:active {
		transform: scale(0.95);
	}

	.nav-button {
		position: absolute;
		top: 50%;
		transform: translateY(-50%);
		background: var(--color-overlay-light);
		color: var(--color-text-inverse);
		border: none;
		padding: var(--space-md);
		cursor: pointer;
		transition: all var(--transition-normal);
		border-radius: var(--radius-full);
		display: flex;
		align-items: center;
		justify-content: center;
		z-index: 5;
	}

	.nav-button svg {
		width: 24px;
		height: 24px;
	}

	.nav-button:hover:not(:disabled) {
		background: var(--color-overlay);
		transform: translateY(-50%) scale(1.1);
	}

	.nav-button:active:not(:disabled) {
		transform: translateY(-50%) scale(0.95);
	}

	.nav-button:disabled {
		opacity: 0.2;
		cursor: not-allowed;
	}

	.nav-button.prev {
		left: var(--space-md);
	}

	.nav-button.next {
		right: var(--space-md);
	}

	.zoom-controls {
		position: absolute;
		bottom: 60px;
		left: 50%;
		transform: translateX(-50%);
		background: var(--color-overlay-dark);
		padding: var(--space-sm) var(--space-md);
		border-radius: var(--radius-lg);
		display: flex;
		align-items: center;
		gap: var(--space-sm);
		z-index: 101;
		box-shadow: var(--shadow-lg);
	}

	.zoom-controls button {
		background: var(--color-overlay-light);
		color: var(--color-text-inverse);
		border: none;
		padding: var(--space-sm);
		border-radius: var(--radius-md);
		cursor: pointer;
		transition: all var(--transition-normal);
		display: flex;
		align-items: center;
		justify-content: center;
	}

	.zoom-controls button:hover:not(:disabled) {
		background: var(--color-primary);
		transform: scale(1.1);
	}

	.zoom-controls button:active:not(:disabled) {
		transform: scale(0.95);
	}

	.zoom-controls button:disabled {
		opacity: 0.3;
		cursor: not-allowed;
	}

	.zoom-level {
		min-width: 50px;
		text-align: center;
		font-size: var(--font-size-sm);
		font-weight: var(--font-weight-semibold);
		color: var(--color-text-inverse);
	}

	.shortcut-hints {
		position: absolute;
		bottom: var(--space-md);
		left: 50%;
		transform: translateX(-50%);
		background: var(--color-overlay-dark);
		padding: var(--space-sm) var(--space-md);
		border-radius: var(--radius-md);
		display: flex;
		gap: var(--space-md);
		font-size: var(--font-size-xs);
		color: var(--color-text-tertiary);
		z-index: 10;
		flex-wrap: wrap;
		justify-content: center;
	}

	@media (max-width: 768px) {
		.nav-button {
			padding: var(--space-sm);
		}

		.nav-button svg {
			width: 20px;
			height: 20px;
		}

		.nav-button.prev {
			left: var(--space-sm);
		}

		.nav-button.next {
			right: var(--space-sm);
		}

		.shortcut-hints {
			display: none;
		}

		.zoom-controls {
			bottom: 140px;
		}
	}

	.photo-info {
		padding: var(--space-md);
		background: linear-gradient(to top, var(--color-overlay-dark), transparent);
		position: absolute;
		bottom: 0;
		left: 0;
		right: 0;
		z-index: 10;
	}

	.photo-details {
		max-width: 800px;
		margin: 0 auto;
	}

	.photo-info h2 {
		font-size: var(--font-size-base);
		font-weight: var(--font-weight-semibold);
		margin: 0 0 var(--space-sm) 0;
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
		color: var(--color-text-inverse);
	}

	.photo-info .meta {
		font-size: var(--font-size-xs);
		color: var(--color-text-tertiary);
		display: flex;
		flex-wrap: wrap;
		gap: var(--space-md);
	}

	.photo-info .meta span {
		display: inline-flex;
		align-items: center;
	}
</style>
