// ===========================================================================
// moon-multipart — Streaming RFC 7578 multipart/form-data parser
// ===========================================================================

///|
/// Streaming multipart parser. Feed byte chunks and receive ParseEvents.
/// Never buffers an entire file in memory — body data is emitted as PartData.
pub(all) struct Parser {
  boundary : String
  delimiter : Bytes
  boundary_prefix : Bytes
  delimiter_len : Int
  options : ParseOptions
  buf : @buffer.Buffer
  mut phase : ParsePhase
  body_buf : @buffer.Buffer
  mut part_name : String
  mut part_filename : String?
  mut part_content_type : String?
  mut part_is_file : Bool
  mut part_count : Int
  mut total_size : Int
  mut first_boundary_found : Bool
  mut finished : Bool
  /// Byte offset from start of input (approximate, for error context)
  mut offset : Int
}

///|
/// Create a new streaming parser.
pub fn Parser::new(boundary : String, options : ParseOptions) -> Parser {
  let prefix = @utf8.encode("--" + boundary)
  let delim = @utf8.encode("\r\n--" + boundary)
  Parser::{
    boundary: boundary.to_string(),
    delimiter: delim,
    boundary_prefix: prefix,
    delimiter_len: delim.length(),
    options,
    buf: @buffer.Buffer(),
    phase: Preamble,
    body_buf: @buffer.Buffer(),
    part_name: "",
    part_filename: None,
    part_content_type: None,
    part_is_file: false,
    part_count: 0,
    total_size: 0,
    first_boundary_found: false,
    finished: false,
    offset: 0,
  }
}

///|
/// Feed a chunk of bytes. Returns ParseEvents emitted during processing.
pub fn Parser::feed(
  self : Parser,
  chunk : Bytes,
) -> Result[Array[ParseEvent], MultipartError] {
  if self.finished {
    return Ok([])
  }
  let events : Array[ParseEvent] = []
  self.buf.write_bytes(chunk)
  self.total_size = self.total_size + chunk.length()
  self.offset = self.offset + chunk.length()

  // Total size check
  let lim = self.options.limits
  if lim.max_total_size > 0 && self.total_size > lim.max_total_size {
    return Err(TotalSizeExceeded(lim.max_total_size))
  }

  let mut progress = true
  while progress {
    progress = false
    let data = self.buf.to_bytes()
    match self.phase {
      Preamble =>
        match try_find_first(data, self.boundary_prefix, self.delimiter) {
          Err(e) => return Err(e)
          Ok(None) =>
            if data.length() > self.delimiter_len {
              let safe = data.length() - self.delimiter_len
              self.buf.reset()
              self.buf.write_bytes(bv(data[safe:]))
            }
          Ok(Some((_idx, consumed))) => {
            self.first_boundary_found = true
            self.buf.reset()
            if consumed < data.length() {
              self.buf.write_bytes(bv(data[consumed:]))
            }
            let after = self.buf.to_bytes()
            if after.length() >= 2 && after[0] == b'\r' && after[1] == b'\n' {
              self.buf.reset()
              if after.length() > 2 {
                self.buf.write_bytes(bv(after[2:]))
              }
              self.phase = Headers
              progress = true
            } else if after.length() >= 2 &&
              after[0] == b'-' &&
              after[1] == b'-' {
              self.buf.reset()
              if after.length() > 2 {
                self.buf.write_bytes(bv(after[2:]))
              }
              self.phase = Done
              self.finished = true
              events.push(Finished)
              progress = true
            }
          }
        }
      Headers =>
        match find_double_crlf(data) {
          None =>
            if lim.max_header_size > 0 && data.length() > lim.max_header_size {
              return Err(HeaderTooLarge(lim.max_header_size))
            }
          Some(end_idx) => {
            let hdr_str = bytes_str(data[0:end_idx])
            self.buf.reset()
            if end_idx + 4 < data.length() {
              self.buf.write_bytes(bv(data[end_idx + 4:]))
            }
            match parse_part_headers(hdr_str, self.options) {
              Err(e) => return Err(e)
              Ok((name, filename, ct)) => {
                self.part_count = self.part_count + 1
                if lim.max_parts > 0 && self.part_count > lim.max_parts {
                  return Err(TooManyParts(lim.max_parts))
                }
                self.part_name = name.to_string()
                self.part_filename = clone_opt(filename)
                self.part_content_type = clone_opt(ct)
                self.part_is_file = match filename {
                  Some(_) => true
                  None => false
                }
                self.body_buf.reset()
                events.push(
                  PartBegin(
                    self.part_name.to_string(),
                    clone_opt(self.part_filename),
                    clone_opt(self.part_content_type),
                  ),
                )
                self.phase = Body
                progress = true
              }
            }
          }
        }
      Body => {
        let (body_bytes, consumed, found, closing) = scan_body(
          data,
          self.delimiter,
          self.delimiter_len,
        )
        if body_bytes.length() > 0 {
          // Size checks
          if self.part_is_file {
            if lim.max_file_size > 0 &&
              self.body_buf.length() + body_bytes.length() > lim.max_file_size {
              return Err(
                FileTooLarge(self.part_name.to_string(), lim.max_file_size),
              )
            }
          } else if lim.max_field_size > 0 &&
            self.body_buf.length() + body_bytes.length() > lim.max_field_size {
            return Err(
              FieldTooLarge(self.part_name.to_string(), lim.max_field_size),
            )
          }
          self.body_buf.write_bytes(body_bytes)
          events.push(PartData(body_bytes))
        }
        if found {
          events.push(PartEnd)
          self.buf.reset()
          if consumed < data.length() {
            self.buf.write_bytes(bv(data[consumed:]))
          }
          let after = self.buf.to_bytes()
          if closing {
            self.buf.reset()
            if after.length() > 2 {
              self.buf.write_bytes(bv(after[2:]))
            }
            self.phase = Done
            self.finished = true
            events.push(Finished)
            progress = true
          } else if after.length() >= 2 &&
            after[0] == b'\r' &&
            after[1] == b'\n' {
            self.buf.reset()
            if after.length() > 2 {
              self.buf.write_bytes(bv(after[2:]))
            }
            self.phase = Headers
            progress = true
          } else if after.length() < 2 {
            self.phase = BoundarySeen
            progress = true
          } else {
            return Err(
              MalformedHeader("Unexpected data after delimiter".to_string()),
            )
          }
        }
      }
      BoundarySeen => {
        let after = self.buf.to_bytes()
        if after.length() >= 2 {
          if after[0] == b'-' && after[1] == b'-' {
            self.buf.reset()
            if after.length() > 2 {
              self.buf.write_bytes(bv(after[2:]))
            }
            self.phase = Done
            self.finished = true
            events.push(Finished)
            progress = true
          } else if after[0] == b'\r' && after[1] == b'\n' {
            self.buf.reset()
            if after.length() > 2 {
              self.buf.write_bytes(bv(after[2:]))
            }
            events.push(PartEnd)
            self.phase = Headers
            progress = true
          } else {
            return Err(
              MalformedHeader("Unexpected data after delimiter".to_string()),
            )
          }
        }
      }
      Done => self.buf.reset()
    }
  }
  Ok(events)
}

