import { AVATAR_CACHE, handleMediaFetch } from './sw-media';
import { onPush, onSubscriptionChange } from './sw-push';
import { roomFromTag, roomTarget } from './lib/push-model';

declare const __MALICE_DEVICE__: boolean;

// Prepended by vite.config.ts's precacheManifestPlugin: every emitted chunk,
// asset, and the wasm, as root-relative URLs.
const sw = self as unknown as ServiceWorkerGlobalScope & { __MALICE_PRECACHE__: string[] };

const CACHE_NAME = 'malice-shell-v1';

sw.addEventListener('install', (event) => {
  event.waitUntil(
    (async () => {
      const cache = await caches.open(CACHE_NAME);
      await cache.addAll(['/', '/index.html', ...sw.__MALICE_PRECACHE__]);
    })()
  );
});

sw.addEventListener('message', (event) => {
  if (event.data?.type === 'SKIP_WAITING') void sw.skipWaiting();
});

sw.addEventListener('activate', (event) => {
  event.waitUntil(
    (async () => {
      const keys = await caches.keys();
      await Promise.all(keys.filter((key) => key !== CACHE_NAME && key !== AVATAR_CACHE).map((key) => caches.delete(key)));
      const shell = await caches.open(CACHE_NAME);
      const keep = new Set(['/', '/index.html', ...sw.__MALICE_PRECACHE__]);
      for (const request of await shell.keys()) {
        const path = new URL(request.url).pathname;
        if (path.startsWith('/assets/') && !keep.has(path)) await shell.delete(request);
      }
      await sw.clients.claim();
    })()
  );
});

async function cacheFirst(request: Request): Promise<Response> {
  const cached = await caches.match(request);
  if (cached) return cached;
  const response = await fetch(request);
  if (response.ok) {
    const cache = await caches.open(CACHE_NAME);
    void cache.put(request, response.clone());
  }
  return response;
}

async function networkFirstNavigation(request: Request): Promise<Response> {
  try {
    return await fetch(request);
  } catch {
    const cached = await caches.match('/index.html');
    if (cached) return cached;
    throw new Error('offline and no cached shell');
  }
}

sw.addEventListener('fetch', (event) => {
  const url = new URL(event.request.url);

  if (__MALICE_DEVICE__ && handleMediaFetch(event)) return;
  if (url.pathname.startsWith('/api/')) return;
  if (url.pathname.startsWith('/assets/') || url.pathname.startsWith('/twemoji/')) {
    event.respondWith(cacheFirst(event.request));
    return;
  }
  if (event.request.mode === 'navigate') {
    event.respondWith(networkFirstNavigation(event.request));
  }
});

sw.addEventListener('push', (event) => onPush(sw, event));
sw.addEventListener('pushsubscriptionchange', (event) => onSubscriptionChange(sw, event));

sw.addEventListener('notificationclick', (event) => {
  event.notification.close();
  const data = event.notification.data as { roomId?: unknown; navigate?: unknown } | null;
  const roomId = typeof data?.roomId === 'string' ? data.roomId : roomFromTag(event.notification.tag);
  const navigate = typeof data?.navigate === 'string' ? data.navigate : null;
  event.waitUntil(
    (async () => {
      const windows = await sw.clients.matchAll({ type: 'window', includeUncontrolled: true });
      const existing = windows.find((client) => client.visibilityState === 'visible') ?? windows[0];
      if (!existing) {
        await sw.clients.openWindow(roomTarget(roomId, navigate));
        return;
      }
      if (roomId) existing.postMessage({ type: 'malice-open-room', roomId });
      await existing.focus();
    })()
  );
});
