///|
pub(all) struct FileSpec {
  name : String
  filename : String
  content_type : String
  body : String
} derive(Debug, Eq)

///|
pub(all) struct MultipartRequest {
  content_type : String
  body : String
} derive(Debug, Eq)

///|
pub(all) struct ParseAndValidateResult {
  form : MultipartForm
  report : ValidationReport
} derive(Debug, Eq)

///|
pub fn file_spec(
  name : String,
  filename : String,
  content_type : String,
  body : String,
) -> FileSpec {
  { name, filename: safe_filename(filename), content_type, body }
}

///|
pub fn text_file_spec(
  name : String,
  filename : String,
  body : String,
) -> FileSpec {
  file_spec(name, filename, "text/plain", body)
}

///|
pub fn binary_file_spec(
  name : String,
  filename : String,
  body : String,
) -> FileSpec {
  file_spec(name, filename, "application/octet-stream", body)
}

///|
pub fn parse_multipart_request(
  content_type : String,
  body : String,
) -> Result[MultipartForm, MultipartError] {
  match boundary_from_content_type(content_type) {
    Ok(boundary) => parse_multipart(body, boundary)
    Err(err) => Err(err)
  }
}

///|
pub fn parse_multipart_request_with_options(
  content_type : String,
  body : String,
  options : ParseOptions,
) -> Result[MultipartForm, MultipartError] {
  match boundary_from_content_type(content_type) {
    Ok(boundary) => parse_multipart_with_options(body, boundary, options)
    Err(err) => Err(err)
  }
}

///|
pub fn encode_fields(
  fields : Array[(String, String)],
  boundary? : String,
) -> Result[EncodedForm, MultipartError] {
  let parts = Array::new()
  let mut i = 0
  while i < fields.length() {
    let (name, value) = fields[i]
    parts.push(text_part(name, value))
    i = i + 1
  }
  match boundary {
    Some(value) => encode_multipart(parts, boundary=value)
    None => encode_multipart(parts)
  }
}

///|
pub fn encode_files(
  files : Array[FileSpec],
  boundary? : String,
) -> Result[EncodedForm, MultipartError] {
  let parts = Array::new()
  let mut i = 0
  while i < files.length() {
    let file = files[i]
    parts.push(
      file_part(
        file.name,
        file.filename,
        file.body,
        content_type=file.content_type,
      ),
    )
    i = i + 1
  }
  match boundary {
    Some(value) => encode_multipart(parts, boundary=value)
    None => encode_multipart(parts)
  }
}

///|
pub fn encode_fields_and_files(
  fields : Array[(String, String)],
  files : Array[FileSpec],
  boundary? : String,
) -> Result[EncodedForm, MultipartError] {
  let parts = Array::new()
  let mut i = 0
  while i < fields.length() {
    let (name, value) = fields[i]
    parts.push(text_part(name, value))
    i = i + 1
  }
  let mut j = 0
  while j < files.length() {
    let file = files[j]
    parts.push(
      file_part(
        file.name,
        file.filename,
        file.body,
        content_type=file.content_type,
      ),
    )
    j = j + 1
  }
  match boundary {
    Some(value) => encode_multipart(parts, boundary=value)
    None => encode_multipart(parts)
  }
}

///|
pub fn encoded_to_request(encoded : EncodedForm) -> MultipartRequest {
  { content_type: encoded.content_type, body: encoded.body }
}

///|
pub fn build_request_from_fields(
  fields : Array[(String, String)],
  boundary? : String,
) -> Result[MultipartRequest, MultipartError] {
  match boundary {
    Some(value) =>
      match encode_fields(fields, boundary=value) {
        Ok(encoded) => Ok(encoded_to_request(encoded))
        Err(err) => Err(err)
      }
    None =>
      match encode_fields(fields) {
        Ok(encoded) => Ok(encoded_to_request(encoded))
        Err(err) => Err(err)
      }
  }
}

///|
pub fn build_upload_request(
  fields : Array[(String, String)],
  files : Array[FileSpec],
  boundary? : String,
) -> Result[MultipartRequest, MultipartError] {
  match boundary {
    Some(value) =>
      match encode_fields_and_files(fields, files, boundary=value) {
        Ok(encoded) => Ok(encoded_to_request(encoded))
        Err(err) => Err(err)
      }
    None =>
      match encode_fields_and_files(fields, files) {
        Ok(encoded) => Ok(encoded_to_request(encoded))
        Err(err) => Err(err)
      }
  }
}

///|
pub fn roundtrip_request(
  request : MultipartRequest,
) -> Result[MultipartForm, MultipartError] {
  parse_multipart_request(request.content_type, request.body)
}

