///|
/// A decoded text form field.
pub(all) struct TextField {
  name : String
  value : String
} derive(Debug, Eq)

///|
/// A decoded uploaded file. `data` preserves the original file bytes.
pub(all) struct UploadedFile {
  field_name : String
  filename : String
  content_type : String
  data : Bytes
} derive(Debug)

///|
/// Returns the submitted filename without Unix or Windows directory
/// components. Generate a separate server-side storage path for the file.
pub fn UploadedFile::filename_basename(self : UploadedFile) -> String {
  filename_basename(self.filename)
}

///|
/// A form entry in original wire order. Repeated names are intentionally kept.
pub(all) enum FormEntry {
  Text(TextField)
  File(UploadedFile)
} derive(Debug)

///|
/// Collected multipart/form-data entries in original wire order.
pub(all) struct FormData {
  entries : Array[FormEntry]
} derive(Debug)

///|
/// Returns the first text value with the supplied field name.
pub fn FormData::text(self : FormData, name : String) -> String? {
  for entry in self.entries {
    match entry {
      Text(field) => if field.name == name { return Some(field.value) }
      File(_) => ()
    }
  }
  None
}

///|
/// Returns every text value with the supplied field name in wire order.
pub fn FormData::text_all(self : FormData, name : String) -> Array[String] {
  let values : Array[String] = []
  for entry in self.entries {
    match entry {
      Text(field) => if field.name == name { values.push(field.value) }
      File(_) => ()
    }
  }
  values
}

///|
/// Returns the first uploaded file with the supplied field name.
pub fn FormData::file(self : FormData, name : String) -> UploadedFile? {
  for entry in self.entries {
    match entry {
      File(file) => if file.field_name == name { return Some(file) }
      Text(_) => ()
    }
  }
  None
}

///|
/// Returns every uploaded file with the supplied field name in wire order.
pub fn FormData::files(self : FormData, name : String) -> Array[UploadedFile] {
  let files : Array[UploadedFile] = []
  for entry in self.entries {
    match entry {
      File(file) => if file.field_name == name { files.push(file) }
      Text(_) => ()
    }
  }
  files
}

///|
/// Resource bounds for the in-memory form collector. Text and file limits are
/// separate because applications commonly accept many small fields but only a
/// small number of larger uploads.
pub(all) struct FormDataLimits {
  max_field_size : Int
  max_file_size : Int
  max_field_count : Int
  max_file_count : Int
}

///|
pub fn FormDataLimits::default() -> FormDataLimits {
  FormDataLimits::{
    max_field_size: 1_048_576,
    max_file_size: 8_388_608,
    max_field_count: 1_000,
    max_file_count: 100,
  }
}

///|
/// A size override for one named form field. It applies to both text and file
/// parts and can only make the corresponding global limit stricter.
pub(all) struct FieldSizeLimit {
  name : String
  max_size : Int
} derive(Debug, Eq)

///|
/// Event consumer that builds an in-memory FormData value from a
/// StreamingParser. Use a future sink-based collector for very large files.
pub struct FormDataCollector {
  entries : Array[FormEntry]
  buffer : @buffer.Buffer
  limits : FormDataLimits
  allowed_fields : Array[String]?
  field_size_limits : Array[FieldSizeLimit]
  mut current_part : FormPartMetadata?
  mut current_size : Int
  mut field_count : Int
  mut file_count : Int
  mut finished : Bool
}

///|
pub fn FormDataCollector::new() -> FormDataCollector {
  FormDataCollector::{
    entries: [],
    buffer: @buffer.Buffer(),
    limits: FormDataLimits::default(),
    allowed_fields: None,
    field_size_limits: [],
    current_part: None,
    current_size: 0,
    field_count: 0,
    file_count: 0,
    finished: false,
  }
}

///|
/// Creates an in-memory collector with a maximum size for one part body.
pub fn FormDataCollector::with_file_size_limit(
  max_file_size : Int,
) -> FormDataCollector {
  let limits = FormDataLimits::default()
  FormDataCollector::{
    entries: [],
    buffer: @buffer.Buffer(),
    limits: FormDataLimits::{ ..limits, max_file_size, },
    allowed_fields: None,
    field_size_limits: [],
    current_part: None,
    current_size: 0,
    field_count: 0,
    file_count: 0,
    finished: false,
  }
}

///|
/// Creates an in-memory collector with separate text-field and file limits.
pub fn FormDataCollector::with_limits(
  limits : FormDataLimits,
) -> FormDataCollector {
  FormDataCollector::{
    entries: [],
    buffer: @buffer.Buffer(),
    limits,
    allowed_fields: None,
    field_size_limits: [],
    current_part: None,
    current_size: 0,
    field_count: 0,
    file_count: 0,
    finished: false,
  }
}

