///|
fn quote(s : String) -> String raise ImapError {
  if s.length() > 4096 || s.iter().any(c => c.to_int() < 32 || c.to_int() > 126) {
    raise Invalid(
      "quoted command arguments require printable ASCII; encode mailbox names with modified UTF-7",
    )
  }
  "\"" +
  s.replace_all(old="\\", new="\\\\").replace_all(old="\"", new="\\\"") +
  "\""
}

///|
/// Injection-safe ASCII expression, retaining IMAP syntax for the server to validate.
fn expression(s : String) -> String raise ImapError {
  if s.is_empty() || s.length() > 8192 {
    raise Invalid("expression length")
  }
  let mut depth = 0
  let mut quoted = false
  let mut escaped = false
  for c in s.iter() {
    if c.to_int() < 32 || c.to_int() > 126 {
      raise Invalid("expression requires printable ASCII")
    }
    if escaped {
      if c != '\\' && c != '"' {
        raise Invalid("invalid quoted escape")
      }
      escaped = false
      continue
    }
    if quoted && c == '\\' {
      escaped = true
      continue
    }
    if c == '"' {
      quoted = !quoted
      continue
    }
    if !quoted {
      if c == '{' || c == '}' {
        raise Invalid("literal markers require the APPEND API")
      }
      if c == '(' {
        depth += 1
      }
      if c == ')' {
        depth -= 1
      }
      if depth < 0 || depth > 32 {
        raise Invalid("expression nesting")
      }
    }
  }
  if quoted || escaped || depth != 0 {
    raise Invalid("unterminated expression")
  }
  s
}

///|
fn flag_list(s : String) -> String raise ImapError {
  if s.length() > 4096 {
    raise Invalid("flag list limit")
  }
  let flags = words(s)
  for flag in flags {
    let name = if flag.has_prefix("\\") { flag[1:].to_owned() } else { flag }
    if name.is_empty() ||
      !name
      .iter()
      .all(c => {
        c.to_int() >= 33 &&
        c.to_int() <= 126 &&
        !"(){%*\"\\]".contains(c.to_string())
      }) {
      raise Invalid("invalid flag")
    }
  }
  "(" + flags.join(" ") + ")"
}

///|
fn command_tail(
  state : State,
  command : String,
  args : Array[String],
) -> String raise ImapError {
  let authenticated = state == Authenticated || state == Selected
  match command {
    "STARTTLS" => {
      if state != NotAuthenticated || !args.is_empty() {
        raise Invalid("STARTTLS state/arity")
      }
      ""
    }
    "LOGIN" => {
      if state != NotAuthenticated || args.length() != 2 {
        raise Invalid("LOGIN state/arity")
      }
      quote(args[0]) + " " + quote(args[1])
    }
    "AUTHENTICATE" => {
      if state != NotAuthenticated || args != ["PLAIN"] {
        raise Invalid("only AUTHENTICATE PLAIN is supported")
      }
      "PLAIN"
    }
    "SELECT" | "EXAMINE" | "CREATE" | "DELETE" | "SUBSCRIBE" | "UNSUBSCRIBE" => {
      if !authenticated || args.length() != 1 {
        raise Invalid("mailbox command state/arity")
      }
      quote(args[0])
    }
    "RENAME" | "LIST" | "LSUB" => {
      if !authenticated || args.length() != 2 {
        raise Invalid("mailbox command state/arity")
      }
      quote(args[0]) + " " + quote(args[1])
    }
    "STATUS" => {
      if !authenticated || args.length() != 2 {
        raise Invalid("STATUS state/arity")
      }
      let items = words(args[1].to_upper())
      if items.is_empty() ||
        items.length() > 5 ||
        !items
        .iter()
        .all(s => {
          ["MESSAGES", "RECENT", "UIDNEXT", "UIDVALIDITY", "UNSEEN"].contains(s)
        }) {
        raise Invalid("STATUS items")
      }
      quote(args[0]) + " (" + items.join(" ") + ")"
    }
    "FETCH" | "UID FETCH" => {
      if state != Selected ||
        args.length() < 1 ||
        args.length() > 2 ||
        !valid_sequence_set(args[0]) {
        raise Invalid("FETCH state/arguments")
      }
      args[0] +
      " " +
      (if args.length() == 1 {
        "(UID FLAGS RFC822.SIZE)"
      } else {
        expression(args[1])
      })
    }
    "COPY" | "UID COPY" | "MOVE" | "UID MOVE" => {
      if state != Selected || args.length() != 2 || !valid_sequence_set(args[0]) {
        raise Invalid("COPY/MOVE state/arguments")
      }
      args[0] + " " + quote(args[1])
    }
    "STORE" | "UID STORE" => {
      if state != Selected || args.length() != 3 || !valid_sequence_set(args[0]) {
        raise Invalid("STORE state/arguments")
      }
      let mode = args[1].to_upper()
      if ![
          "FLAGS", "+FLAGS", "-FLAGS", "FLAGS.SILENT", "+FLAGS.SILENT", "-FLAGS.SILENT",
        ].contains(mode) {
        raise Invalid("STORE mode")
      }
      args[0] + " " + mode + " " + flag_list(args[2])
    }
    "SEARCH" | "UID SEARCH" => {
      if state != Selected || args.length() != 1 {
        raise Invalid("SEARCH state/arity")
      }
      expression(args[0])
    }
    "APPEND" => {
      if !authenticated || args.length() < 2 || args.length() > 3 {
        raise Invalid("APPEND state/arity")
      }
      if args[1].is_empty() || !args[1].iter().all(c => c >= '0' && c <= '9') {
        raise Invalid("APPEND length")
      }
      let size = @strconv.parse_int(args[1]) catch {
        _ => raise Invalid("APPEND length")
      }
      if size > 1048576 {
        raise Invalid("APPEND literal limit")
      }
      quote(args[0]) +
      (if args.length() == 3 { " " + flag_list(args[2]) } else { "" }) +
      " {" +
      size.to_string() +
      "}"
    }
    "CHECK" | "CLOSE" | "EXPUNGE" | "UNSELECT" => {
      if state != Selected || !args.is_empty() {
        raise Invalid("selected command state/arity")
      }
      ""
    }
    "IDLE" => {
      if !authenticated || !args.is_empty() {
        raise Invalid("IDLE state/arity")
      }
      ""
    }
    "CAPABILITY" | "NOOP" | "LOGOUT" => {
      if !args.is_empty() {
        raise Invalid("command arity")
      }
      ""
    }
    _ => raise Invalid("unsupported command")
  }
}