// ===========================================================
// PushOptIn — the ask, and the off switch, for browser notifications.
//
// Until now email was the only way to reach a collector, and the drip's human-click
// numbers say most of them never open one. Price alerts are the thing worth an
// interruption: a want-list record getting cheaper has a deadline attached.
//
// Rules this component exists to respect:
//
//  * NEVER prompt on load. Notification.requestPermission() must come from a real
//    tap, and a permission prompt a visitor did not ask for is how a site gets
//    permanently blocked in that browser — one dismissal and the API stops working
//    for good. So: our own explanation first, the browser's prompt only on a click.
//  * Signed-in only. A subscription has to belong to somebody or there is nothing to
//    notify them about.
//  * iOS needs the site installed to the Home Screen before push works at all.
//    Showing an Enable button that silently fails there is worse than saying so.
//  * Dismissal sticks. The inline variant is asked once; after that it is only in
//    account settings.
//
// variant: "inline" (a dismissible card, used on /today) | "row" (a settings row)
// ===========================================================
const { useState: usePoState, useEffect: usePoEffect } = React;

const PO_DISMISS_KEY = "bbr:push:dismissed";

function bbrB64ToUint8(b64url) {
  const pad = "=".repeat((4 - (b64url.length % 4)) % 4);
  const b64 = (b64url + pad).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;
}

// iOS only delivers push to an installed (standalone) web app. Everywhere else this
// is false and irrelevant.
function bbrIosNeedsInstall() {
  const ua = navigator.userAgent || "";
  const isIos = /iPad|iPhone|iPod/.test(ua) ||
    (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
  if (!isIos) return false;
  const standalone = window.navigator.standalone === true ||
    (window.matchMedia && window.matchMedia("(display-mode: standalone)").matches);
  return !standalone;
}

function PushOptIn({ variant = "inline", user }) {
  // "checking" | "unsupported" | "needs-install" | "off" | "on" | "blocked" | "working"
  const [state, setState] = usePoState("checking");
  const [err, setErr] = usePoState("");
  const [dismissed, setDismissed] = usePoState(() => {
    try { return localStorage.getItem(PO_DISMISS_KEY) === "1"; } catch (e) { return false; }
  });

  usePoEffect(() => {
    let alive = true;
    (async () => {
      if (!("serviceWorker" in navigator) || !("PushManager" in window) || !("Notification" in window)) {
        if (alive) setState("unsupported");
        return;
      }
      if (bbrIosNeedsInstall()) { if (alive) setState("needs-install"); return; }
      if (Notification.permission === "denied") { if (alive) setState("blocked"); return; }
      try {
        // Registering is safe and silent — sw.js has no fetch behaviour beyond an
        // offline fallback — and we need the registration to read the existing
        // subscription, so a returning device shows "on" without being asked again.
        const reg = await navigator.serviceWorker.register("/sw.js");
        const sub = await reg.pushManager.getSubscription();
        if (alive) setState(sub ? "on" : "off");
      } catch (e) {
        if (alive) { setState("unsupported"); setErr(String((e && e.message) || e)); }
      }
    })();
    return () => { alive = false; };
  }, []);

  async function accessToken() {
    try {
      const { data } = await window.BBR_supabase.auth.getSession();
      return (data && data.session && data.session.access_token) || null;
    } catch (e) { return null; }
  }

  async function enable() {
    setErr("");
    setState("working");
    try {
      const perm = await Notification.requestPermission();
      if (perm !== "granted") { setState(perm === "denied" ? "blocked" : "off"); return; }

      const keyResp = await fetch("/api/deep-dives?action=push-key");
      if (!keyResp.ok) throw new Error("notifications are not configured yet");
      const { key } = await keyResp.json();
      if (!key) throw new Error("notifications are not configured yet");

      const reg = await navigator.serviceWorker.register("/sw.js");
      await navigator.serviceWorker.ready;
      const sub = await reg.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: bbrB64ToUint8(key),
      });

      const token = await accessToken();
      if (!token) throw new Error("sign in again to turn these on");
      const save = await fetch("/api/deep-dives?action=push-subscribe", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ subscription: sub.toJSON(), accessToken: token }),
      });
      if (!save.ok) {
        // Don't leave the browser holding a subscription the server does not know
        // about: it would look enabled here and never deliver anything.
        await sub.unsubscribe().catch(() => {});
        throw new Error("could not save that — try again");
      }
      setState("on");
    } catch (e) {
      setState(Notification.permission === "denied" ? "blocked" : "off");
      setErr(String((e && e.message) || e));
    }
  }

  async function disable() {
    setErr("");
    setState("working");
    try {
      const reg = await navigator.serviceWorker.getRegistration("/sw.js");
      const sub = reg && (await reg.pushManager.getSubscription());
      if (sub) {
        await fetch("/api/deep-dives?action=push-unsubscribe", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ endpoint: sub.endpoint }),
        }).catch(() => {});
        await sub.unsubscribe().catch(() => {});
      }
      setState("off");
    } catch (e) {
      setState("on");
      setErr(String((e && e.message) || e));
    }
  }

  function dismiss() {
    try { localStorage.setItem(PO_DISMISS_KEY, "1"); } catch (e) {}
    setDismissed(true);
  }

  if (!user) return null;
  if (state === "checking" || state === "unsupported") return null;

  const copy = {
    "needs-install": ["Add The Lead-In to your Home Screen", "iPhone and iPad only deliver notifications to an installed app. Share → Add to Home Screen, then come back here."],
    blocked: ["Notifications are blocked", "Your browser is refusing them for this site. Turn them back on in its site settings, then reload."],
    off: ["Get told when a want-list record drops", "One notification when something you are watching gets materially cheaper. Not the daily record, not marketing."],
    on: ["Price alerts are on", "You'll get a notification when a want-list record moves enough to matter."],
    working: ["One moment…", ""],
  }[state] || ["", ""];

  const action =
    state === "off" ? <button className="po-btn" onClick={enable}>Turn on alerts</button> :
    state === "on" ? <button className="po-btn po-btn--off" onClick={disable}>Turn off</button> :
    null;

  if (variant === "row") {
    return (
      <div className="po-row">
        <div className="po-row-text">
          <div className="po-row-h">{copy[0]}</div>
          {copy[1] ? <div className="po-row-sub">{copy[1]}</div> : null}
          {err ? <div className="po-err">{err}</div> : null}
        </div>
        {action}
      </div>
    );
  }

  // Inline: only ever shown as an ask, and only once.
  if (state === "on" || dismissed) return null;
  return (
    <aside className="po-card">
      <div className="po-card-text">
        <div className="po-card-h">{copy[0]}</div>
        {copy[1] ? <p className="po-card-sub">{copy[1]}</p> : null}
        {err ? <div className="po-err">{err}</div> : null}
      </div>
      <div className="po-card-actions">
        {action}
        <button className="po-skip" onClick={dismiss}>Not now</button>
      </div>
    </aside>
  );
}

Object.assign(window, { PushOptIn });
