"use client";

import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";
import { Icon } from "@/components/ui/Icon";
import { usePopover } from "@/lib/ui/popover";
import {
  installAudioUnlock,
  notifyPermissionStore,
  playPing,
  requestNotifyPermission,
  setSoundEnabled,
  showDesktopNotification,
  soundStore,
} from "@/lib/ui/sound";
import { markAllReadAction } from "./notification-actions";

type Item = {
  id: string;
  type: string;
  title: string;
  body: string | null;
  link: string | null;
  read: boolean;
  createdAt: string;
};

const POLL_MS = 15000;

function timeAgo(iso: string): string {
  const diff = Date.now() - new Date(iso).getTime();
  const min = Math.floor(diff / 60000);
  if (min < 1) return "gerade eben";
  if (min < 60) return `vor ${min} Min`;
  const h = Math.floor(min / 60);
  if (h < 24) return `vor ${h} Std`;
  return `vor ${Math.floor(h / 24)} T`;
}

export function NotificationsButton() {
  const { open, toggle, ref } = usePopover("notifications");
  const [items, setItems] = useState<Item[]>([]);
  const [unread, setUnread] = useState(0);
  const [loading, setLoading] = useState(false);
  const sound = useSyncExternalStore(
    soundStore.subscribe,
    soundStore.get,
    soundStore.getServerSnapshot,
  );
  const permission = useSyncExternalStore(
    notifyPermissionStore.subscribe,
    notifyPermissionStore.get,
    notifyPermissionStore.getServerSnapshot,
  );

  // Bereits gemeldete Benachrichtigungen (id → Zeitstempel). Der Zeitstempel
  // ist nötig, weil Chat-Meldungen je Kanal zusammengefasst und dabei
  // aktualisiert statt neu angelegt werden.
  const seenRef = useRef<Map<string, string> | null>(null);

  const load = useCallback(async () => {
    try {
      setLoading(true);
      const res = await fetch("/api/notifications", { cache: "no-store" });
      if (!res.ok) return;
      const data = (await res.json()) as { items: Item[]; unread: number };
      setItems(data.items);
      setUnread(data.unread);

      // Erster Lauf: nur merken, nicht melden (sonst pingt jeder Seitenaufruf).
      const seen = seenRef.current;
      const fresh = seen
        ? data.items.filter((n) => !n.read && seen.get(n.id) !== n.createdAt)
        : [];
      seenRef.current = new Map(data.items.map((n) => [n.id, n.createdAt]));

      if (fresh.length > 0) {
        void playPing();
        // Nur die neueste als Desktop-Hinweis, sonst wird der Bildschirm geflutet.
        const newest = fresh[0];
        showDesktopNotification(
          fresh.length > 1 ? `${fresh.length} neue Benachrichtigungen` : newest.title,
          {
            body: fresh.length > 1 ? newest.title : (newest.body ?? undefined),
            link: newest.link,
            tag: newest.link ?? newest.id,
          },
        );
      }
    } catch {
      /* Netzfehler — beim nächsten Intervall erneut */
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    const removeUnlock = installAudioUnlock();

    void load();
    const id = setInterval(load, POLL_MS);
    // Nach Tab-Wechsel sofort aktualisieren statt bis zum Intervall zu warten.
    const onVisible = () => {
      if (document.visibilityState === "visible") void load();
    };
    document.addEventListener("visibilitychange", onVisible);
    window.addEventListener("focus", onVisible);

    return () => {
      clearInterval(id);
      document.removeEventListener("visibilitychange", onVisible);
      window.removeEventListener("focus", onVisible);
      removeUnlock();
    };
  }, [load]);

  useEffect(() => {
    if (open) void load();
  }, [open, load]);

  const markAll = async () => {
    await markAllReadAction();
    setUnread(0);
    setItems((prev) => prev.map((i) => ({ ...i, read: true })));
  };

  const toggleSound = () => {
    const next = !sound;
    setSoundEnabled(next);
    if (next) void playPing();
  };

  const askPermission = () => {
    void requestNotifyPermission();
  };

  return (
    <div className="relative" ref={ref}>
      <button
        className="btn btn-ghost btn-sm relative"
        aria-label="Benachrichtigungen"
        aria-haspopup="menu"
        aria-expanded={open}
        onClick={toggle}
      >
        <Icon name="bell" size={16} />
        {unread > 0 ? (
          <span
            className="absolute -right-1 -top-1 grid h-[18px] min-w-[18px] place-items-center rounded-full px-1 text-[10px] font-black"
            style={{ background: "var(--grad-brand)", color: "#0a0e22" }}
          >
            {unread > 9 ? "9+" : unread}
          </span>
        ) : null}
      </button>

      {open ? (
        <div role="menu" className="card-sm pop-solid animate-in absolute right-0 mt-2 w-80" style={{ zIndex: 60 }}>
          <div className="mb-2 flex items-center justify-between gap-2 px-1">
            <span className="font-bold">Benachrichtigungen</span>
            <div className="flex items-center gap-2">
              <button
                className="text-muted transition-colors hover:text-[color:var(--blue)]"
                onClick={toggleSound}
                aria-label={sound ? "Ton ausschalten" : "Ton einschalten"}
                title={sound ? "Ton ausschalten" : "Ton einschalten"}
              >
                <Icon name={sound ? "bell" : "close"} size={15} />
              </button>
              {unread > 0 ? (
                <button
                  className="text-xs font-bold text-muted transition-colors hover:text-[color:var(--blue)]"
                  onClick={markAll}
                >
                  Alle gelesen
                </button>
              ) : null}
            </div>
          </div>

          {permission === "default" ? (
            <button
              className="mb-2 w-full rounded-xl px-2 py-1.5 text-left text-xs font-bold transition-colors hover:bg-[rgba(158,215,255,.06)]"
              style={{ color: "var(--blue)" }}
              onClick={askPermission}
            >
              Desktop-Benachrichtigungen aktivieren
            </button>
          ) : null}

          <div className="flex max-h-[340px] flex-col gap-1 overflow-y-auto">
            {loading && items.length === 0 ? (
              <p className="text-muted px-1 py-6 text-center text-sm">Lädt …</p>
            ) : items.length === 0 ? (
              <p className="text-muted px-1 py-6 text-center text-sm">Keine Benachrichtigungen.</p>
            ) : (
              items.map((n) => {
                const inner = (
                  <div className="flex items-start gap-2">
                    <span
                      className="mt-1.5 inline-block h-2 w-2 shrink-0 rounded-full"
                      style={{ background: n.read ? "transparent" : "var(--blue)" }}
                    />
                    <div className="min-w-0">
                      <div className="truncate text-sm font-bold">{n.title}</div>
                      {n.body ? <div className="truncate text-xs text-muted">{n.body}</div> : null}
                      <div className="mt-0.5 text-[11px] text-muted">{timeAgo(n.createdAt)}</div>
                    </div>
                  </div>
                );
                return n.link ? (
                  <a key={n.id} href={n.link} className="rounded-xl px-2 py-2 transition-colors hover:bg-[rgba(158,215,255,.06)]">
                    {inner}
                  </a>
                ) : (
                  <div key={n.id} className="rounded-xl px-2 py-2">
                    {inner}
                  </div>
                );
              })
            )}
          </div>
        </div>
      ) : null}
    </div>
  );
}
