///|
/// A shell-free process description.
///
/// `program` is an executable name or path. Every element of `arguments` is
/// passed as one literal argument. Shell operators such as `|`, `>`, `&&`,
/// `$()`, and `*` have no special meaning.
pub struct Cmd {
  program_ : String
  arguments_ : Array[String]
  cwd_ : String?
  environment_ : Map[String, String]
  inherit_environment_ : Bool
  stdin_ : String?
  no_console_window_ : Bool
} derive(Debug)

///|
/// Create a process description from an executable and its literal arguments.
///
/// # Example
/// ```mbt check
/// test {
///   let cmd = Cmd::new("git", ["status", "--short"])
///   inspect(cmd.program(), content="git")
///   debug_inspect(cmd.arguments(), content="[\"status\", \"--short\"]")
/// }
/// ```
pub fn Cmd::new(program : String, arguments : Array[String]) -> Cmd {
  {
    program_: program,
    arguments_: arguments.copy(),
    cwd_: None,
    environment_: {},
    inherit_environment_: true,
    stdin_: None,
    no_console_window_: false,
  }
}

///|
/// Return a copy with one literal argument appended.
pub fn Cmd::arg(self : Cmd, argument : String) -> Cmd {
  let arguments = self.arguments_.copy()
  arguments.push(argument)
  {
    program_: self.program_,
    arguments_: arguments,
    cwd_: self.cwd_,
    environment_: self.environment_.copy(),
    inherit_environment_: self.inherit_environment_,
    stdin_: self.stdin_,
    no_console_window_: self.no_console_window_,
  }
}

///|
/// Return a copy with literal arguments appended.
pub fn Cmd::args(self : Cmd, arguments : ArrayView[String]) -> Cmd {
  let all_arguments = self.arguments_.copy()
  for argument in arguments {
    all_arguments.push(argument)
  }
  {
    program_: self.program_,
    arguments_: all_arguments,
    cwd_: self.cwd_,
    environment_: self.environment_.copy(),
    inherit_environment_: self.inherit_environment_,
    stdin_: self.stdin_,
    no_console_window_: self.no_console_window_,
  }
}

///|
/// Return a copy that runs in `directory`.
pub fn Cmd::cwd(self : Cmd, directory : String) -> Cmd {
  {
    program_: self.program_,
    arguments_: self.arguments_.copy(),
    cwd_: Some(directory),
    environment_: self.environment_.copy(),
    inherit_environment_: self.inherit_environment_,
    stdin_: self.stdin_,
    no_console_window_: self.no_console_window_,
  }
}

///|
/// Return a copy with one environment variable set for the child process.
pub fn Cmd::env(self : Cmd, name : String, value : String) -> Cmd {
  let environment = self.environment_.copy()
  environment[name] = value
  {
    program_: self.program_,
    arguments_: self.arguments_.copy(),
    cwd_: self.cwd_,
    environment_: environment,
    inherit_environment_: self.inherit_environment_,
    stdin_: self.stdin_,
    no_console_window_: self.no_console_window_,
  }
}

///|
/// Return a copy with all entries in `environment` set for the child process.
pub fn Cmd::envs(self : Cmd, environment : Map[String, String]) -> Cmd {
  let all_environment = self.environment_.copy()
  all_environment.merge_in_place(environment)
  {
    program_: self.program_,
    arguments_: self.arguments_.copy(),
    cwd_: self.cwd_,
    environment_: all_environment,
    inherit_environment_: self.inherit_environment_,
    stdin_: self.stdin_,
    no_console_window_: self.no_console_window_,
  }
}

///|
/// Return a copy that does not inherit the parent environment.
///
/// Variables added with `env` or `envs` are still supplied.
pub fn Cmd::clear_env(self : Cmd) -> Cmd {
  {
    program_: self.program_,
    arguments_: self.arguments_.copy(),
    cwd_: self.cwd_,
    environment_: self.environment_.copy(),
    inherit_environment_: false,
    stdin_: self.stdin_,
    no_console_window_: self.no_console_window_,
  }
}

///|
/// Return a copy that receives `input` on standard input.
pub fn Cmd::stdin(self : Cmd, input : String) -> Cmd {
  {
    program_: self.program_,
    arguments_: self.arguments_.copy(),
    cwd_: self.cwd_,
    environment_: self.environment_.copy(),
    inherit_environment_: self.inherit_environment_,
    stdin_: Some(input),
    no_console_window_: self.no_console_window_,
  }
}

///|
/// Return a copy with creation of a console window disabled on Windows.
///
/// This option has no effect on other platforms.
pub fn Cmd::no_console_window(self : Cmd) -> Cmd {
  {
    program_: self.program_,
    arguments_: self.arguments_.copy(),
    cwd_: self.cwd_,
    environment_: self.environment_.copy(),
    inherit_environment_: self.inherit_environment_,
    stdin_: self.stdin_,
    no_console_window_: true,
  }
}

///|
/// Start a two-command pipeline.
pub fn Cmd::pipe(self : Cmd, next : Cmd) -> Pipeline {
  Pipeline::new([self, next])
}

///|
/// Return the executable name or path.
pub fn Cmd::program(self : Cmd) -> String {
  self.program_
}

///|
/// Return the literal argument vector.
pub fn Cmd::arguments(self : Cmd) -> Array[String] {
  self.arguments_.copy()
}

///|
/// Return the configured working directory.
pub fn Cmd::working_directory(self : Cmd) -> String? {
  self.cwd_
}

///|
/// Return a copy of the configured environment additions.
pub fn Cmd::environment(self : Cmd) -> Map[String, String] {
  self.environment_.copy()
}

///|
/// Whether the child inherits the parent environment.
pub fn Cmd::inherits_environment(self : Cmd) -> Bool {
  self.inherit_environment_
}

///|
/// Return the configured standard input text, if any.
pub fn Cmd::standard_input(self : Cmd) -> String? {
  self.stdin_
}