/// <reference types="@sveltejs/kit" />
/// <reference lib="webworker" />

import type { ApiEnvelope } from '@owlboard/api-schema-types';
import { build, files, version } from '$service-worker';

const sw = self as unknown as ServiceWorkerGlobalScope;
const CACHE_NAME = `owlboard-cache-${version}`;
const ASSETS = [
	...build,
	...files.filter((file) => !file.endsWith('manifest.json') && !file.startsWith('/icons/')),
	'/'
];

sw.addEventListener('install', (event) => {
	sw.skipWaiting();
	event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS)));
});

sw.addEventListener('activate', (event) => {
	event.waitUntil(
		Promise.all([
			caches.keys().then((keys) => {
				return Promise.all(
					keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))
				);
			}),
			sw.clients.claim()
		])
	);
});

sw.addEventListener('fetch', (event) => {
	const { request } = event;
	if (request.method !== 'GET') return;

	const url = new URL(request.url);

	// Uncached static assets
	if (url.pathname.endsWith('manifest.json') || url.pathname.includes('/icons/')) {
		event.respondWith(fetch(request));
		return;
	}

	// Static Assets (Cache-First)
	if (ASSETS.includes(url.pathname) || url.pathname.startsWith('/_app/')) {
		event.respondWith(caches.match(request).then((res) => res || fetch(request)));
		return;
	}

	// Cachable API Responses - stale-while-revalidate
	if (url.pathname === '/api/v3/locationFilter/data') {
		event.respondWith(
			caches.open('ob-dynamic-cache').then(async (cache) => {
				const cachedResponse = await cache.match(request);

				// Ensure fallback response exists
				const makeOfflineFallback = () => {
					const errorObject: ApiEnvelope.Envelope = {
						e: {
							code: 'NETWORK_DISCONNECTED',
							msg: 'Cannot connect to the OwlBoard server'
						},
						t: Math.floor(Date.now() / 1000)
					};
					return new Response(JSON.stringify(errorObject), {
						status: 503,
						headers: { 'Content-Type': 'application/json' }
					});
				};

				const networkFetch = fetch(request)
					.then((networkResponse) => {
						if (networkResponse.ok) {
							cache.put(request, networkResponse.clone());
						}
						return networkResponse;
					})
					.catch(() => {
						// If offline & cache empty, return fallback
						return makeOfflineFallback();
					});

				// If cachedResponse is undefined, networkFetch safely resolves to a Response
				return cachedResponse || networkFetch;
			})
		);
		return;
	}

	// Offline API Fallback (Network-First)
	if (url.pathname.startsWith('/api') || url.hostname.includes('api')) {
		event.respondWith(
			fetch(request).catch(() => {
				const errorObject: ApiEnvelope.Envelope = {
					e: {
						code: 'NETWORK_DISCONNECTED',
						msg: 'Cannot connect to the OwlBoard server'
					},
					t: Math.floor(Date.now() / 1000)
				};
				return new Response(JSON.stringify(errorObject), {
					status: 503,
					headers: { 'Content-Type': 'application/json' }
				});
			})
		);
		return;
	}

	// Nav fallback - return fallback page, if offline and page requested
	if (request.mode === 'navigate' || request.headers.get('accept')?.includes('text/html')) {
		event.respondWith(
			fetch(request).catch(() => {
				// Serve svelte fallback page
				return caches.match('/') as Promise<Response>;
			})
		);
		return;
	}
});
