<?php
/**
 * EduTrack Admin/Staff Service Worker
 * Served as application/javascript with dynamic path constants baked in.
 */
header('Content-Type: application/javascript; charset=utf-8');
header('Cache-Control: no-cache, no-store, must-revalidate'); // SW itself must never be cached

require_once __DIR__ . '/config.php';

$base = rtrim((string) parse_url(rtrim(BASE_URL, '/'), PHP_URL_PATH), '/'); // e.g. "" or "/edutrack"
$ver  = '1.0.3'; // bump this to force all clients to update
?>
/* EduTrack Admin Service Worker — v<?= $ver ?> */
'use strict';

const CACHE      = 'edutrack-admin-v<?= $ver ?>';
const BASE       = '<?= $base ?>';          // e.g. '/edutrack' or ''
const OFFLINE    = BASE + '/offline.html';

// ── Assets to cache immediately on install ────────────────────────────────
const PRECACHE = [
  OFFLINE,
  BASE + '/css/style.css',
  BASE + '/js/main.js',
];

// ── Install: pre-cache shell assets ──────────────────────────────────────
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE).then(cache =>
      cache.addAll(PRECACHE.map(url => new Request(url, { cache: 'reload' })))
    ).catch(err => console.warn('[EduTrack SW] Precache failed:', err))
  );
  self.skipWaiting();
});

// ── Activate: delete old cache versions ──────────────────────────────────
self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(keys =>
      Promise.all(
        keys.filter(k => k.startsWith('edutrack-admin-') && k !== CACHE)
            .map(k => caches.delete(k))
      )
    )
  );
  self.clients.claim();
});

// ── Fetch: routing logic ──────────────────────────────────────────────────
self.addEventListener('fetch', event => {
  const req = event.request;
  const url = new URL(req.url);

  // Only handle GET requests
  if (req.method !== 'GET') return;

  // ── CDN assets (Bootstrap, FontAwesome, Google Fonts) ── Stale-While-Revalidate
  if (
    url.hostname.includes('jsdelivr.net') ||
    url.hostname.includes('cloudflare.com') ||
    url.hostname.includes('fonts.googleapis.com') ||
    url.hostname.includes('fonts.gstatic.com') ||
    url.hostname.includes('code.jquery.com')
  ) {
    event.respondWith(staleWhileRevalidate(req));
    return;
  }

  // Only intercept same-origin requests from this point
  if (url.origin !== self.location.origin) return;
  if (BASE && !url.pathname.startsWith(BASE)) return;

  // ── Static assets — Cache First ───────────────────────────────────────
  if (/\.(css|js|png|jpg|jpeg|gif|svg|webp|ico|woff|woff2|ttf|eot)(\?.*)?$/i.test(url.pathname)) {
    event.respondWith(cacheFirst(req));
    return;
  }

  // ── Ajax endpoints — Network First, short cache, JSON offline response ─
  if (url.pathname.includes('/ajax/') || url.pathname.includes('/worker/')) {
    event.respondWith(networkFirstAjax(req));
    return;
  }

  // ── PHP pages — Network First, cache for offline fallback ─────────────
  event.respondWith(networkFirstPage(req));
});

// ── Strategy: Cache First ────────────────────────────────────────────────
async function cacheFirst(req) {
  const cached = await caches.match(req);
  if (cached) return cached;
  try {
    const res = await fetch(req);
    if (res.ok) {
      const cache = await caches.open(CACHE);
      cache.put(req, res.clone());
    }
    return res;
  } catch {
    return new Response('Asset unavailable offline.', { status: 503 });
  }
}

// ── Strategy: Network First → cache → offline.html ───────────────────────
async function networkFirstPage(req) {
  try {
    const res = await fetch(req);
    if (res.ok || res.type === 'opaqueredirect') {
      const cache = await caches.open(CACHE);
      cache.put(req, res.clone());
    }
    return res;
  } catch {
    const cached = await caches.match(req);
    if (cached) return cached;
    const offline = await caches.match(OFFLINE);
    return offline || new Response('<h1>Offline</h1>', {
      status: 503,
      headers: { 'Content-Type': 'text/html' },
    });
  }
}

// ── Strategy: Network First → cache → JSON offline error ─────────────────
async function networkFirstAjax(req) {
  try {
    const res = await fetch(req);
    if (res.ok) {
      // Only cache safe read-style ajax (skip explicit save/delete/update actions)
      const urlStr = req.url;
      const isMutation = /action=(save|delete|update|insert|remove|reset|upload|generate)/i.test(urlStr);
      if (!isMutation) {
        const cache = await caches.open(CACHE);
        // Keep ajax cache short: 10 minutes via a headers trick isn't possible in SW,
        // so we cache unconditionally but the NetworkFirst strategy ensures fresh data
        // is always preferred.
        cache.put(req, res.clone());
      }
    }
    return res;
  } catch {
    const cached = await caches.match(req);
    if (cached) return cached;
    return new Response(
      JSON.stringify({
        success: false,
        offline: true,
        message: 'You are offline. Please reconnect to continue.',
      }),
      { status: 503, headers: { 'Content-Type': 'application/json' } }
    );
  }
}

// ── Strategy: Stale While Revalidate ─────────────────────────────────────
async function staleWhileRevalidate(req) {
  const cache  = await caches.open(CACHE);
  const cached = await cache.match(req);

  const networkFetch = fetch(req)
    .then(res => {
      if (res.ok) cache.put(req, res.clone());
      return res;
    })
    .catch(() => null);

  return cached || (await networkFetch) || new Response('', { status: 503 });
}
