/* ════════════════════════════════════════════════════════════════════════
   MSINDAHA Operations — NOTIFICATIONS
   One live listener per session feeds everything: the topbar bell, the
   sidebar badge, the browser-tab title, the Notifications page, toasts,
   the sound, and (opt-in) desktop notifications while the tab is hidden.
════════════════════════════════════════════════════════════════════════ */

const NOTIF_META = {
  assigned: { icon: IC.Target, tone: "blue", toast: "assign", label: "Assignments" },
  "new-ticket": { icon: IC.Ticket, tone: "blue", toast: "info", label: "New tickets" },
  comment: { icon: IC.Comment, tone: "purple", toast: "comment", label: "Comments" },
  status: { icon: IC.Activity, tone: "amber", toast: "info", label: "Status" },
  progress: { icon: IC.CheckCheck, tone: "green", toast: "success", label: "Status" },
  resolved: { icon: IC.CheckCircle, tone: "green", toast: "success", label: "Status" },
  completed: { icon: IC.CheckCircle, tone: "green", toast: "success", label: "Status" },
  info: { icon: IC.Bell, tone: "blue", toast: "info", label: "Other" },
};
const notifMeta = (type) => NOTIF_META[type] || NOTIF_META.info;
const NOTIF_GROUPS = {
  assignments: ["assigned", "new-ticket"],
  comments: ["comment"],
  status: ["status", "progress", "resolved", "completed"],
};

const NotifCtx = React.createContext(null);
const useNotifications = () => React.useContext(NotifCtx);

