/// <reference types="@sveltejs/kit" />
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="webworker" />

import { build, files, version } from '$service-worker';

const sw = self as unknown as ServiceWorkerGlobalScope;

// Unique cache names
const CACHE_BUILD = `cache-build-${version}`;
const CACHE_STATIC = `cache-static-${version}`;
const CACHE_IMAGES = `cache-images-${version}`;
const CACHE_PAGES = `cache-pages-${version}`;

// Assets to precache
const buildAssets = new Set(build);
const staticAssets = new Set(files);

sw.addEventListener('install', (event) => {
	event.waitUntil(
		Promise.all([
			// Cache build assets (JS, CSS)
			caches.open(CACHE_BUILD).then((cache) => cache.addAll(build)),
			// Cache static assets (fonts, etc.) - filter out large files
			caches.open(CACHE_STATIC).then((cache) => {
				const smallStaticFiles = files.filter(
					(file) =>
						!file.endsWith('.json') ||
						file === '/manifest.json'
				);
				return cache.addAll(smallStaticFiles);
			})
		]).then(() => {
			// Skip waiting to activate immediately
			sw.skipWaiting();
		})
	);
});

sw.addEventListener('activate', (event) => {
	event.waitUntil(
		caches.keys().then((keys) => {
			return Promise.all(
				keys
					.filter((key) => {
						// Delete old caches
						return (
							key !== CACHE_BUILD &&
							key !== CACHE_STATIC &&
							key !== CACHE_IMAGES &&
							key !== CACHE_PAGES
						);
					})
					.map((key) => caches.delete(key))
			);
		}).then(() => {
			// Take control of all clients immediately
			sw.clients.claim();
		})
	);
});

sw.addEventListener('fetch', (event) => {
	const url = new URL(event.request.url);

	// Only handle same-origin requests
	if (url.origin !== location.origin) return;

	// Skip API requests - let them go to network (IndexedDB handles offline)
	if (url.pathname.startsWith('/api/')) return;

	// Handle SvelteKit __data.json requests for cacheable routes (recipes, glaube, root)
	// Cache successful responses, serve from cache when offline
	const isCacheableDataRoute = url.pathname.includes('__data.json') &&
		(url.pathname.match(/^\/(rezepte|recipes|glaube|faith|fitness)(\/|$)/) || url.pathname === '/__data.json');

	if (isCacheableDataRoute) {
		event.respondWith(
			(async () => {
				const cache = await caches.open(CACHE_PAGES);

				// Create a cache key without query parameters
				// SvelteKit adds ?x-sveltekit-invalidated=... which we need to ignore
				const cacheKey = url.pathname;

				try {
					// Try network first
					const response = await fetch(event.request);

					// Cache successful responses for offline use (using pathname as key)
					if (response.ok) {
						cache.put(cacheKey, response.clone());
					}

					return response;
				} catch {
					// Network failed - try to serve from cache (ignoring query params)
					const cached = await cache.match(cacheKey);
					if (cached) {
						return cached;
					}

					// No cached data available - return error response
					// The page will need to handle this gracefully
					return new Response(JSON.stringify({ error: 'offline' }), {
						status: 503,
						headers: { 'Content-Type': 'application/json' }
					});
				}
			})()
		);
		return;
	}

	// Handle recipe images (thumbnails and full images)
	if (
		url.pathname.startsWith('/static/rezepte/') &&
		(url.pathname.includes('/thumb/') || url.pathname.includes('/full/'))
	) {
		event.respondWith(
			(async () => {
				const cache = await caches.open(CACHE_IMAGES);

				// Try exact match first
				const cached = await cache.match(event.request);
				if (cached) return cached;

				// Try to fetch from network
				try {
					const response = await fetch(event.request);
					if (response.ok) {
						// Cache thumbnails for offline use
						if (url.pathname.includes('/thumb/')) {
							cache.put(event.request, response.clone());
						}
					}
					return response;
				} catch {
					// Network failed - try to serve thumbnail as fallback for full
					if (url.pathname.includes('/full/')) {
						// Extract filename and try to find cached thumbnail
						const filename = url.pathname.split('/').pop();
						if (filename) {
							const thumbUrl = `/static/rezepte/thumb/${filename}`;
							const thumbCached = await cache.match(thumbUrl);
							if (thumbCached) {
								return thumbCached;
							}
						}
					}

					// No fallback available
					return new Response('', { status: 404 });
				}
			})()
		);
		return;
	}

	// For build assets - cache first
	if (buildAssets.has(url.pathname)) {
		event.respondWith(
			caches.match(event.request).then((cached) => {
				return cached || fetch(event.request);
			})
		);
		return;
	}

	// For static assets - cache first
	if (staticAssets.has(url.pathname)) {
		event.respondWith(
			caches.match(event.request).then((cached) => {
				return cached || fetch(event.request);
			})
		);
		return;
	}

	// For navigation requests (HTML pages) - network first, cache response, fallback to cache
	if (event.request.mode === 'navigate') {
		event.respondWith(
			(async () => {
				const cache = await caches.open(CACHE_PAGES);

				// Use pathname only for cache key (ignore query params)
				const cacheKey = url.pathname;

				try {
					// Try network first
					const response = await fetch(event.request);

					// Cache successful HTML responses for cacheable pages (using pathname as key)
					const isCacheablePage = response.ok && (
						url.pathname.match(/^\/(rezepte|recipes|glaube|faith|fitness)(\/|$)/) ||
						url.pathname === '/'
					);
					if (isCacheablePage) {
						cache.put(cacheKey, response.clone());
					}

					return response;
				} catch {
					// Network failed - try to serve from cache (ignoring query params)
					const cached = await cache.match(cacheKey);
					if (cached) {
						return cached;
					}

					// For recipe routes, redirect to the offline shell with the target URL
					// The offline shell will then do client-side navigation to load from IndexedDB
					// Skip if this is already the offline-shell or an offline navigation to prevent loops
					const isRecipeRoute = url.pathname.match(/^\/(rezepte|recipes)(\/|$)/);
					const isOfflineShell = url.pathname.includes('/offline-shell');
					const isOfflineNavigation = url.searchParams.has('_offline');

					if (isRecipeRoute && !isOfflineShell && !isOfflineNavigation) {
						const isEnglish = url.pathname.startsWith('/recipes');
						const shellPath = isEnglish ? '/recipes/offline-shell' : '/rezepte/offline-shell';

						// Check if we have the offline shell cached
						const shellCached = await cache.match(shellPath);
						if (shellCached) {
							// Redirect to the offline shell with the original URL as a query param
							const redirectUrl = `${shellPath}?redirect=${encodeURIComponent(url.pathname + url.search)}`;
							return Response.redirect(redirectUrl, 302);
						}
					}

					// Last resort - return a basic offline response
					return new Response(
						'<!DOCTYPE html><html><head><meta charset="utf-8"><title>Offline</title></head><body><h1>Offline</h1><p>Please connect to the internet and try again.</p></body></html>',
						{ headers: { 'Content-Type': 'text/html' } }
					);
				}
			})()
		);
		return;
	}
});

