/**
 * <rstack-notification-bell> — Notification bell with dropdown panel.
 *
 * Shows unread count badge over a bell icon. Click opens a dropdown
 * with the notification list. Listens for real-time WS events and
 * polls /api/notifications/count as a fallback.
 */

import { getSession } from "./rstack-identity";

const POLL_INTERVAL = 30_000; // 30s fallback poll

interface NotificationItem {
  id: string;
  category: string;
  eventType: string;
  title: string;
  body: string | null;
  spaceSlug: string | null;
  actorUsername: string | null;
  actionUrl: string | null;
  metadata: Record<string, any>;
  createdAt: string;
  read: boolean;
}

export class RStackNotificationBell extends HTMLElement {
  #shadow: ShadowRoot;
  #unreadCount = 0;
  #notifications: NotificationItem[] = [];
  #pollTimer: ReturnType<typeof setInterval> | null = null;
  #open = false;
  #loading = false;
  #pushSupported = false;
  #pushSubscribed = false;

  constructor() {
    super();
    this.#shadow = this.attachShadow({ mode: "open" });
  }

  connectedCallback() {
    this.#render();
    this.#fetchCount();
    this.#pollTimer = setInterval(() => this.#fetchCount(), POLL_INTERVAL);

    // Listen for real-time WS notifications
    window.addEventListener("rspace-notification", this.#onWsNotification as EventListener);

    // Re-render on auth change
    document.addEventListener("auth-change", this.#onAuthChange);

    // Check push support
    this.#pushSupported = "PushManager" in window && "serviceWorker" in navigator;
    if (this.#pushSupported) this.#checkPushState();
  }

  disconnectedCallback() {
    if (this.#pollTimer) clearInterval(this.#pollTimer);
    window.removeEventListener("rspace-notification", this.#onWsNotification as EventListener);
    document.removeEventListener("auth-change", this.#onAuthChange);
  }

  #onAuthChange = () => {
    this.#unreadCount = 0;
    this.#notifications = [];
    this.#open = false;
    this.#render();
    this.#fetchCount();
    // Restart poll timer if it was stopped by a 401
    if (!this.#pollTimer) {
      this.#pollTimer = setInterval(() => this.#fetchCount(), POLL_INTERVAL);
    }
    // Auto-subscribe to push if permission already granted
    if (this.#pushSupported && Notification.permission === "granted" && !this.#pushSubscribed) {
      this.#subscribePush();
    }
  };

  #onWsNotification = (e: CustomEvent) => {
    const data = e.detail;
    if (!data?.notification) return;

    this.#unreadCount = data.unreadCount ?? this.#unreadCount + 1;
    this.#updateAppBadge(this.#unreadCount);
    // Prepend to list if panel is loaded
    this.#notifications.unshift({
      ...data.notification,
      read: false,
    });
    this.#render();
  };

  #getToken(): string | null {
    const session = getSession();
    return session?.accessToken ?? null;
  }

  async #fetchCount() {
    const token = this.#getToken();
    if (!token) {
      this.#unreadCount = 0;
      this.#render();
      return;
    }

    try {
      const res = await fetch("/api/notifications/count", {
        headers: { Authorization: `Bearer ${token}` },
      });
      if (res.ok) {
        const data = await res.json();
        this.#unreadCount = data.unreadCount || 0;
        this.#updateAppBadge(this.#unreadCount);
        this.#render();
      } else if (res.status === 401) {
        // Token rejected — stop polling until auth changes
        this.#unreadCount = 0;
        if (this.#pollTimer) { clearInterval(this.#pollTimer); this.#pollTimer = null; }
        this.#render();
      }
    } catch {
      // Silently fail
    }
  }

  async #fetchNotifications() {
    const token = this.#getToken();
    if (!token) return;

    this.#loading = true;
    this.#render();

    try {
      const res = await fetch("/api/notifications?limit=20", {
        headers: { Authorization: `Bearer ${token}` },
      });
      if (res.ok) {
        const data = await res.json();
        this.#notifications = data.notifications || [];
      }
    } catch {
      // Silently fail
    }
    this.#loading = false;
    this.#render();
  }

  async #markRead(id: string) {
    const token = this.#getToken();
    if (!token) return;

    try {
      await fetch(`/api/notifications/${id}/read`, {
        method: "PATCH",
        headers: { Authorization: `Bearer ${token}` },
      });
    } catch {
      // Silently fail
    }

