import { WebSocket } from 'ws';
import { connectedUsers } from './state';
import { ConnectedUser } from './types';

interface WsMessage {
  [key: string]: unknown;
  type: string;
}

export function broadcastToServer(serverId: string, message: WsMessage, excludeOderId?: string): void {
  console.log(`Broadcasting to server ${serverId}, excluding ${excludeOderId}:`, message.type);

  // Deduplicate by oderId so users with multiple connections (e.g. from
  // different signal URLs routing to the same server) receive the
  // broadcast only once.
  const sentToOderIds = new Set<string>();

  connectedUsers.forEach((user) => {
    if (user.serverIds.has(serverId) && user.oderId !== excludeOderId && !sentToOderIds.has(user.oderId)) {
      sentToOderIds.add(user.oderId);
      console.log(`  -> Sending to ${user.displayName} (${user.oderId})`);
      user.ws.send(JSON.stringify(message));
    }
  });
}

export function notifyServerOwner(ownerId: string, message: WsMessage): void {
  const owner = findUserByOderId(ownerId);

  if (owner) {
    owner.ws.send(JSON.stringify(message));
  }
}

export function getUniqueUsersInServer(serverId: string, excludeOderId?: string): ConnectedUser[] {
  const usersByOderId = new Map<string, ConnectedUser>();

  connectedUsers.forEach((user) => {
    if (user.oderId === excludeOderId || !user.serverIds.has(serverId) || user.ws.readyState !== WebSocket.OPEN) {
      return;
    }

    usersByOderId.set(user.oderId, user);
  });

  return Array.from(usersByOderId.values());
}

export function isOderIdConnectedToServer(oderId: string, serverId: string, excludeConnectionId?: string): boolean {
  return Array.from(connectedUsers.entries()).some(([connectionId, user]) =>
    connectionId !== excludeConnectionId
    && user.oderId === oderId
    && user.serverIds.has(serverId)
    && user.ws.readyState === WebSocket.OPEN
  );
}

export function getServerIdsForOderId(oderId: string, excludeConnectionId?: string): string[] {
  const serverIds = new Set<string>();

  connectedUsers.forEach((user, connectionId) => {
    if (connectionId === excludeConnectionId || user.oderId !== oderId || user.ws.readyState !== WebSocket.OPEN) {
      return;
    }

    user.serverIds.forEach((serverId) => serverIds.add(serverId));
  });

  return Array.from(serverIds);
}

export function notifyUser(oderId: string, message: WsMessage): void {
  const user = findUserByOderId(oderId);

  if (user) {
    user.ws.send(JSON.stringify(message));
  }
}

export function findUserByOderId(oderId: string) {
  let match: ConnectedUser | undefined;

  connectedUsers.forEach((user) => {
    if (user.oderId === oderId && user.ws.readyState === WebSocket.OPEN) {
      match = user;
    }
  });

  return match;
}
