/// Serialization of bare items (RFC 9651 §4.1.3.1) and each scalar type
/// (§4.1.4 through §4.1.11).

///|
/// Serializes a bare item, dispatching on its variant.
pub fn serialize_bare_item(
  buf : @buffer.Buffer,
  bare : BareItem,
) -> Result[Unit, SfError] {
  match bare {
    Integer(v) => serialize_integer(buf, v)
    Decimal(d) =>
      match d.to_canonical_string() {
        Err(e) => Err(e)
        Ok(s) => {
          ws(buf, s)
          Ok(())
        }
      }
    StringItem(s) => serialize_string(buf, s)
    Token(s) => serialize_token(buf, s)
    ByteSequence(b) => serialize_byte_sequence(buf, b)
    Boolean(v) => serialize_boolean(buf, v)
    Date(v) => serialize_date(buf, v)
    DisplayString(s) => serialize_display_string(buf, s)
  }
}

///|
/// Serializes an Integer (RFC 9651 §4.1.4), validating the fifteen-digit
/// range.
pub fn serialize_integer(
  buf : @buffer.Buffer,
  v : Int64,
) -> Result[Unit, SfError] {
  if v < INT64_MIN_VALID || v > INT64_MAX_VALID {
    return Err(serr("integer out of range"))
  }
  if v < 0L {
    wb(buf, b'-')
  }
  ws(buf, v.abs().to_string())
  Ok(())
}

///|
/// Serializes a String (RFC 9651 §4.1.6), escaping `"` and `\`.
pub fn serialize_string(
  buf : @buffer.Buffer,
  s : String,
) -> Result[Unit, SfError] {
  let units = s.code_units()
  for i in 0.. 0x7E {
      return Err(serr("string contains a non-visible character"))
    }
  }
  let bytes = @utf8.encode(s, bom=false)
  wb(buf, b'"')
  for i in 0.. Result[Unit, SfError] {
  let bytes = @utf8.encode(s, bom=false)
  if bytes.is_empty() {
    return Err(serr("empty token"))
  }
  let first = bytes[0]
  if !is_alpha(first) && first != b'*' {
    return Err(serr("token does not start with a valid character"))
  }
  for i in 0.. Result[Unit, SfError] {
  wb(buf, b':')
  ws(buf, @base64.encode(b, padding=true))
  wb(buf, b':')
  Ok(())
}

///|
/// Serializes a Boolean (RFC 9651 §4.1.9).
pub fn serialize_boolean(
  buf : @buffer.Buffer,
  v : Bool,
) -> Result[Unit, SfError] {
  wb(buf, b'?')
  wb(buf, if v { b'1' } else { b'0' })
  Ok(())
}

///|
/// Serializes a Date (RFC 9651 §4.1.10): `@` followed by an Integer.
/// The value is a UTC seconds delta; no timezone conversion is performed.
pub fn serialize_date(buf : @buffer.Buffer, v : Int64) -> Result[Unit, SfError] {
  wb(buf, b'@')
  serialize_integer(buf, v)
}

///|
/// Serializes a Display String (RFC 9651 §4.1.11): `%"..."` with
/// percent-encoded UTF-8 bytes. Percent hex digits are lowercase.
pub fn serialize_display_string(
  buf : @buffer.Buffer,
  s : String,
) -> Result[Unit, SfError] {
  if !has_no_lone_surrogates(s) {
    return Err(serr("display string contains an unpaired surrogate"))
  }
  let bytes = @utf8.encode(s, bom=false)
  wb(buf, b'%')
  wb(buf, b'"')
  for i in 0..= (0x7F).to_byte() {
      wb(buf, b'%')
      write_hex_byte(buf, b)
    } else {
      wb(buf, b)
    }
  }
  wb(buf, b'"')
  Ok(())
}

///|
/// Checks that a String contains no unpaired UTF-16 surrogate code units.
/// Unpaired surrogates would not round-trip through UTF-8 encoding.
fn has_no_lone_surrogates(s : String) -> Bool {
  let units = s.code_units()
  let mut i = 0
  while i < units.length() {
    let u = units[i]
    if u >= 0xD800 && u <= 0xDBFF {
      // High surrogate: the following unit must be a low surrogate.
      if i + 1 >= units.length() {
        return false
      }
      let nxt = units[i + 1]
      if !(nxt >= 0xDC00 && nxt <= 0xDFFF) {
        return false
      }
      i = i + 2
    } else if u >= 0xDC00 && u <= 0xDFFF {
      return false
    } else {
      i = i + 1
    }
  }
  true
}