"use client";

// ═══════════════════════════════════════════════════════════════
//  useClientNotificationSocket
//
//  Connects the CLIENT PORTAL to the same real-time gateway the
//  staff app uses (NotificationGateway, namespace "/notifications").
//  Without this the client's browser has no socket open, so it never
//  receives pushes even though the backend emits to its user:<id>
//  room — which is exactly why admin got live notifications and the
//  client did not.
//
//  The gateway (notification.gateway.ts) reads the token from
//  handshake.auth.token, decodes it, and joins the socket to
//  `user:<id>` (+ role rooms). It then emits with emitToUsers()/
//  emitToRoles(). So the client just needs to connect with its
//  clientPortalToken — the same token every other client-portal
//  call already uses.
//
//  ── Install ──
//   1. npm i socket.io-client         (in the client portal app)
//   2. drop this file in e.g. lib/hooks/useClientNotificationSocket.ts
//   3. mount it ONCE, high in the client dashboard tree
//      (dashboard/layout.tsx is ideal):
//
//        "use client";
//        import { useClientNotificationSocket } from "@/lib/hooks/useClientNotificationSocket";
//        export default function DashboardLayout({ children }) {
//          useClientNotificationSocket();          // ← live bell
//          return <>{children}</>;
//        }
//
//   4. (optional) pass a callback to react to events yourself:
//        useClientNotificationSocket((n) => {
//          // n = { type, title, body, reference_id, ... }
//          // e.g. bump an unread counter, toast, refetch bell list
//        });
// ═══════════════════════════════════════════════════════════════

import { useEffect, useRef } from "react";
import { io, type Socket } from "socket.io-client";

export interface ClientNotification {
  id?: number;
  type: string;
  title: string;
  body?: string;
  reference_id?: number;
  reference_type?: string;
  is_urgent?: boolean;
  requires_action?: boolean;
  link_url?: string;
  payload?: any;
  created_at?: string;
}

// The gateway lives at the SERVER ROOT (namespace "/notifications"),
// NOT under the "/api" path. Derive the origin from the API base and
// allow an explicit override via NEXT_PUBLIC_WS_URL.
function resolveSocketOrigin(): string {
  const explicit = process.env.NEXT_PUBLIC_WS_URL;
  if (explicit) return explicit.replace(/\/$/, "");
  const api = process.env.NEXT_PUBLIC_API_URL || "http://localhost:3008/api";
  // strip a trailing "/api" (and any trailing slash) to get the origin
  return api.replace(/\/api\/?$/, "").replace(/\/$/, "");
}

function getClientToken(): string {
  if (typeof window === "undefined") return "";
  return localStorage.getItem("clientPortalToken") || "";
}

/**
 * Opens a single socket to /notifications for the logged-in client and
 * keeps it alive for the life of the component. Fires `onNotification`
 * for every pushed event. Safe to call once near the top of the client
 * dashboard; it reconnects automatically and cleans up on unmount.
 */
export function useClientNotificationSocket(
  onNotification?: (n: ClientNotification) => void,
) {
  const socketRef = useRef<Socket | null>(null);
  const cbRef = useRef(onNotification);
  cbRef.current = onNotification;

  useEffect(() => {
    const token = getClientToken();
    if (!token) return; // not logged in yet - nothing to subscribe to

    const origin = resolveSocketOrigin();
    const socket = io(`${origin}/notifications`, {
      // gateway reads client.handshake.auth.token
      auth: { token },
      transports: ["websocket", "polling"],
      withCredentials: true,
      reconnection: true,
      reconnectionAttempts: Infinity,
      reconnectionDelay: 1500,
    });
    socketRef.current = socket;

    socket.on("connect", () => {
      // eslint-disable-next-line no-console
      console.log("🔔 [client] notification socket connected:", socket.id);
    });
    socket.on("connect_error", (err) => {
      // eslint-disable-next-line no-console
      console.warn("🔔 [client] notification socket error:", err.message);
    });

    // The backend emits these event names (see NotificationsService).
    // We listen to all of them and forward to the callback.
    const handle = (n: ClientNotification) => cbRef.current?.(n);
    socket.on("notification", handle);
    socket.on("notification:new", handle);
    socket.on("notification:urgent", handle);
    socket.on("notification:action", handle);

    return () => {
      socket.off("notification", handle);
      socket.off("notification:new", handle);
      socket.off("notification:urgent", handle);
      socket.off("notification:action", handle);
      socket.disconnect();
      socketRef.current = null;
    };
  }, []);

  return socketRef;
}

export default useClientNotificationSocket;