///|
enum Command {
  Me
  Guilds
  Channels(@model.GuildId)
  Messages(
    @model.ChannelId,
    limit~ : Int,
    before~ : @model.MessageId?,
    after~ : @model.MessageId?
  )
  Send(@model.ChannelId, content~ : String?, reply_to~ : @model.MessageId?)
  Api(@dhttp.Route, Json?)
}

///|
fn validate_content(content : String) -> String raise UsageError {
  if content.trim(chars=" \n\r\t") == "" {
    raise UsageError("refusing to send an empty message")
  }
  content
}

///|
fn[T : ToJson] to_json_array(items : Array[T]) -> Json {
  Json::array(items.map(item => item.to_json()))
}

///|
async fn run(client : @dhttp.Client, command : Command) -> Json {
  match command {
    Me => client.get_current_user().to_json()
    Guilds => {
      let pages = client.paginate_current_user_guilds(
        page_size=200,
        with_counts=true,
      )
      let guilds = []
      while pages.next_page() is Some(page) {
        guilds.append(page)
      }
      to_json_array(guilds)
    }
    Channels(id) => to_json_array(client.guild_ref(id).channels())
    Messages(id, limit~, before~, after~) => {
      let messages = client
        .channel_ref(id)
        .messages(before?, after?, page_size=limit.min(MAX_PAGE_SIZE))
        .collect(max=limit)
      messages.sort_by((a, b) => b.id.compare(a.id))
      to_json_array(messages)
    }
    Send(id, content~, reply_to~) => {
      let content = match content {
        Some(text) => text
        None => {
          let data = @stdio.stdin.read_all()
          validate_content(
            data.text() catch {
              _ => raise UsageError("stdin is not valid UTF-8")
            },
          )
        }
      }
      client.channel_ref(id).send(content~, reply_to?).to_json()
    }
    Api(route, body) => client.request(route, body?)
  }
}