import type { Logger, StreamCloseEvent, StreamMessageEvent, TypedEventTarget, AbortOptions } from './index.ts'
import type { Uint8ArrayList } from 'uint8arraylist'

/**
 * The direction of the message stream
 */
export type MessageStreamDirection = 'inbound' | 'outbound'

/**
 * The states a message stream can be in
 */
export type MessageStreamStatus = 'open' | 'closing' | 'closed' | 'aborted' | 'reset'

/**
 * The states the readable end of a message stream can be in
 */
export type MessageStreamReadStatus = 'readable' | 'paused' | 'closing' | 'closed'

/**
 * The states the writable end of a message stream can be in
 */
export type MessageStreamWriteStatus = 'writable' | 'closing' | 'closed'

/**
 * An object that records the times of various events
 */
export interface MessageStreamTimeline {
  /**
   * A timestamp of when the message stream was opened
   */
  open: number

  /**
   * A timestamp of when the message stream was closed for both reading and
   * writing by both ends of the stream
   */
  close?: number
}

export interface MessageStreamEvents {
  /**
   * Data was received from the remote end of the message stream
   */
  message: StreamMessageEvent

  /**
   * The local send buffer has emptied and the stream may be written to once
   * more, unless it is currently closing.
   */
  drain: Event

  /**
   * The underlying resource is closed - no further events will be emitted and
   * the stream cannot be used to send or receive any more data.
   *
   * When the `.error` field is set, the `local` property of the event will be
   * `true` value if the `.abort` was invoked, otherwise it means a remote error
   * occurred and the peer sent a reset signal.
   */
  close: StreamCloseEvent

  /**
   * Where the stream implementation supports half-closing, it may emit this
   * event when the remote end of the stream closes it's writable end.
   *
   * After this event is received no further 'message' events will be emitted
   * though the stream can still be written to, if it has not been closed at
   * this end.
   */
  remoteCloseWrite: Event

  /**
   * The outgoing write queue emptied - there are no more bytes queued for
   * sending to the remote end of the stream.
   */
  idle: Event
}

export interface MessageStream<Timeline extends MessageStreamTimeline = MessageStreamTimeline> extends TypedEventTarget<MessageStreamEvents>, AsyncIterable<Uint8Array | Uint8ArrayList> {
  /**
   * The current status of the message stream
   */
  status: MessageStreamStatus

  /**
   * Timestamps of when stream events occurred
   */
  timeline: Timeline

  /**
   * A logging implementation that can be used to log stream-specific messages
   */
  log: Logger

  /**
   * Whether this stream is inbound or outbound
   */
  direction: MessageStreamDirection

  /**
   * The maximum number of bytes to store when paused. If receipt of more bytes
   * from the remote end of the stream causes the buffer size to exceed this
   * value the stream will be reset and a 'close' event emitted.
   *
   * This value can be changed at runtime.
   */
  maxReadBufferLength: number

  /**
   * When the `.send` method returns false it means that the underlying resource
   * has signalled that it's write buffer is full. If the user continues to call
   * `.send`, outgoing bytes are stored in an internal buffer until the
   * underlying resource signals that it can accept more data.
   *
   * If the size of that internal buffer exceed this value the stream will be
   * reset and a 'close' event emitted.
   *
   * This value can be changed at runtime.
   */
  maxWriteBufferLength?: number

  /**
   * If no data is transmitted over the stream in this many ms, the stream will
   * be aborted with an InactivityTimeoutError
   */
  inactivityTimeout: number

  /**
   * If this property is `true`, the underlying transport has signalled that its
   * write buffer is full and that `.send` should not be called again.
   *
   * A `drain` event will be emitted after which is its safe to call `.send`
   * again to resume sending.
   */
  writableNeedsDrain: boolean

  /**
   * Returns the number of bytes that are queued to be read
   */
  readBufferLength: number

  /**
   * Returns the number of bytes that are queued to be written
   */
  writeBufferLength: number

  /**
   * Write data to the stream. If the method returns false it means the
   * internal buffer is now full and the caller should wait for the 'drain'
   * event before sending more data.
   *
   * This method may throw if:
   * - The internal send buffer is full
   * - The stream has previously been closed for writing locally or remotely
   */
  send (data: Uint8Array | Uint8ArrayList): boolean

  /**
   * Stop accepting new data to send and return a promise that resolves when any
   * unsent data has been written into the underlying resource.
   */
  close (options?: AbortOptions): Promise<void>

  /**
   * Stop accepting new data to send, discard any unsent/unread data, and emit a
   * 'close' event with the 'error' property set to the passed error.
   */
  abort (err: Error): void

  /**
   * Stop emitting further 'message' events. Any received data will be stored in
   * an internal buffer. If the buffer size reaches `maxReadBufferLength`, the
   * stream will be reset and a StreamAbortEvent emitted.
   *
   * If the underlying resource supports it, the remote peer will be instructed
   * to pause transmission of further data.
   */
  pause (): void

  /**
   * Resume emitting 'message' events.
   *
   * If the underlying resource supports it, the remote peer will be informed
   * that it is ok to start sending data again.
   */
  resume (): void

  /**
   * Queue the passed data to be emitted as a 'message' event either during the
   * next tick or sooner if data is received from the underlying resource.
   */
  push (buf: Uint8Array | Uint8ArrayList): void

  /**
   * Similar to the `.push` method, except this ensures the passed data is
   * emitted before any other queued data.
   */
  unshift (data: Uint8Array | Uint8ArrayList): void

  /**
   * Returns a promise that resolves when the stream can accept new data or
   * rejects if the stream is closed or reset before this occurs.
   */
  onDrain (options?: AbortOptions): Promise<void>
}