// Handle messages from the app
sw.addEventListener('message', (event) => {
	if (event.data?.type === 'CACHE_PAGES') {
		const urls: string[] = event.data.urls;
		caches.open(CACHE_PAGES).then((cache) => {
			Promise.all(
				urls.map((url) =>
					fetch(url, { credentials: 'same-origin' })
						.then((response) => {
							if (response.ok) {
								return cache.put(url, response);
							}
						})
						.catch(() => {
							// Ignore failed page fetches
						})
				)
			);
		});
	}

	if (event.data?.type === 'CACHE_IMAGES') {
		const urls: string[] = event.data.urls;
		const requestId = event.data.requestId;

		caches.open(CACHE_IMAGES).then(async (cache) => {
			// Cache images in batches to avoid overwhelming the network
			const batchSize = 10;
			const total = urls.length;
			let completed = 0;

			// Report progress to all clients
			async function reportProgress(done: boolean = false) {
				const clients = await sw.clients.matchAll();
				for (const client of clients) {
					client.postMessage({
						type: 'CACHE_IMAGES_PROGRESS',
						requestId,
						completed,
						total,
						done
					});
				}
			}

			// Report initial state
			await reportProgress();

			for (let i = 0; i < urls.length; i += batchSize) {
				const batch = urls.slice(i, i + batchSize);

				await Promise.all(
					batch.map((url) =>
						fetch(url)
							.then((response) => {
								if (response.ok) {
									return cache.put(url, response);
								}
							})
							.catch(() => {
								// Ignore failed image fetches
							})
							.finally(() => {
								completed++;
							})
					)
				);

				// Report progress after each batch
				await reportProgress();

				// Small delay between batches
				if (i + batchSize < urls.length) {
					await new Promise((resolve) => setTimeout(resolve, 100));
				}
			}

			// Report completion
			await reportProgress(true);
		});
	}

	if (event.data?.type === 'CACHE_DATA') {
		const urls: string[] = event.data.urls;
		caches.open(CACHE_PAGES).then((cache) => {
			// Cache __data.json files in batches to avoid overwhelming the network
			const batchSize = 20;
			let index = 0;

			function cacheBatch() {
				const batch = urls.slice(index, index + batchSize);
				if (batch.length === 0) return;

				Promise.all(
					batch.map((url) =>
						fetch(url)
							.then((response) => {
								if (response.ok) {
									return cache.put(url, response);
								}
							})
							.catch(() => {
								// Ignore failed fetches
							})
					)
				).then(() => {
					index += batchSize;
					if (index < urls.length) {
						// Small delay between batches
						setTimeout(cacheBatch, 50);
					}
				});
			}

			cacheBatch();
		});
	}
});
