///|
/// Parse a non-negative decimal integer without accepting signs, whitespace,
/// alternate bases, or overflow. tus integer fields use this restricted form.
pub fn parse_non_negative_i64(
  field_name : String,
  input : String,
) -> Result[Int64, TusError] {
  let value = trim_ows(input)
  if value.length() == 0 {
    return Err(invalid_integer(field_name, input))
  }
  let mut result = 0L
  for character in value {
    if character < '0' || character > '9' {
      return Err(invalid_integer(field_name, input))
    }
    let digit = character.to_int() - '0'.to_int()
    if result > (9223372036854775807L - digit.to_int64()) / 10L {
      return Err(integer_overflow(field_name, input))
    }
    result = result * 10L + digit.to_int64()
  }
  Ok(result)
}

///|
pub fn parse_non_negative_int(
  field_name : String,
  input : String,
) -> Result[Int, TusError] {
  match parse_non_negative_i64(field_name, input) {
    Err(error) => Err(error)
    Ok(value) =>
      if value > 2147483647L {
        Err(integer_overflow(field_name, input))
      } else {
        Ok(value.to_int())
      }
  }
}

///|
pub fn checked_add_i64(
  left : Int64,
  right : Int64,
  field_name? : String = "upload-offset",
) -> Result[Int64, TusError] {
  if left < 0L || right < 0L {
    return Err(
      invalid_integer(field_name, left.to_string() + "+" + right.to_string()),
    )
  }
  if left > 9223372036854775807L - right {
    return Err(
      integer_overflow(field_name, left.to_string() + "+" + right.to_string()),
    )
  }
  Ok(left + right)
}

///|
pub fn checked_body_end(
  offset : Int64,
  body_length : Int,
) -> Result[Int64, TusError] {
  checked_add_i64(offset, body_length.to_int64(), field_name="upload-offset")
}

///|
pub fn decimal(value : Int64) -> String {
  value.to_string()
}

///|
pub fn is_canonical_decimal(value : String) -> Bool {
  if value == "0" {
    return true
  }
  if value.length() == 0 || value[0].to_int() == '0'.to_int() {
    return false
  }
  for character in value {
    if character < '0' || character > '9' {
      return false
    }
  }
  true
}

///|
pub fn require_content_length(request : TusRequest) -> Result[Int, TusError] {
  match
    request.headers.required_singleton("content-length", MissingContentLength) {
    Err(error) => Err(error)
    Ok(value) =>
      match parse_non_negative_int("content-length", value) {
        Err(error) => Err(error)
        Ok(declared) =>
          if declared != request.body.length() {
            Err(body_length_mismatch(declared, request.body.length()))
          } else {
            Ok(declared)
          }
      }
  }
}

///|
pub fn optional_upload_length(headers : Headers) -> Result[Int64?, TusError] {
  match headers.singleton("upload-length") {
    Err(error) => Err(error)
    Ok(None) => Ok(None)
    Ok(Some(value)) =>
      match parse_non_negative_i64("upload-length", value) {
        Ok(length) => Ok(Some(length))
        Err(error) => Err(error)
      }
  }
}

///|
pub fn required_upload_offset(headers : Headers) -> Result[Int64, TusError] {
  match headers.required_singleton("upload-offset", MissingUploadOffset) {
    Err(error) => Err(error)
    Ok(value) => parse_non_negative_i64("upload-offset", value)
  }
}

///|
pub fn optional_defer_length(headers : Headers) -> Result[Bool, TusError] {
  match headers.singleton("upload-defer-length") {
    Err(error) => Err(error)
    Ok(None) => Ok(false)
    Ok(Some(value)) =>
      if trim_ows(value) == "1" {
        Ok(true)
      } else {
        Err(
          tus_error(
            InvalidDeferredLength,
            "TUS_DEFER_LENGTH_INVALID",
            "Upload-Defer-Length must be exactly 1",
            header_name=Some("upload-defer-length"),
            expected=Some("1"),
            actual=Some(value),
          ),
        )
      }
  }
}

///|
pub fn validate_declared_size(
  length : Int64,
  limits : TusLimits,
) -> Result[Unit, TusError] {
  if length > limits.max_upload_size {
    Err(upload_too_large(limits.max_upload_size, length))
  } else {
    Ok(())
  }
}

///|
pub fn validate_patch_size(
  body : Bytes,
  limits : TusLimits,
) -> Result[Unit, TusError] {
  if body.length() > limits.max_patch_bytes {
    Err(
      tus_error(
        BodyTooLarge,
        "TUS_PATCH_BODY_TOO_LARGE",
        "PATCH body exceeds the configured per-request byte limit",
        status=413,
        expected=Some(limits.max_patch_bytes.to_string()),
        actual=Some(body.length().to_string()),
      ),
    )
  } else {
    Ok(())
  }
}