/// <reference types="@sveltejs/kit" />
import { build, files, version } from '$service-worker';

// Create a unique cache name for this deployment
const CACHE = `cache-${version}`;

const ASSETS = [
    ...build, // the app itself
    // Exclude /fonts/ — 667 font files totalling ~134MB. Only 3 are used by the
    // browser (app.css); the rest are badge-print fonts for server/Electron use.
    // cache.addAll() is atomic, so including them would silently abort the entire
    // SW install on most mobile devices (quota ~50-100MB). The browser's normal
    // HTTP cache handles font requests when the print page is actually visited.
    ...files.filter(f => !f.startsWith('/fonts/'))
];

self.addEventListener('install', (event) => {
    // Create a new cache and add all files to it
    async function addFilesToCache() {
        const cache = await caches.open(CACHE);
        await cache.addAll(ASSETS);
    }

    event.waitUntil(addFilesToCache());

    // Skip the waiting phase so the new service worker activates immediately
    // instead of waiting for all existing tabs to close first.
    // WHY: Without this, a user who leaves idaa.org open all day (common for
    // IDAA members) will run the old JS bundle for hours after a fix is deployed.
    // The new SW is installed in the background but sits idle until every tab
    // using the old version is closed — which may never happen in practice.
    self.skipWaiting();
});

self.addEventListener('activate', (event) => {
    async function activate() {
        // Remove previous cached data from other deployments.
        for (const key of await caches.keys()) {
            if (key !== CACHE) await caches.delete(key);
        }

        // Take control of all open tabs immediately after activation so they use
        // the new service worker (and new cached assets) without needing a reload.
        await self.clients.claim();

        // Tell every open window tab to reload so it runs the new JS bundle.
        // WHY: clients.claim() makes this SW the active controller but does not
        // swap the JS already executing in those tabs. The `controllerchange`
        // listener in +layout.svelte handles tabs already running new JS, but tabs
        // stuck on old JS (e.g. from a build months ago, before that listener was
        // added) never registered it. This postMessage reaches those tabs directly.
        // Tabs that ignore it are still on old code and need a manual Full Reset.
        const client_list = await self.clients.matchAll({ type: 'window' });
        for (const client of client_list) {
            client.postMessage({ type: 'SW_ACTIVATE_RELOAD' });
        }
    }

    event.waitUntil(activate());
});

self.addEventListener('fetch', (event) => {
    // Only handle same-origin GET requests for the app shell and static assets.
    // Chromium can surface private-network/CORS failures on cross-origin API calls,
    // so we intentionally leave those requests to the browser untouched here.
    if (event.request.method !== 'GET') return;
    if (!event.request.url.startsWith('http')) return;

    // Skip CDN/API/extension requests. This worker should only cache the app origin.
    const request_url = new URL(event.request.url);
    if (request_url.origin !== self.location.origin) return;

    async function respond() {
        const cache = await caches.open(CACHE);

        // App build assets and static files are safe to serve directly from cache.
        if (ASSETS.includes(request_url.pathname)) {
            const cachedResponse = await cache.match(request_url.pathname);
            if (cachedResponse) return cachedResponse;
        }

        // For same-origin runtime requests, prefer the network and fall back to cache if offline.
        try {
            const response = await fetch(event.request);

            if (response.status === 200) {
                try {
                    await cache.put(event.request, response.clone());
                } catch (err) {
                    console.warn('Service worker cache put skipped:', err);
                }
            }

            return response;
        } catch (err) {
            const cachedResponse = await cache.match(event.request);
            if (cachedResponse) return cachedResponse;
            throw err;
        }
    }

    event.respondWith(respond());
});
