<script lang="ts">
	import { Button } from '$lib/components/ui/button';
	import * as Card from '$lib/components/ui/card';
	import FileDropZone from '$lib/components/ui/file-drop-zone.svelte';
	import { authStore } from '$lib/stores/auth.svelte';
	import * as AlertDialog from '$lib/components/ui/alert-dialog';

	// =============== Image Upload ===============
	let uploading = $state(false);
	let uploadFiles = $state<File[]>([]);
	let uploadProgress = $state(0);
	let uploadResults = $state<{ name: string; url?: string; error?: string }[]>([]);

	function handleFilesDropped(files: File[]) {
		uploadFiles = files;
	}

	/** Helper: turn any File into a JPEG-encoded Blob. */
	function toJpegBlob(file: File): Promise<Blob> {
		return new Promise((resolve, reject) => {
			const img = new Image();
			img.onload = () => {
				const canvas = document.createElement('canvas');
				canvas.width = img.width;
				canvas.height = img.height;
				const ctx = canvas.getContext('2d');
				if (!ctx) return reject(new Error('2D context not available'));
				ctx.drawImage(img, 0, 0);
				canvas.toBlob(
					(blob) => {
						if (!blob) return reject(new Error('Canvas toBlob failed'));
						resolve(blob);
					},
					'image/jpeg',
					0.92
				);
			};
			img.onerror = () => reject(new Error('Image load failed'));
			img.src = URL.createObjectURL(file);
		});
	}

	/** Resize to max 1500px on the *short* side, only scale down, never up. */
	function resizeShortSide(blob: Blob): Promise<Blob> {
		return new Promise((resolve, reject) => {
			const img = new Image();
			img.onload = () => {
				let { width, height } = img;
				const maxShortSide = 1500;

				// Only resize if image is larger than target
				const shortSide = Math.min(width, height);
				if (shortSide > maxShortSide) {
					if (width < height) {
						const scale = maxShortSide / width;
						width = maxShortSide;
						height = Math.round(height * scale);
					} else {
						const scale = maxShortSide / height;
						height = maxShortSide;
						width = Math.round(width * scale);
					}
				}

				const canvas = document.createElement('canvas');
				canvas.width = width;
				canvas.height = height;
				const ctx = canvas.getContext('2d');
				if (!ctx) return reject(new Error('2D context not available'));
				ctx.drawImage(img, 0, 0, width, height);
				canvas.toBlob(
					(blob) => {
						if (!blob) return reject(new Error('Canvas toBlob failed'));
						resolve(blob);
					},
					'image/jpeg',
					0.92
				);
			};
			img.onerror = () => reject(new Error('Image load failed'));
			img.src = URL.createObjectURL(blob);
		});
	}

	/** Create a 250×250 thumbnail. First scale down so short side is 250px, then center-crop to 250×250 square. */
	function createThumbnail(blob: Blob): Promise<Blob> {
		return new Promise((resolve, reject) => {
			const img = new Image();
			img.onload = () => {
				const targetShortSide = 250;
				const thumbSize = 250;
				let { width, height } = img;

				const shortSide = Math.min(width, height);
				if (shortSide > targetShortSide) {
					const scale = targetShortSide / shortSide;
					width = Math.round(width * scale);
					height = Math.round(height * scale);
				}

				const canvas = document.createElement('canvas');
				canvas.width = thumbSize;
				canvas.height = thumbSize;
				const ctx = canvas.getContext('2d');
				if (!ctx) return reject(new Error('2D context not available'));

				ctx.imageSmoothingEnabled = true;
				ctx.imageSmoothingQuality = 'high';

				const destX = (thumbSize - width) / 2;
				const destY = (thumbSize - height) / 2;
				ctx.drawImage(img, 0, 0, img.width, img.height, destX, destY, width, height);

				canvas.toBlob(
					(blob) => {
						if (!blob) return reject(new Error('Canvas toBlob failed'));
						resolve(blob);
					},
					'image/jpeg',
					0.92
				);
			};
			img.onerror = () => reject(new Error('Image load failed'));
			img.src = URL.createObjectURL(blob);
		});
	}

	let availableTags = $state<string[]>([]);
	let loadingTags = $state(true);
	let isMobile = $state(false);

	async function fetchTags() {
		try {
			const response = await fetch('/api/portfolio/tags');
			if (response.ok) {
				const data = await response.json();
				availableTags = data.map((t: { name: string }) => t.name);
			}
		} catch (e) {
			console.error('Failed to fetch tags:', e);
		} finally {
			loadingTags = false;
		}
	}

	// Check for mobile on mount
	if (typeof window !== 'undefined') {
		isMobile = window.matchMedia('(pointer: coarse)').matches;
	}

	fetchTags();

	let tags = $state<string[]>([]);
	let input = $state('');
	let selectedSuggestionIndex = $state(-1);
	let inputRef = $state<HTMLInputElement | undefined>(undefined);
	let showConfirmUploadAlert = $state(false);

	const suggestions = $derived.by(() => {
		const q = input.trim().toLowerCase();
		if (!q) return [];

		return availableTags
			.map((t) => t.toLowerCase())
			.filter((t) => t.startsWith(q) && !tags.includes(t))
			.slice(0, 6);
	});

	function handleTagInput(e: Event) {
		const value = (e.target as HTMLInputElement).value;

		if (value.includes(',')) {
			addTag(value);
			input = '';
		}
		selectedSuggestionIndex = -1;
	}
	
	function handlePaste(e: ClipboardEvent) {
		const value = e.clipboardData?.getData('text') || '';
		if (value.includes(',')) {
			e.preventDefault();
			addTag(value);
			input = '';
		}
	}

	function isSemantic(tag: string) {
		return tag.includes(':');
	}

	function addTag(raw: string) {
		raw.split(',').forEach((p) => {
			let t = p.trim().toLowerCase();
			if (!t) return;
			t = t.replace(/^colour:/, 'color:');
			if (!tags.includes(t)) tags = [...tags, t];
		});
	}

	function handleKey(e: KeyboardEvent) {
		// Prevent tab from moving focus away from this input
		if (e.key === 'Tab') {
			e.preventDefault();
			// Allow arrow key navigation when tab is pressed
			if (e.shiftKey) {
				// Shift+Tab - go to previous suggestion (wrap to end)
				if (suggestions.length > 0) {
					if (selectedSuggestionIndex <= 0) {
						selectedSuggestionIndex = suggestions.length - 1;
					} else {
						selectedSuggestionIndex = selectedSuggestionIndex - 1;
					}
				}
			} else {
				// Tab - go to next suggestion (wrap to start)
				if (suggestions.length > 0) {
					if (selectedSuggestionIndex >= suggestions.length - 1) {
						selectedSuggestionIndex = 0;
					} else {
						selectedSuggestionIndex = selectedSuggestionIndex + 1;
					}
				}
			}
			return;
		}
		
		// Handle mobile keyboard action keys
		const isActionKey = ['Enter', 'Done', 'Go'].includes(e.key);
		
		if (isActionKey) {
			e.preventDefault();
		}

		if (e.key === 'ArrowDown') {
			e.preventDefault();
			if (suggestions.length > 0) {
				// Wrap to start if at end
				if (selectedSuggestionIndex >= suggestions.length - 1) {
					selectedSuggestionIndex = 0;
				} else {
					selectedSuggestionIndex = Math.min(selectedSuggestionIndex + 1, suggestions.length - 1);
				}
			}
			return;
		}

		if (e.key === 'ArrowUp') {
			e.preventDefault();
			if (suggestions.length > 0) {
				// Wrap to end if at start
				if (selectedSuggestionIndex <= 0) {
					selectedSuggestionIndex = suggestions.length - 1;
				} else {
					selectedSuggestionIndex = Math.max(selectedSuggestionIndex - 1, -1);
				}
			}
			return;
		}

		if (e.key === 'Enter' || e.key === 'Done' || e.key === 'Go') {
			// If there's a highlighted suggestion, select it
			if (selectedSuggestionIndex >= 0 && suggestions[selectedSuggestionIndex]) {
				selectSuggestion(suggestions[selectedSuggestionIndex]);
				return;
			}
			
			// If input has text, add it as tag
			if (input.trim()) {
				addTag(input);
				input = '';
				return;
			}
			
			// If input is empty but we have tags, show confirmation to upload
			if (tags.length > 0) {
				showConfirmUploadAlert = true;
				return;
			}
			
			return;
		}

		if (e.key === 'Backspace' && !input && tags.length) {
			tags = tags.slice(0, -1);
		}
	}

	function selectSuggestion(tag: string) {
		addTag(tag);
		input = '';
		selectedSuggestionIndex = -1;
	}

	function removeTag(tag: string) {
		tags = tags.filter((t) => t !== tag);
	}

	/** Core upload function – now processes the images before sending. */
	async function uploadOneOrMany() {
		// Add any pending input as tag before uploading
		if (input.trim()) {
			addTag(input);
			input = '';
		}
		
		if (!uploadFiles.length) return;
		uploading = true;
		uploadResults = [];
		uploadProgress = 0;

		const startTs = Date.now(); // timestamp of button click

		for (let i = 0; i < uploadFiles.length; i++) {
			const file = uploadFiles[i];
			const fd = new FormData();

			try {
				/* -------- 1. Turn whatever the user gave us into JPEG ------- */
				const jpegBlob = await toJpegBlob(file);

				/* -------- 2. Create the two processed versions ------------- */
				const resizedBlob = await resizeShortSide(jpegBlob);
				const thumbBlob = await createThumbnail(jpegBlob);

				/* -------- 3. Generate filenames -------------------------------- */
				const ts = startTs - i; // 1 ms decrement per file
				const baseName = `${ts}.jpg`;
				const thumbName = `${ts}_thumb.jpg`;

				/* -------- 4. Attach to FormData -------------------------------- */
				fd.append('file', resizedBlob, baseName);
				fd.append('thumbnail', thumbBlob, thumbName);

				if (tags.length > 0) {
					fd.append('tags', tags.join(','));
				}

				/* -------- 5. Call the API ------------------------------------- */
				const response = await fetch('/api/portfolio/images', {
					method: 'POST',
					headers: {
						Authorization: `Bearer ${authStore.currentToken}`
					},
					body: fd
				});

				if (!response.ok) {
					const errText = await response.text();
					uploadResults.push({
						name: file.name,
						error: errText || `Server error: ${response.status}`
					});
				} else {
					const result = await response.json();
					uploadResults.push({
						name: file.name,
						url: result.url
					});
				}
			} catch (err: unknown) {
				uploadResults.push({
					name: file.name,
					error: err instanceof Error ? err.message : 'Unknown error'
				});
			}

			uploadProgress = Math.round(((i + 1) / uploadFiles.length) * 100);
		}

		uploading = false;
		uploadFiles = [];
	}
