///|
let ws_conn_counter : Ref[Int32] = Ref(0)

///|
fn fresh_ws_id() -> Int32 {
  ws_conn_counter.val = ws_conn_counter.val + 1
  ws_conn_counter.val
}

///|
/// A WebSocket connection handed to user handlers.
pub struct WsConn {
  conn : @async_websocket.Conn
  id : Int32
}

///|
/// Receive the next message as text. Binary messages are decoded lossily.
/// Raises when the connection closes.
pub async fn WsConn::recv_text(self : WsConn) -> String {
  let message = self.conn.recv()
  message.read_all().text() catch {
    _err => ""
  }
}

///|
/// Send a text message.
pub async fn WsConn::send_text(self : WsConn, text : String) -> Unit {
  self.conn.send_text(text)
}

///|
/// Close the connection politely.
pub async fn WsConn::close(self : WsConn) -> Unit {
  self.conn.send_close()
}

///|
/// Event handlers for a WebSocket connection. The framework runs the
/// receive loop: `on_message` fires per text message, and `on_close` fires
/// exactly once when the connection closes or errors.
pub(all) struct WsHandler {
  on_message : async (WsConn, String) -> Unit
  on_close : async (WsConn) -> Unit
}

///|
/// A broadcast hub: register connections as they open and broadcast text to
/// all of them. Runs inside the single-threaded event loop, so no locking
/// is needed. Failed sends are dropped silently.
pub struct WsHub {
  conns : Array[WsConn]
}

///|
pub fn WsHub::new() -> WsHub {
  { conns: [], }
}

///|
pub fn WsHub::join(self : WsHub, conn : WsConn) -> Unit {
  self.conns.push(conn)
}

///|
pub fn WsHub::leave(self : WsHub, conn : WsConn) -> Unit {
  let mut at = -1
  for i, candidate in self.conns {
    if candidate.id == conn.id {
      at = i
    }
  }
  if at >= 0 {
    let _ = self.conns.remove(at)
  }
}

///|
pub async fn WsHub::broadcast(self : WsHub, text : String) -> Unit {
  for conn in self.conns {
    conn.send_text(text) catch {
      _err => ()
    }
  }
}

///|
/// Register a WebSocket route: connections to `path` are upgraded and the
/// framework runs the receive loop, invoking the handler's events.
pub fn App::ws(self : App, path : String, handler : WsHandler) -> Unit {
  self.ws_routes.push({
    path,
    handler: async fn(conn) {
      let wc : WsConn = { conn, id: fresh_ws_id(), }
      try {
        while true {
          let text = wc.recv_text()
          (handler.on_message)(wc, text)
        }
      } catch {
        _closed => (handler.on_close)(wc) catch { _err => () }
      }
    },
  })
}

///|
/// Drive the event loop of a single WebSocket connection with a handler.
/// Used by `App::ws`; exposed for tests and custom serving setups.
pub async fn run_ws(handler : WsHandler, conn : @async_websocket.Conn) -> Unit {
  let wc : WsConn = { conn, id: fresh_ws_id(), }
  try {
    while true {
      let text = wc.recv_text()
      (handler.on_message)(wc, text)
    }
  } catch {
    _closed => (handler.on_close)(wc) catch { _err => () }
  }
}