"use client";

// Offline notifications data layer (Web Push). Registers the service worker, subscribes the browser
// with IIOS's VAPID key, and hands the subscription to the be-crm door so IIOS can reach this user
// while no CRM tab is open. Push needs a real backend (VAPID key + delivery), so it is only offered
// when the Shell is configured (live); demo mode reports it unsupported.
//
// Live contract (be-crm data door → IIOS /v1/notifications/*):
//   query  crm.messenger.push.vapidKey    {}                          -> { key }   ('' = push disabled server-side)
//   cmd    crm.messenger.push.subscribe   { endpoint, keys, userAgent } -> { ok }
//   cmd    crm.messenger.push.unsubscribe { endpoint }                 -> { ok }

import { useCallback, useEffect, useState } from "react";
import { useAppShell } from "@abe-kap/appshell-sdk/react";
import { isShellConfigured } from "./appshell";

const SHELL = isShellConfigured();
const SW_URL = "/push-sw.js";

export type PushPermission = "default" | "granted" | "denied";

export interface PushState {
  /** Browser can do Web Push AND we have a live backend to deliver it. */
  supported: boolean;
  /** OS/browser permission for notifications. */
  permission: PushPermission;
  /** This browser currently has an active push subscription registered with the backend. */
  subscribed: boolean;
  /** A subscribe/unsubscribe round-trip is in flight. */
  busy: boolean;
  error: string | null;
  enable: () => Promise<void>;
  disable: () => Promise<void>;
}

/** VAPID keys travel as URL-safe base64; PushManager wants raw bytes. */
function urlBase64ToUint8Array(base64: string): Uint8Array {
  const padding = "=".repeat((4 - (base64.length % 4)) % 4);
  const normalized = (base64 + padding).replace(/-/g, "+").replace(/_/g, "/");
  const raw = atob(normalized);
  const out = new Uint8Array(raw.length);
  for (let i = 0; i < raw.length; i++) out[i] = raw.charCodeAt(i);
  return out;
}

function browserSupportsPush(): boolean {
  return typeof window !== "undefined" && "serviceWorker" in navigator && "PushManager" in window && "Notification" in window;
}

/** Serialize a PushSubscription into the door's { endpoint, keys } shape. */
function toSubscribeBody(sub: PushSubscription): { endpoint: string; keys: { p256dh: string; auth: string }; userAgent: string } {
  const json = sub.toJSON();
  return {
    endpoint: sub.endpoint,
    keys: { p256dh: json.keys?.p256dh ?? "", auth: json.keys?.auth ?? "" },
    userAgent: typeof navigator !== "undefined" ? navigator.userAgent.slice(0, 400) : "",
  };
}

export function usePushNotifications(): PushState {
  const { sdk } = useAppShell();
  const [supported] = useState<boolean>(() => SHELL && browserSupportsPush());
  const [permission, setPermission] = useState<PushPermission>("default");
  const [subscribed, setSubscribed] = useState(false);
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState<string | null>(null);

  // Reflect the current OS permission + whether a subscription already exists (e.g. across reloads).
  useEffect(() => {
    if (!supported) return;
    setPermission(Notification.permission as PushPermission);
    let cancelled = false;
    navigator.serviceWorker.ready
      .then((reg) => reg.pushManager.getSubscription())
      .then((sub) => {
        if (!cancelled) setSubscribed(Boolean(sub));
      })
      .catch(() => {});
    return () => {
      cancelled = true;
    };
  }, [supported]);

  const enable = useCallback(async () => {
    if (!supported) return;
    setBusy(true);
    setError(null);
    try {
      const perm = await Notification.requestPermission();
      setPermission(perm as PushPermission);
      if (perm !== "granted") throw new Error("Notifications permission was not granted.");

      const reg = await navigator.serviceWorker.register(SW_URL);
      await navigator.serviceWorker.ready;

      const { key } = await sdk.query<{ key: string }>("crm.messenger.push.vapidKey", {});
      if (!key) throw new Error("Push is not enabled on the server (no VAPID key).");

      const existing = await reg.pushManager.getSubscription();
      const sub =
        existing ??
        (await reg.pushManager.subscribe({
          userVisibleOnly: true,
          // Cast: this lib's BufferSource type pins ArrayBuffer, but a plain Uint8Array is valid here.
          applicationServerKey: urlBase64ToUint8Array(key) as BufferSource,
        }));

      await sdk.command("crm.messenger.push.subscribe", toSubscribeBody(sub));
      setSubscribed(true);
    } catch (e) {
      setError(e instanceof Error ? e.message : "Could not enable notifications.");
    } finally {
      setBusy(false);
    }
  }, [supported, sdk]);

  const disable = useCallback(async () => {
    if (!supported) return;
    setBusy(true);
    setError(null);
    try {
      const reg = await navigator.serviceWorker.ready;
      const sub = await reg.pushManager.getSubscription();
      if (sub) {
        // Tell the backend first (still has the endpoint), then drop the local subscription.
        await sdk.command("crm.messenger.push.unsubscribe", { endpoint: sub.endpoint }).catch(() => {});
        await sub.unsubscribe();
      }
      setSubscribed(false);
    } catch (e) {
      setError(e instanceof Error ? e.message : "Could not disable notifications.");
    } finally {
      setBusy(false);
    }
  }, [supported, sdk]);

  return { supported, permission, subscribed, busy, error, enable, disable };
}
