///|
/// Incremental byte parser with bounded per-response storage; literals never pass through UTF-8.
pub struct Decoder {
  priv line : Array[Byte]
  priv segments : Array[String]
  priv literals : Array[Bytes]
  priv literal : Array[Byte]
  priv mut expected : Int
  priv mut held : Int
  priv mut syntax_size : Int
  priv mut failed : Bool
} derive(Debug)

///|
pub fn Decoder::new() -> Decoder {
  {
    line: [],
    segments: [],
    literals: [],
    literal: [],
    expected: -1,
    held: 0,
    syntax_size: 0,
    failed: false,
  }
}

///|
fn text(b : Bytes) -> String raise ImapError {
  @utf8.decode(b, ignore_bom=false) catch {
    _ => raise Invalid("invalid UTF-8 response syntax")
  }
}

///|
fn words(line : String) -> Array[String] {
  line.split(" ").filter(s => !s.is_empty()).map(s => s.to_owned()).collect()
}

///|
fn response_word(line : String) -> String {
  let fields = words(line)
  if fields.length() >= 2 {
    fields[1].to_upper()
  } else {
    ""
  }
}

///|
fn text_response(line : String) -> Bool {
  if !line.has_prefix("* ") {
    return true
  }
  match response_word(line) {
    "OK" | "NO" | "BAD" | "BYE" | "PREAUTH" => true
    _ => false
  }
}

///|
fn suffix_size(line : String) -> Int raise ImapError {
  if !line.has_suffix("}") {
    return -1
  }
  let chars = line.to_array()
  let mut quoted = false
  let mut escaped = false
  let mut start = -1
  for i, c in chars {
    if escaped {
      escaped = false
      continue
    }
    if quoted && c == '\\' {
      escaped = true
      continue
    }
    if c == '"' {
      quoted = !quoted
    }
    if !quoted && c == '{' {
      start = i
    }
  }
  if quoted || start < 0 {
    return -1
  }
  let size = String::from_array(chars[start + 1:chars.length() - 1])
  if size.is_empty() || !size.iter().all(c => c >= '0' && c <= '9') {
    raise Invalid("literal length")
  }
  let n = @strconv.parse_int(size) catch {
    _ => raise Invalid("literal length overflow")
  }
  if n > 1048576 {
    raise Invalid("literal limit")
  }
  n
}

///|
pub fn Decoder::feed(
  self : Decoder,
  bytes : Bytes,
) -> Array[Response] raise ImapError {
  if self.failed {
    raise Invalid("decoder failed")
  }
  errdefer {
    self.failed = true
  }
  let out = []
  for byte in bytes {
    if self.expected >= 0 {
      self.literal.push(byte)
      self.held += 1
      if self.literal.length() == self.expected {
        self.literals.push(Bytes::from_array(self.literal))
        self.literal.clear()
        self.expected = -1
      }
      continue
    }
    let n = self.line.length()
    if byte == 10 {
      if n == 0 || self.line[n - 1] != 13 {
        raise Invalid("bare newline")
      }
      ignore(self.line.pop())
      let line = text(Bytes::from_array(self.line))
      self.line.clear()
      self.syntax_size += n - 1
      if self.syntax_size > 65536 {
        raise Invalid("response syntax limit")
      }
      let first = if self.segments.is_empty() { line } else { self.segments[0] }
      let size = if text_response(first) { -1 } else { suffix_size(line) }
      self.segments.push(line)
      if size >= 0 {
        if self.literals.length() >= 64 || size > 2097152 - self.held {
          raise Invalid("aggregate literal limit")
        }
        if size == 0 {
          self.literals.push(b"")
        } else {
          self.expected = size
        }
      } else {
        out.push({
          line: self.segments.join("\n"),
          literals: self.literals.copy(),
        })
        self.segments.clear()
        self.literals.clear()
        self.held = 0
        self.syntax_size = 0
      }
    } else {
      if byte == 0 || (n > 0 && self.line[n - 1] == 13) {
        raise Invalid("invalid response syntax byte")
      }
      if n >= 65537 || self.syntax_size + n > 65536 {
        raise Invalid("response line limit")
      }
      self.line.push(byte)
    }
  }
  out
}

///|
pub fn Decoder::has_partial(self : Decoder) -> Bool {
  self.expected >= 0 || !self.line.is_empty() || !self.segments.is_empty()
}

///|
pub fn Decoder::finish(self : Decoder) -> Unit raise ImapError {
  if self.failed || self.has_partial() {
    self.failed = true
    raise Invalid("truncated stream")
  }
}