/**
 * Dashboard Real-time Events Manager
 *
 * Manages SSE connections and event broadcasting for dashboard.
 * This is a separate module from the API route to avoid Next.js export constraints.
 */

import { logger } from "./logger";

// Connection limits to prevent memory leaks
const MAX_CONNECTIONS_PER_USER = 5;
const MAX_TOTAL_CONNECTIONS = 5000;

// Event types that can be sent to clients
export type DashboardEventType =
  | "connection"
  | "new_message"
  | "conversation_updated"
  | "new_booking"
  | "booking_updated"
  | "analytics_updated"
  | "new_lead"
  | "workflow_status"
  | "notification"
  | "heartbeat";

export interface DashboardEvent {
  type: DashboardEventType;
  data: unknown;
  timestamp: string;
}

// In-memory store for active SSE connections
// In production, use Redis Pub/Sub for multi-instance support
export const activeConnections = new Map<
  string,
  Set<ReadableStreamDefaultController<Uint8Array>>
>();

// Format message for SSE
export function formatSSEMessage(event: DashboardEvent): string {
  const data = JSON.stringify({
    ...event,
    timestamp: event.timestamp || new Date().toISOString(),
  });
  return `event: ${event.type}\ndata: ${data}\n\n`;
}

// Global event emitter for broadcasting events
export function emitDashboardEvent(userId: string, event: DashboardEvent) {
  const controllers = activeConnections.get(userId);
  if (!controllers || controllers.size === 0) return;

  const eventString = formatSSEMessage(event);
  const encoder = new TextEncoder();
  const data = encoder.encode(eventString);

  controllers.forEach((controller) => {
    try {
      controller.enqueue(data);
    } catch {
      // Controller may be closed, will be cleaned up
    }
  });
}

// Broadcast to all users (for system-wide events)
export function broadcastDashboardEvent(event: DashboardEvent) {
  activeConnections.forEach((_, userId) => {
    emitDashboardEvent(userId, event);
  });
}

// Utility function to emit events from webhook handlers
export function notifyDashboard(
  userId: string,
  eventType: DashboardEventType,
  data: unknown,
) {
  const event: DashboardEvent = {
    type: eventType,
    data,
    timestamp: new Date().toISOString(),
  };
  emitDashboardEvent(userId, event);
}

/**
 * Add a new SSE connection with limits to prevent memory leaks
 * @returns true if connection was added, false if rejected
 */
export function addConnection(
  userId: string,
  controller: ReadableStreamDefaultController<Uint8Array>,
): boolean {
  const totalConnections = Array.from(activeConnections.values()).reduce(
    (sum, set) => sum + set.size,
    0,
  );

  if (totalConnections >= MAX_TOTAL_CONNECTIONS) {
    logger.warn("SSE connection limit reached, rejecting new connection");
    controller.close();
    return false;
  }

  const userConnections = activeConnections.get(userId) || new Set();
  if (userConnections.size >= MAX_CONNECTIONS_PER_USER) {
    // Evict oldest connection for this user
    const oldest = userConnections.values().next().value;
    if (oldest) {
      try {
        oldest.close();
      } catch {
        // Controller may already be closed
      }
      userConnections.delete(oldest);
    }
  }

  userConnections.add(controller);
  activeConnections.set(userId, userConnections);
  return true;
}

/**
 * Remove a connection when it's closed
 */
export function removeConnection(
  userId: string,
  controller: ReadableStreamDefaultController<Uint8Array>,
): void {
  const userConnections = activeConnections.get(userId);
  if (userConnections) {
    userConnections.delete(controller);
    if (userConnections.size === 0) {
      activeConnections.delete(userId);
    }
  }
}

/**
 * Cleanup stale SSE connections by sending ping and removing dead ones
 */
export function cleanupStaleConnections(): void {
  const encoder = new TextEncoder();
  const pingData = encoder.encode("event: ping\ndata: {}\n\n");

  for (const [userId, controllers] of activeConnections.entries()) {
    for (const controller of controllers) {
      try {
        controller.enqueue(pingData);
      } catch {
        // Controller is dead, remove it
        controllers.delete(controller);
      }
    }
    if (controllers.size === 0) {
      activeConnections.delete(userId);
    }
  }
}

// Run cleanup every 30 seconds to prevent memory leaks from stale connections
if (typeof setInterval !== "undefined") {
  setInterval(cleanupStaleConnections, 30000);
}