///|
/// Signal end of input. Returns error if body was incomplete.
pub fn Parser::finish(
  self : Parser,
) -> Result[Array[ParseEvent], MultipartError] {
  if self.finished {
    return Ok([])
  }
  let events : Array[ParseEvent] = []
  match self.phase {
    Preamble =>
      if !self.first_boundary_found {
        return Err(IncompleteBody)
      } else {
        ()
      }
    Headers => return Err(IncompleteBody)
    Body => return Err(IncompleteBody)
    BoundarySeen =>
      if self.buf.to_bytes().length() < 2 {
        return Err(IncompleteBody)
      } else {
        ()
      }
    Done => ()
  }
  self.finished = true
  events.push(Finished)
  Ok(events)
}

///|
/// Is parsing complete?
pub fn Parser::is_finished(self : Parser) -> Bool {
  self.finished
}

// ---------------------------------------------------------------------------
// High-level convenience API
// ---------------------------------------------------------------------------

///|
/// Parse a complete multipart body in one shot.
/// Convenience wrapper that feeds all bytes at once and collects all parts.
pub fn parse_all(
  body : Bytes,
  boundary : String,
  options : ParseOptions,
) -> Result[MultipartForm, MultipartError] {
  let parser = Parser::new(boundary, options)
  let form = MultipartForm::new()
  match parser.feed(body) {
    Err(e) => return Err(e)
    Ok(evts) => collect_events(form, evts)
  }
  match parser.finish() {
    Err(e) => return Err(e)
    Ok(evts) => collect_events(form, evts)
  }
  Ok(form)
}

///|
fn collect_events(form : MultipartForm, events : Array[ParseEvent]) -> Unit {
  let mut cur_name : String? = None
  let mut cur_filename : String? = None
  let mut cur_ct : String? = None
  let cur_data = @buffer.Buffer()
  for evt in events {
    match evt {
      PartBegin(n, f, ct) => {
        cur_name = Some(n)
        cur_filename = f
        cur_ct = ct
        cur_data.reset()
      }
      PartData(d) => cur_data.write_bytes(d)
      PartEnd => {
        let nm = match cur_name {
          Some(x) => x
          None => continue
        }
        match cur_filename {
          Some(fname) => {
            let ct_val = match cur_ct {
              Some(c) => Some(c)
              None => None
            }
            let file_data = cur_data.to_bytes()
            form.parts.push(File(nm, fname, ct_val, file_data))
          }
          None => {
            let field_bytes = cur_data.to_bytes()
            let chars : Array[Char] = []
            for bi = 0; bi < field_bytes.length(); bi = bi + 1 {
              chars.push(field_bytes[bi].to_char())
            }
            form.parts.push(Field(nm, String::from_array(chars)))
          }
        }
      }
      Finished => ()
    }
  }
}

