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

/**
 * Service worker minimal — consultation hors ligne des dernières données.
 *
 * Deux stratégies, et pas une de plus :
 *
 * - **Le coffre du build** (JS, CSS, polices, icônes) est mis en cache à
 *   l'installation et servi depuis le cache. Ces fichiers portent une empreinte
 *   dans leur nom : ils ne changent jamais sous le même nom.
 * - **Les pages et l'API** passent par le réseau d'abord, avec repli sur le
 *   cache. Une veille législative doit montrer l'état le plus récent qu'elle
 *   peut atteindre ; servir une page périmée alors que le réseau répond serait
 *   trompeur sur un sujet où une date de promulgation change tout.
 *
 * Le hors-ligne n'est donc pas un mode de consultation normal : c'est un filet
 * pour les connexions instables, fréquentes en zone rurale guadeloupéenne. La
 * page servie depuis le cache le signale à l'utilisateur via l'en-tête ajouté
 * `X-Depuis-Cache`.
 */
import { build, files, version } from '$service-worker';

const sw = self as unknown as ServiceWorkerGlobalScope;

const CACHE_COFFRE = `coffre-${version}`;
const CACHE_PAGES = `pages-${version}`;

// Les polices et le manifeste valent d'être précachés ; pas les gros binaires.
const A_PRECACHER = [...build, ...files.filter((f) => !f.endsWith('.map'))];

sw.addEventListener('install', (evenement) => {
	evenement.waitUntil(
		caches.open(CACHE_COFFRE).then((cache) => cache.addAll(A_PRECACHER))
	);
	// Le nouveau worker prend la main sans attendre la fermeture des onglets :
	// une correction de données ne doit pas rester bloquée derrière un onglet
	// ouvert depuis une semaine.
	sw.skipWaiting();
});

sw.addEventListener('activate', (evenement) => {
	evenement.waitUntil(
		(async () => {
			const noms = await caches.keys();
			await Promise.all(
				noms
					.filter((nom) => nom !== CACHE_COFFRE && nom !== CACHE_PAGES)
					.map((nom) => caches.delete(nom))
			);
			await sw.clients.claim();
		})()
	);
});

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

	const url = new URL(requete.url);
	// Rien qui ne vienne de cette origine : la politique de sécurité de contenu
	// interdit déjà les tiers, le worker ne les mettra pas en cache non plus.
	if (url.origin !== location.origin) return;

	// Coffre du build : cache d'abord, sans condition.
	if (A_PRECACHER.includes(url.pathname)) {
		evenement.respondWith(
			caches
				.open(CACHE_COFFRE)
				.then((cache) => cache.match(url.pathname))
				.then((reponse) => reponse ?? fetch(requete))
		);
		return;
	}

	// Pages et API : réseau d'abord, cache en repli.
	evenement.respondWith(reseauPuisCache(requete));
});

async function reseauPuisCache(requete: Request): Promise<Response> {
	const cache = await caches.open(CACHE_PAGES);

	try {
		const reponse = await fetch(requete);
		if (reponse.ok) {
			cache.put(requete, reponse.clone());
		}
		return reponse;
	} catch {
		const enCache = await cache.match(requete);
		if (enCache) {
			// On signale explicitement que la réponse peut être périmée.
			const entetes = new Headers(enCache.headers);
			entetes.set('X-Depuis-Cache', 'true');
			return new Response(enCache.body, {
				status: enCache.status,
				statusText: enCache.statusText,
				headers: entetes
			});
		}

		return new Response(
			`<!doctype html><html lang="fr"><head><meta charset="utf-8">
			<title>Hors ligne</title>
			<style>
				body{background:#0D0D0D;color:#FFF8E7;font-family:system-ui,sans-serif;
				display:grid;place-items:center;min-height:100vh;margin:0;text-align:center;padding:2rem}
				h1{font-size:1.4rem;text-transform:uppercase;letter-spacing:-0.01em}
				p{color:#B6B1A6;max-width:40ch}
				.bar{height:6px;width:56px;margin:1.5rem auto 0;background:linear-gradient(to right,
				#0D0D0D 0 25%,#FDB813 25% 50%,#00D66C 50% 75%,#FF1654 75% 100%)}
			</style></head><body><div>
			<h1>Pa ni rézo</h1>
			<p>Cette page n'a pas encore été consultée et le réseau est indisponible.
			Les pages déjà visitées restent accessibles.</p>
			<div class="bar"></div>
			</div></body></html>`,
			{ status: 503, headers: { 'content-type': 'text/html; charset=utf-8' } }
		);
	}
}
