/* notifications.jsx — in-app notification center + opt-in browser push.

   Notifications are derived client-side from the live event feed the app
   already polls: when a new parsed signal arrives that matches the user's
   trigger preferences and an analyst they're following, it becomes a
   notification. No server-side storage needed for the in-app center.

   Per-user preferences (delivery + which triggers) persist in localStorage,
   keyed by Discord user id; browser-push permission is per-device by nature.
   Defaults to entries & exits; users tune it in the bell's settings view.
*/

const NOTIF_TRIGGERS = [
  ["open",  "Entries",   "New positions opened"],
  ["add",   "Adds",      "Added to a position"],
  ["trim",  "Trims",     "Partial profit taken"],
  ["cut",   "Cuts",      "Stopped out / cut"],
  ["close", "Closes",    "Position fully closed"],
  ["hold",  "Updates",   "Holds / status updates"],
  ["watch", "Watchlist", "New watchlist / analysis ideas"],
  ["recap", "Recaps",    "A new daily recap is published"],
];
// "Entries & exits" on by default; the noisier ones (holds, watchlist, recaps)
// off. New keys default in via the read() merge, so no version bump is needed to
// add an off-by-default trigger (bumping would re-run the v<2 hold reset).
const NOTIF_PREFS_V = 2;
const NOTIF_DEFAULTS = { v: NOTIF_PREFS_V, push: false, triggers: { open: true, add: true, trim: true, cut: true, close: true, hold: false, watch: false, recap: false } };

function useNotifPrefs(userId) {
  const key = "notif:prefs:" + (userId || "anon");
  const read = () => {
    try {
      const raw = JSON.parse(localStorage.getItem(key) || "null");
      if (raw) {
        const merged = { ...NOTIF_DEFAULTS, ...raw, triggers: { ...NOTIF_DEFAULTS.triggers, ...(raw.triggers || {}) } };
        // v<2: an earlier build defaulted "Updates"/Holds ON and that choice got
        // persisted, so holds pushed by default — reset it OFF once (users can
        // re-enable it in settings). The sync effect then pushes the fix server-side.
        if (raw.v !== NOTIF_PREFS_V) { merged.triggers = { ...merged.triggers, hold: false }; merged.v = NOTIF_PREFS_V; }
        return merged;
      }
    } catch (_) {}
    return NOTIF_DEFAULTS;
  };
  const [prefs, setPrefs] = useState(read);
  useEffect(() => { setPrefs(read()); }, [key]);                                   // re-read when the user changes
  useEffect(() => { try { localStorage.setItem(key, JSON.stringify(prefs)); } catch (_) {} }, [key, prefs]);
  return [prefs, setPrefs];
}

// ── Web Push (real background notifications via the service worker) ──────────
// Public VAPID key — safe to embed; the matching private key lives only in the
// bot's env. iOS only delivers Web Push to a PWA ADDED TO THE HOME SCREEN.
const VAPID_PUBLIC_KEY = "BM_xMXrSk6H6qVp1uQnU9ClJhXVdZuklWNFAyUATXHq1uv4kPabiqI94vHsgwSx9RgrrJ0joyhbTq7iTa9VmtmQ";
function urlBase64ToUint8Array(b64) {
  const pad = "=".repeat((4 - (b64.length % 4)) % 4);
  const s = (b64 + pad).replace(/-/g, "+").replace(/_/g, "/");
  const raw = atob(s);
  const arr = new Uint8Array(raw.length);
  for (let i = 0; i < raw.length; i++) arr[i] = raw.charCodeAt(i);
  return arr;
}
function pushSupported() {
  return typeof navigator !== "undefined" && "serviceWorker" in navigator
    && typeof window !== "undefined" && "PushManager" in window && typeof Notification !== "undefined";
}
async function getPushSubscription(create) {
  if (!pushSupported()) return null;
  const reg = await navigator.serviceWorker.register("/sw.js");
  await navigator.serviceWorker.ready;
  let sub = await reg.pushManager.getSubscription();
  if (!sub && create) {
    sub = await reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY) });
  }
  return sub;
}
// Register/refresh the server-side subscription with current triggers + analysts.
async function syncPushSubscription({ triggers, enabledAnalysts, create }) {
  const sub = await getPushSubscription(create);
  if (!sub) return null;
  await tapeFetch(apiBase() + "/api/push/subscribe", {
    method: "POST", headers: { "content-type": "application/json" },
    body: JSON.stringify({ subscription: sub.toJSON(), triggers, enabled_analysts: enabledAnalysts }),
  });
  return sub;
}
async function dropPushSubscription() {
  if (!pushSupported()) return;
  try {
    const reg = await navigator.serviceWorker.getRegistration();
    const sub = reg && (await reg.pushManager.getSubscription());
    if (sub) {
      await tapeFetch(apiBase() + "/api/push/unsubscribe", {
        method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ endpoint: sub.endpoint }),
      }).catch(() => {});
      await sub.unsubscribe().catch(() => {});
    }
  } catch (_) {}
}

