///|
/// A typed topic for one event type. It holds one or more event handlers and
/// the middleware around them. A topic starts with one handler, so a topic
/// with no handlers is not a value. An event has no reply.
struct Topic[C, Ev] {
  /// The handlers in add order. No function changes this array after the
  /// topic exists.
  handlers : Array[(C, Ev) -> Unit raise]
  /// The first item is the outermost step. No function changes this array
  /// after the topic exists.
  middleware : Array[Middleware[C, Ev, Unit]]
}

///|
/// Build a topic from its first event handler.
pub fn[C, Ev] topic(handle : (C, Ev) -> Unit raise) -> Topic[C, Ev] {
  { handlers: [handle], middleware: [], }
}

///|
/// Give a new topic with `handle` after the handlers that the topic has. The
/// topic itself does not change.
pub fn[C, Ev] Topic::add(
  self : Topic[C, Ev],
  handle : (C, Ev) -> Unit raise,
) -> Topic[C, Ev] {
  let handlers = self.handlers.copy()
  handlers.push(handle)
  { handlers, middleware: self.middleware, }
}

///|
/// Give a new topic with `middleware` inside the middleware that the topic
/// has. A topic middleware wraps the whole publish: its next step runs all
/// handlers. The first middleware attached is the outermost step. The topic
/// itself does not change.
pub fn[C, Ev] Topic::use_(
  self : Topic[C, Ev],
  middleware : Middleware[C, Ev, Unit],
) -> Topic[C, Ev] {
  let steps = self.middleware.copy()
  steps.push(middleware)
  { handlers: self.handlers, middleware: steps, }
}

///|
/// Publish an event with a context value. The middleware runs in attach order
/// around the handlers. Every handler runs one time, in add order, also after
/// a handler raises. When one or more handlers raise, the publish raises one
/// `MediatorError::PublishFailed` after the last handler, so a topic
/// middleware gets that one combined error from its next step.
pub fn[C, Ev] Topic::publish(
  self : Topic[C, Ev],
  ctx : C,
  ev : Ev,
) -> Unit raise {
  let handlers = self.handlers
  run(
    self.middleware,
    0,
    (c, e) => {
      let errors : Array[Error?] = Array::make(handlers.length(), None)
      for index, handle in handlers {
        handle(c, e) catch {
          error => errors[index] = Some(error)
        }
      }
      if MediatorError::from_publish_errors(errors) is Some(error) {
        raise error
      }
    },
    ctx,
    ev,
  )
}