// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import * as TextUtils from '../../models/text_utils/text_utils.js';

import {Events, type EventSourceMessage, type NetworkRequest} from './NetworkRequest.js';
import {ServerSentEventsParser} from './ServerSentEventsProtocol.js';

/**
 * Server sent events only arrive via CDP (Explicit Network.eventSourceMessageReceived) when
 * the page uses "EventSource" in the code.
 *
 * If the page manually uses 'fetch' or XHR we have to do the protocol parsing
 * ourselves.
 *
 * `ServerSentEvents` is a small helper class that manages this distinction for a specific
 * request, stores the event data and sends out "EventSourceMessageAdded" events for a request.
 */
export class ServerSentEvents {
  readonly #request: NetworkRequest;
  readonly #parser?: ServerSentEventsParser;

  // In the case where we parse the events ourselves we use the time of the last 'dataReceived'
  // event for all the events that come out of the corresponding chunk of data.
  #lastDataReceivedTime = 0;

  readonly #eventSourceMessages: EventSourceMessage[] = [];

  constructor(request: NetworkRequest, parseFromStreamedData: boolean) {
    this.#request = request;

    // Only setup parsing if we don't get the events over CDP directly.
    if (parseFromStreamedData) {
      this.#lastDataReceivedTime = request.pseudoWallTime(request.startTime);
      this.#parser = new ServerSentEventsParser(this.#onParserEvent.bind(this), request.charset() ?? undefined);

      // Get the streaming content and add the already received bytes if someone else started
      // the streaming earlier.
      void this.#request.requestStreamingContent().then(streamingContentData => {
        if (!TextUtils.StreamingContentData.isError(streamingContentData)) {
          void this.#parser?.addBase64Chunk(streamingContentData.content().base64);
          streamingContentData.addEventListener(
              TextUtils.StreamingContentData.Events.CHUNK_ADDED, ({data: {chunk}}) => {
                this.#lastDataReceivedTime = request.pseudoWallTime(request.endTime);
                void this.#parser?.addBase64Chunk(chunk);
              });
        }
      });
    }
  }

  get eventSourceMessages(): readonly EventSourceMessage[] {
    return this.#eventSourceMessages;
  }

  /** Forwarded Network.eventSourceMessage received */
  onProtocolEventSourceMessageReceived(eventName: string, data: string, eventId: string, time: number): void {
    this.#recordMessageAndDispatchEvent({
      eventName,
      eventId,
      data,
      time,
    });
  }

  #onParserEvent(eventName: string, data: string, eventId: string): void {
    this.#recordMessageAndDispatchEvent({
      eventName,
      eventId,
      data,
      time: this.#lastDataReceivedTime,
    });
  }

  #recordMessageAndDispatchEvent(message: EventSourceMessage): void {
    this.#eventSourceMessages.push(message);
    this.#request.dispatchEventToListeners(Events.EVENT_SOURCE_MESSAGE_ADDED, message);
  }
}
