'use strict';
// ═══════════════════════════════════════════════
// sw.js -- Service Worker for Forestorers.org PWA
// Network-first for all same-origin requests, cache fallback for offline
// ═══════════════════════════════════════════════

const CACHE_VERSION = 'frj-v15-20260729a';

// Explicit asset list -- pre-cached on install for offline support
const SHELL_ASSETS = [
  './',
  './index.html',
  './manifest.json',
  './css/style.css',
  './fonts/fonts.css',
  './fonts/dmmono-aFTR7PB1QTsUX8KYvrGyEY2tbZX9.woff2',
  './fonts/dmmono-aFTR7PB1QTsUX8KYvrGyEYOtbQ.woff2',
  './fonts/dmmono-aFTR7PB1QTsUX8KYvumzEY2tbZX9.woff2',
  './fonts/dmmono-aFTR7PB1QTsUX8KYvumzEYOtbQ.woff2',
  './fonts/dmmono-aFTU7PB1QTsUX8KYthSQBLyM.woff2',
  './fonts/dmmono-aFTU7PB1QTsUX8KYthqQBA.woff2',
  './fonts/dmsans-rP2Yp2ywxg089UriI5-g4vlH9VoD8Cmcqbu0-K4.woff2',
  './fonts/dmsans-rP2Yp2ywxg089UriI5-g4vlH9VoD8Cmcqbu6-K6h9Q.woff2',
  './js/storage.js',
  './js/badges.js',
  './js/photos.js',
  './js/identity.js',
  './js/p2p.js',
  './js/journal.js',
  './js/contacts.js',
  './js/messenger.js',
  './js/webcam.js',
  './js/dashboard.js',
  './js/compare.js',
  './js/export.js',
  './js/storagehealth.js',
  './js/sync.js',
  './js/encryption.js',
  './js/devices.js',
  './js/profile.js',
  './js/timeline.js',
  './js/buddynet.js',
  './js/notifications.js',
  './js/gallery.js',
  './js/data.js',
  './js/activity.js',
  './js/app.js',
  './js/events.js'
];

// Allow the page to force-activate a waiting SW (used by Settings "Check for updates" button)
self.addEventListener('message', evt => {
  if (evt.data?.type === 'SKIP_WAITING') self.skipWaiting();
});

// Install: pre-cache shell assets (bypass HTTP cache with cache:'reload')
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_VERSION)
      .then(cache => Promise.all(
        SHELL_ASSETS.map(url =>
          fetch(url, { cache: 'reload' })
            .then(resp => { if (resp.ok) cache.put(url, resp); })
            .catch(() => {})
        )
      ))
      .then(() => self.skipWaiting())
  );
});

// Activate: clear old caches, then notify all open tabs to reload
self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys()
      .then(keys => Promise.all(
        keys.filter(k => k !== CACHE_VERSION).map(k => caches.delete(k))
      ))
      .then(() => self.clients.claim())
      .then(() => self.clients.matchAll({ type: 'window' }))
      .then(clients => {
        clients.forEach(c => c.postMessage({ type: 'SW_UPDATED', version: CACHE_VERSION }));
      })
  );
});

// Fetch: network-first for all same-origin requests, cache fallback for offline
self.addEventListener('fetch', event => {
  const url = new URL(event.request.url);

  // Never intercept WebSocket, blob:, data:, or cross-origin requests
  if (url.protocol === 'ws:' || url.protocol === 'wss:') return;
  if (url.protocol === 'blob:' || url.protocol === 'data:') return;
  if (url.origin !== self.location.origin) return;
  // Never intercept the version stamp — the client uses it to detect stale builds,
  // so it MUST come straight from the server, not from any cache layer.
  if (url.pathname.endsWith('/version.txt')) return;

  // Build a request that bypasses HTTP cache (conditional revalidation)
  // so network-first truly means "ask the server", not "ask the HTTP cache"
  const req = new Request(event.request, { cache: 'no-cache' });

  event.respondWith(
    fetch(req)
      .then(response => {
        if (response.ok) {
          const clone = response.clone();
          // Cache using pathname only (strip query string) so offline fallback works
          const cacheKey = new Request(url.pathname);
          caches.open(CACHE_VERSION).then(cache => cache.put(cacheKey, clone));
        }
        return response;
      })
      .catch(() => {
        // Offline -- try cache by pathname (ignoring query strings)
        return caches.match(new Request(url.pathname));
      })
  );
});
