// The hash commands, named as in redis-py. A hash maps field names to string values;
// these map to MoonBit `String`-keyed access over the same `Client`.

///|
/// `HSET key field value...`: set one or more fields, returning the count of fields that
/// were new.
pub async fn Client::hset(
  self : Client,
  key : String,
  fields : Array[(String, String)],
) -> Int64 raise RedisError {
  let args : Array[Bytes] = [b"HSET", arg_string(key)]
  for (f, v) in fields {
    args.push(arg_string(f))
    args.push(arg_string(v))
  }
  expect_int64(self.command(args))
}

///|
/// `HSETNX key field value`: set `field` only if absent. `true` if set.
pub async fn Client::hsetnx(
  self : Client,
  key : String,
  field : String,
  value : String,
) -> Bool raise RedisError {
  expect_bool(
    self.command([
      b"HSETNX",
      arg_string(key),
      arg_string(field),
      arg_string(value),
    ]),
  )
}

///|
/// `HMSET key field value...`: set several fields atomically. Deprecated in favour of
/// `HSET` but kept for parity with redis-py.
pub async fn Client::hmset(
  self : Client,
  key : String,
  fields : Array[(String, String)],
) -> Unit raise RedisError {
  let args : Array[Bytes] = [b"HMSET", arg_string(key)]
  for (f, v) in fields {
    args.push(arg_string(f))
    args.push(arg_string(v))
  }
  status_ok(self.command(args))
}

///|
/// `HGET key field`: the value at `field`, or `None` if the key or field is absent.
pub async fn Client::hget(
  self : Client,
  key : String,
  field : String,
) -> Bytes? raise RedisError {
  expect_bytes(self.command([b"HGET", arg_string(key), arg_string(field)]))
}

///|
/// `HMGET key field...`: the value at each field, `None` where absent, in order.
pub async fn Client::hmget(
  self : Client,
  key : String,
  fields : Array[String],
) -> Array[Bytes?] raise RedisError {
  let args : Array[Bytes] = [b"HMGET", arg_string(key)]
  for f in fields {
    args.push(arg_string(f))
  }
  let items = expect_array(self.command(args))
  items.map(x => expect_bytes(x))
}

///|
/// `HGETALL key`: every field/value pair as an array of alternating field, value.
pub async fn Client::hgetall(
  self : Client,
  key : String,
) -> Array[(String, String)] raise RedisError {
  let items = expect_array(self.command([b"HGETALL", arg_string(key)]))
  decode_pairs(items)
}

///|
/// `HDEL key field...`: delete the given fields, returning how many existed.
pub async fn Client::hdel(
  self : Client,
  key : String,
  fields : Array[String],
) -> Int64 raise RedisError {
  let args : Array[Bytes] = [b"HDEL", arg_string(key)]
  for f in fields {
    args.push(arg_string(f))
  }
  expect_int64(self.command(args))
}

///|
/// `HEXISTS key field`: whether `field` exists in the hash.
pub async fn Client::hexists(
  self : Client,
  key : String,
  field : String,
) -> Bool raise RedisError {
  expect_bool(self.command([b"HEXISTS", arg_string(key), arg_string(field)]))
}

///|
/// `HLEN key`: the number of fields in the hash.
pub async fn Client::hlen(
  self : Client,
  key : String,
) -> Int64 raise RedisError {
  expect_int64(self.command([b"HLEN", arg_string(key)]))
}

///|
/// `HSTRLEN key field`: the byte length of `field`'s value (0 if absent).
pub async fn Client::hstrlen(
  self : Client,
  key : String,
  field : String,
) -> Int64 raise RedisError {
  expect_int64(self.command([b"HSTRLEN", arg_string(key), arg_string(field)]))
}

///|
/// `HINCRBY key field n`: add `n` to `field`'s integer value, returning the result.
pub async fn Client::hincrby(
  self : Client,
  key : String,
  field : String,
  n : Int64,
) -> Int64 raise RedisError {
  expect_int64(
    self.command([b"HINCRBY", arg_string(key), arg_string(field), arg_int64(n)]),
  )
}