function NotificationBell({ events, watchlist, publishedRecaps, enabled, analysts, userId, brand, onOpenTicker }) {
  const [prefs, setPrefs] = useNotifPrefs(userId);
  const [open, setOpen] = useState(false);
  const [view, setView] = useState("list");        // list | settings
  const wrapRef = useRef(null);

  const readKey = "notif:read:" + (userId || "anon");
  const [lastRead, setLastRead] = useState(() => Number(localStorage.getItem(readKey)) || 0);
  const [highlightFrom, setHighlightFrom] = useState(0);
  useEffect(() => { setLastRead(Number(localStorage.getItem(readKey)) || 0); }, [readKey]);

  // Qualifying notifications — trade events (cross-post duplicates collapsed),
  // plus watchlist ideas and published daily recaps when those triggers are on.
  // Merged and sorted newest-first. (Trade "watch" events are skipped here; the
  // Watchlist trigger is fed from the richer tape_watchlist entries instead.)
  const items = useMemo(() => {
    const ev = dedupeEvents(events)
      .filter(e => e.action !== "watch" && prefs.triggers[e.action] && enabled.has(e.analyst))
      .map(e => ({ id: e.id, ts: e.ts, analyst: e.analyst, ticker: e.ticker, action: e.action, contract: e.contract }));
    const wl = prefs.triggers.watch
      ? (watchlist || []).filter(e => enabled.has(e.analyst)).map(e => ({
          id: "wl:" + e.id, ts: e.ts, analyst: e.analyst,
          ticker: (e.tickers && e.tickers[0]) || null,
          label: e.kind === "analysis" ? "Analysis" : "Watchlist",
          action: "watch", contract: null, extra: Math.max(0, (e.tickers || []).length - 1),
        }))
      : [];
    const rc = prefs.triggers.recap
      ? (publishedRecaps || []).map(p => ({
          id: "rc:" + (p.id || `${p.guildId}:${p.date || p.ts}`),
          ts: p.ts != null ? p.ts : (p.date ? Date.parse(p.date) : 0),
          analyst: null, ticker: null, action: "recap", contract: null,
        }))
      : [];
    return [...ev, ...wl, ...rc].sort((a, b) => b.ts - a.ts).slice(0, 60);
  }, [events, watchlist, publishedRecaps, prefs.triggers, enabled]);

  const unread = useMemo(() => items.reduce((n, it) => n + (it.ts > lastRead ? 1 : 0), 0), [items, lastRead]);

  // ── Keep the server-side push subscription in sync with the user's triggers
  //    and enabled analysts. Actual delivery is server→service-worker (so it
  //    works in the background / when the PWA is closed — required on iOS).
  useEffect(() => {
    if (!prefs.push) return;
    syncPushSubscription({ triggers: prefs.triggers, enabledAnalysts: [...enabled], create: false })
      .catch(() => {});
  }, [prefs.push, prefs.triggers, enabled]);

  // Close on outside click / Escape
  useEffect(() => {
    if (!open) return;
    const onDown = (e) => { if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false); };
    const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
    document.addEventListener("mousedown", onDown);
    document.addEventListener("keydown", onKey);
    return () => { document.removeEventListener("mousedown", onDown); document.removeEventListener("keydown", onKey); };
  }, [open]);

  function toggle() {
    setOpen(o => {
      const next = !o;
      if (next) {
        setView("list");
        setHighlightFrom(lastRead);                                                // freeze what was "new"
        if (items.length && items[0].ts > lastRead) {
          setLastRead(items[0].ts);
          try { localStorage.setItem(readKey, String(items[0].ts)); } catch (_) {}
        }
      }
      return next;
    });
  }

  async function togglePush(on) {
    if (!on) { setPrefs(p => ({ ...p, push: false })); dropPushSubscription(); return; }
    if (!pushSupported()) {
      toast.error("This device can't do push here. On iPhone, add Sinux Signals to your Home Screen, open it from there, then turn this on.");
      return;
    }
    let perm = Notification.permission;
    if (perm === "default") { try { perm = await Notification.requestPermission(); } catch (_) {} }
    if (perm !== "granted") { toast.error("Notifications are blocked — allow them for this site in your settings."); return; }
    try {
      const sub = await syncPushSubscription({ triggers: prefs.triggers, enabledAnalysts: [...enabled], create: true });
      if (!sub) { toast.error("Couldn't enable push on this device."); return; }
      setPrefs(p => ({ ...p, push: true }));
      toast.success("Push notifications on — even when the app is closed.");
    } catch (e) {
      toast.error("Couldn't enable push — " + (e && e.message || "try again"));
    }
  }
  function toggleTrigger(k) {
    setPrefs(p => ({ ...p, triggers: { ...p.triggers, [k]: !p.triggers[k] } }));
  }

  const colorOf = (h) => (analysts.find(a => a.handle === h)?.color) || "var(--accent)";

  return (
    <div className="notif-wrap" data-tour="bell" ref={wrapRef}>
      <button className={cx("icon-btn notif-bell", unread > 0 && "has-unread")} title="Notifications" onClick={toggle}>
        {I("bell", { size: 17 })}
        {unread > 0 && <span className="notif-dot">{unread > 9 ? "9+" : unread}</span>}
      </button>

      {open && <div className="notif-backdrop" onClick={() => setOpen(false)} aria-hidden="true" />}
      {open && (
        <div className="notif-panel">
          <div className="notif-hdr">
            <span className="notif-title">{view === "settings" ? "Notification settings" : "Notifications"}</span>
            <span className="notif-hdr-actions">
              <button className={cx("icon-btn", view === "settings" && "active")} title={view === "settings" ? "Back to list" : "Settings"}
                onClick={() => setView(v => v === "settings" ? "list" : "settings")}>
                {I(view === "settings" ? "back" : "settings", { size: 15 })}
              </button>
              <button className="icon-btn notif-close" title="Close" onClick={() => setOpen(false)}>{I("close", { size: 15 })}</button>
            </span>
          </div>

          {view === "list" ? (
            <div className="notif-list">
              {items.length === 0 ? (
                <div className="notif-empty">
                  {Object.values(prefs.triggers).some(Boolean)
                    ? "You're all caught up — new signals will show up here."
                    : "Notifications are all switched off. Open settings to choose what you'd like to hear about."}
                </div>
              ) : items.map(n => (
                <button key={n.id} className={cx("notif-item", n.ts > highlightFrom && "fresh")}
                  onClick={() => { if (n.ticker && onOpenTicker) onOpenTicker(n.ticker); setOpen(false); }}>
                  <span className="notif-item-dot" style={{ background: n.action === "recap" ? "var(--accent)" : colorOf(n.analyst) }} />
                  <span className="notif-item-main">
                    <span className="notif-item-r1">
                      {n.action === "recap" ? (
                        <span className="notif-item-recap">{I("recap", { size: 13 })} Daily recap published</span>
                      ) : (
                        <>
                          {n.ticker ? <span className="ticker">${n.ticker}</span> : <span className="notif-item-kind">{n.label}</span>}
                          <ActionPill kind={n.action} />
                          {n.contract && <span className="notif-item-ct mono">{n.contract}</span>}
                          {n.extra > 0 && <span className="notif-item-more mono">+{n.extra}</span>}
                        </>
                      )}
                    </span>
                    <span className="notif-item-r2 mono">{n.analyst ? `${n.analyst} · ` : ""}{fmtTime(n.ts, { short: true })}</span>
                  </span>
                </button>
              ))}
            </div>
          ) : (
            <div className="notif-settings">
              <div className="notif-set-sec">
                <div className="notif-set-row">
                  <div className="notif-set-txt">
                    <div className="notif-set-name">Push notifications</div>
                    <div className="notif-set-sub">Alerts even when the app is closed. Per-device. On iPhone: add to your Home Screen and open it from there first.</div>
                  </div>
                  <label className="switch" title="Browser notifications">
                    <input type="checkbox" checked={!!prefs.push} onChange={e => togglePush(e.target.checked)} />
                    <span className="switch-track"><span className="switch-thumb" /></span>
                  </label>
                </div>
                {prefs.push && typeof Notification !== "undefined" && Notification.permission === "denied" &&
                  <div className="notif-set-warn">Your browser is blocking notifications for this site — allow them in the address-bar site settings.</div>}
              </div>
              <div className="notif-set-sec">
                <div className="notif-set-lbl">Notify me about</div>
                {NOTIF_TRIGGERS.map(([k, label, desc]) => (
                  <label key={k} className="notif-trig">
                    <input type="checkbox" checked={!!prefs.triggers[k]} onChange={() => toggleTrigger(k)} />
                    <span className="notif-trig-box">{prefs.triggers[k] && I("check", { size: 12 })}</span>
                    <span className="notif-trig-txt"><strong>{label}</strong><span>{desc}</span></span>
                  </label>
                ))}
              </div>
              <div className="notif-set-foot">The in-app bell always works. Notifications follow the analysts you have enabled in Sources.</div>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

Object.assign(window, { NotificationBell, useNotifPrefs });
