///|
fn decode_set_result(value : RawValue) -> Bool raise {
  match value.desc() {
    Error(err) => raise response_error(err)
    String(value, hint=_) => decode_utf8(value) == "OK"
    Null => false
    _ => raise UnexpectedResponse("expected OK or null")
  }
}

///|
fn Command::get(key : String) -> Command[String?] {
  Command(ArgsBuilder(b"GET")..push_string(key).build(), value => {
    value.as_option(item => item.as_string())
  })
}

///|
fn Command::set(
  key : String,
  value : String,
  ex? : Int,
  px? : Int,
  nx? : Bool,
  xx? : Bool,
) -> Command[Bool] {
  let args = ArgsBuilder(b"SET")
  args.push_string(key)
  args.push_string(value)
  if ex is Some(seconds) {
    args.push_bytes(b"EX")
    args.push_int(seconds)
  }
  if px is Some(milliseconds) {
    args.push_bytes(b"PX")
    args.push_int(milliseconds)
  }
  if nx is Some(true) {
    args.push_bytes(b"NX")
  }
  if xx is Some(true) {
    args.push_bytes(b"XX")
  }
  Command(args.build(), decode_set_result)
}

///|
fn Command::set_get(
  key : String,
  value : String,
  ex? : Int,
  px? : Int,
  nx? : Bool,
  xx? : Bool,
) -> Command[String?] {
  let args = ArgsBuilder(b"SET")
  args.push_string(key)
  args.push_string(value)
  if ex is Some(seconds) {
    args.push_bytes(b"EX")
    args.push_int(seconds)
  }
  if px is Some(milliseconds) {
    args.push_bytes(b"PX")
    args.push_int(milliseconds)
  }
  if nx is Some(true) {
    args.push_bytes(b"NX")
  }
  if xx is Some(true) {
    args.push_bytes(b"XX")
  }
  args.push_bytes(b"GET")
  Command(args.build(), value => value.as_option(item => item.as_string()))
}

///|
fn Command::mget(keys : ArrayView[String]) -> Command[Array[String?]] {
  Command(ArgsBuilder(b"MGET")..push_strings(keys).build(), value => {
    value.as_array(item => item.as_option(item => item.as_string()))
  })
}

///|
fn Command::mset(items : ArrayView[(String, String)]) -> Command[Unit] {
  Command(ArgsBuilder(b"MSET")..push_pairs(items).build(), value => {
    value.expect_ok()
  })
}

///|
fn Command::msetnx(items : ArrayView[(String, String)]) -> Command[Bool] {
  Command(ArgsBuilder(b"MSETNX")..push_pairs(items).build(), value => {
    value.as_bool_int()
  })
}

///|
fn Command::incr(key : String) -> Command[Int] {
  Command(ArgsBuilder(b"INCR")..push_string(key).build(), value => {
    value.as_int()
  })
}

///|
fn Command::incrby(key : String, increment : Int) -> Command[Int] {
  Command(
    ArgsBuilder(b"INCRBY")..push_string(key)..push_int(increment).build(),
    value => value.as_int(),
  )
}

///|
fn Command::decr(key : String) -> Command[Int] {
  Command(ArgsBuilder(b"DECR")..push_string(key).build(), value => {
    value.as_int()
  })
}

///|
fn Command::decrby(key : String, decrement : Int) -> Command[Int] {
  Command(
    ArgsBuilder(b"DECRBY")..push_string(key)..push_int(decrement).build(),
    value => value.as_int(),
  )
}

///|
fn Command::setnx(key : String, value : String) -> Command[Bool] {
  Command(ArgsBuilder(b"SETNX")..push_string(key)..push_string(value).build(), value => {
    value.as_bool_int()
  })
}

///|
fn Command::setex(key : String, seconds : Int, value : String) -> Command[Unit] {
  Command(
    ArgsBuilder(b"SETEX")
    ..push_string(key)
    ..push_int(seconds)
    ..push_string(value)
    .build(),
    value => value.expect_ok(),
  )
}

///|
fn Command::psetex(
  key : String,
  milliseconds : Int,
  value : String,
) -> Command[Unit] {
  Command(
    ArgsBuilder(b"PSETEX")
    ..push_string(key)
    ..push_int(milliseconds)
    ..push_string(value)
    .build(),
    value => value.expect_ok(),
  )
}

///|
fn Command::incrbyfloat(key : String, increment : Double) -> Command[Double] {
  Command(
    ArgsBuilder(b"INCRBYFLOAT")
    ..push_string(key)
    ..push_double(increment)
    .build(),
    value => value.as_double(),
  )
}

///|
fn Command::append(key : String, value : String) -> Command[Int] {
  Command(
    ArgsBuilder(b"APPEND")..push_string(key)..push_string(value).build(),
    value => value.as_int(),
  )
}

///|
fn Command::strlen(key : String) -> Command[Int] {
  Command(ArgsBuilder(b"STRLEN")..push_string(key).build(), value => {
    value.as_int()
  })
}

///|
fn Command::getrange(key : String, start : Int, end : Int) -> Command[String] {
  Command(
    ArgsBuilder(b"GETRANGE")
    ..push_string(key)
    ..push_int(start)
    ..push_int(end)
    .build(),
    value => value.as_string(),
  )
}

///|
fn Command::setrange(
  key : String,
  offset : Int,
  value : String,
) -> Command[Int] {
  Command(
    ArgsBuilder(b"SETRANGE")
    ..push_string(key)
    ..push_int(offset)
    ..push_string(value)
    .build(),
    value => value.as_int(),
  )
}