/* global caches, Notification */

function readGatewayToken() {
  const params = new URLSearchParams(window.location.search);
  const token = params.get('token');
  return token && token.trim() ? token.trim() : null;
}

function withToken(path) {
  const token = readGatewayToken();
  if (!token) {
    return path;
  }
  const separator = path.includes('?') ? '&' : '?';
  return `${path}${separator}token=${encodeURIComponent(token)}`;
}

function isLocalhostHost(hostname) {
  return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1';
}

async function unregisterAllServiceWorkers() {
  if (!('serviceWorker' in navigator)) {
    return;
  }
  const registrations = await navigator.serviceWorker.getRegistrations();
  await Promise.all(registrations.map((registration) => registration.unregister()));
}

async function clearFlynnUiCaches() {
  if (!('caches' in globalThis)) {
    return;
  }
  const cacheKeys = await caches.keys();
  const targets = cacheKeys.filter((key) => key.startsWith('flynn-webchat-'));
  await Promise.all(targets.map((key) => caches.delete(key)));
}

async function requestJson(path, options) {
  const response = await fetch(withToken(path), {
    ...(options ?? {}),
    headers: {
      'Content-Type': 'application/json',
      ...(options?.headers ?? {}),
    },
  });

  let body = null;
  try {
    body = await response.json();
  } catch {
    body = null;
  }

  if (!response.ok) {
    const message = typeof body?.error === 'string'
      ? body.error
      : `Request failed (${response.status})`;
    throw new Error(message);
  }

  return body;
}

function urlBase64ToUint8Array(base64String) {
  const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
  const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
  const rawData = window.atob(base64);
  const outputArray = new Uint8Array(rawData.length);

  for (let i = 0; i < rawData.length; ++i) {
    outputArray[i] = rawData.charCodeAt(i);
  }

  return outputArray;
}

export function isPushSupported() {
  return 'serviceWorker' in navigator && 'PushManager' in window && 'Notification' in window;
}

export async function registerPwaServiceWorker() {
  if (!('serviceWorker' in navigator)) {
    return null;
  }
  if (isLocalhostHost(window.location.hostname)) {
    // Local dev/runtime should always reflect latest dashboard assets.
    await unregisterAllServiceWorkers();
    await clearFlynnUiCaches();
    return null;
  }
  const registration = await navigator.serviceWorker.register(withToken('/sw.js'));
  // Ask the browser to check for a newer worker on app load.
  void registration.update().catch(() => undefined);
  return registration;
}

export async function getPushStatus() {
  const supported = isPushSupported();
  if (!supported) {
    return {
      supported: false,
      permission: 'unsupported',
      subscribed: false,
      enabled: false,
      configured: false,
      message: 'Push is not supported by this browser.',
    };
  }

  const registration = await navigator.serviceWorker.ready;
  const subscription = await registration.pushManager.getSubscription();
  const publicKeyPayload = await requestJson('/webchat/push/public-key', { method: 'GET' });
  const configured = Boolean(publicKeyPayload?.vapidPublicKey);

  return {
    supported: true,
    permission: Notification.permission,
    subscribed: Boolean(subscription),
    enabled: Boolean(publicKeyPayload?.enabled),
    configured,
    message: null,
  };
}

export async function enablePushNotifications() {
  if (!isPushSupported()) {
    throw new Error('Push notifications are not supported by this browser.');
  }

  const publicKeyPayload = await requestJson('/webchat/push/public-key', { method: 'GET' });
  if (!publicKeyPayload?.enabled) {
    throw new Error('WebChat push is disabled on the gateway.');
  }
  if (!publicKeyPayload?.vapidPublicKey) {
    throw new Error('Gateway is missing server.webchat_push.vapid_public_key.');
  }

  const permission = await Notification.requestPermission();
  if (permission !== 'granted') {
    throw new Error('Notification permission was not granted.');
  }

  const registration = await navigator.serviceWorker.ready;
  let subscription = await registration.pushManager.getSubscription();

  if (!subscription) {
    subscription = await registration.pushManager.subscribe({
      userVisibleOnly: true,
      applicationServerKey: urlBase64ToUint8Array(publicKeyPayload.vapidPublicKey),
    });
  }

  const json = subscription.toJSON();
  await requestJson('/webchat/push/subscriptions', {
    method: 'POST',
    body: JSON.stringify({
      endpoint: subscription.endpoint,
      keys: json.keys ?? {},
      userAgent: navigator.userAgent,
    }),
  });

  return getPushStatus();
}

export async function disablePushNotifications() {
  if (!('serviceWorker' in navigator)) {
    return {
      supported: false,
      permission: 'unsupported',
      subscribed: false,
      enabled: false,
      configured: false,
      message: 'Push is not supported by this browser.',
    };
  }

  const registration = await navigator.serviceWorker.ready;
  const subscription = await registration.pushManager.getSubscription();
  if (subscription) {
    await requestJson('/webchat/push/subscriptions', {
      method: 'DELETE',
      body: JSON.stringify({ endpoint: subscription.endpoint }),
    });
    await subscription.unsubscribe();
  }

  return getPushStatus();
}