///|
/// Creates a collector that rejects any part whose form field name is not in
/// `allowed_fields`. Repeated occurrences of an allowed name remain valid.
pub fn FormDataCollector::with_allowed_fields(
  allowed_fields : Array[String],
  limits? : FormDataLimits = FormDataLimits::default(),
) -> FormDataCollector {
  FormDataCollector::{
    entries: [],
    buffer: @buffer.Buffer(),
    limits,
    allowed_fields: Some(allowed_fields),
    field_size_limits: [],
    current_part: None,
    current_size: 0,
    field_count: 0,
    file_count: 0,
    finished: false,
  }
}

///|
/// Creates a collector with global limits plus stricter per-field size limits.
pub fn FormDataCollector::with_field_size_limits(
  limits : FormDataLimits,
  field_size_limits : Array[FieldSizeLimit],
) -> FormDataCollector {
  FormDataCollector::{
    entries: [],
    buffer: @buffer.Buffer(),
    limits,
    allowed_fields: None,
    field_size_limits,
    current_part: None,
    current_size: 0,
    field_count: 0,
    file_count: 0,
    finished: false,
  }
}

///|
/// Consumes one streaming event.
pub fn FormDataCollector::push(
  self : FormDataCollector,
  event : StreamEvent,
) -> Result[Unit, MultipartError] {
  if self.finished {
    return Err(MultipartError::InvalidStreamEvent)
  }
  match event {
    PartBegin(headers) => {
      guard self.current_part is None else {
        return Err(MultipartError::InvalidStreamEvent)
      }
      let part = match parse_form_part_metadata(headers) {
        Ok(part) => part
        Err(error) => return Err(error)
      }
      match self.allowed_fields {
        Some(allowed) =>
          if !contains_string(allowed, part.name) {
            return Err(MultipartError::UnexpectedField(part.name))
          }
        None => ()
      }
      if part.filename is Some(_) {
        if self.file_count >= self.limits.max_file_count {
          return Err(
            MultipartError::FileCountExceeded(self.limits.max_file_count),
          )
        }
      } else if self.field_count >= self.limits.max_field_count {
        return Err(
          MultipartError::FieldCountExceeded(self.limits.max_field_count),
        )
      }
      self.buffer.reset()
      self.current_size = 0
      self.current_part = Some(part)
      Ok(())
    }
    PartData(data) => {
      guard self.current_part is Some(part) else {
        return Err(MultipartError::InvalidStreamEvent)
      }
      let next_size = self.current_size + data.length()
      if part.filename is Some(_) {
        let limit = effective_field_size_limit(
          part.name,
          self.limits.max_file_size,
          self.field_size_limits,
        )
        if next_size > limit {
          return Err(MultipartError::FileTooLarge(limit))
        }
      } else {
        let limit = effective_field_size_limit(
          part.name,
          self.limits.max_field_size,
          self.field_size_limits,
        )
        if next_size > limit {
          return Err(MultipartError::FieldTooLarge(limit))
        }
      }
      self.buffer.write_bytes(data[:])
      self.current_size = next_size
      Ok(())
    }
    PartEnd => {
      guard self.current_part is Some(part) else {
        return Err(MultipartError::InvalidStreamEvent)
      }
      let data = self.buffer.to_bytes()
      match part.filename {
        Some(filename) => {
          self.entries.push(
            FormEntry::File(UploadedFile::{
              field_name: part.name,
              filename,
              content_type: part.content_type,
              data,
            }),
          )
          self.file_count = self.file_count + 1
        }
        None => {
          self.entries.push(
            FormEntry::Text(TextField::{
              name: part.name,
              value: @utf8.decode_lossy(data[:]),
            }),
          )
          self.field_count = self.field_count + 1
        }
      }
      self.current_part = None
      Ok(())
    }
    Finished => {
      guard self.current_part is None else {
        return Err(MultipartError::InvalidStreamEvent)
      }
      self.finished = true
      Ok(())
    }
  }
}

///|
fn contains_string(values : Array[String], expected : String) -> Bool {
  for value in values {
    if value == expected {
      return true
    }
  }
  false
}

///|
fn effective_field_size_limit(
  name : String,
  global_limit : Int,
  limits : Array[FieldSizeLimit],
) -> Int {
  let mut effective = global_limit
  for limit in limits {
    if limit.name == name && limit.max_size < effective {
      effective = limit.max_size
    }
  }
  effective
}

///|
/// Returns the final collected data once the Finished event has been consumed.
pub fn FormDataCollector::finish(
  self : FormDataCollector,
) -> Result[FormData, MultipartError] {
  if self.finished && self.current_part is None {
    Ok(FormData::{ entries: self.entries })
  } else {
    Err(MultipartError::InvalidStreamEvent)
  }
}