///|
/// `HINCRBYFLOAT key field f`: add `f` to `field`'s value, returning the new value.
pub async fn Client::hincrbyfloat(
  self : Client,
  key : String,
  field : String,
  f : Double,
) -> Double raise RedisError {
  expect_double(
    self.command([
      b"HINCRBYFLOAT",
      arg_string(key),
      arg_string(field),
      arg_double(f),
    ]),
  )
}

///|
/// `HKEYS key`: the field names of the hash.
pub async fn Client::hkeys(
  self : Client,
  key : String,
) -> Array[String] raise RedisError {
  let items = expect_array(self.command([b"HKEYS", arg_string(key)]))
  items.map(x => @utf8.decode_lossy(expect_bytes(x).unwrap()))
}

///|
/// `HVALS key`: the values of the hash.
pub async fn Client::hvals(
  self : Client,
  key : String,
) -> Array[String] raise RedisError {
  let items = expect_array(self.command([b"HVALS", arg_string(key)]))
  items.map(x => @utf8.decode_lossy(expect_bytes(x).unwrap()))
}

///|
/// `HSCAN key cursor [MATCH pattern] [COUNT n]`: an incremental hash field scan.
/// Returns the next cursor and the field/value pairs found this call.
pub async fn Client::hscan(
  self : Client,
  key : String,
  cursor : String,
  pattern? : String,
  count? : Int32,
) -> (String, Array[(String, String)]) raise RedisError {
  let args : Array[Bytes] = [b"HSCAN", arg_string(key), arg_string(cursor)]
  push_some(args, b"MATCH", pattern.map(arg_string))
  push_some(args, b"COUNT", count.map(arg_int))
  let arr = expect_array(self.command(args))
  guard arr.length() >= 2 else { raise Protocol("unexpected HSCAN reply") }
  let next = @utf8.decode_lossy(expect_bytes(arr[0]).unwrap())
  let pairs = decode_pairs(expect_array(arr[1]))
  (next, pairs)
}

///|
/// `HRANDFIELD key [count] [WITHVALUES]`: one or more random fields, optionally with
/// their values.
pub async fn Client::hrandfield(
  self : Client,
  key : String,
  count? : Int32,
) -> Array[String] raise RedisError {
  let args : Array[Bytes] = [b"HRANDFIELD", arg_string(key)]
  match count {
    Some(n) => args.push(arg_int(n))
    None => ()
  }
  let items = expect_some(self.command(args))
  items.map(expect_string_value)
}

///|
/// `HRANDFIELD key count WITHVALUES`: random fields with their values. A negative `count` may
/// repeat a field.
pub async fn Client::hrandfield_values(
  self : Client,
  key : String,
  count : Int32,
) -> Array[(String, String)] raise RedisError {
  let args : Array[Bytes] = [
    b"HRANDFIELD",
    arg_string(key),
    arg_int(count),
    b"WITHVALUES",
  ]
  let items = expect_array(self.command(args))
  if items is [@resp.Array(_), ..] {
    return items.map(item => {
      guard item is @resp.Array([field, value]) else {
        raise Protocol("unexpected field/value pair")
      }
      (expect_string_value(field), expect_string_value(value))
    })
  }
  decode_pairs(items)
}

///|
/// Decode a flat array of alternating field, value into field/value pairs.
fn decode_pairs(
  items : Array[@resp.RespValue],
) -> Array[(String, String)] raise RedisError {
  let out : Array[(String, String)] = []
  for i = 0; i < items.length(); i = i + 2 {
    let field = @utf8.decode_lossy(expect_bytes(items[i]).unwrap())
    let value = @utf8.decode_lossy(expect_bytes(items[i + 1]).unwrap())
    out.push((field, value))
  }
  out
}