/* Tx OS service worker — Web Push only.
 *
 * Scope: registered with `scope: BASE_URL`, so the SW only controls
 * paths under the SPA's base path (e.g. `/`, or `/tx-os/` in a future
 * subpath deployment). Every URL the SW touches — icons, navigation
 * targets — is resolved against `self.registration.scope` so the same
 * code works at root and at a subpath without code changes.
 *
 * We deliberately do NOT cache app assets here — Tx OS is self-hosted
 * on a single machine, so the network is fast and reliable. Adding
 * cache layers would risk shipping stale React bundles after an
 * upgrade.
 */

function scopedUrl(relative) {
  // self.registration.scope is the full origin + base path with a
  // trailing slash (e.g. "https://host/" or "https://host/tx-os/").
  // Strip a leading slash from `relative` so URL() treats it as a
  // path under scope rather than origin-relative.
  const cleaned = String(relative || "").replace(/^\/+/, "");
  const resolved = new URL(cleaned, self.registration.scope);
  // Preserve the query string and hash so notification deep-links
  // (e.g. "/meetings?meeting=12" or "/notes?thread=7") survive the click
  // handler — taking only .pathname would drop them and land the user on
  // the generic page instead of the specific item.
  return resolved.pathname + resolved.search + resolved.hash;
}

self.addEventListener("install", () => {
  self.skipWaiting();
});

self.addEventListener("activate", (event) => {
  event.waitUntil(self.clients.claim());
});

self.addEventListener("push", (event) => {
  /** @type {{title?: string, body?: string, tag?: string, url?: string, type?: string}} */
  let payload = {};
  try {
    payload = event.data ? event.data.json() : {};
  } catch {
    payload = { title: event.data ? event.data.text() : "Tx OS" };
  }

  const title = payload.title || "Tx OS";
  const iconUrl = scopedUrl("icons/icon-192.png");
  const options = {
    body: payload.body || "",
    tag: payload.tag || payload.type || "tx-os",
    renotify: true,
    icon: iconUrl,
    badge: iconUrl,
    data: {
      url: payload.url || "/",
      type: payload.type || null,
    },
  };

  event.waitUntil(self.registration.showNotification(title, options));
});

self.addEventListener("notificationclick", (event) => {
  event.notification.close();
  const rawTarget = (event.notification.data && event.notification.data.url) || "/";
  const targetPath = scopedUrl(rawTarget);

  event.waitUntil(
    (async () => {
      const allClients = await self.clients.matchAll({
        type: "window",
        includeUncontrolled: true,
      });
      // Prefer an already-open Tx OS tab under our scope; navigate it
      // to the target and focus.
      for (const client of allClients) {
        try {
          await client.focus();
          if ("navigate" in client) {
            await client.navigate(targetPath);
          }
          return;
        } catch {
          /* try next */
        }
      }
      if (self.clients.openWindow) {
        await self.clients.openWindow(targetPath);
      }
    })(),
  );
});
