// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.

const NEWLINE_REGEXP = /\r\n|\r|\n/;
const encoder = new TextEncoder();

/**
 * Represents a message in the Server-Sent Event (SSE) protocol.
 *
 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#fields}
 */
export interface ServerSentEventMessage {
  /** Ignored by the client. */
  comment?: string;
  /** A string identifying the type of event described. */
  event?: string;
  /** The data field for the message. Split by new lines. */
  data?: string;
  /** The event ID to set the {@linkcode EventSource} object's last event ID value. */
  id?: string | number;
  /** The reconnection time. */
  retry?: number;
}

function assertHasNoNewline(value: string, varName: string) {
  if (value.match(NEWLINE_REGEXP) !== null) {
    throw new RangeError(`${varName} cannot contain a newline`);
  }
}

/**
 * Converts a server-sent message object into a string for the client.
 *
 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#event_stream_format}
 */
function stringify(message: ServerSentEventMessage): Uint8Array {
  const lines = [];
  if (message.comment) {
    assertHasNoNewline(message.comment, "`message.comment`");
    lines.push(`:${message.comment}`);
  }
  if (message.event) {
    assertHasNoNewline(message.event, "`message.event`");
    lines.push(`event:${message.event}`);
  }
  if (message.data) {
    message.data.split(NEWLINE_REGEXP).forEach((line) =>
      lines.push(`data:${line}`)
    );
  }
  if (message.id) {
    assertHasNoNewline(message.id.toString(), "`message.id`");
    lines.push(`id:${message.id}`);
  }
  if (message.retry) lines.push(`retry:${message.retry}`);
  return encoder.encode(lines.join("\n") + "\n\n");
}

/**
 * Transforms server-sent message objects into strings for the client.
 *
 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events}
 *
 * @example Usage
 * ```ts no-assert
 * import {
 *   type ServerSentEventMessage,
 *   ServerSentEventStream,
 * } from "@std/http/server-sent-event-stream";
 *
 * const stream = ReadableStream.from<ServerSentEventMessage>([
 *   { data: "hello there" }
 * ]).pipeThrough(new ServerSentEventStream());
 * new Response(stream, {
 *   headers: {
 *     "content-type": "text/event-stream",
 *     "cache-control": "no-cache",
 *   },
 * });
 * ```
 */
export class ServerSentEventStream
  extends TransformStream<ServerSentEventMessage, Uint8Array> {
  constructor() {
    super({
      transform: (message, controller) => {
        controller.enqueue(stringify(message));
      },
    });
  }
}

// denoCacheMetadata={"headers":{"cf-cache-status":"HIT","content-security-policy":"default-src 'none'; script-src 'none'; style-src 'none'; img-src 'none'; font-src 'none'; connect-src 'none'; frame-src 'none'; object-src 'none'; frame-ancestors 'none'; sandbox; form-action 'none';","report-to":"{\"group\":\"cf-nel\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://a.nel.cloudflare.com/report/v4?s=9etp3omo79KCj3ctEcNTOfRTwTlCE1876y%2B8r1CvJyM9rBZSxKHlZL6lVll%2B1IlyZWMIzpt8VGXdVHYf2RqUT5DhrY2%2FUlOaS4BPs7kUBZEwKykmI6DAWf8%3D\"}]}","alt-svc":"h3=\":443\"; ma=86400","server":"cloudflare","access-control-expose-headers":"*","date":"Tue, 28 Jul 2026 12:31:15 GMT","cf-ray":"a223e656c8137db6-LAX","cross-origin-resource-policy":"cross-origin","content-type":"text/typescript","accept-ranges":"bytes","cache-control":"public, max-age=31536000, immutable","set-cookie":"__cf_bm=.0jNBU0dG9k179PliZMJP6cqgIVw.0pwOgmXwebIVPM-1785241875.0119832-1.0.1.1-7IlKCf0jEyd7UfES7weQ_fttq2CvJk64yX8GasLp34t36VvDcYOaegPDwlt7XYXOsGETF4Ux136CjCMkvUAVIQc2x8h2.nE_vKf2ZyqgeaPh5dd6zHBqPpZ6v8Lh1j8H; HttpOnly; SameSite=None; Secure; Path=/; Domain=jsr.io; Expires=Tue, 28 Jul 2026 13:01:15 GMT","nel":"{\"report_to\":\"cf-nel\",\"success_fraction\":0.0,\"max_age\":604800}","etag":"\"dbbc7c0cfb9feb0f452dc63b78d173bf\"","content-length":"2771","access-control-allow-origin":"*","vary":"Origin","x-jsr-backend":"modules","x-robots-tag":"noindex","age":"126790","x-content-type-options":"nosniff"},"url":"https://jsr.io/@std/http/0.224.5/server_sent_event_stream.ts","time":1785241875}