// The bridge from a decoded `RespValue` to the MoonBit type a typed command promises.
// Every helper raises `RedisError::Protocol` when the reply is not the shape the command
// documented, so a caller that asked for an integer gets an integer or a precise error.

///|
/// Assert a status reply (the `+OK` most write commands return). Any other shape is a
/// protocol error.
pub fn status_ok(reply : @resp.RespValue) -> Unit raise RedisError {
  match reply {
    @resp.SimpleString(_) => ()
    other => raise Protocol("expected status, got " + @resp.resp_kind(other))
  }
}

///|
/// A reply as an `Int64`: an integer value, or the `+1`/`-1` a command aliases to.
pub fn expect_int64(reply : @resp.RespValue) -> Int64 raise RedisError {
  match reply {
    @resp.Integer(n) => n
    other => raise Protocol("expected integer, got " + @resp.resp_kind(other))
  }
}

///|
/// A number redis writes as text: `TIME`, and the ports and counts of sentinel and stream
/// replies, come back as strings under both protocols.
pub fn expect_number(reply : @resp.RespValue) -> Int64 raise RedisError {
  match reply {
    @resp.Integer(n) => n
    @resp.BulkString(b) =>
      @string.parse_int64(@utf8.decode_lossy(b[:])) catch {
        _ => raise Protocol("expected a number, got bulk")
      }
    other => raise Protocol("expected a number, got " + @resp.resp_kind(other))
  }
}

///|
/// The members of a reply that is one bulk value without a count and an array with one, and
/// nothing when the key is missing.
pub fn expect_some(
  reply : @resp.RespValue,
) -> Array[@resp.RespValue] raise RedisError {
  match reply {
    @resp.BulkString(_) | @resp.SimpleString(_) => [reply]
    other => expect_array(other)
  }
}

///|
/// A reply as a `Bool`: the `1`/`0` redis uses for existence, success and comparison.
pub fn expect_bool(reply : @resp.RespValue) -> Bool raise RedisError {
  match reply {
    @resp.Integer(n) => n != 0
    other =>
      raise Protocol("expected integer 0/1, got " + @resp.resp_kind(other))
  }
}

///|
/// A reply as binary bytes, or `None` for the nil a missing key decodes to.
pub fn expect_bytes(reply : @resp.RespValue) -> Bytes? raise RedisError {
  match reply {
    @resp.BulkString(b) | @resp.VerbatimString(_, b) => Some(b)
    @resp.Null => None
    other =>
      raise Protocol("expected bulk string, got " + @resp.resp_kind(other))
  }
}

///|
/// A reply as a `String`, or `None` for nil.
pub fn expect_string(reply : @resp.RespValue) -> String? raise RedisError {
  match reply {
    @resp.BulkString(b) | @resp.VerbatimString(_, b) =>
      Some(@utf8.decode_lossy(b[:]))
    @resp.SimpleString(s) => Some(s)
    @resp.Null => None
    other => raise Protocol("expected string, got " + @resp.resp_kind(other))
  }
}

///|
/// A reply as a `String`, raising on nil (for commands that always return a value).
pub fn expect_string_value(reply : @resp.RespValue) -> String raise RedisError {
  match expect_string(reply) {
    Some(s) => s
    None => raise Protocol("expected string, got null")
  }
}

///|
/// A reply as an `Array[RespValue]`: an array, or the nil an empty result decodes to.
pub fn expect_array(
  reply : @resp.RespValue,
) -> Array[@resp.RespValue] raise RedisError {
  match reply {
    @resp.Array(items) | @resp.RespSet(items) | @resp.Push(items) => items
    // RESP3 answers HGETALL and its kin with a map where RESP2 had a flat array.
    @resp.RespMap(pairs) => {
      let out = []
      for pair in pairs {
        out.push(pair.0)
        out.push(pair.1)
      }
      out
    }
    @resp.Null => []
    other => raise Protocol("expected array, got " + @resp.resp_kind(other))
  }
}

///|
/// A reply as a `Double`: a double, the integer a score may come back as, or a parsed
/// decimal string.
pub fn expect_double(reply : @resp.RespValue) -> Double raise RedisError {
  match reply {
    @resp.Double(d) => d
    @resp.Integer(n) => n.to_double()
    @resp.SimpleString(s) =>
      @string.parse_double(s) catch {
        _ => raise Protocol("expected double, got " + s)
      }
    @resp.BulkString(b) =>
      @string.parse_double(@utf8.decode_lossy(b[:])) catch {
        _ => raise Protocol("expected double, got bulk")
      }
    other => raise Protocol("expected double, got " + @resp.resp_kind(other))
  }
}

///|
/// A score that may be missing: RESP3 sends a double, RESP2 a string, and both nil.
pub fn expect_score(reply : @resp.RespValue) -> Double? raise RedisError {
  match reply {
    @resp.Null => None
    other => Some(expect_double(other))
  }
}

///|
/// A reply as a map (RESP3 `%`) of key/value pairs.
pub fn expect_map(
  reply : @resp.RespValue,
) -> Array[(@resp.RespValue, @resp.RespValue)] raise RedisError {
  match reply {
    @resp.RespMap(pairs) => pairs
    other => raise Protocol("expected map, got " + @resp.resp_kind(other))
  }
}