// Swarnil service worker — offline support + runtime caching + push
// Bump VERSION on breaking asset changes; assets are stale-while-revalidate
// so normal deploys self-heal on the next visit anyway.
const VERSION = 'swarnil-v3';
const STATIC_CACHE = VERSION + ':static';
const PAGE_CACHE = VERSION + ':pages';
const IMAGE_CACHE = VERSION + ':images';
const OFFLINE_URL = '/offline/';
const PAGE_LIMIT = 50;   // most-recent pages kept for offline reading
const IMAGE_LIMIT = 80;  // most-recent images

self.addEventListener('install', (event) => {
	event.waitUntil(
		caches.open(PAGE_CACHE)
			.then((cache) => cache.addAll([OFFLINE_URL, '/']))
			.then(() => self.skipWaiting())
	);
});

self.addEventListener('activate', (event) => {
	event.waitUntil(
		(async () => {
			const keys = await caches.keys();
			await Promise.all(keys.filter((k) => !k.startsWith(VERSION)).map((k) => caches.delete(k)));
			// Answer navigations straight from the network while the worker boots
			if (self.registration.navigationPreload) {
				await self.registration.navigationPreload.enable();
			}
			await self.clients.claim();
		})()
	);
});

self.addEventListener('message', (event) => {
	if (event.data && event.data.type === 'SKIP_WAITING') self.skipWaiting();
});

async function trimCache(name, max) {
	const cache = await caches.open(name);
	const keys = await cache.keys();
	if (keys.length <= max) return;
	await cache.delete(keys[0]);
	return trimCache(name, max);
}

// Stale-while-revalidate: serve cache instantly, refresh in the background
function staleWhileRevalidate(event, cacheName) {
	event.respondWith(
		caches.open(cacheName).then((cache) =>
			cache.match(event.request).then((hit) => {
				const refresh = fetch(event.request)
					.then((res) => {
						if (res && res.ok) cache.put(event.request, res.clone());
						return res;
					})
					.catch(() => hit);
				return hit || refresh;
			})
		)
	);
}

self.addEventListener('fetch', (event) => {
	const req = event.request;
	if (req.method !== 'GET') return;

	const url = new URL(req.url);
	if (url.origin !== location.origin) return;
	// Never intercept Ghost admin/api/members/redirect traffic, or uploaded
	// media/files (large); /content/images/ IS cached below.
	if (/^\/(ghost|members|r)\//.test(url.pathname)) return;
	if (/^\/content\/(?!images\/)/.test(url.pathname)) return;

	// Theme + Ghost core assets: stale-while-revalidate
	if (url.pathname.startsWith('/assets/') || url.pathname.startsWith('/public/')) {
		return staleWhileRevalidate(event, STATIC_CACHE);
	}

	// Uploaded images: cache-first with an LRU-ish cap
	if (url.pathname.startsWith('/content/images/')) {
		event.respondWith(
			caches.open(IMAGE_CACHE).then((cache) =>
				cache.match(req).then(
					(hit) =>
						hit ||
						fetch(req).then((res) => {
							if (res && res.ok) {
								cache.put(req, res.clone());
								trimCache(IMAGE_CACHE, IMAGE_LIMIT);
							}
							return res;
						})
				)
			)
		);
		return;
	}

	// Pages: network-first (with navigation preload), cache fallback, then offline page
	if (req.mode === 'navigate' || (req.headers.get('accept') || '').includes('text/html')) {
		event.respondWith(
			(async () => {
				try {
					const preloaded = await event.preloadResponse;
					const res = preloaded || (await fetch(req));
					if (res && res.ok) {
						const copy = res.clone();
						caches.open(PAGE_CACHE).then((cache) => {
							cache.put(req, copy);
							trimCache(PAGE_CACHE, PAGE_LIMIT);
						});
					}
					return res;
				} catch (err) {
					const hit = await caches.match(req);
					return hit || caches.match(OFFLINE_URL);
				}
			})()
		);
	}
});

// Generic Web Push handlers (only when OneSignal isn't managing push —
// its imported worker ships its own). Works with any VAPID-based sender.
self.addEventListener('push', (event) => {
	let data = {};
	try { data = event.data ? event.data.json() : {}; } catch (e) {
		data = { body: event.data && event.data.text() };
	}
	const title = data.title || 'Swarnil Singhai';
	event.waitUntil(
		self.registration.showNotification(title, {
			body: data.body || 'Something new just shipped.',
			icon: data.icon || '/favicon.ico',
			badge: data.badge || undefined,
			data: { url: data.url || '/' }
		})
	);
});

self.addEventListener('notificationclick', (event) => {
	event.notification.close();
	const url = (event.notification.data && event.notification.data.url) || '/';
	event.waitUntil(
		self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((wins) => {
			for (const win of wins) {
				if (new URL(win.url).pathname === url && 'focus' in win) return win.focus();
			}
			return self.clients.openWindow(url);
		})
	);
});
