import { randomUUID } from 'crypto';
import { WebSocketServer } from 'ws';

// src/screenReceiver/server/index.ts
var WS_OPEN = 1;
var ScreenReceiverSignalingHub = class {
  constructor(options = {}) {
    this.rooms = /* @__PURE__ */ new Map();
    this.clients = /* @__PURE__ */ new Map();
    this.logger = options.logger;
    this.createPeerId = options.createPeerId ?? randomUUID;
  }
  attachClient(socket) {
    socket.on("message", (raw) => {
      const message = this.parseMessage(raw);
      if (!message) {
        this.send(socket, { type: "error", reason: "invalid_json" });
        return;
      }
      this.handleMessage(socket, message);
    });
    socket.on("close", () => this.removeClient(socket));
    socket.on("error", () => this.removeClient(socket));
  }
  close() {
    for (const socket of this.clients.keys()) {
      socket.close();
    }
    this.rooms.clear();
    this.clients.clear();
  }
  parseMessage(raw) {
    try {
      if (typeof raw === "string") return JSON.parse(raw);
      if (raw instanceof ArrayBuffer) return JSON.parse(Buffer.from(raw).toString("utf-8"));
      if (Array.isArray(raw)) return JSON.parse(Buffer.concat(raw).toString("utf-8"));
      return JSON.parse(raw.toString());
    } catch {
      return null;
    }
  }
  handleMessage(socket, message) {
    if (isJoinMessage(message)) {
      this.joinRoom(socket, message);
      return;
    }
    const sender = this.clients.get(socket);
    if (!sender) {
      this.send(socket, { type: "error", reason: "join_first" });
      return;
    }
    if (isSignalMessage(message)) {
      this.forwardSignal(sender, message);
      return;
    }
    this.send(socket, { type: "error", reason: "unknown_type" });
  }
  joinRoom(socket, message) {
    const roomId = typeof message.roomId === "string" ? message.roomId.trim() : "";
    if (!roomId) {
      this.send(socket, { type: "error", reason: "missing_room_id" });
      return;
    }
    this.removeClient(socket, false);
    const role = message.role === "broadcaster" || message.role === "sender" ? "broadcaster" : "viewer";
    const peerId = typeof message.peerId === "string" && message.peerId.trim() ? message.peerId : this.createPeerId();
    const record = { peerId, roomId, role, socket };
    this.clients.set(socket, record);
    const room = this.rooms.get(roomId) ?? /* @__PURE__ */ new Map();
    room.set(peerId, record);
    this.rooms.set(roomId, room);
    this.send(socket, { type: "joined", roomId, peerId, role });
    this.send(socket, {
      type: "room_state",
      roomId,
      peers: [...room.values()].filter((peer) => peer.peerId !== peerId).map((peer) => ({ peerId: peer.peerId, role: peer.role }))
    });
    this.broadcast(roomId, socket, { type: "peer_joined", roomId, peerId, role });
    this.log(`[screenReceiver] peer ${peerId} joined room ${roomId} as ${role}`);
  }
  forwardSignal(sender, message) {
    const room = this.rooms.get(sender.roomId);
    if (!room) return;
    const payload = { ...message, fromPeerId: sender.peerId };
    const targetPeerId = typeof message.targetPeerId === "string" ? message.targetPeerId : void 0;
    if (targetPeerId) {
      const target = room.get(targetPeerId);
      if (!target) {
        this.send(sender.socket, { type: "error", reason: "target_not_found", targetPeerId });
        return;
      }
      this.send(target.socket, payload);
      return;
    }
    this.broadcast(sender.roomId, sender.socket, payload);
  }
  broadcast(roomId, from, payload) {
    const room = this.rooms.get(roomId);
    if (!room) return;
    for (const peer of room.values()) {
      if (peer.socket === from || peer.socket.readyState !== WS_OPEN) continue;
      this.send(peer.socket, payload);
    }
  }
  removeClient(socket, notifyRoom = true) {
    const record = this.clients.get(socket);
    if (!record) return;
    this.clients.delete(socket);
    const room = this.rooms.get(record.roomId);
    if (!room) return;
    room.delete(record.peerId);
    if (room.size === 0) {
      this.rooms.delete(record.roomId);
    } else if (notifyRoom) {
      this.broadcast(record.roomId, socket, {
        type: "peer_left",
        roomId: record.roomId,
        peerId: record.peerId
      });
    }
    this.log(`[screenReceiver] peer ${record.peerId} left room ${record.roomId}`);
  }
  send(socket, payload) {
    if (socket.readyState !== WS_OPEN) return;
    socket.send(JSON.stringify(payload));
  }
  log(message) {
    this.logger?.(message);
  }
};
function isObject(value) {
  return typeof value === "object" && value !== null;
}
function isJoinMessage(value) {
  return isObject(value) && value.type === "join" && typeof value.roomId === "string";
}
function isSignalMessage(value) {
  if (!isObject(value)) return false;
  return value.type === "offer" || value.type === "answer" || value.type === "ice";
}
function createScreenReceiverWebSocketServer(options) {
  const hub = new ScreenReceiverSignalingHub(options.hubOptions);
  const wss = new WebSocketServer(options.serverOptions);
  wss.on("connection", (socket) => hub.attachClient(socket));
  return {
    wss,
    hub,
    close() {
      hub.close();
      wss.close();
    }
  };
}

// src/screenReceiver/server/next.ts
var DEFAULT_GLOBAL_KEY = "__sa2kit_screen_receiver_wss__";
function registerScreenReceiverForNext(options = {}) {
  if (process.env.NEXT_RUNTIME !== "nodejs") return null;
  const port = options.port ?? Number(process.env.SCREEN_RECEIVER_WS_PORT || 8787);
  const path = options.path ?? process.env.SCREEN_RECEIVER_WS_PATH ?? "/ws";
  const key = options.globalKey ?? DEFAULT_GLOBAL_KEY;
  const globalRef = globalThis;
  if (globalRef[key]) return globalRef[key];
  const server = createScreenReceiverWebSocketServer({
    serverOptions: { port, path },
    hubOptions: {
      logger: options.logger ?? ((message) => console.log(message))
    }
  });
  globalRef[key] = server;
  return server;
}

export { registerScreenReceiverForNext };
//# sourceMappingURL=next.mjs.map
//# sourceMappingURL=next.mjs.map