///|
/// Raw Redis response passed to custom command decoders.
///
/// Use the `as_*` helpers to turn a raw response into the type your custom
/// command should return.
///
/// Example:
/// ```moonbit nocheck
/// @redis.Command([b"DBSIZE"], value => value.as_int())
/// ```
struct RawValue {
  value : @resp.Value
}

///|
fn RawValue::from_resp(value : @resp.Value) -> RawValue {
  { value, }
}

///|
fn RawValue::desc(self : RawValue) -> @resp.ValueDesc {
  self.value.desc
}

///|
fn @resp.Value::raw(self : @resp.Value) -> RawValue {
  RawValue::from_resp(self)
}

///|
/// Reads the response as a UTF-8 string.
///
/// Example:
/// ```moonbit nocheck
/// @redis.Command([b"PING"], value => value.as_string())
/// ```
pub fn RawValue::as_string(self : RawValue) -> String raise {
  match self.desc() {
    Error(err) => raise response_error(err)
    String(value, hint=_) => decode_utf8(value)
    _ => raise UnexpectedResponse("expected string")
  }
}

///|
/// Reads the response as raw bytes.
///
/// Example:
/// ```moonbit nocheck
/// @redis.Command([b"GET", b"blob"], value => value.as_bytes())
/// ```
pub fn RawValue::as_bytes(self : RawValue) -> Bytes raise {
  match self.desc() {
    Error(err) => raise response_error(err)
    String(value, hint=_) => value
    _ => raise UnexpectedResponse("expected string")
  }
}

///|
/// Returns whether the response is Redis null.
///
/// Example:
/// ```moonbit nocheck
/// @redis.Command([b"GET", b"missing"], value => value.is_null())
/// ```
pub fn RawValue::is_null(self : RawValue) -> Bool {
  self.desc() is Null
}

///|
/// Reads the response as `Int`.
///
/// Example:
/// ```moonbit nocheck
/// @redis.Command([b"DBSIZE"], value => value.as_int())
/// ```
pub fn RawValue::as_int(self : RawValue) -> Int raise {
  match self.desc() {
    Error(err) => raise response_error(err)
    Integer(value) => int64_to_int(value)
    _ => raise UnexpectedResponse("expected integer")
  }
}

///|
/// Reads the response as `Int64`.
pub fn RawValue::as_int64(self : RawValue) -> Int64 raise {
  match self.desc() {
    Error(err) => raise response_error(err)
    Integer(value) => value
    _ => raise UnexpectedResponse("expected integer")
  }
}

///|
/// Reads the response as `Bool`.
///
/// This accepts Redis boolean replies and integer replies where `0` means
/// `false` and `1` means `true`.
pub fn RawValue::as_bool(self : RawValue) -> Bool raise {
  match self.desc() {
    Error(err) => raise response_error(err)
    Boolean(value) => value
    Integer(0L) => false
    Integer(1L) => true
    _ => raise UnexpectedResponse("expected boolean")
  }
}

///|
fn RawValue::as_bool_int(self : RawValue) -> Bool raise {
  match self.desc() {
    Error(err) => raise response_error(err)
    Integer(0L) => false
    Integer(1L) => true
    _ => raise UnexpectedResponse("expected boolean integer")
  }
}

///|
/// Reads the response as `Double`.
pub fn RawValue::as_double(self : RawValue) -> Double raise {
  match self.desc() {
    Error(err) => raise response_error(err)
    Double(value) => value
    String(value, hint=_) =>
      @string.parse_double(decode_utf8(value)) catch {
        _ => raise UnexpectedResponse("expected double")
      }
    _ => raise UnexpectedResponse("expected double")
  }
}

///|
/// Succeeds only when the response is `OK`.
pub fn RawValue::expect_ok(self : RawValue) -> Unit raise {
  guard self.as_string() == "OK" else {
    raise UnexpectedResponse("expected OK")
  }
}

///|
/// Reads the response as an array by decoding each item with `convert`.
pub fn[T] RawValue::as_array(
  self : RawValue,
  convert : (RawValue) -> T raise,
) -> Array[T] raise {
  match self.desc() {
    Error(err) => raise response_error(err)
    Array(values) | Set(values) => {
      let out : Array[T] = Array::new(capacity=values.length())
      for value in values {
        out.push(convert(value.raw()))
      }
      out
    }
    _ => raise UnexpectedResponse("expected array or set")
  }
}

