///|
/// A chat room: the server speaks first, then echoes each message until the client leaves.
async fn room(ws : @moonapi.WebSocket) -> Unit {
  ws.accept(subprotocol="chat")
  ws.send_text("welcome to room \{ws.param("room").unwrap_or("?")}")
  for ;; {
    match ws.receive() {
      WsText(t) => ws.send_text("echo: " + t)
      WsBinary(b) => ws.send_bytes(b)
    }
  }
}

///|
/// Request and reply in JSON: add up the numbers sent, answer, close.
async fn sum(ws : @moonapi.WebSocket) -> Unit {
  ws.accept()
  let total = match ws.receive_json() {
    Array(items) =>
      items.fold(init=0.0, (acc, j) => {
        match j {
          Number(n, ..) => acc + n
          _ => acc
        }
      })
    _ => 0.0
  }
  ws.send_json(Json::number(total))
  ws.close()
}

///|
/// WebSocket routes (← FastAPI's `@app.websocket`). A handler holds the live connection: it can
/// speak first, answer as messages arrive, and a client that leaves ends it quietly. `param` and
/// `offered_subprotocols` mirror the HTTP context. A test client drives both routes in-process
/// over the same ASGI path a server serves.
///
///     moon run src/examples/18-websocket --target native
#coverage.skip
async fn main {
  let app = @moonapi.App::new()
  app.websocket("/ws/{room}", room)
  app.websocket("/sum", sum)
  let client = @testing.Client::new(app.to_asgi())
  println("room /ws/233:")
  client.websocket(path="/ws/233", subprotocols=["chat", "superchat"], s => {
    println("  subprotocol: \{s.subprotocol.unwrap_or("-")}")
    println("  " + s.receive_text())
    s.send_text("hi")
    println("  " + s.receive_text())
    s.send_bytes(b"\x02\x33\x33")
    println("  bytes back: \{s.receive_bytes().length()}")
  })
  println("sum /sum:")
  client.websocket(path="/sum", s => {
    s.send_json([1, 2, 3.5])
    println("  total: \{s.receive_json().stringify()}")
  })
}