///|
/// A typed sender for one request type. It holds the one handler for that
/// request type and the middleware around it. The context type `C`, the
/// request type `Req`, and the reply type `Res` are fixed at compile time, so a
/// request of another type does not compile. A sender exists only when the
/// application builds it from a handler: the compiler is the registry, and
/// there is no runtime lookup.
struct Sender[C, Req, Res] {
  handle : (C, Req) -> Res raise
  /// The first item is the outermost step. No function changes this array
  /// after the sender exists.
  middleware : Array[Middleware[C, Req, Res]]
}

///|
/// Build a sender from its one handler. The handler takes the context value
/// and the request, and gives the reply or raises an error.
pub fn[C, Req, Res] sender(
  handle : (C, Req) -> Res raise,
) -> Sender[C, Req, Res] {
  { handle, middleware: [], }
}

///|
/// Give a new sender with `middleware` inside the middleware that the sender
/// has. The first middleware attached is the outermost step. The sender
/// itself does not change.
pub fn[C, Req, Res] Sender::use_(
  self : Sender[C, Req, Res],
  middleware : Middleware[C, Req, Res],
) -> Sender[C, Req, Res] {
  let steps = self.middleware.copy()
  steps.push(middleware)
  { handle: self.handle, middleware: steps, }
}

///|
/// Send a request with a context value. The middleware runs in attach order
/// around the one handler, and the sender gives the reply. An error that no
/// middleware catches passes out unchanged.
pub fn[C, Req, Res] Sender::send(
  self : Sender[C, Req, Res],
  ctx : C,
  req : Req,
) -> Res raise {
  run(self.middleware, 0, self.handle, ctx, req)
}

///|
/// Run the middleware from `index` to the end, then the handler.
fn[C, Req, Res] run(
  steps : Array[Middleware[C, Req, Res]],
  index : Int32,
  handle : (C, Req) -> Res raise,
  ctx : C,
  req : Req,
) -> Res raise {
  if index >= steps.length() {
    handle(ctx, req)
  } else {
    (steps[index].wrap)((c, r) => run(steps, index + 1, handle, c, r), ctx, req)
  }
}