// ---------------------------------------------------------------------------
// Internal helpers — boundary detection
// ---------------------------------------------------------------------------

///|
fn try_find_first(
  data : Bytes,
  prefix : Bytes,
  delimiter : Bytes,
) -> Result[(Int, Int)?, MultipartError] {
  let plen = prefix.length()
  let dlen = delimiter.length()
  // Try --boundary at position 0
  if data.length() >= plen && byte_eq(data, 0, prefix) {
    let mut c = plen
    let dl = data.length()
    while c < dl && (data[c] == b' ' || data[c] == b'\t') {
      c = c + 1
    }
    return Ok(Some((0, c)))
  }
  // Try \r\n--boundary
  if data.length() >= dlen {
    match find_bytes(data, delimiter, 0) {
      Some(i) => {
        let mut c = i + dlen
        let dl2 = data.length()
        while c < dl2 && (data[c] == b' ' || data[c] == b'\t') {
          c = c + 1
        }
        Ok(Some((i, c)))
      }
      None => Ok(None)
    }
  } else {
    Ok(None)
  }
}

///|
fn scan_body(
  data : Bytes,
  delimiter : Bytes,
  dlen : Int,
) -> (Bytes, Int, Bool, Bool) {
  let dl = data.length()
  if dl < dlen {
    return (b"", 0, false, false)
  }
  match find_bytes(data, delimiter, 0) {
    None => {
      let safe = dl - (dlen - 1)
      if safe <= 0 {
        return (b"", 0, false, false)
      }
      (bv(data[0:safe]), safe, false, false)
    }
    Some(i) => {
      let body = if i > 0 { bv(data[0:i]) } else { b"" }
      let after = i + dlen
      let closing = after + 1 < dl &&
        data[after] == b'-' &&
        data[after + 1] == b'-'
      let consumed = if closing { after + 2 } else { after }
      (body, consumed, true, closing)
    }
  }
}

///|
fn find_double_crlf(data : Bytes) -> Int? {
  let len = data.length()
  let mut i = 0
  while i + 3 < len {
    if data[i] == b'\r' &&
      data[i + 1] == b'\n' &&
      data[i + 2] == b'\r' &&
      data[i + 3] == b'\n' {
      return Some(i)
    }
    i = i + 1
  }
  None
}

// ---------------------------------------------------------------------------
// Header parsing (with strict/compat mode)
// ---------------------------------------------------------------------------

///|
fn parse_part_headers(
  hdr_block : String,
  opts : ParseOptions,
) -> Result[(String, String?, String?), MultipartError] {
  let mut name : String? = None
  let mut filename : String? = None
  let mut content_type : String? = None
  let mut has_disposition = false

  let lines = hdr_block.split("\r\n")
  for line in lines {
    let line_str = line.to_owned()
    if line_str.length() == 0 {
      continue
    }
    match parse_header_line(line_str) {
      None => continue
      Some((hn, hv)) =>
        if is_content_type(hn) {
          content_type = Some(hv)
        } else if is_content_disposition(hn) {
          has_disposition = true
          match parse_content_disposition(hv, opts) {
            Err(e) => return Err(e)
            Ok((n, f)) => {
              name = Some(n)
              filename = f
            }
          }
        }
    }
  }
  if !has_disposition {
    return Err(MissingDisposition)
  }
  match name {
    None => Err(MissingName)
    Some(n) => Ok((n, filename, content_type))
  }
}

// ---------------------------------------------------------------------------
// Byte utilities
// ---------------------------------------------------------------------------

///|
fn byte_eq(data : Bytes, start : Int, pattern : Bytes) -> Bool {
  let pl = pattern.length()
  if start + pl > data.length() {
    return false
  }
  let mut i = 0
  while i < pl {
    if data[start + i] != pattern[i] {
      return false
    }
    i = i + 1
  }
  true
}

///|
fn find_bytes(data : Bytes, pattern : Bytes, from : Int) -> Int? {
  let bl = data.length()
  let pl = pattern.length()
  if pl == 0 || bl < pl {
    return None
  }
  let mut i = from
  while i <= bl - pl {
    if byte_eq(data, i, pattern) {
      return Some(i)
    }
    i = i + 1
  }
  None
}

///|
fn bytes_str(view : BytesView) -> String {
  let b = @buffer.Buffer()
  b.write_bytesview(view)
  let bytes = b.to_bytes()
  let chars : Array[Char] = []
  for i = 0; i < bytes.length(); i = i + 1 {
    chars.push(bytes[i].to_char())
  }
  String::from_array(chars)
}

///|
fn bv(view : BytesView) -> Bytes {
  let b = @buffer.Buffer()
  b.write_bytesview(view)
  b.to_bytes()
}

///|
fn clone_opt(opt : String?) -> String? {
  match opt {
    None => None
    Some(s) => Some(s.to_string())
  }
}