"use client";

import { useEffect } from "react";
import { useToastStore } from "@/lib/stores/toast-store";

// Registers /sw.js unconditionally — earlier this lived inside
// PwaInstallPrompt which only mounts after the age-gate, so first-time
// visitors never had a service worker installed and the browser's PWA
// install criteria never fired.
//
// Once registration succeeds AND the user has previously granted
// notification permission, we (re-)subscribe to push and POST the
// subscription to /api/push/subscribe. We never prompt for permission
// here — that's gated behind an explicit opt-in CTA in /settings/notifications.

const VAPID_PUBLIC_KEY = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY;

function urlBase64ToUint8Array(base64String: string): Uint8Array<ArrayBuffer> {
  const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
  const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
  const raw = window.atob(base64);
  // PushManager.subscribe wants Uint8Array<ArrayBuffer>, not the default
  // Uint8Array<ArrayBufferLike>. Allocate explicitly to preserve the narrow type.
  const buffer = new ArrayBuffer(raw.length);
  const arr = new Uint8Array(buffer);
  for (let i = 0; i < raw.length; i++) arr[i] = raw.charCodeAt(i);
  return arr;
}

async function syncPushSubscription(reg: ServiceWorkerRegistration) {
  if (!VAPID_PUBLIC_KEY) return;
  if (!("Notification" in window) || !("PushManager" in window)) return;
  if (Notification.permission !== "granted") return;
  try {
    let sub = await reg.pushManager.getSubscription();
    if (!sub) {
      sub = await reg.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY),
      });
    }
    // R17 B.4: surface backend save failures in DevTools so support can
    // diagnose "I subscribed but never get notifications". Was a silent
    // .catch(() => {}).
    await fetch("/api/push/subscribe", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(sub.toJSON()),
    }).catch((e) => console.error("[push] backend save failed:", e));
  } catch (err) {
    console.error("[push] subscribe failed", err);
  }
}

// R17 B.5: register the periodicsync handler the SW already implements
// (sw.js:141 widget-update). Browser support is limited (Chrome on
// Android with the site installed as a PWA) — failure is silent.
async function registerPeriodicSync(reg: ServiceWorkerRegistration) {
  type PSync = { register(tag: string, opts: { minInterval: number }): Promise<void> };
  const ps = (reg as unknown as { periodicSync?: PSync }).periodicSync;
  if (!ps) return;
  try {
    await ps.register("widget-update", { minInterval: 60 * 60 * 1000 });
  } catch {
    // Browser may refuse without "periodic-background-sync" permission —
    // expected on most installs.
  }
}

export default function ServiceWorkerRegistrar() {
  useEffect(() => {
    if (typeof window === "undefined") return;
    if (!("serviceWorker" in navigator)) return;
    if (window.location.protocol !== "https:" && window.location.hostname !== "localhost") return;

    // R17 B.3: notify when a new SW version takes over so users see fresh
    // assets without a manual hard-refresh after a deploy. The SW already
    // calls skipWaiting() in its install handler; we listen on the client
    // for the controllerchange event and prompt a reload.
    let refreshing = false;
    navigator.serviceWorker.addEventListener("controllerchange", () => {
      if (refreshing) return;
      refreshing = true;
      window.location.reload();
    });

    navigator.serviceWorker
      .register("/sw.js")
      .then((reg) => {
        // Don't prompt — only sync if permission was already granted
        // (e.g., from a prior session or the /settings/notifications opt-in).
        void syncPushSubscription(reg);
        void registerPeriodicSync(reg);

        // R17 B.3: surface "Update available" toast when a new SW
        // installs while the page is still controlled by an older one.
        // Tapping the toast posts SKIP_WAITING; the SW's message
        // listener swaps and the controllerchange handler reloads.
        reg.addEventListener("updatefound", () => {
          const installing = reg.installing;
          if (!installing) return;
          installing.addEventListener("statechange", () => {
            if (
              installing.state === "installed" &&
              navigator.serviceWorker.controller
            ) {
              try {
                useToastStore.getState().addToast(
                  "info",
                  "New version available — tap to refresh.",
                );
              } catch {
                // Toast store may not be ready on first paint; the
                // controllerchange listener still triggers a reload
                // when the SW takes over.
              }
              installing.postMessage({ type: "SKIP_WAITING" });
            }
          });
        });
      })
      .catch((err) => console.error("[sw] register failed", err));
  }, []);
  return null;
}
