///|
/// Where a child process's standard input comes from.
///
/// `Text` is encoded as UTF-8. `Binary` is written verbatim, which is what a
/// child expecting a compressed or otherwise non-textual stream needs.
/// `FromFile` is the structured form of a shell's `< path`; the file must
/// already exist.
pub(all) enum Stdin {
  Text(String)
  Binary(Bytes)
  FromFile(String)
} derive(Debug)

///|
/// Where a child process's standard output or standard error goes.
///
/// `Capture` returns the stream to the caller: `output` collects it into
/// `Output`, `each_line` delivers stdout line by line, and `status` has no
/// return channel so it inherits instead. `Inherit` hands the stream to the
/// parent's own descriptor.
///
/// `ToFile` and `AppendToFile` are the structured forms of a shell's `> path`
/// and `>> path`. Both create the file if it is missing; `ToFile` truncates an
/// existing one. A redirected stream is absent from `Output`, where it appears
/// as an empty string.
pub(all) enum Redirect {
  Capture
  Inherit
  ToFile(String)
  AppendToFile(String)
} derive(Debug)

///|
/// How a child is stopped when a run is cancelled.
///
/// Cancellation happens when a timeout expires, when a capture limit is
/// exceeded, or when any stage of a pipeline fails.
///
/// `Kill` ends the child immediately and is the default, because a sandboxed
/// runtime should not have to wait on an untrusted child. `Graceful` first asks
/// the child to stop — `SIGTERM`, or `SIGBREAK` on Windows — and kills it only
/// if it has not exited within `grace_ms`, which is what a child that must
/// flush a file or release a lock needs.
///
/// A graceful teardown delays the cancelling call by up to `grace_ms`: a
/// `timeout_ms` of 20 with `grace_ms` of 1000 raises `TimeoutError` after
/// roughly a second, not after 20 milliseconds.
pub(all) enum Cancel {
  Kill
  Graceful(grace_ms~ : Int)
} derive(Debug)

///|
/// 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.
///
/// The representation is abstract and a `Cmd` is immutable once built, so a
/// plan that has been inspected or approved is the same plan that runs.
struct Cmd {
  program : String
  arguments : Array[String]
  cwd : String?
  env : Map[String, String]
  inherit_env : Bool
  stdin : Stdin?
  stdout : Redirect
  stderr : Redirect
  cancel : Cancel
  no_console_window : Bool
} derive(Debug)

///|
/// Describe one process.
///
/// Standard input is closed unless `stdin` is given, so a non-interactive run
/// cannot accidentally wait on ambient input. `env` adds to the inherited
/// environment; pass `inherit_env=false` to start from an empty one.
/// `no_console_window` suppresses a console window on Windows and is ignored
/// elsewhere.
///
/// `stdout` and `stderr` default to `Capture`, so `output` collects them.
///
/// `arguments` and `env` are copied, so later changes to the caller's
/// collections do not reach the command.
///
/// # Example
/// ```mbt check
/// test {
///   let cmd = @myshell.Cmd("git", ["status", "--short"], cwd="workspace")
///   inspect(cmd.program(), content="git")
///   debug_inspect(cmd.cwd(), content="Some(\"workspace\")")
/// }
/// ```
pub fn Cmd::Cmd(
  program : String,
  arguments : Array[String],
  cwd? : String,
  env? : Map[String, String] = {},
  inherit_env? : Bool = true,
  stdin? : Stdin,
  stdout? : Redirect = Capture,
  stderr? : Redirect = Capture,
  cancel? : Cancel = Kill,
  no_console_window? : Bool = false,
) -> Cmd {
  {
    program,
    arguments: arguments.copy(),
    cwd,
    env: env.copy(),
    inherit_env,
    stdin,
    stdout,
    stderr,
    cancel,
    no_console_window,
  }
}

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

///|
/// The literal argument vector, as a read-only view.
pub fn Cmd::arguments(self : Cmd) -> ArrayView[String] {
  self.arguments[:]
}

///|
/// The configured working directory.
pub fn Cmd::cwd(self : Cmd) -> String? {
  self.cwd
}

///|
/// A copy of the environment entries added for the child.
pub fn Cmd::env(self : Cmd) -> Map[String, String] {
  self.env.copy()
}

///|
/// Whether the child also receives the parent environment.
pub fn Cmd::inherit_env(self : Cmd) -> Bool {
  self.inherit_env
}

///|
/// The configured standard input, if any.
pub fn Cmd::stdin(self : Cmd) -> Stdin? {
  self.stdin
}

///|
/// Where standard output goes.
pub fn Cmd::stdout(self : Cmd) -> Redirect {
  self.stdout
}

///|
/// Where standard error goes.
pub fn Cmd::stderr(self : Cmd) -> Redirect {
  self.stderr
}

///|
/// How the child is stopped when the run is cancelled.
pub fn Cmd::cancel(self : Cmd) -> Cancel {
  self.cancel
}

///|
/// Whether creation of a console window is suppressed on Windows.
pub fn Cmd::no_console_window(self : Cmd) -> Bool {
  self.no_console_window
}

///|
pub extend Cmd with @debug.Debug::{to_repr}

///|
pub extend Stdin with @debug.Debug::{to_repr}

///|
pub extend Redirect with @debug.Debug::{to_repr}

///|
pub extend Cancel with @debug.Debug::{to_repr}