///|
// Append `arg` to a Windows command line, quoting/escaping it so that
// `CommandLineToArgvW` (and the C runtime's argv parser) recovers `arg`
// verbatim. Ported from moonbitlang/async's `write_arg_with_windows_escape`.
// The rules come from
// https://learn.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments:
// backslashes are literal unless they precede a `"`, in which case each is
// doubled and the `"` is escaped as `\"`.
#cfg(platform="windows")
fn StringBuilder::write_arg_with_windows_escape(
  builder : StringBuilder,
  arg : StringView,
) -> Unit {
  if arg is "" {
    builder.write("\"\"")
    return
  }
  // Do not add double quotes if it is unnecessary to do so.
  // This helps with simple cases when the program is not using C argv syntax,
  // such as `rundll32.exe`.
  let need_quote = for char in arg {
    if char is (' ' | '\t' | '"') {
      break true
    }
  } nobreak {
    false
  }
  if !need_quote {
    builder.write_stringview(arg)
    return
  }
  let mut segment_start = 0
  let mut index = 0
  let mut trailing_backslash = 0
  fn flush(skip : Int) {
    if index > segment_start {
      builder.write_stringview(arg[segment_start:index - trailing_backslash])
      if trailing_backslash > 0 {
        builder.write_string(String::make(trailing_backslash * 2, '\\'))
      }
    }
    segment_start = index + skip
  }

  builder.write_char('"')
  while index < arg.length() {
    match arg.code_unit_at(index) {
      '"' => {
        flush(1)
        builder <+ "\\\""
      }
      '\\' => trailing_backslash += 1
      _ => trailing_backslash = 0
    }
    index += 1
  }
  flush(0)
  builder.write_char('"')
}

///|
#cfg(platform="windows")
fn test_arg(arg : String) -> String {
  StringBuilder::new()..write_arg_with_windows_escape(arg).to_string()
}

///|
// Cases from
// https://learn.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments,
// ported from moonbitlang/async's `write_arg_with_windows_escape` tests.
#cfg(platform="windows")
test "windows command line arg escape" {
  inspect(
    test_arg("a b c"),
    content=(
      #|"a b c"
    ),
  )
  inspect(
    test_arg("ab\"c"),
    content=(
      #|"ab\"c"
    ),
  )
  inspect(test_arg("\\"), content="\\")
  inspect(test_arg("a\\\\\\c"), content="a\\\\\\c")
  inspect(
    test_arg("a\\\"c"),
    content=(
      #|"a\\\"c"
    ),
  )
  inspect(
    test_arg("a\\\\b c"),
    content=(
      #|"a\\b c"
    ),
  )
  inspect(
    test_arg("ab\" c d"),
    content=(
      #|"ab\" c d"
    ),
  )
}

///|
#cfg(platform="windows")
test "windows command line arg escape empty" {
  inspect(
    test_arg(""),
    content=(
      #|""
    ),
  )
}

///|
#cfg(platform="windows")
test "windows command line arg escape leading quote" {
  inspect(
    test_arg("\"abcd"),
    content=(
      #|"\"abcd"
    ),
  )
}