/**
 * public/sw.js — service worker (ES module; must be registered with {type:'module'}).
 *
 * Network-first caching + A1 push notifications + notificationclick routing.
 * Pure push/notification helpers live in sw-push.js (no SW globals → testable).
 *
 * IMPORTANT: This file uses ES module `import` syntax. The SW registration in
 * public/main.ts must pass { type: 'module' }:
 *   navigator.serviceWorker.register('./sw.js', { type: 'module' })
 */

import { buildPushNotification, resolveNotificationClick } from './sw-push.js'

const CACHE = 'webterm-v1'

self.addEventListener('install', () => self.skipWaiting())
self.addEventListener('activate', (e) => e.waitUntil(self.clients.claim()))

self.addEventListener('fetch', (e) => {
  const req = e.request
  if (req.method !== 'GET') return
  const url = new URL(req.url)
  // Bypass: WS upgrade, hook endpoints, and push subscription routes (A1)
  if (
    url.pathname === '/term' ||
    url.pathname.startsWith('/hook') ||
    url.pathname.startsWith('/push')
  )
    return

  e.respondWith(
    fetch(req)
      .then((res) => {
        const copy = res.clone()
        caches.open(CACHE).then((c) => c.put(req, copy))
        return res
      })
      .catch(() => caches.match(req)),
  )
})

/** A1: show a notification when a push arrives. */
self.addEventListener('push', (e) => {
  let payload
  try {
    payload = e.data?.json()
  } catch {
    return // malformed push — ignore
  }
  if (!payload || typeof payload !== 'object') return

  const { title, options } = buildPushNotification(payload)
  e.waitUntil(self.registration.showNotification(title, options))
})

/**
 * A1: handle notification action clicks.
 * Allow/Deny → POST /hook/decision with capability token (SEC-C1).
 * Body click  → focus / open the terminal session tab.
 */
self.addEventListener('notificationclick', (e) => {
  e.notification.close()
  const result = resolveNotificationClick(e.notification, e.action)

  if (result.kind === 'decision') {
    // credentials:'same-origin' ensures Origin header is sent so the server
    // can validate it alongside the capability token (SEC-C1).
    e.waitUntil(
      fetch('/hook/decision', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: result.body,
      }).catch((err) => {
        console.error('[sw] /hook/decision fetch failed', err)
      }),
    )
  } else {
    // Focus an existing window if available, otherwise open a new one.
    e.waitUntil(
      self.clients
        .matchAll({ type: 'window', includeUncontrolled: true })
        .then((clientList) => {
          for (const client of clientList) {
            if ('focus' in client) return client.focus()
          }
          return self.clients.openWindow(result.url)
        }),
    )
  }
})