function NotificationsProvider({ uid, onOpenItem, children }) {
  const push = useToast();
  const [items, setItems] = React.useState([]);
  const [loaded, setLoaded] = React.useState(false);
  const [muted, setMuted] = useLocalStorage("msindaha_notif_muted", false);
  const [desktopOn, setDesktopOn] = useLocalStorage("msindaha_notif_desktop", false);
  const supported = "Notification" in window;
  const [perm, setPerm] = React.useState(() => (supported ? Notification.permission : "unsupported"));
  const [ringKey, setRingKey] = React.useState(0);
  const seenRef = React.useRef(null);
  const openRef = React.useRef(onOpenItem);
  openRef.current = onOpenItem;
  const prefsRef = React.useRef({});
  prefsRef.current = { muted, desktopOn, perm };

  const markRead = React.useCallback((id) => {
    const { db, doc, updateDoc } = window.__fb;
    return updateDoc(doc(db, "notifications", id), { read: true }).catch((e) =>
      console.error("markRead failed:", e),
    );
  }, []);

  const open = React.useCallback(
    (n) => {
      if (!n) return;
      if (!n.read) markRead(n._id);
      const tab =
        n.collName === "tickets" ? "tickets" : n.collName === "tasks" || n.collName === "notes" ? "tasks" : "notifications";
      openRef.current?.({ tab, id: n.collName === "notes" ? null : n.itemId, dept: n.dept });
    },
    [markRead],
  );
  const openRefFn = React.useRef(open);
  openRefFn.current = open;

  const announce = React.useCallback(
    (fresh) => {
      const { muted, desktopOn, perm } = prefsRef.current;
      if (!muted) playNotifySound();
      setRingKey((k) => k + 1);
      fresh.slice(0, 3).forEach((n) => {
        push({
          type: notifMeta(n.type).toast,
          title: n.title,
          msg: n.msg,
          duration: 8000,
          action: n.itemId ? { label: "Open", onClick: () => openRefFn.current(n) } : null,
        });
      });
      if (fresh.length > 3)
        push({
          type: "bell",
          title: `${fresh.length - 3} more new notifications`,
          action: { label: "View all", onClick: () => openRef.current?.({ tab: "notifications" }) },
        });
      const backgrounded = document.visibilityState !== "visible" || !document.hasFocus();
      if (desktopOn && perm === "granted" && backgrounded) {
        fresh.slice(0, 3).forEach((n) => {
          try {
            const dn = new Notification(n.title, {
              body: n.msg || "",
              tag: n._id,
              icon: "imgs/Logowhite.png",
            });
            dn.onclick = () => {
              window.focus();
              openRefFn.current(n);
              dn.close();
            };
          } catch (e) {
            console.warn("Desktop notification failed:", e);
          }
        });
      }
    },
    [push],
  );

  React.useEffect(() => {
    if (!uid) return;
    const { db, collection, query, where, orderBy, limit, onSnapshot } = window.__fb;
    seenRef.current = null;
    return onSnapshot(
      query(
        collection(db, "notifications"),
        where("recipientUid", "==", uid),
        orderBy("createdAt", "desc"),
        limit(100),
      ),
      (snap) => {
        const rows = snap.docs.map((d) => ({ _id: d.id, ...d.data() }));
        setItems(rows);
        setLoaded(true);
        // First snapshot = history, never announced (no replay on reload).
        if (seenRef.current === null) {
          seenRef.current = new Set(rows.map((r) => r._id));
          return;
        }
        const recent = Date.now() - 10 * 60000;
        const fresh = rows.filter(
          (n) => !seenRef.current.has(n._id) && !n.read && new Date(n.createdAt).getTime() > recent,
        );
        rows.forEach((r) => seenRef.current.add(r._id));
        if (fresh.length) announce(fresh);
      },
      (err) => {
        console.error("notifications snapshot error:", err);
        setLoaded(true);
      },
    );
  }, [uid]);

  const unreadItems = React.useMemo(() => items.filter((n) => !n.read), [items]);
  const unread = unreadItems.length;

  // "(3) MSINDAHA — Payroll" in the browser tab.
  React.useEffect(() => {
    const base = document.title.replace(/^\(\d+\+?\)\s*/, "");
    document.title = unread ? `(${unread > 99 ? "99+" : unread}) ${base}` : base;
  }, [unread]);

  const markAllRead = React.useCallback(async () => {
    const { db, doc, writeBatch, updateDoc } = window.__fb;
    const list = items.filter((n) => !n.read);
    if (!list.length) return;
    try {
      if (writeBatch) {
        for (let i = 0; i < list.length; i += 400) {
          const batch = writeBatch(db);
          list.slice(i, i + 400).forEach((n) => batch.update(doc(db, "notifications", n._id), { read: true }));
          await batch.commit();
        }
      } else await Promise.all(list.map((n) => updateDoc(doc(db, "notifications", n._id), { read: true })));
    } catch (e) {
      push({ type: "error", title: "Couldn't mark all as read", msg: writeErrorMsg(e) });
    }
  }, [items, push]);

  const remove = React.useCallback(
    (id) => {
      const { db, doc, deleteDoc } = window.__fb;
      return deleteDoc(doc(db, "notifications", id)).catch((e) =>
        push({ type: "error", title: "Couldn't remove notification", msg: writeErrorMsg(e) }),
      );
    },
    [push],
  );

  const toggleDesktop = React.useCallback(
    async (on) => {
      if (!on) return setDesktopOn(false);
      if (!supported) {
        push({ type: "warning", title: "Not supported", msg: "This browser doesn't support desktop notifications." });
        return;
      }
      let p = Notification.permission;
      if (p !== "granted") p = await Notification.requestPermission();
      setPerm(p);
      if (p === "granted") {
        setDesktopOn(true);
        push({ type: "success", title: "Desktop alerts are on", msg: "You'll be alerted even when this tab is in the background." });
      } else {
        setDesktopOn(false);
        push({
          type: "warning",
          title: "Notifications are blocked",
          msg: "Allow notifications for this site from the padlock icon in the address bar, then try again.",
          duration: 9000,
        });
      }
    },
    [supported, push],
  );

  const value = {
    items,
    loaded,
    unread,
    unreadItems,
    markRead,
    markAllRead,
    remove,
    open,
    muted,
    setMuted,
    desktopOn: desktopOn && perm === "granted",
    toggleDesktop,
    perm,
    supported,
    ringKey,
  };
  return <NotifCtx.Provider value={value}>{children}</NotifCtx.Provider>;
}

// Groups notifications under "Today" / "Yesterday" / "Mon, Oct 5".
function groupByDay(list) {
  const groups = [];
  list.forEach((n) => {
    const day = localDateOf(n.createdAt) || "unknown";
    let g = groups[groups.length - 1];
    if (!g || g.day !== day) {
      const diff = day === "unknown" ? null : daysBetween(todayISO(), day);
      const label =
        diff === 0
          ? "Today"
          : diff === -1
            ? "Yesterday"
            : day === "unknown"
              ? "Earlier"
              : parseISODate(day).toLocaleDateString("en-US", { weekday: "long", month: "short", day: "numeric" });
      g = { day, label, items: [] };
      groups.push(g);
    }
    g.items.push(n);
  });
  return groups;
}