    const n = this.#notifications.find(n => n.id === id);
    if (n && !n.read) {
      n.read = true;
      this.#unreadCount = Math.max(0, this.#unreadCount - 1);
      this.#render();
    }
  }

  async #markAllRead() {
    const token = this.#getToken();
    if (!token) return;

    try {
      await fetch("/api/notifications/read-all", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${token}`,
          "Content-Type": "application/json",
        },
        body: "{}",
      });
    } catch {
      // Silently fail
    }

    this.#notifications.forEach(n => n.read = true);
    this.#unreadCount = 0;
    this.#updateAppBadge(0);
    this.#render();
  }

  async #dismiss(id: string) {
    const token = this.#getToken();
    if (!token) return;

    try {
      await fetch(`/api/notifications/${id}`, {
        method: "DELETE",
        headers: { Authorization: `Bearer ${token}` },
      });
    } catch {
      // Silently fail
    }

    const idx = this.#notifications.findIndex(n => n.id === id);
    if (idx >= 0) {
      const n = this.#notifications[idx];
      if (!n.read) this.#unreadCount = Math.max(0, this.#unreadCount - 1);
      this.#notifications.splice(idx, 1);
      this.#render();
    }
  }

  async #acceptCommitment(notifId: string, spaceSlug: string, resultId: string) {
    const token = this.#getToken();
    if (!token) return;

    try {
      const res = await fetch(`https://${spaceSlug}.rspace.online/rtime/api/solver-results/${resultId}/accept`, {
        method: "POST",
        headers: { Authorization: `Bearer ${token}` },
      });
      if (res.ok) {
        await this.#markRead(notifId);
      } else {
        const err = await res.json().catch(() => ({ error: "Failed to accept" }));
        console.error("[commitment] accept failed:", (err as any).error);
      }
    } catch (e) {
      console.error("[commitment] accept error:", e);
    }
  }

  async #declineCommitment(notifId: string, spaceSlug: string, resultId: string) {
    const token = this.#getToken();
    if (!token) return;

    try {
      const res = await fetch(`https://${spaceSlug}.rspace.online/rtime/api/solver-results/${resultId}/reject`, {
        method: "POST",
        headers: { Authorization: `Bearer ${token}` },
      });
      if (res.ok) {
        await this.#markRead(notifId);
      } else {
        const err = await res.json().catch(() => ({ error: "Failed to decline" }));
        console.error("[commitment] decline failed:", (err as any).error);
      }
    } catch (e) {
      console.error("[commitment] decline error:", e);
    }
  }

  async #acceptInvite(notifId: string, spaceSlug: string, inviteToken: string) {
    const token = this.#getToken();
    if (!token) return;

    try {
      const res = await fetch(`/api/spaces/${spaceSlug}/invite/accept`, {
        method: "POST",
        headers: {
          Authorization: `Bearer ${token}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ inviteToken }),
      });
      if (res.ok) {
        await this.#markRead(notifId);
        // Navigate to the space
        window.location.href = `https://${spaceSlug}.rspace.online`;
      } else {
        const err = await res.json().catch(() => ({ error: "Failed to accept" }));
        console.error("[invite] accept failed:", (err as any).error);
      }
    } catch (e) {
      console.error("[invite] accept error:", e);
    }
  }

  // ── Push subscription ──

  async #checkPushState() {
    if (!this.#pushSupported) return;
    try {
      const reg = await navigator.serviceWorker.ready;
      const sub = await reg.pushManager.getSubscription();
      this.#pushSubscribed = !!sub;
    } catch {
      this.#pushSubscribed = false;
    }
  }

  async #subscribePush() {
    if (!this.#pushSupported) return;
    try {
      const permission = await Notification.requestPermission();
      if (permission !== "granted") return;

      // Fetch VAPID public key
      const keyRes = await fetch("/api/notifications/push/vapid-public-key");
      if (!keyRes.ok) return;
      const { publicKey } = await keyRes.json();
      if (!publicKey) return;

      const reg = await navigator.serviceWorker.ready;
      const sub = await reg.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: this.#urlBase64ToUint8Array(publicKey) as BufferSource,
      });

      const token = this.#getToken();
      if (!token) return;

      await fetch("/api/notifications/push/subscribe", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${token}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify(sub.toJSON()),
      });

      this.#pushSubscribed = true;
      this.#render();
    } catch (err) {
      console.warn("[push] subscribe failed:", err);
    }
  }

  #urlBase64ToUint8Array(base64String: string): Uint8Array {
    const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
    const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
    const raw = atob(base64);
    const arr = new Uint8Array(raw.length);
    for (let i = 0; i < raw.length; i++) arr[i] = raw.charCodeAt(i);
    return arr;
  }

  // ── App badge ──

  #updateAppBadge(count: number) {
    if ("setAppBadge" in navigator) {
      if (count > 0) {
        (navigator as any).setAppBadge(count);
      } else {
        (navigator as any).clearAppBadge();
      }
    }
  }

  /** Public toggle — called from identity dropdown on mobile */
  toggle() { this.#togglePanel(); }

  #togglePanel() {
    this.#open = !this.#open;
    if (this.#open && this.#notifications.length === 0) {
      this.#fetchNotifications();
    }
    this.#render();
  }

  #categoryIcon(category: string): string {
    switch (category) {
      case "space": return "🏠";
      case "module": return "📦";
      case "system": return "🔐";
      case "social": return "💬";
      default: return "🔔";
    }
  }

  #timeAgo(iso: string): string {
    const diff = Date.now() - new Date(iso).getTime();
    const mins = Math.floor(diff / 60_000);
    if (mins < 1) return "just now";
    if (mins < 60) return `${mins}m ago`;
    const hrs = Math.floor(mins / 60);
    if (hrs < 24) return `${hrs}h ago`;
    const days = Math.floor(hrs / 24);
    return `${days}d ago`;
  }

  #render() {
    const session = getSession();
    if (!session) {
      this.#shadow.innerHTML = "";
      return;
    }

    const badge = this.#unreadCount > 0
      ? `<span class="badge">${this.#unreadCount > 99 ? "99+" : this.#unreadCount}</span>`
      : "";

    let panelHTML = "";
    if (this.#open) {
      const pushBtn = this.#pushSupported && !this.#pushSubscribed && Notification.permission !== "denied"
        ? `<button class="push-btn" data-action="enable-push">Enable push</button>`
        : "";
      const header = `
        <div class="panel-header">
          <span class="panel-title">Notifications</span>
          <div class="panel-actions">
            ${pushBtn}
            ${this.#unreadCount > 0 ? `<button class="mark-all-btn" data-action="mark-all-read">Mark all read</button>` : ""}
          </div>
        </div>
      `;

      let body: string;
      if (this.#loading) {
        body = `<div class="panel-empty">Loading...</div>`;
      } else if (this.#notifications.length === 0) {
        body = `<div class="panel-empty">No notifications yet</div>`;
      } else {
        body = this.#notifications.map(n => {
          const isInvite = n.eventType === "space_invite" && n.metadata?.inviteToken && !n.read;
          const isCommitment = n.eventType === "commitment_requested" && n.metadata?.resultId && !n.read;
          const hasActions = isInvite || isCommitment;
          const inviteButtons = isInvite ? `
            <div class="notif-actions">
              <button class="notif-accept" data-accept="${n.id}" data-space="${n.spaceSlug}" data-token="${n.metadata.inviteToken}">Accept</button>
              <button class="notif-decline" data-decline="${n.id}">Decline</button>
            </div>` : "";
          const commitmentButtons = isCommitment ? `
            <div class="notif-actions">
              <button class="notif-accept" data-commit-accept="${n.id}" data-space="${n.spaceSlug}" data-result="${n.metadata.resultId}">Accept</button>
              <button class="notif-decline" data-commit-decline="${n.id}" data-space="${n.spaceSlug}" data-result="${n.metadata.resultId}">Decline</button>
            </div>` : "";
          return `
          <div class="notif-item ${n.read ? "read" : "unread"} ${hasActions ? "invite" : ""}" data-id="${n.id}" ${hasActions ? 'data-no-nav="true"' : ""}>
            <div class="notif-icon">${this.#categoryIcon(n.category)}</div>
            <div class="notif-content">
              <div class="notif-title">${n.title}</div>
              ${n.body ? `<div class="notif-body">${n.body}</div>` : ""}
              ${inviteButtons}
              ${commitmentButtons}
              <div class="notif-meta">
                ${n.actorUsername ? `<span class="notif-actor">${n.actorUsername}</span>` : ""}
                <span class="notif-time">${this.#timeAgo(n.createdAt)}</span>
              </div>
            </div>
            <button class="notif-dismiss" data-dismiss="${n.id}" title="Dismiss">&times;</button>
          </div>`;
        }).join("");
      }

      panelHTML = `<div class="panel">${header}${body}</div>`;
    }

    this.#shadow.innerHTML = `
      <style>${STYLES}</style>
      <div class="bell-wrapper">
        <button class="bell-btn" id="bell-toggle" aria-label="Notifications">
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
            <path d="M13.73 21a2 2 0 0 1-3.46 0"/>
          </svg>
          ${badge}
        </button>
        ${panelHTML}
      </div>
    `;

    // ── Event listeners ──
    const toggleBtn = this.#shadow.getElementById("bell-toggle");
    toggleBtn?.addEventListener("click", (e) => {
      e.stopPropagation();
      this.#togglePanel();
    });

    // Close on outside click
    const closeHandler = () => {
      if (this.#open) {
        this.#open = false;
        this.#render();
      }
    };
    document.addEventListener("pointerdown", closeHandler, { once: true });

    // Stop propagation from panel clicks
    this.#shadow.querySelector(".panel")?.addEventListener("pointerdown", (e) => e.stopPropagation());

    // Mark all read
    this.#shadow.querySelector('[data-action="mark-all-read"]')?.addEventListener("click", (e) => {
      e.stopPropagation();
      this.#markAllRead();
    });

    // Enable push
    this.#shadow.querySelector('[data-action="enable-push"]')?.addEventListener("click", (e) => {
      e.stopPropagation();
      this.#subscribePush();
    });

    // Notification item clicks (mark read + navigate, skip for invites)
    this.#shadow.querySelectorAll(".notif-item").forEach((el) => {
      const id = (el as HTMLElement).dataset.id!;
      const noNav = (el as HTMLElement).dataset.noNav === "true";
      el.addEventListener("click", (e) => {
        e.stopPropagation();
        if (noNav) return; // invite items handle via buttons
        this.#markRead(id);
        const n = this.#notifications.find(n => n.id === id);
        if (n?.actionUrl) {
          window.location.href = n.actionUrl;
        }
      });
    });

    // Accept invite buttons
    this.#shadow.querySelectorAll("[data-accept]").forEach((btn) => {
      const el = btn as HTMLElement;
      btn.addEventListener("click", (e) => {
        e.stopPropagation();
        this.#acceptInvite(el.dataset.accept!, el.dataset.space!, el.dataset.token!);
      });
    });

    // Decline invite buttons
    this.#shadow.querySelectorAll("[data-decline]").forEach((btn) => {
      const id = (btn as HTMLElement).dataset.decline!;
      btn.addEventListener("click", (e) => {
        e.stopPropagation();
        this.#dismiss(id);
      });
    });

    // Accept commitment buttons
    this.#shadow.querySelectorAll("[data-commit-accept]").forEach((btn) => {
      const el = btn as HTMLElement;
      btn.addEventListener("click", (e) => {
        e.stopPropagation();
        this.#acceptCommitment(el.dataset.commitAccept!, el.dataset.space!, el.dataset.result!);
      });
    });

    // Decline commitment buttons
    this.#shadow.querySelectorAll("[data-commit-decline]").forEach((btn) => {
      const el = btn as HTMLElement;
      btn.addEventListener("click", (e) => {
        e.stopPropagation();
        this.#declineCommitment(el.dataset.commitDecline!, el.dataset.space!, el.dataset.result!);
      });
    });

    // Dismiss buttons
    this.#shadow.querySelectorAll(".notif-dismiss").forEach((btn) => {
      const id = (btn as HTMLElement).dataset.dismiss!;
      btn.addEventListener("click", (e) => {
        e.stopPropagation();
        this.#dismiss(id);
      });
    });
  }

  static define(tag = "rstack-notification-bell") {
    if (!customElements.get(tag)) customElements.define(tag, RStackNotificationBell);
  }
}

// ============================================================================
// STYLES
// ============================================================================

const STYLES = `
:host {
  display: inline-flex;
  align-items: center;
}

