///|
pub struct FieldCollector {
  limits : FieldCollectorLimits
  fields : Array[FormField]
  mut active_meta : @model.PartMeta?
  body : @buffer.Buffer
  mut total_field_bytes : Int64
  mut message_ended : Bool
}

///|
fn validate_collector_limits(
  limits : FieldCollectorLimits,
) -> Unit raise FormError {
  if limits.max_fields <= 0 {
    raise InvalidCollectorLimits(reason="max_fields must be positive")
  }
  if limits.max_field_bytes <= 0L || limits.max_total_field_bytes <= 0L {
    raise InvalidCollectorLimits(reason="field byte limits must be positive")
  }
}

///|
pub fn FieldCollector::new(
  limits : FieldCollectorLimits,
) -> FieldCollector raise FormError {
  validate_collector_limits(limits)
  {
    limits,
    fields: [],
    active_meta: None,
    body: Buffer(size_hint=256),
    total_field_bytes: 0L,
    message_ended: false,
  }
}

///|
fn FieldCollector::begin(
  self : FieldCollector,
  meta : @model.PartMeta,
) -> Unit raise FormError {
  if self.message_ended || self.active_meta is Some(_) {
    raise UnexpectedEvent(
      reason="PartBegin is not valid in the current collector state",
    )
  }
  if meta.filename is Some(_) {
    raise FilePartRejected(part_index=meta.index)
  }
  let observed = self.fields.length() + 1
  if observed > self.limits.max_fields {
    raise FieldCountExceeded(limit=self.limits.max_fields, observed~)
  }
  self.active_meta = Some(meta)
  self.body.reset()
}

///|
fn FieldCollector::append(
  self : FieldCollector,
  bytes : Bytes,
) -> Unit raise FormError {
  match self.active_meta {
    None => raise UnexpectedEvent(reason="PartData without an active field")
    Some(_) => ()
  }
  let observed_field = self.body.length().to_int64() + bytes.length().to_int64()
  if observed_field > self.limits.max_field_bytes {
    raise FieldLimitExceeded(
      limit=self.limits.max_field_bytes,
      observed=observed_field,
    )
  }
  let observed_total = self.total_field_bytes + bytes.length().to_int64()
  if observed_total > self.limits.max_total_field_bytes {
    raise FieldLimitExceeded(
      limit=self.limits.max_total_field_bytes,
      observed=observed_total,
    )
  }
  self.body.write_bytes(bytes)
  self.total_field_bytes = observed_total
}

///|
fn FieldCollector::end(
  self : FieldCollector,
  index : Int,
) -> Unit raise FormError {
  let meta = match self.active_meta {
    Some(meta) => meta
    None => raise UnexpectedEvent(reason="PartEnd without an active field")
  }
  if meta.index != index {
    raise UnexpectedEvent(
      reason="PartEnd index does not match the active field",
    )
  }
  self.fields.push({ meta, value: self.body.to_bytes() })
  self.body.reset()
  self.active_meta = None
}

///|
/// Consume parser events without buffering file parts.
pub fn FieldCollector::consume(
  self : FieldCollector,
  events : Array[@model.MultipartEvent],
) -> Unit raise FormError {
  for event in events {
    match event {
      PartBegin(meta) => self.begin(meta)
      PartData(bytes) => self.append(bytes)
      PartEnd(index) => self.end(index)
      MessageEnd => {
        if self.active_meta is Some(_) {
          raise UnexpectedEvent(reason="MessageEnd before PartEnd")
        }
        self.message_ended = true
      }
    }
  }
}

///|
/// Return an owned snapshot after the parser has emitted `MessageEnd`.
pub fn FieldCollector::finish(
  self : FieldCollector,
) -> Array[FormField] raise FormError {
  if !self.message_ended || self.active_meta is Some(_) {
    raise UnexpectedEvent(reason="collector cannot finish before MessageEnd")
  }
  self.fields.copy()
}

///|
fn ascii_decode(value : Bytes, field_name : String) -> String raise FormError {
  for byte in value {
    if byte.to_int() > 127 {
      raise InvalidTextEncoding(field_name~, charset="us-ascii")
    }
  }
  @utf8.decode(value) catch {
    _ => raise InvalidTextEncoding(field_name~, charset="us-ascii")
  }
}

///|
fn decode_field(
  field : FormField,
  charset : String,
) -> DecodedField raise FormError {
  let normalized = charset.to_lower()
  let value = if normalized == "utf-8" || normalized == "utf8" {
    @utf8.decode(field.value) catch {
      _ =>
        raise InvalidTextEncoding(
          field_name=field.meta.name,
          charset=normalized,
        )
    }
  } else if normalized == "us-ascii" || normalized == "ascii" {
    ascii_decode(field.value, field.meta.name)
  } else {
    raise InvalidTextEncoding(field_name=field.meta.name, charset=normalized)
  }
  { name: field.meta.name, value, content_type: field.meta.content_type }
}

///|
/// Decode collected fields using an explicit default or a `_charset_` field.
/// Only UTF-8 and US-ASCII are accepted by the strict decoding policy.
pub fn FieldCollector::decoded_fields(
  self : FieldCollector,
  default_charset? : String = "utf-8",
) -> Array[DecodedField] raise FormError {
  let mut charset = default_charset
  for field in self.fields {
    if field.meta.name == "_charset_" {
      charset = ascii_decode(field.value, field.meta.name)
        .trim()
        .to_owned()
        .to_lower()
    }
  }
  let decoded : Array[DecodedField] = []
  for field in self.fields {
    if field.meta.name != "_charset_" {
      decoded.push(decode_field(field, charset))
    }
  }
  decoded
}