function NotifRow({ n, onOpen, onRemove }) {
  const meta = notifMeta(n.type);
  const Icon = meta.icon;
  return (
    <div
      className={cx("notif-item", !n.read && "unread")}
      role="button"
      tabIndex={0}
      onClick={() => onOpen(n)}
      onKeyDown={(e) => {
        if (e.key === "Enter") onOpen(n);
      }}
    >
      <span className="notif-avatar-wrap">
        {n.actorName || n.actorEmail ? (
          <Avatar name={n.actorName} email={n.actorEmail} size={34} title="" />
        ) : (
          <span className={cx("notif-item-icon", `tone-${meta.tone}`)}>
            <Icon />
          </span>
        )}
        <span className={cx("notif-type-badge", `tone-${meta.tone}`)}>
          <Icon />
        </span>
      </span>
      <div className="notif-item-body">
        <div className="notif-item-title">{n.title}</div>
        {n.msg && <div className="notif-item-msg">{n.msg}</div>}
        <div className="notif-item-time">
          {timeAgo(n.createdAt)}
          {n.actorName ? ` · ${n.actorName}` : ""}
        </div>
      </div>
      {!n.read && <span className="notif-item-dot" aria-label="Unread" />}
      {onRemove && (
        <button
          className="icon-btn sm notif-remove"
          title="Remove"
          aria-label="Remove notification"
          onClick={(e) => {
            e.stopPropagation();
            onRemove(n._id);
          }}
        >
          <IC.X />
        </button>
      )}
    </div>
  );
}

function DesktopAlertPrompt() {
  const n = useNotifications();
  const [dismissed, setDismissed] = useLocalStorage("msindaha_notif_prompt_dismissed", false);
  if (!n.supported || n.desktopOn || n.perm === "denied" || dismissed) return null;
  return (
    <div className="notif-prompt">
      <IC.Monitor />
      <div>
        <strong>Get desktop alerts</strong>
        <span>Be notified even when this tab is in the background.</span>
      </div>
      <button className="btn btn-primary btn-sm" onClick={() => n.toggleDesktop(true)}>
        Enable
      </button>
      <button className="icon-btn sm" aria-label="Dismiss" onClick={() => setDismissed(true)}>
        <IC.X />
      </button>
    </div>
  );
}

function NotificationBell() {
  const n = useNotifications();
  const { navigate } = useApp();
  const [open, setOpen] = React.useState(false);
  const [filter, setFilter] = React.useState("all");
  const [ringing, setRinging] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (!n.ringKey) return;
    setRinging(true);
    const t = setTimeout(() => setRinging(false), 900);
    return () => clearTimeout(t);
  }, [n.ringKey]);
  const list = (filter === "unread" ? n.unreadItems : n.items).slice(0, 40);
  const openItem = (item) => {
    setOpen(false);
    n.open(item);
  };
  return (
    <>
      <button
        ref={ref}
        className={cx("icon-btn topbar-btn notif-bell-btn", ringing && "ringing", open && "active")}
        onClick={() => setOpen((o) => !o)}
        aria-label={`Notifications${n.unread ? ` (${n.unread} unread)` : ""}`}
        title="Notifications"
      >
        <IC.Bell />
        {n.unread > 0 && <span className="notif-badge">{n.unread > 99 ? "99+" : n.unread}</span>}
      </button>
      <Popover
        open={open}
        anchorRef={ref}
        onClose={() => setOpen(false)}
        width={392}
        placement="bottom-end"
        className="notif-pop"
      >
        <div className="notif-panel-head">
          <div className="notif-panel-title">
            <strong>Notifications</strong>
            {n.unread > 0 && <span className="pill-count">{n.unread} new</span>}
          </div>
          <div className="notif-panel-actions">
            <button
              className="icon-btn sm"
              title={n.muted ? "Sound off — click to unmute" : "Sound on — click to mute"}
              aria-label={n.muted ? "Unmute sound" : "Mute sound"}
              onClick={() => n.setMuted((m) => !m)}
            >
              {n.muted ? <IC.VolumeX /> : <IC.Volume />}
            </button>
            {n.unread > 0 && (
              <button className="link-btn" onClick={n.markAllRead}>
                <IC.CheckCheck /> Mark all read
              </button>
            )}
          </div>
        </div>
        <div className="notif-filter">
          <Segmented
            size="sm"
            value={filter}
            onChange={setFilter}
            options={[
              { value: "all", label: "All" },
              { value: "unread", label: "Unread", count: n.unread || null },
            ]}
          />
        </div>
        <DesktopAlertPrompt />
        <div className="notif-list">
          {!n.loaded ? (
            <div className="notif-loading">
              <Spinner />
            </div>
          ) : list.length === 0 ? (
            <EmptyState compact icon={IC.Bell} title={filter === "unread" ? "No unread notifications" : "You're all caught up"}>
              Assignments, comments and status changes that involve you show up here.
            </EmptyState>
          ) : (
            groupByDay(list).map((g) => (
              <React.Fragment key={g.day}>
                <div className="notif-group">{g.label}</div>
                {g.items.map((item) => (
                  <NotifRow key={item._id} n={item} onOpen={openItem} />
                ))}
              </React.Fragment>
            ))
          )}
        </div>
        <div className="notif-panel-foot">
          <button
            className="link-btn"
            onClick={() => {
              setOpen(false);
              navigate("notifications");
            }}
          >
            View all notifications <IC.ArrowRight />
          </button>
        </div>
      </Popover>
    </>
  );
}

