import { assertRealtimeRoomName, type RealtimeMessage } from "./messages.ts";

export interface BrowserRoomConnection {
  readonly name: string;
  send(message: unknown): BrowserRoomConnection;
  on(
    type: string | ((message: unknown) => void),
    callback?: (message: unknown) => void,
  ): BrowserRoomConnection;
  close(): void;
}

export interface WrnexusRealtimeWindow extends Window {
  wire?: {
    room?: (name: string, query?: string) => BrowserRoomConnection;
  };
}

const QUERY_KEY = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;

export function roomQuery(
  params: Record<string, string | number | boolean | null | undefined>,
): string {
  const query = new URLSearchParams();
  for (const [key, value] of Object.entries(params)) {
    if (!QUERY_KEY.test(key)) throw new TypeError(`Invalid realtime query key: ${key}`);
    if (value !== undefined && value !== null) query.set(key, String(value));
  }
  const result = query.toString();
  if (result.length > 2_048) throw new RangeError("Realtime room query exceeds 2048 characters.");
  return result;
}

export function connectRoom(
  name: string,
  options: {
    query?: Record<string, string | number | boolean | null | undefined>;
    window?: WrnexusRealtimeWindow;
  } = {},
): BrowserRoomConnection {
  const roomName = assertRealtimeRoomName(name);
  const target = options.window ?? (globalThis as unknown as WrnexusRealtimeWindow);
  const factory = target.wire?.room;
  if (!factory) {
    throw new Error(
      "WRN-REALTIME-CLIENT-NOT-READY: add a RealtimeRoom/data-room component before calling connectRoom().",
    );
  }
  return factory(roomName, options.query ? roomQuery(options.query) : undefined);
}

export function sendRoomMessage<T>(
  room: BrowserRoomConnection,
  message: RealtimeMessage<T> | T,
): BrowserRoomConnection {
  return room.send(message);
}
