<script lang="ts">
	import { onMount } from 'svelte';
	import { Button } from '$lib/components/ui/button';
	import { Input } from '$lib/components/ui/input';
	import * as Select from '$lib/components/ui/select';
	import { Search, Download, Star, RefreshCw, Settings2, List, Play, Copy, Clipboard, Check, Server, Icon, ChevronRight, ChevronDown, Loader2, Tag, Calendar, HardDrive, Trash2 } from 'lucide-svelte';
	import ConfirmPopover from '$lib/components/ConfirmPopover.svelte';
	import { toast } from 'svelte-sonner';
	import { whale } from '@lucide/lab';
	import * as Dialog from '$lib/components/ui/dialog';
	import { Label } from '$lib/components/ui/label';
	import { Badge } from '$lib/components/ui/badge';
	import CreateContainerModal from '../containers/CreateContainerModal.svelte';
	import ImagePullModal from './ImagePullModal.svelte';
	import CopyToRegistryModal from './CopyToRegistryModal.svelte';
	import { canAccess } from '$lib/stores/auth';
	import { currentEnvironment, appendEnvParam } from '$lib/stores/environment';
	import PageHeader from '$lib/components/PageHeader.svelte';

	interface Registry {
		id: number;
		name: string;
		url: string;
		username?: string;
		hasCredentials: boolean;
		is_default: boolean;
	}

	interface SearchResult {
		name: string;
		description: string;
		star_count: number;
		is_official: boolean;
		is_automated: boolean;
	}

	interface TagInfo {
		name: string;
		size?: number;
		lastUpdated?: string;
		digest?: string;
	}

	interface ExpandedImageState {
		loading: boolean;
		error: string;
		tags: TagInfo[];
	}

	let registries = $state<Registry[]>([]);
	let expandedImages = $state<Record<string, ExpandedImageState>>({});
	let selectedRegistryId = $state<number | null>(null);

	let searchTerm = $state('');
	let results = $state<SearchResult[]>([]);
	let loading = $state(false);
	let browsing = $state(false);
	let searched = $state(false);
	let browseMode = $state(false);
	let errorMessage = $state('');

	// Copy to registry modal state
	let showCopyModal = $state(false);
	let copyImageName = $state('');
	let copyImageTag = $state('latest');

	// Run modal state
	let showRunModal = $state(false);
	let runImageName = $state('');

	// Pull modal state
	let showPullModal = $state(false);
	let pullImageName = $state('');

	// Scanner settings - scanning enabled if scanner is configured
	let envHasScanning = $state(false);

	// Delete confirmation state
	let confirmDeleteKey = $state<string | null>(null);
	let deleting = $state(false);


	let scrollContainer: HTMLDivElement | undefined;

	let selectedRegistry = $derived(registries.find(r => r.id === selectedRegistryId));

	// Check if a registry is Docker Hub
	function isDockerHub(registry: Registry): boolean {
		const url = registry.url.toLowerCase();
		return url.includes('docker.io') ||
			   url.includes('hub.docker.com') ||
			   url.includes('registry.hub.docker.com');
	}

	// Check if registry supports browsing (not Docker Hub)
	let supportsBrowsing = $derived(() => {
		if (!selectedRegistry) return false;
		return !isDockerHub(selectedRegistry);
	});

	// Get registries that can be pushed to (exclude Docker Hub and source registry)
	let pushableRegistries = $derived(registries.filter(r => {
		return !isDockerHub(r) && r.id !== selectedRegistryId;
	}));

	async function fetchRegistries() {
		try {
			const response = await fetch('/api/registries');
			registries = await response.json();
			if (!selectedRegistryId && registries.length > 0) {
				const defaultRegistry = registries.find(r => r.is_default);
				selectedRegistryId = defaultRegistry?.id ?? registries[0].id;
			}
		} catch (error) {
			console.error('Failed to fetch registries:', error);
		}
	}

	async function fetchScannerSettings(envId?: number | null) {
		try {
			const url = envId ? `/api/settings/scanner?env=${envId}&settingsOnly=true` : '/api/settings/scanner?settingsOnly=true';
			const response = await fetch(url);
			if (response.ok) {
				const data = await response.json();
				const scanner = data.settings?.scanner ?? 'none';
				// Scanning is enabled if a scanner is configured
				envHasScanning = scanner !== 'none';
			}
		} catch (error) {
			console.error('Failed to fetch scanner settings:', error);
		}
	}

	// Re-fetch scanner settings when environment changes
	$effect(() => {
		const envId = $currentEnvironment?.id;
		fetchScannerSettings(envId);
	});

	async function search() {
		if (!searchTerm.trim()) return;

		loading = true;
		searched = true;
		browseMode = false;
		errorMessage = '';
		try {
			let url = `/api/registry/search?term=${encodeURIComponent(searchTerm)}`;
			if (selectedRegistryId) {
				url += `&registry=${selectedRegistryId}`;
			}
			if ($currentEnvironment?.id) {
				url += `&env=${$currentEnvironment.id}`;
			}
			const response = await fetch(url);
			if (response.ok) {
				results = await response.json();
			} else {
				const data = await response.json();
				errorMessage = data.error || 'Search failed';
				results = [];
			}
		} catch (error) {
			console.error('Failed to search images:', error);
			errorMessage = 'Failed to search images';
			results = [];
		} finally {
			loading = false;
		}
	}

	async function browse() {
		if (!selectedRegistryId) return;

		browsing = true;
		searched = true;
		browseMode = true;
		errorMessage = '';
		try {
			const response = await fetch(`/api/registry/catalog?registry=${selectedRegistryId}`);
			if (response.ok) {
				results = await response.json();
			} else {
				const data = await response.json();
				errorMessage = data.error || 'Failed to browse registry';
				results = [];
			}
		} catch (error) {
			console.error('Failed to browse registry:', error);
			errorMessage = 'Failed to browse registry';
			results = [];
		} finally {
			browsing = false;
		}
	}

	function handleKeydown(e: KeyboardEvent) {
		if (e.key === 'Enter') {
			search();
		}
	}


	function buildFullImageName(name: string): string {
		// Build full image name with registry prefix if applicable
		if (selectedRegistry && supportsBrowsing()) {
			// Extract host from URL (e.g., "https://registry.example.com" -> "registry.example.com")
			const urlObj = new URL(selectedRegistry.url);
			return `${urlObj.host}/${name}`;
		}
		return name;
	}

	function handleRegistryChange() {
		// Clear results when registry changes
		results = [];
		searched = false;
		browseMode = false;
		errorMessage = '';
		expandedImages = {};
	}

	async function toggleImageExpansion(imageName: string) {
		if (expandedImages[imageName]) {
			// Collapse
			const { [imageName]: _, ...rest } = expandedImages;
			expandedImages = rest;
		} else {
			// Expand and fetch tags
			expandedImages = {
				...expandedImages,
				[imageName]: { loading: true, error: '', tags: [] }
			};

			try {
				let url = `/api/registry/tags?image=${encodeURIComponent(imageName)}`;
				if (selectedRegistryId) {
					url += `&registry=${selectedRegistryId}`;
				}

				const response = await fetch(url);
				if (response.ok) {
					const tags = await response.json();
					expandedImages = {
						...expandedImages,
						[imageName]: { loading: false, error: '', tags }
					};
				} else {
					const data = await response.json();
					expandedImages = {
						...expandedImages,
						[imageName]: { loading: false, error: data.error || 'Failed to fetch tags', tags: [] }
					};
				}
			} catch (error: any) {
				expandedImages = {
					...expandedImages,
					[imageName]: { loading: false, error: error.message || 'Failed to fetch tags', tags: [] }
				};
			}
		}
	}

	function formatBytes(bytes?: number): string {
		if (!bytes) return '-';
		if (bytes < 1024) return `${bytes} B`;
		if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
		if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
		return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
	}

	function formatDate(dateStr?: string): string {
		if (!dateStr) return '-';
		const date = new Date(dateStr);
		const now = new Date();
		const diffMs = now.getTime() - date.getTime();
		const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));

		if (diffDays === 0) return 'today';
		if (diffDays === 1) return 'yesterday';
		if (diffDays < 7) return `${diffDays} days ago`;
		if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago`;
		if (diffDays < 365) return `${Math.floor(diffDays / 30)} months ago`;
		return `${Math.floor(diffDays / 365)} years ago`;
	}

	function openCopyModal(imageName: string, tag?: string) {
		// Build full image name with registry prefix (no tag - modal handles that)
		copyImageName = buildFullImageName(imageName);
		copyImageTag = tag || 'latest';
		showCopyModal = true;
	}

	function openRunModal(imageName: string, tag?: string) {
		// Build full image name with registry prefix if applicable
		const imageWithTag = tag ? `${imageName}:${tag}` : imageName;
		if (selectedRegistry && supportsBrowsing()) {
			const urlObj = new URL(selectedRegistry.url);
			runImageName = `${urlObj.host}/${imageWithTag}`;
		} else {
			runImageName = imageWithTag;
		}
		showRunModal = true;
	}

	function openPullModal(imageName: string, tag?: string) {
		// Build full image name with registry prefix if applicable
		const imageWithTag = tag ? `${imageName}:${tag}` : imageName;
		pullImageName = buildFullImageName(imageWithTag);
		showPullModal = true;
	}

	async function deleteTag(imageName: string, tag: string) {
		if (!selectedRegistryId) return;

		deleting = true;
		try {
			const response = await fetch(`/api/registry/image?registry=${selectedRegistryId}&image=${encodeURIComponent(imageName)}&tag=${encodeURIComponent(tag)}`, {
				method: 'DELETE'
			});

			if (response.ok) {
				toast.success(`Deleted ${imageName}:${tag}`);
				// Refresh tags for this image
				const state = expandedImages[imageName];
				if (state) {
					expandedImages = {
						...expandedImages,
						[imageName]: {
							...state,
							tags: state.tags.filter(t => t.name !== tag)
						}
					};
				}
			} else {
				const data = await response.json();
				toast.error(data.error || 'Failed to delete image');
			}
		} catch (error: any) {
			toast.error(error.message || 'Failed to delete image');
		} finally {
			deleting = false;
			confirmDeleteKey = null;
		}
	}

	function getTypeClasses(type: string): string {
		const base = 'text-xs px-1.5 py-0.5 rounded-sm font-medium inline-block w-14 text-center';
		switch (type) {
			case 'official':
				return `${base} bg-emerald-200 dark:bg-emerald-800 text-emerald-900 dark:text-emerald-100`;
			case 'automated':
				return `${base} bg-sky-200 dark:bg-sky-800 text-sky-900 dark:text-sky-100`;
			default:
				return `${base} bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300`;
		}
	}

	onMount(() => {
		// Only fetch registries if user has permission
		if ($canAccess('registries', 'view')) {
			fetchRegistries();
		}
	});
</script>

<div class="h-full flex flex-col gap-3 overflow-hidden">
	<div class="shrink-0 flex flex-wrap justify-between items-center gap-3">
		<PageHeader icon={Download} title="Registry" showConnection={false} />
		{#if $canAccess('registries', 'edit')}
		<a href="/settings?tab=registries" class="inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground h-8 rounded-md px-3 text-xs">
			<Settings2 class="w-4 h-4" />
			Manage registries
		</a>
		{/if}
	</div>

	<!-- Registry Selector + Search Bar -->
	<div class="shrink-0 flex gap-2">
		<Select.Root type="single" value={selectedRegistryId ? String(selectedRegistryId) : undefined} onValueChange={(v) => { selectedRegistryId = Number(v); handleRegistryChange(); }}>
			<Select.Trigger class="h-9 w-48">
				{@const selected = registries.find(r => r.id === selectedRegistryId)}
				{#if selected && isDockerHub(selected)}
					<Icon iconNode={whale} class="w-4 h-4 mr-2 text-muted-foreground" />
				{:else}
					<Server class="w-4 h-4 mr-2 text-muted-foreground" />
				{/if}
				<span>{selected ? `${selected.name}${selected.hasCredentials ? ' (auth)' : ''}` : 'Select registry'}</span>
			</Select.Trigger>
			<Select.Content>
				{#each registries as registry}
					<Select.Item value={String(registry.id)} label={registry.name}>
						{#if isDockerHub(registry)}
							<Icon iconNode={whale} class="w-4 h-4 mr-2 text-muted-foreground" />
						{:else}
							<Server class="w-4 h-4 mr-2 text-muted-foreground" />
						{/if}
						{registry.name}
						{#if registry.hasCredentials}
							<Badge variant="outline" class="ml-2 text-xs">auth</Badge>
						{/if}
					</Select.Item>
				{/each}
			</Select.Content>
		</Select.Root>
		<div class="relative flex-1">
			<Search class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
			<Input
				type="text"
				placeholder={selectedRegistry ? `Search ${selectedRegistry.name} for images...` : 'Search for images...'}
				bind:value={searchTerm}
				onkeydown={handleKeydown}
				class="pl-10"
			/>
		</div>
		<Button onclick={search} disabled={loading || browsing || !searchTerm.trim()}>
			{#if loading}
				<RefreshCw class="w-4 h-4 mr-1 animate-spin" />
			{:else}
				<Search class="w-4 h-4 mr-1" />
			{/if}
			Search
		</Button>
		{#if supportsBrowsing()}
			<Button variant="outline" onclick={browse} disabled={loading || browsing}>
				{#if browsing}
					<RefreshCw class="w-4 h-4 mr-1 animate-spin" />
				{:else}
					<List class="w-4 h-4 mr-1" />
				{/if}
				Browse
			</Button>
		{/if}
	</div>

	<!-- Results -->
	{#if loading || browsing}
		<p class="text-muted-foreground text-sm">{browsing ? 'Loading catalog...' : 'Searching...'}</p>
	{:else if errorMessage}
		<p class="text-red-600 dark:text-red-400 text-sm">{errorMessage}</p>
	{:else if searched && results.length === 0}
		<p class="text-muted-foreground text-sm">
			{browseMode ? 'No images found in this registry' : `No images found for "${searchTerm}"`}
		</p>
	{:else if results.length > 0}
		<div
			bind:this={scrollContainer}
			class="flex-1 min-h-0 rounded-lg overflow-auto"
		>
			<table class="w-full text-sm">
				<thead class="bg-muted sticky top-0 z-10">
					<tr class="border-b">
						<th class="text-left py-1.5 px-2 font-medium">Name</th>
						{#if !browseMode}
							<th class="text-left py-1.5 px-2 font-medium">Description</th>
							<th class="text-center py-1.5 px-2 font-medium w-16">Stars</th>
							<th class="text-center py-1.5 px-2 font-medium w-20">Type</th>
						{/if}
					</tr>
				</thead>
				<tbody>
					{#each results as result (result.name)}
						{@const isExpanded = !!expandedImages[result.name]}
						{@const expandState = expandedImages[result.name]}
						<!-- Main row -->
						<tr
							class="border-b border-muted hover:bg-muted/30 transition-colors cursor-pointer"
							onclick={() => toggleImageExpansion(result.name)}
						>
							<td class="py-1.5 px-2">
								<div class="flex items-center gap-1.5">
									{#if isExpanded}
										<ChevronDown class="w-3.5 h-3.5 text-muted-foreground shrink-0" />
									{:else}
										<ChevronRight class="w-3.5 h-3.5 text-muted-foreground shrink-0" />
									{/if}
									<code class="text-xs">{result.name}</code>
								</div>
							</td>
							{#if !browseMode}
								<td class="py-1.5 px-2">
									<span class="text-xs text-muted-foreground line-clamp-1" title={result.description}>
										{result.description || '-'}
									</span>
								</td>
								<td class="py-1.5 px-2 text-center">
									<div class="flex items-center justify-center gap-1">
										<Star class="w-3 h-3 text-yellow-500" />
										<span class="text-xs">{result.star_count.toLocaleString()}</span>
									</div>
								</td>
								<td class="py-1.5 px-2 text-center">
									{#if result.is_official}
										<span class={getTypeClasses('official')}>Official</span>
									{:else if result.is_automated}
										<span class={getTypeClasses('automated')}>Auto</span>
									{:else}
										<span class="text-muted-foreground text-xs">-</span>
									{/if}
								</td>
							{/if}
						</tr>
						<!-- Expanded tags row -->
						{#if isExpanded}
							<tr class="border-b border-muted bg-muted/20">
								<td colspan={browseMode ? 1 : 4} class="py-2 px-2 pl-8">
									{#if expandState?.loading}
										<div class="flex items-center gap-2 text-xs text-muted-foreground py-2">
											<Loader2 class="w-3.5 h-3.5 animate-spin" />
											<span>Loading tags...</span>
										</div>
									{:else if expandState?.error}
										<div class="text-xs text-red-500 py-2">
											{expandState.error}
										</div>
									{:else if expandState?.tags && expandState.tags.length > 0}
										<div class="max-h-64 overflow-y-auto">
											<table class="text-xs">
												<thead class="text-muted-foreground sticky top-0 bg-muted/50">
													<tr>
														<th class="text-left py-1 px-2 pr-4 font-medium">Tag</th>
														<th class="text-left py-1 px-2 pr-4 font-medium">Size</th>
														<th class="text-left py-1 px-2 pr-4 font-medium">Modified</th>
														<th class="text-left py-1 px-2 font-medium">Actions</th>
													</tr>
												</thead>
												<tbody>
													{#each expandState.tags as tag}
														<tr class="hover:bg-muted/30 transition-colors">
															<td class="py-1 px-2 pr-4">
																<div class="flex items-center gap-1.5">
																	<Tag class="w-3 h-3 text-muted-foreground shrink-0" />
																	<code class="font-medium">{tag.name}</code>
																</div>
															</td>
															<td class="py-1 px-2 pr-4 text-muted-foreground whitespace-nowrap">
																{formatBytes(tag.size)}
															</td>
															<td class="py-1 px-2 pr-4 text-muted-foreground whitespace-nowrap">
																{formatDate(tag.lastUpdated)}
															</td>
															<td class="py-1 px-2">
																<div class="flex items-center gap-1">
																	<button
																		onclick={() => openPullModal(result.name, tag.name)}
																		title={envHasScanning ? "Pull and scan this tag" : "Pull this tag"}
																		class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded hover:bg-muted transition-colors whitespace-nowrap"
																	>
																		<Download class="w-3 h-3 text-muted-foreground" />
																		<span class="text-muted-foreground">{envHasScanning ? 'Pull & scan' : 'Pull'}</span>
																	</button>
																	<button
																		onclick={() => openRunModal(result.name, tag.name)}
																		title="Run container with this tag"
																		class="p-1 rounded hover:bg-muted transition-colors"
																	>
																		<Play class="w-3 h-3 text-muted-foreground hover:text-foreground" />
																	</button>
																	{#if pushableRegistries.length > 0}
																		<button
																			onclick={() => openCopyModal(result.name, tag.name)}
																			title="Copy to another registry"
																			class="p-1 rounded hover:bg-muted transition-colors"
																		>
																			<Copy class="w-3 h-3 text-muted-foreground hover:text-foreground" />
																		</button>
																	{/if}
																	{#if supportsBrowsing()}
																		{@const deleteKey = `${result.name}:${tag.name}`}
																		<ConfirmPopover
																			title="Delete tag"
																			description="Are you sure you want to delete {result.name}:{tag.name}? This cannot be undone."
																			confirmText="Delete"
																			open={confirmDeleteKey === deleteKey}
																			onConfirm={() => deleteTag(result.name, tag.name)}
																			onOpenChange={(open) => confirmDeleteKey = open ? deleteKey : null}
																		>
																			<button
																				title="Delete this tag"
																				class="p-1 rounded hover:bg-red-100 dark:hover:bg-red-900/30 transition-colors"
																				disabled={deleting}
																			>
																				<Trash2 class="w-3 h-3 text-muted-foreground hover:text-red-600 dark:hover:text-red-400" />
																			</button>
																		</ConfirmPopover>
																	{/if}
																</div>
															</td>
														</tr>
													{/each}
												</tbody>
											</table>
										</div>
									{:else}
										<div class="text-xs text-muted-foreground py-2">
											No tags found
										</div>
									{/if}
								</td>
							</tr>
						{/if}
					{/each}
				</tbody>
			</table>
		</div>
	{:else}
		<div class="text-center py-12 text-muted-foreground">
			<Download class="w-12 h-12 mx-auto mb-4 opacity-50" />
			<p class="text-sm">
				{#if supportsBrowsing()}
					Search or browse {selectedRegistry?.name || 'a registry'} to find images
				{:else}
					Search {selectedRegistry?.name || 'a registry'} to find and pull images
				{/if}
			</p>
		</div>
	{/if}
</div>

<!-- Copy to Registry Modal -->
<CopyToRegistryModal
	bind:open={showCopyModal}
	imageName={copyImageName}
	initialTag={copyImageTag}
	registries={registries}
	sourceRegistryId={selectedRegistryId}
/>

<!-- Create Container Modal -->
<CreateContainerModal bind:open={showRunModal} prefilledImage={runImageName} autoPull={true} />

<!-- Pull/Scan Modal -->
<ImagePullModal bind:open={showPullModal} imageName={pullImageName} envHasScanning={envHasScanning} />
