// Web Push enablement for the current device. The service worker itself is
// registered globally by nav.js; this module owns the *subscription* handshake:
// ask permission, subscribe with the server's VAPID key, and register the
// resulting endpoint against the signed-in user (via account.js's cookie-aware
// apiFetch). Used by the /alerts page to render a "Notifications on this device"
// control.
//
// Push + service workers require a secure context (HTTPS, or http://localhost),
// so on a plain-HTTP LAN origin this cleanly reports "unsupported" instead of
// throwing.
import { apiFetch, uv } from "./account.js";

export function supported() {
  return (
    typeof window !== "undefined" &&
    window.isSecureContext &&
    "serviceWorker" in navigator &&
    "PushManager" in window &&
    "Notification" in window
  );
}

// The applicationServerKey must be a Uint8Array of the base64url-decoded VAPID
// public key.
function urlB64ToUint8Array(base64) {
  const padding = "=".repeat((4 - (base64.length % 4)) % 4);
  const b64 = (base64 + padding).replace(/-/g, "+").replace(/_/g, "/");
  const raw = atob(b64);
  const out = new Uint8Array(raw.length);
  for (let i = 0; i < raw.length; i++) out[i] = raw.charCodeAt(i);
  return out;
}

async function registration() {
  // nav.js registered sw.js already; ready resolves once it's active.
  return navigator.serviceWorker.ready;
}

// Does an existing subscription's baked-in applicationServerKey still match the
// server's current VAPID public key? After a key rotation it won't: the browser
// keeps signing pushes the server can no longer authenticate, so we must replace
// the subscription. `options.applicationServerKey` is an ArrayBuffer of the raw
// key bytes (or null on browsers that don't expose it — treat that as a mismatch
// and re-subscribe rather than leaving a possibly-stale subscription in place).
function applicationServerKeyMatches(sub, serverKey) {
  const current = sub.options && sub.options.applicationServerKey;
  if (!current) return false;
  const a = new Uint8Array(current);
  if (a.length !== serverKey.length) return false;
  for (let i = 0; i < a.length; i++) if (a[i] !== serverKey[i]) return false;
  return true;
}

// Is this device currently subscribed? (a PushSubscription exists locally)
export async function isEnabled() {
  if (!supported()) return false;
  try {
    const reg = await registration();
    return !!(await reg.pushManager.getSubscription());
  } catch (e) {
    return false;
  }
}

export function permission() {
  return supported() ? Notification.permission : "unsupported";
}

// Subscribe this device and register it server-side. Throws on failure so the
// caller can surface a message.
export async function enable() {
  if (!supported()) throw new Error("This browser doesn't support push notifications.");
  const perm = await Notification.requestPermission();
  if (perm !== "granted") throw new Error("Notification permission was not granted.");

  const reg = await registration();
  let sub = await reg.pushManager.getSubscription();

  // Always resolve the server's current VAPID key: it's needed to subscribe, and
  // — when a subscription already exists — to detect a key rotation. A subscription
  // minted under an old key silently stops receiving pushes, so on a mismatch we
  // drop it and re-subscribe with the new key. Matching key = happy path, untouched.
  const keyRes = await apiFetch(uv("push/vapid-key"));
  if (!keyRes.ok) throw new Error("Couldn't fetch the server key.");
  const { key } = await keyRes.json();
  const serverKey = urlB64ToUint8Array(key);

  if (sub && !applicationServerKeyMatches(sub, serverKey)) {
    await sub.unsubscribe();
    sub = null;
  }
  if (!sub) {
    sub = await reg.pushManager.subscribe({
      userVisibleOnly: true,
      applicationServerKey: serverKey,
    });
  }
  const res = await apiFetch(uv("push/subscribe"), { method: "POST", json: sub.toJSON() });
  if (!res.ok) throw new Error("Couldn't register this device.");
}

// Unsubscribe locally and remove the endpoint server-side.
export async function disable() {
  if (!supported()) return;
  const reg = await registration();
  const sub = await reg.pushManager.getSubscription();
  if (!sub) return;
  try {
    await apiFetch(uv("push/subscribe"), { method: "DELETE", json: { endpoint: sub.endpoint } });
  } catch (e) { /* best-effort server cleanup */ }
  await sub.unsubscribe();
}

// Ask the server to push a test notification to every device on the account.
export async function sendTest() {
  const res = await apiFetch(uv("push/test"), { method: "POST" });
  if (!res.ok) throw new Error("Couldn't send a test notification.");
  return res.json();
}