///|
pub fn parse_and_validate_request(
  content_type : String,
  body : String,
  policy : ValidationPolicy,
) -> Result[ParseAndValidateResult, MultipartError] {
  match parse_multipart_request(content_type, body) {
    Ok(form) => Ok({ form, report: form.validate(policy) })
    Err(err) => Err(err)
  }
}

///|
pub fn validate_upload_request(
  request : MultipartRequest,
  policy : ValidationPolicy,
) -> Result[ValidationReport, MultipartError] {
  match parse_and_validate_request(request.content_type, request.body, policy) {
    Ok(result) => Ok(result.report)
    Err(err) => Err(err)
  }
}

///|
pub fn require_single_file(
  form : MultipartForm,
  name : String,
) -> Result[FormPart, MultipartError] {
  let files = form.files(name)
  if files.length() == 0 {
    Err(MalformedBody("required file is missing: " + name))
  } else if files.length() > 1 {
    Err(MalformedBody("expected a single file for field: " + name))
  } else {
    Ok(files[0])
  }
}

///|
pub fn require_single_field(
  form : MultipartForm,
  name : String,
) -> Result[String, MultipartError] {
  let values = form.field_values(name)
  if values.length() == 0 {
    Err(MalformedBody("required field is missing: " + name))
  } else if values.length() > 1 {
    Err(MalformedBody("expected a single value for field: " + name))
  } else {
    Ok(values[0])
  }
}

///|
pub fn collect_file_names(form : MultipartForm) -> Array[String] {
  let names = Array::new()
  let files = form.file_parts()
  let mut i = 0
  while i < files.length() {
    match files[i].filename {
      Some(filename) => names.push(filename)
      None => ()
    }
    i = i + 1
  }
  names
}

///|
pub fn collect_text_pairs(form : MultipartForm) -> Array[(String, String)] {
  let pairs = Array::new()
  let fields = form.text_parts()
  let mut i = 0
  while i < fields.length() {
    pairs.push((fields[i].name, fields[i].body))
    i = i + 1
  }
  pairs
}

///|
pub fn form_debug_report(form : MultipartForm) -> Array[String] {
  let lines = Array::new()
  lines.push("MoonFormData report")
  lines.push("boundary=" + form.boundary)
  let summary = form.summary()
  lines.push(summary.to_line())
  let debug_lines = form.debug_lines()
  let mut i = 0
  while i < debug_lines.length() {
    lines.push(debug_lines[i])
    i = i + 1
  }
  lines
}

///|
pub fn request_debug_report(
  request : MultipartRequest,
) -> Result[Array[String], MultipartError] {
  match roundtrip_request(request) {
    Ok(form) => Ok(form_debug_report(form))
    Err(err) => Err(err)
  }
}

///|
pub fn webhook_summary(
  content_type : String,
  body : String,
) -> Result[String, MultipartError] {
  match parse_multipart_request(content_type, body) {
    Ok(form) => Ok(form.summary().to_line())
    Err(err) => Err(err)
  }
}

///|
pub fn bad_request_message(err : MultipartError) -> String {
  "multipart/form-data request is invalid: " + err.message()
}

///|
pub fn ok_request_message(form : MultipartForm) -> String {
  "multipart/form-data request accepted: " + form.summary().to_line()
}

///|
pub fn accept_upload_request(
  request : MultipartRequest,
  policy : ValidationPolicy,
) -> Result[String, MultipartError] {
  match validate_upload_request(request, policy) {
    Ok(report) =>
      if report.is_ok() {
        match roundtrip_request(request) {
          Ok(form) => Ok(ok_request_message(form))
          Err(err) => Err(err)
        }
      } else {
        Err(MalformedBody(report.summary()))
      }
    Err(err) => Err(err)
  }
}

///|
pub fn request_content_length(request : MultipartRequest) -> Int {
  request.body.length()
}

///|
pub fn request_boundary(
  request : MultipartRequest,
) -> Result[String, MultipartError] {
  boundary_from_content_type(request.content_type)
}

///|
pub fn request_has_multipart_content_type(request : MultipartRequest) -> Bool {
  is_multipart_form_data_content_type(request.content_type)
}

///|
pub fn request_with_body(
  request : MultipartRequest,
  body : String,
) -> MultipartRequest {
  { content_type: request.content_type, body }
}

///|
pub fn request_with_content_type(
  request : MultipartRequest,
  content_type : String,
) -> MultipartRequest {
  { content_type, body: request.body }
}

///|
pub fn fixture_to_request(fixture : MultipartFixture) -> MultipartRequest {
  {
    content_type: "multipart/form-data; boundary=" + fixture.boundary,
    body: fixture.body,
  }
}