/** Private Live continuous wire budgets; legacy transports do not enforce them. */
export const REALTIME_MAX_FRAME_BYTES = 128 * 1024;
export const REALTIME_MAX_BUFFERED_BYTES = 128 * 1024;

export type EncodedRealtimeFrame = {
  data: string | ArrayBuffer | ArrayBufferView<ArrayBuffer> | Blob;
  byteLength: number;
};

/** Serialize once, measuring the exact payload passed to the browser. */
export function encodeRealtimeFrame(value: unknown): EncodedRealtimeFrame {
  if (value instanceof ArrayBuffer)
    return { data: value, byteLength: value.byteLength };
  if (ArrayBuffer.isView(value))
    return {
      data: value as ArrayBufferView<ArrayBuffer>,
      byteLength: value.byteLength,
    };
  if (value instanceof Blob) return { data: value, byteLength: value.size };
  const data = typeof value === 'string' ? value : JSON.stringify(value);
  if (data === undefined)
    throw new Error('Realtime event could not be encoded as JSON');
  return { data, byteLength: new TextEncoder().encode(data).byteLength };
}

export function assertRealtimeFrameBudget(
  frame: EncodedRealtimeFrame,
  bufferedAmount: number,
): void {
  if (frame.byteLength > REALTIME_MAX_FRAME_BYTES)
    throw new Error(
      `Realtime frame exceeds the ${REALTIME_MAX_FRAME_BYTES}-byte limit; reduce the event payload`,
    );
  if (bufferedAmount + frame.byteLength > REALTIME_MAX_BUFFERED_BYTES)
    throw new Error(
      `Realtime send buffer is full (${REALTIME_MAX_BUFFERED_BYTES}-byte limit); wait before retrying`,
    );
}