</script>

<Card.Root>
	<Card.Header>
		<div class="flex items-center justify-between">
			<div>
				<Card.Title>Image Upload</Card.Title>
				<Card.Description>Upload images for the portfolio or other uses.</Card.Description>
			</div>
			<div class="hidden rounded-lg p-2 md:block">
				<svg
					xmlns="http://www.w3.org/2000/svg"
					class="h-6 w-6"
					viewBox="0 0 24 24"
					fill="none"
					stroke="currentColor"
					stroke-width="2"
				>
					<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>
			</div>
		</div>
	</Card.Header>
	<Card.Content class="space-y-4">
		<FileDropZone onfiles={handleFilesDropped} accept="image/*" multiple>
			<div class="p-6 text-center">
				<p class="text-sm text-gray-500">Drop files here, or click to open the file picker</p>
			</div>
		</FileDropZone>

		<div class="mt-4">
			<div class="text-sm text-gray-600">Selected files ({uploadFiles.length})</div>
			<div class="mt-2 max-h-40 space-y-2 overflow-y-auto">
				{#each uploadFiles as f (f.name)}
					<div class="flex items-center justify-between rounded bg-gray-50 p-2">
						<div>{f.name} • {Math.round(f.size / 1024)}KB</div>
						<button
							class="text-red-500"
							onclick={() => (uploadFiles = uploadFiles.filter((x) => x !== f))}
						>
							Remove
						</button>
					</div>
				{/each}
			</div>
			{#if uploadResults.length > 0}
				<div class="mt-4 text-sm text-gray-600">Upload Results</div>
				<div class="mt-2 max-h-40 space-y-2 overflow-y-auto">
					{#each uploadResults as result (result.url || result.name)}
						<div
							class="rounded p-2 text-xs {result.error
								? 'bg-red-100 text-red-800'
								: 'bg-emerald-100 text-emerald-800'}"
						>
							{result.name}: {result.error ? `Failed: ${result.error}` : `Success: `}<a
								href={result.url}
								target="_blank"
								rel="noopener noreferrer"
								class="underline hover:text-emerald-600">{result.url}</a
							>
						</div>
					{/each}
				</div>
			{/if}
		</div>

		<div class="space-y-1">
			<label class="text-sm font-medium text-gray-700">Tags</label>

			<div class="relative">
				<div
					class="flex min-h-[44px] w-full flex-wrap gap-1.5 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 focus-within:outline-none"
				>
					{#each tags as tag (tag)}
						<span
							class="flex items-center gap-1 rounded-full px-2 py-1 text-xs
							{isSemantic(tag) ? 'bg-indigo-100 text-indigo-800' : 'bg-emerald-100 text-emerald-800'}"
						>
							{tag}

							<button
								type="button"
								class="ml-0.5 flex h-4 w-4 items-center justify-center rounded-full leading-none
								{isSemantic(tag)
									? 'text-indigo-700 hover:bg-indigo-200'
									: 'text-emerald-700 hover:bg-emerald-200'}"
								onclick={() => removeTag(tag)}
								aria-label={`Remove ${tag}`}
							>
								×
							</button>
						</span>
					{/each}

					<input
						type="search"
						enterkeyhint="done"
						autocomplete="off"
						autocorrect="off"
						autocapitalize="off"
						spellcheck="false"
						class="min-w-[100px] flex-1 border-none bg-transparent text-sm outline-none placeholder:text-muted-foreground"
						bind:this={inputRef}
						bind:value={input}
						onkeydown={handleKey}
						oninput={handleTagInput}
						onpaste={handlePaste}
						placeholder={tags.length ? '' : 'Add tags…'}
					/>
				</div>

				{#if input.length && suggestions.length}
					<div class="absolute right-0 left-0 z-10 mt-1 rounded-md border bg-white shadow-md">
						{#each suggestions as s, i (s)}
							{@const isHighlighted = isMobile ? i === 0 : i === selectedSuggestionIndex}
							<div
								class="cursor-pointer px-3 py-3 text-sm touch-manipulation {isHighlighted
									? 'bg-primary/10 text-primary font-medium'
									: 'hover:bg-gray-100'}"
								onclick={() => selectSuggestion(s)}
							>
								{s}
							</div>
						{/each}
					</div>
				{/if}
			</div>

			<p class="text-xs text-gray-500">
				Add searchable tags here such as <code>`scooby doo`</code> or filterable categories like
				<code>`style:french`</code>
				or
				<code>`colour:green`</code>
			</p>
		</div>
		<div class="flex items-center justify-end gap-2">
			<Button onclick={uploadOneOrMany} disabled={!uploadFiles.length || uploading}>
				{uploading ? `Uploading (${uploadProgress}%)` : 'Upload Selected Files'}
			</Button>
		</div>
	</Card.Content>
</Card.Root>

<AlertDialog.Root bind:open={showConfirmUploadAlert}>
	<AlertDialog.Content class="z-[60]">
		<AlertDialog.Header>
			<AlertDialog.Title>Upload with {tags.length} tag{tags.length === 1 ? '' : 's'}?</AlertDialog.Title>
			<AlertDialog.Description>
				You have {tags.length} tag{tags.length === 1 ? '' : 's'} selected:
				<span class="font-medium">{tags.join(', ')}</span>. 
				Ready to upload {uploadFiles.length} file{uploadFiles.length === 1 ? '' : 's'}?
			</AlertDialog.Description>
		</AlertDialog.Header>
		<AlertDialog.Footer>
			<AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
			<AlertDialog.Action onclick={uploadOneOrMany}>Upload</AlertDialog.Action>
		</AlertDialog.Footer>
	</AlertDialog.Content>
</AlertDialog.Root>
