// Decoding a value that is already in hand, rather than a field of an object.
//
// The `take_*` family reads a named field; these read the value itself, which
// is what an array element and a union member are. Generated code passes them
// as decoders: `@data.take_array(rest, "langs", path~, @data.expect_string)`.
//
// Unlike `take_*`, a wrong type here RAISES rather than answering `None`. There
// is no `extra` to fall back into -- an array element that is not what the
// schema says it is has nowhere to go, and dropping it would silently shorten
// the list.

///|
pub fn expect_string(
  value : LexValue,
  path : String,
) -> String raise DecodeError {
  match value.as_string() {
    Some(s) => s
    None =>
      raise DecodeError(path~, reason="expected string, got \{value.kind()}")
  }
}

///|
pub fn expect_int(value : LexValue, path : String) -> Int64 raise DecodeError {
  match value.as_int() {
    Some(i) => i
    None =>
      raise DecodeError(path~, reason="expected integer, got \{value.kind()}")
  }
}

///|
pub fn expect_bool(value : LexValue, path : String) -> Bool raise DecodeError {
  match value.as_bool() {
    Some(b) => b
    None =>
      raise DecodeError(path~, reason="expected boolean, got \{value.kind()}")
  }
}

///|
pub fn expect_bytes(value : LexValue, path : String) -> Bytes raise DecodeError {
  match value.as_bytes() {
    Some(b) => b
    None =>
      raise DecodeError(path~, reason="expected bytes, got \{value.kind()}")
  }
}

///|
pub fn expect_link(
  value : LexValue,
  path : String,
) -> @syntax.Cid raise DecodeError {
  match value.as_link() {
    Some(c) => c
    None =>
      raise DecodeError(path~, reason="expected cid-link, got \{value.kind()}")
  }
}

///|
/// A string in one of the eleven Lexicon formats.
pub fn[T] expect_format(
  value : LexValue,
  path : String,
  parse : (String) -> T raise @syntax.SyntaxError,
) -> T raise DecodeError {
  let text = expect_string(value, path)
  parse(text) catch {
    e => raise DecodeError(path~, reason=e.reason())
  }
}

///|
/// An array, every element decoded by `parse`. All-or-nothing, for the same
/// reason `take_array` is: a half-decoded list round-trips as a shorter list.
pub fn[T] expect_array(
  value : LexValue,
  path : String,
  parse : (LexValue, String) -> T raise DecodeError,
) -> Array[T] raise DecodeError {
  guard value.as_array() is Some(items) else {
    raise DecodeError(path~, reason="expected array, got \{value.kind()}")
  }
  let out = Array::new(capacity=items.length())
  for i, item in items {
    out.push(parse(item, index_path(path, i)))
  }
  out
}

///|
/// Adds a `$type` to an object, for a value being encoded as a union member.
///
/// The UNION owns the tag, not the member: `com.atproto.repo.strongRef` is a
/// bare `{uri, cid}` on its own and only carries a `$type` where a union needs
/// to tell it from its siblings. Tagging the member type instead -- which is
/// what rsky does, inconsistently -- puts a `$type` on values that should not
/// have one.
///
/// A non-object is returned unchanged; there is nowhere to put a tag, and a
/// union whose members are not objects could not be dispatched anyway.
pub fn tagged(value : LexValue, type_ : String) -> LexValue {
  match value {
    Obj(fields) => {
      let out : Map[String, LexValue] = Map([])
      out["$type"] = Str(type_)
      for key, item in fields {
        if key != "$type" {
          out[key] = item
        }
      }
      Obj(out)
    }
    other => other
  }
}