/* PPPHP console service worker — tiny, auth-safe app-shell cache.
 *
 *   Pages (navigations): NETWORK-FIRST and NEVER cached — the console is
 *     authenticated, so caching HTML could serve a stale/foreign session. When
 *     the network is unreachable we show a small precached offline page.
 *   Static assets (css/js/fonts/images): stale-while-revalidate — instant repeat
 *     loads, refreshed in the background. These carry no user data.
 *   Everything else (JSON/API, the SSE stream, POSTs): passed straight through,
 *     never cached, so data stays live and mutations are untouched.
 *
 * Bump CACHE to invalidate on release; `activate` purges older versions.
 */
var CACHE = 'ppphp-console-v1';
var OFFLINE_URL = new URL('offline.html', self.location).href;
var OFFLINE_HTML =
    '<!doctype html><meta charset="utf-8">' +
    '<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">' +
    '<title>Offline</title>' +
    '<style>html{color-scheme:light dark}body{font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;' +
    'display:flex;min-height:100vh;align-items:center;justify-content:center;margin:0;padding:2rem;text-align:center;' +
    'background:#0b1020;color:#e7e9f0}h1{font-size:1.25rem;margin:.2rem 0}p{opacity:.7;margin:0}' +
    'button{margin-top:1.3rem;padding:.6rem 1.4rem;border:0;border-radius:10px;background:#4f7cf7;color:#fff;font:600 14px system-ui;cursor:pointer}</style>' +
    '<body><div><div style="font-size:2.4rem">⚡</div><h1>You’re offline</h1>' +
    '<p>Reconnect to load this page.</p><button onclick="location.reload()">Retry</button></div></body>';

self.addEventListener('install', function (e) {
    // Precache only the (tiny) offline page — everything else caches on demand.
    e.waitUntil(
        caches.open(CACHE)
            .then(function (c) { return c.add(new Request(OFFLINE_URL, { cache: 'reload' })); })
            .catch(function () {})
            .then(function () { return self.skipWaiting(); })
    );
});

self.addEventListener('activate', function (e) {
    e.waitUntil(
        caches.keys().then(function (ks) {
            return Promise.all(ks.filter(function (k) { return k !== CACHE; }).map(function (k) { return caches.delete(k); }));
        }).then(function () { return self.clients.claim(); })
    );
});

self.addEventListener('fetch', function (e) {
    var req = e.request;
    if (req.method !== 'GET') return;                       // never touch POST/PUT/DELETE
    var url;
    try { url = new URL(req.url); } catch (x) { return; }
    if (url.origin !== self.location.origin) return;        // same-origin only
    if (url.pathname.indexOf('/notifications/stream') !== -1) return; // leave the SSE stream alone

    // Navigations: network-first, no HTML caching, offline fallback.
    if (req.mode === 'navigate') {
        e.respondWith(
            fetch(req).catch(function () {
                return caches.match(OFFLINE_URL).then(function (hit) {
                    return hit || new Response(OFFLINE_HTML, { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
                });
            })
        );
        return;
    }

    // Static assets: stale-while-revalidate.
    if (/\.(?:css|js|mjs|png|jpe?g|svg|gif|webp|avif|woff2?|ttf|otf|eot|ico|webmanifest)$/.test(url.pathname)) {
        e.respondWith(
            caches.open(CACHE).then(function (c) {
                return c.match(req).then(function (hit) {
                    var net = fetch(req).then(function (res) {
                        if (res && res.ok && res.type === 'basic') { c.put(req, res.clone()); }
                        return res;
                    }).catch(function () { return hit; });
                    return hit || net;
                });
            })
        );
    }
    // Anything else (JSON/API) falls through to the network, uncached.
});
