// @ts-nocheck
// The Lounge - https://github.com/thelounge/thelounge
/* global clients */
"use strict";

const cacheName = "531d949a9c";
const excludedPathsFromCache = /^(?:socket\.io|storage|uploads|cdn-cgi)\//;

self.addEventListener("install", function () {
	self.skipWaiting();
});

self.addEventListener("activate", function (event) {
	event.waitUntil(
		caches
			.keys()
			.then((names) =>
				Promise.all(
					names.filter((name) => name !== cacheName).map((name) => caches.delete(name))
				)
			)
	);

	event.waitUntil(self.clients.claim());
});

self.addEventListener("fetch", function (event) {
	if (event.request.method !== "GET") {
		return;
	}

	const url = event.request.url;
	const scope = self.registration.scope;

	// Skip cross-origin requests
	if (!url.startsWith(scope)) {
		return;
	}

	const path = url.substring(scope.length);

	// Skip ignored paths
	if (excludedPathsFromCache.test(path)) {
		return;
	}

	event.respondWith(networkOrCache(event));
});

async function putInCache(request, response) {
	const cache = await caches.open(cacheName);
	await cache.put(request, response);
}

async function cleanRedirect(response) {
	// Not all browsers support the Response.body stream, so fall back
	// to reading the entire body into memory as a blob.
	const bodyPromise = "body" in response ? Promise.resolve(response.body) : response.blob();

	const body = await bodyPromise;

	// new Response() is happy when passed either a stream or a Blob.
	return new Response(body, {
		headers: response.headers,
		status: response.status,
		statusText: response.statusText,
	});
}

async function networkOrCache(event) {
	try {
		let response = await fetch(event.request, {
			cache: "no-cache",
			redirect: "follow",
		});

		if (response.redirected) {
			response = await cleanRedirect(response.clone());
		}

		if (response.ok) {
			if (cacheName !== "dev") {
				event.waitUntil(putInCache(event.request, response));
			}

			return response.clone();
		}

		throw new Error(`Request failed with HTTP ${response.status}`);
	} catch (e) {
		// eslint-disable-next-line no-console
		console.error(e.message, event.request.url);

		if (event.clientId) {
			const client = await clients.get(event.clientId);

			if (client) {
				client.postMessage({
					type: "fetch-error",
					message: e.message,
				});
			}
		}

		const cache = await caches.open(cacheName);
		const matching = await cache.match(event.request);

		return matching || Promise.reject("request-not-in-cache");
	}
}

self.addEventListener("message", function (event) {
	showNotification(event, event.data);
});

self.addEventListener("push", function (event) {
	if (!event.data) {
		return;
	}

	showNotification(event, event.data.json());
});

function showNotification(event, payload) {
	if (payload.type !== "notification") {
		return;
	}

	// get current notification, close it, and draw new
	event.waitUntil(
		self.registration
			.getNotifications({
				tag: `chan-${payload.chanId}`,
			})
			.then((notifications) => {
				for (const notification of notifications) {
					notification.close();
				}

				return self.registration.showNotification(payload.title, {
					tag: `chan-${payload.chanId}`,
					badge: "img/icon-alerted-black-transparent-bg-72x72px.png",
					icon: "img/icon-alerted-grey-bg-192x192px.png",
					body: payload.body,
					timestamp: payload.timestamp,
				});
			})
	);
}

self.addEventListener("notificationclick", function (event) {
	event.notification.close();

	event.waitUntil(
		clients
			.matchAll({
				includeUncontrolled: true,
				type: "window",
			})
			.then((clientList) => {
				if (clientList.length === 0) {
					if (clients.openWindow) {
						return clients.openWindow(`.#/${event.notification.tag}`);
					}

					return;
				}

				const client = findSuitableClient(clientList);

				client.postMessage({
					type: "open",
					channel: event.notification.tag,
				});

				if ("focus" in client) {
					client.focus();
				}
			})
	);
});

function findSuitableClient(clientList) {
	for (let i = 0; i < clientList.length; i++) {
		const client = clientList[i];

		if (client.focused || client.visibilityState === "visible") {
			return client;
		}
	}

	return clientList[0];
}
