/**
 * Browser-side helpers for V2 Web Push subscription management.
 *
 * Spec: docs/superpowers/specs/2026-05-28-browser-notifications-v2-webpush.md.
 *
 * Platform detection lives here too — iOS Safari only supports Web Push
 * when the site is installed to the Home Screen (i.e., running in
 * standalone display mode). UI must show the "add to home screen"
 * affordance before the Push API is even reachable.
 */

export function isPushSupported(): boolean {
  if (typeof window === 'undefined') return false;
  return (
    'serviceWorker' in navigator &&
    'PushManager' in window &&
    'Notification' in window
  );
}

export function isStandalonePWA(): boolean {
  if (typeof window === 'undefined') return false;
  if (window.matchMedia && window.matchMedia('(display-mode: standalone)').matches) {
    return true;
  }
  // iOS Safari (Home Screen Web App) — non-standard property.
  return (navigator as { standalone?: boolean }).standalone === true;
}

export function isIOS(): boolean {
  if (typeof navigator === 'undefined') return false;
  // iPadOS reports as Mac in modern Safari; detect via touch points.
  const ua = navigator.userAgent;
  if (/iPad|iPhone|iPod/.test(ua)) return true;
  if (ua.includes('Mac') && navigator.maxTouchPoints > 1) return true;
  return false;
}

export interface PushSubscriptionDTO {
  endpoint: string;
  p256dh: string;
  auth: string;
  userAgent: string;
}

/**
 * Convert the base64url-encoded VAPID public key from the server into the
 * Uint8Array shape that pushManager.subscribe expects. The Push API
 * mandates the conversion; passing a string yields InvalidAccessError.
 */
export function urlBase64ToUint8Array(base64String: string): Uint8Array {
  const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
  const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
  const rawData = atob(base64);
  const buf = new Uint8Array(rawData.length);
  for (let i = 0; i < rawData.length; i++) {
    buf[i] = rawData.charCodeAt(i);
  }
  return buf;
}

function arrayBufferToBase64Url(buffer: ArrayBuffer): string {
  const bytes = new Uint8Array(buffer);
  let binary = '';
  for (let i = 0; i < bytes.byteLength; i++) {
    binary += String.fromCharCode(bytes[i]!);
  }
  return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

/** Inspect the SW for an existing subscription without prompting. */
export async function getCurrentPushSubscription(): Promise<PushSubscription | null> {
  if (!isPushSupported()) return null;
  const reg = await navigator.serviceWorker.ready;
  return reg.pushManager.getSubscription();
}

/**
 * Request a new subscription. Call sites are responsible for ensuring this
 * is invoked from a user gesture (button click); browsers reject permission
 * prompts triggered outside of one.
 */
export async function subscribePush(vapidPublicKey: string): Promise<PushSubscriptionDTO> {
  if (!isPushSupported()) throw new Error('push not supported in this browser');
  if (Notification.permission === 'denied') {
    throw new Error('notification permission denied');
  }
  if (Notification.permission === 'default') {
    const result = await Notification.requestPermission();
    if (result !== 'granted') {
      throw new Error(`permission ${result}`);
    }
  }
  const reg = await navigator.serviceWorker.ready;
  // pushManager.subscribe wants BufferSource. The lib.dom typings narrow
  // BufferSource to Uint8Array<ArrayBuffer> in recent TypeScript versions,
  // so we round-trip through a fresh ArrayBuffer to avoid the
  // SharedArrayBuffer-vs-ArrayBuffer variance complaint.
  const keyBytes = urlBase64ToUint8Array(vapidPublicKey);
  const applicationServerKey = keyBytes.buffer.slice(
    keyBytes.byteOffset,
    keyBytes.byteOffset + keyBytes.byteLength,
  ) as ArrayBuffer;
  const sub = await reg.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey,
  });
  return {
    endpoint: sub.endpoint,
    p256dh: arrayBufferToBase64Url(sub.getKey('p256dh')!),
    auth: arrayBufferToBase64Url(sub.getKey('auth')!),
    userAgent: navigator.userAgent.slice(0, 200),
  };
}

/** Remove the subscription registered with the browser's push service. */
export async function unsubscribePush(): Promise<boolean> {
  const sub = await getCurrentPushSubscription();
  if (!sub) return false;
  return sub.unsubscribe();
}