///|
/// Reads Redis null as `None`; otherwise decodes the value with `convert`.
///
/// Example:
/// ```moonbit nocheck
/// @redis.Command([b"GET", b"name"], value => {
///   value.as_option(item => item.as_string())
/// })
/// ```
pub fn[T] RawValue::as_option(
  self : RawValue,
  convert : (RawValue) -> T raise,
) -> T? raise {
  match self.desc() {
    Error(err) => raise response_error(err)
    Null => None
    _ => Some(convert(self))
  }
}

///|
/// Reads the response as a `HashSet` by decoding each item with `convert`.
pub fn[T : Hash + Eq] RawValue::as_set(
  self : RawValue,
  convert : (RawValue) -> T raise,
) -> @hashset.HashSet[T] raise {
  let set : @hashset.HashSet[T] = HashSet([])
  match self.desc() {
    Error(err) => raise response_error(err)
    Set(values) | Array(values) =>
      for value in values {
        set.add(convert(value.raw()))
      }
    _ => raise UnexpectedResponse("expected array or set")
  }
  set
}

///|
fn RawValue::as_string_array_or_empty(self : RawValue) -> Array[String] raise {
  self
  .as_option(value => value.as_array(item => item.as_string()))
  .unwrap_or([])
}

///|
/// Reads the response as a string map.
///
/// Example:
/// ```moonbit nocheck
/// @redis.Command([b"HGETALL", b"user:1"], value => value.as_string_map())
/// ```
pub fn RawValue::as_string_map(self : RawValue) -> Map[String, String] raise {
  let map : Map[String, String] = Map([])
  match self.desc() {
    Error(err) => raise response_error(err)
    Array(values) => {
      guard values.length() % 2 == 0 else {
        raise UnexpectedResponse("expected even string array")
      }
      for i in 0..<(values.length() / 2) {
        map[values[i * 2].raw().as_string()] = values[i * 2 + 1]
          .raw()
          .as_string()
      }
    }
    Map(entries) =>
      for entry in entries {
        map[entry.0.raw().as_string()] = entry.1.raw().as_string()
      }
    _ => raise UnexpectedResponse("expected string map")
  }
  map
}

///|
fn RawValue::as_score_pairs(self : RawValue) -> Array[(String, Double)] raise {
  match self.desc() {
    Error(err) => raise response_error(err)
    Array(values) =>
      // RESP3 returns WITHSCORES as nested [member, score] pairs, while RESP2
      // returns a flat [member, score, ...] array.
      if values.all(value => value.desc is Array(_)) {
        let out : Array[(String, Double)] = Array::new(capacity=values.length())
        for value in values {
          match value.desc {
            Array([value, score]) =>
              out.push((value.raw().as_string(), score.raw().as_double()))
            _ => raise UnexpectedResponse("expected scored pair array")
          }
        }
        out
      } else {
        guard values.length() % 2 == 0 else {
          raise UnexpectedResponse("expected even scored array")
        }
        let out : Array[(String, Double)] = Array::new(
          capacity=values.length() / 2,
        )
        for i in 0..<(values.length() / 2) {
          out.push(
            (
              values[i * 2].raw().as_string(),
              values[i * 2 + 1].raw().as_double(),
            ),
          )
        }
        out
      }
    _ => raise UnexpectedResponse("expected scored array")
  }
}

///|
fn RawValue::as_scan_strings(self : RawValue) -> (String, Array[String]) raise {
  match self.desc() {
    Error(err) => raise response_error(err)
    Array([cursor, values]) =>
      (
        cursor.raw().as_string(),
        values.raw().as_array(value => value.as_string()),
      )
    _ => raise UnexpectedResponse("expected scan response")
  }
}

///|
fn RawValue::as_scan_map(
  self : RawValue,
) -> (String, Map[String, String]) raise {
  match self.desc() {
    Error(err) => raise response_error(err)
    Array([cursor, values]) =>
      (cursor.raw().as_string(), values.raw().as_string_map())
    _ => raise UnexpectedResponse("expected hash scan response")
  }
}

///|
fn RawValue::as_scan_set(
  self : RawValue,
) -> (String, @hashset.HashSet[String]) raise {
  match self.desc() {
    Error(err) => raise response_error(err)
    Array([cursor, values]) =>
      (
        cursor.raw().as_string(),
        values.raw().as_set(value => value.as_string()),
      )
    _ => raise UnexpectedResponse("expected set scan response")
  }
}

///|
fn RawValue::as_scan_score_pairs(
  self : RawValue,
) -> (String, Array[(String, Double)]) raise {
  match self.desc() {
    Error(err) => raise response_error(err)
    Array([cursor, values]) =>
      (cursor.raw().as_string(), values.raw().as_score_pairs())
    _ => raise UnexpectedResponse("expected sorted set scan response")
  }
}