///|
/// Callback-based file/part sink. The callbacks receive parser-owned event
/// data immediately; applications can write file bytes without using the
/// in-memory `FieldCollector`.
pub struct PartSinkAdapter {
  on_begin : (@model.PartMeta) -> Result[Unit, FormError]
  on_data : (Bytes) -> Result[Unit, FormError]
  on_end : (Int) -> Result[Unit, FormError]
  on_message_end : () -> Result[Unit, FormError]
}

///|
pub fn PartSinkAdapter::new(
  on_begin : (@model.PartMeta) -> Result[Unit, FormError],
  on_data : (Bytes) -> Result[Unit, FormError],
  on_end : (Int) -> Result[Unit, FormError],
  on_message_end : () -> Result[Unit, FormError],
) -> PartSinkAdapter {
  { on_begin, on_data, on_end, on_message_end }
}

///|
fn raise_sink(result : Result[Unit, FormError]) -> Unit raise FormError {
  match result {
    Ok(_) => ()
    Err(error) => raise error
  }
}

///|
/// Deliver events to a caller-supplied sink in protocol order.
pub fn drive_events(
  events : Array[@model.MultipartEvent],
  sink : PartSinkAdapter,
) -> Unit raise FormError {
  for event in events {
    match event {
      PartBegin(meta) => raise_sink((sink.on_begin)(meta))
      PartData(bytes) => raise_sink((sink.on_data)(bytes))
      PartEnd(index) => raise_sink((sink.on_end)(index))
      MessageEnd => raise_sink((sink.on_message_end)())
    }
  }
}

///|
/// A transport-neutral source of already-owned body chunks. Native async and
/// framework adapters can wrap their own readers behind this callback without
/// changing the all-target core parser.
pub struct ChunkSourceAdapter {
  next_chunk : () -> Result[Bytes?, FormError]
}

///|
pub fn ChunkSourceAdapter::new(
  next_chunk : () -> Result[Bytes?, FormError],
) -> ChunkSourceAdapter {
  { next_chunk, }
}

///|
/// Drive a parser until the source returns `None`, then require `finish()`.
pub fn parse_from_source(
  source : ChunkSourceAdapter,
  parser : @parser.MultipartParser,
) -> Array[@model.MultipartEvent] raise FormError {
  let events : Array[@model.MultipartEvent] = []
  for ;; {
    match (source.next_chunk)() {
      Err(error) => raise error
      Ok(Some(chunk)) => {
        let parsed = parser.feed(chunk) catch {
          error => raise Protocol(error~)
        }
        events.append(parsed)
      }
      Ok(None) => {
        let completed = parser.finish() catch {
          error => raise Protocol(error~)
        }
        events.append(completed)
        return events
      }
    }
  }
}