import {
  MessageType,
  XmlMessage,
  pipelines,
  utils,
  components,
} from 'media-stream-library'

/**
 * Metadata handlers
 *
 * For video streams that also include ONVIF metadata,
 * once can specify a parser + synced callback to be run
 * whenever metadata occurs on the stream.
 *
 * The `parser` provided produces a scheduled message, that
 * will be returned later through the `cb` synched callback,
 * whenever the message is synchronized with the presentation.
 */
export interface MetadataXMLMessage extends XmlMessage {
  readonly xmlDocument: XMLDocument
}
export interface ScheduledMessage {
  readonly ntpTimestamp: number | undefined
  readonly data: unknown
}
export type MetadataParser = (msg: MetadataXMLMessage) => ScheduledMessage
export type MetadataCallback = (msg: ScheduledMessage) => void
export interface MetadataHandler {
  /**
   * A parser that will receive an XML message and should return
   * a new message with at least an ntpTimestamp.
   */
  readonly parser: MetadataParser
  /**
   * A synchronized callback that will be called whenever the message
   * produced by the parser is in sync with the video.
   */
  readonly cb: MetadataCallback
}

/**
 * Attach ONVIF metadata handlers to a pipeline
 *
 * @param pipeline The (HTML5 video) pipeline to modify
 * @param handlers The handlers to deal with XML data
 */
export const attachMetadataHandler = (
  pipeline: pipelines.Html5VideoPipeline,
  { parser, cb }: MetadataHandler,
): utils.Scheduler<ScheduledMessage> => {
  /**
   * When a metadata handler is available on this component, it will be
   * called in sync with the player, using a scheduler to synchronize the
   * callback with the video presentation time.
   */
  const scheduler = new utils.Scheduler(pipeline, cb, 30)
  const xmlParser = new DOMParser()

  const xmlMessageHandler = (msg: XmlMessage) => {
    const xmlDocument = xmlParser.parseFromString(
      msg.data.toString(),
      'text/xml',
    )
    const newMsg = parser({ ...msg, xmlDocument })
    if (msg.ntpTimestamp !== undefined) {
      scheduler.run(newMsg)
    }
  }

  // Add extra components to the pipeline.
  const onvifDepay = new components.ONVIFDepay()
  const onvifHandlerPipe = components.Tube.fromHandlers((msg) => {
    if (msg.type === MessageType.XML) {
      xmlMessageHandler(msg)
    }
  }, undefined)
  pipeline.insertAfter(pipeline.rtsp, onvifDepay)
  pipeline.insertAfter(onvifDepay, onvifHandlerPipe)

  // Initialize the scheduler when presentation time is ready
  pipeline.onSync = (ntpPresentationTime: number) =>
    scheduler.init(ntpPresentationTime)

  return scheduler
}