function NotificationsPage() {
  const n = useNotifications();
  const [filter, setFilter] = useLocalStorage("msindaha_notif_filter", "all");
  const counts = {
    all: n.items.length,
    unread: n.unread,
    assignments: n.items.filter((x) => NOTIF_GROUPS.assignments.includes(x.type)).length,
    comments: n.items.filter((x) => NOTIF_GROUPS.comments.includes(x.type)).length,
    status: n.items.filter((x) => NOTIF_GROUPS.status.includes(x.type)).length,
  };
  const list = n.items.filter((x) => {
    if (filter === "unread") return !x.read;
    if (NOTIF_GROUPS[filter]) return NOTIF_GROUPS[filter].includes(x.type);
    return true;
  });
  return (
    <div className="page-notifications">
      <div className="page-header">
        <div>
          <h2>Notifications</h2>
          <p>Everything assigned to you, commented on, or updated on items you're part of.</p>
        </div>
        <div className="page-header-actions">
          <button className="btn" onClick={n.markAllRead} disabled={!n.unread}>
            <IC.CheckCheck /> Mark all as read
          </button>
        </div>
      </div>

      <div className="notif-page-layout">
        <div className="card card-flush">
          <div className="card-toolbar">
            <Segmented
              value={filter}
              onChange={setFilter}
              options={[
                { value: "all", label: "All", count: counts.all },
                { value: "unread", label: "Unread", count: counts.unread },
                { value: "assignments", label: "Assignments", count: counts.assignments },
                { value: "comments", label: "Comments", count: counts.comments },
                { value: "status", label: "Status", count: counts.status },
              ]}
            />
          </div>
          <div className="notif-page-list">
            {!n.loaded ? (
              <div className="notif-loading">
                <Spinner />
              </div>
            ) : list.length === 0 ? (
              <EmptyState icon={IC.Bell} title="Nothing here">
                {filter === "unread" ? "You've read everything." : "No notifications match this filter."}
              </EmptyState>
            ) : (
              groupByDay(list).map((g) => (
                <React.Fragment key={g.day}>
                  <div className="notif-group">{g.label}</div>
                  {g.items.map((item) => (
                    <NotifRow key={item._id} n={item} onOpen={n.open} onRemove={n.remove} />
                  ))}
                </React.Fragment>
              ))
            )}
          </div>
          {n.items.length >= 100 && <div className="card-foot muted">Showing your latest 100 notifications.</div>}
        </div>

        <div className="notif-settings">
          <div className="card">
            <div className="section-head">
              <IC.Gear /> Alert settings
            </div>
            <div className="setting-row">
              <div>
                <div className="setting-title">Sound</div>
                <div className="setting-desc">Play a short chime when something new arrives.</div>
              </div>
              <Switch checked={!n.muted} onChange={(on) => n.setMuted(!on)} label="Notification sound" />
            </div>
            <div className="setting-row">
              <div>
                <div className="setting-title">Desktop alerts</div>
                <div className="setting-desc">
                  {n.perm === "denied"
                    ? "Blocked by your browser — allow notifications for this site from the address bar."
                    : !n.supported
                      ? "Not supported by this browser."
                      : "Pop-up alerts while this tab is in the background."}
                </div>
              </div>
              <Switch
                checked={n.desktopOn}
                onChange={n.toggleDesktop}
                disabled={!n.supported || n.perm === "denied"}
                label="Desktop alerts"
              />
            </div>
          </div>
          <div className="card">
            <div className="section-head">
              <IC.Info /> You're notified when
            </div>
            <ul className="notif-rules">
              <li>A ticket or task is assigned to you or your department</li>
              <li>Someone comments on an item you created, own or are assigned to</li>
              <li>The status of your ticket or task changes</li>
              <li>A new unassigned ticket needs triage (supervisors &amp; admins)</li>
            </ul>
          </div>
        </div>
      </div>
    </div>
  );
}