.bell-wrapper {
  position: relative;
}

.bell-btn {
  position: relative;
  background: none;
  border: none;
  color: var(--rs-text-muted, #94a3b8);
  cursor: pointer;
  padding: 6px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: color 0.15s, background 0.15s;
}
.bell-btn:hover, .bell-btn:active {
  color: var(--rs-text-primary, #e2e8f0);
  background: var(--rs-bg-hover, rgba(255,255,255,0.05));
}

.badge {
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 16px;
  height: 16px;
  border-radius: 8px;
  background: #ef4444;
  color: white;
  font-size: 10px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 4px;
  line-height: 1;
  pointer-events: none;
}

.panel {
  position: absolute;
  top: 100%;
  right: 0;
  margin-top: 8px;
  width: 360px;
  max-height: 480px;
  overflow-y: auto;
  touch-action: pan-y;
  border-radius: 10px;
  background: var(--rs-bg-surface, #1e293b);
  border: 1px solid var(--rs-border, rgba(255,255,255,0.1));
  box-shadow: var(--rs-shadow-lg, 0 8px 30px rgba(0,0,0,0.3));
  z-index: 200;
}

.panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
  border-bottom: 1px solid var(--rs-border, rgba(255,255,255,0.1));
}

.panel-title {
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--rs-text-primary, #e2e8f0);
}

.panel-actions {
  display: flex;
  gap: 4px;
  align-items: center;
}

.mark-all-btn, .push-btn {
  background: none;
  border: none;
  color: var(--rs-accent, #06b6d4);
  font-size: 0.75rem;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 4px;
  transition: background 0.15s;
}
.mark-all-btn:hover, .push-btn:hover, .mark-all-btn:active, .push-btn:active {
  background: var(--rs-bg-hover, rgba(255,255,255,0.05));
}

.push-btn {
  color: var(--rs-text-muted, #94a3b8);
  border: 1px solid var(--rs-border, rgba(255,255,255,0.1));
}

.panel-empty {
  padding: 32px 16px;
  text-align: center;
  color: var(--rs-text-muted, #94a3b8);
  font-size: 0.8rem;
}

.notif-item {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 16px;
  cursor: pointer;
  transition: background 0.15s;
  border-bottom: 1px solid var(--rs-border, rgba(255,255,255,0.1));
}
.notif-item:hover, .notif-item:active {
  background: var(--rs-bg-hover, rgba(255,255,255,0.05));
}
.notif-item.unread {
  background: rgba(6, 182, 212, 0.05);
}
.notif-item.unread .notif-title {
  font-weight: 600;
}

.notif-icon {
  flex-shrink: 0;
  font-size: 1rem;
  margin-top: 2px;
}

.notif-content {
  flex: 1;
  min-width: 0;
}

.notif-title {
  font-size: 0.8rem;
  color: var(--rs-text-primary, #e2e8f0);
  line-height: 1.3;
}

.notif-body {
  font-size: 0.75rem;
  color: var(--rs-text-muted, #94a3b8);
  margin-top: 2px;
  line-height: 1.3;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.notif-meta {
  display: flex;
  gap: 8px;
  margin-top: 4px;
  font-size: 0.7rem;
  color: var(--rs-text-muted, #64748b);
}

.notif-actor {
  font-weight: 500;
}

.notif-actions {
  display: flex;
  gap: 8px;
  margin-top: 6px;
}

.notif-accept, .notif-decline {
  padding: 4px 12px;
  border-radius: 6px;
  font-size: 0.75rem;
  font-weight: 600;
  cursor: pointer;
  border: none;
  transition: opacity 0.15s;
}
.notif-accept {
  background: linear-gradient(135deg, #14b8a6, #0d9488);
  color: white;
}
.notif-decline {
  background: none;
  border: 1px solid var(--rs-border, rgba(255,255,255,0.15));
  color: var(--rs-text-muted, #94a3b8);
}
.notif-accept:hover, .notif-decline:hover, .notif-accept:active, .notif-decline:active {
  opacity: 0.85;
}

.notif-dismiss {
  flex-shrink: 0;
  background: none;
  border: none;
  color: var(--rs-text-muted, #64748b);
  font-size: 1rem;
  cursor: pointer;
  padding: 2px 4px;
  border-radius: 4px;
  opacity: 0;
  transition: opacity 0.15s, color 0.15s;
}
.notif-item:hover .notif-dismiss {
  opacity: 1;
}
.notif-dismiss:hover, .notif-dismiss:active {
  color: var(--rs-text-primary, #e2e8f0);
}

@media (max-width: 640px) {
  .bell-btn { display: none; }
  .panel {
    position: fixed;
    top: 60px;
    right: 8px;
    left: 8px;
    width: auto;
    max-height: calc(100vh - 80px);
  }
}
`;
