///|
/// Raised when a number is not an exit code.
pub(all) suberror ExitCodeError {
  /// The number is outside 0 to 255.
  InvalidExitCode(Int32)
} derive(Eq, Debug)

///|
/// The exit code of a process: a number from 0 to 255.
struct ExitCode {
  value : Int32
} derive(Eq, Hash, Debug)

///|
pub fn ExitCode::ExitCode(value : Int32) -> ExitCode raise ExitCodeError {
  if value < 0 || value > 255 {
    raise InvalidExitCode(value)
  }
  { value, }
}

///|
/// 0: the command did what it was asked.
pub fn ExitCode::success() -> ExitCode {
  { value: 0, }
}

///|
/// 1: the command failed.
pub fn ExitCode::failure() -> ExitCode {
  { value: 1, }
}

///|
/// 2: the arguments are not a valid use of the command.
pub fn ExitCode::usage() -> ExitCode {
  { value: 2, }
}

///|
pub fn ExitCode::to_int(self : ExitCode) -> Int32 {
  self.value
}

///|
/// What `run` decided to do with the arguments.
pub enum Run {
  Text(String)
  Unknown(String)
}

///|
/// The exit code of the result: success for text, a usage error for an
/// unknown command.
pub fn Run::exit_code(self : Run) -> ExitCode {
  match self {
    Text(_) => ExitCode::success()
    Unknown(_) => ExitCode::usage()
  }
}

///|
pub fn usage() -> String {
  let lines = ["Usage: mission-control ", "", "Commands:"]
  // Names and descriptions line up, with at least four spaces between them.
  let width = @contracts.commands
    .iter()
    .fold(init=7, (w, c) => {
      if c.name().length() > w {
        c.name().length()
      } else {
        w
      }
    })
  for command in @contracts.commands {
    let name = command.name()
    let pad = " ".repeat(width + 4 - name.length())
    lines.push("  \{name}\{pad}\{command.description()}")
  }
  lines.join("\n")
}

///|
/// Run one invocation. `args` does not include the program name.
pub fn run(args : Array[String]) -> Run {
  let name = match args {
    [] => "help"
    [command, ..] => command
  }
  try @contracts.Command::Command(name) catch {
    UnknownCommand(_) => Unknown(name)
  } noraise {
    Help => Text(usage())
    Version => Text("mission-control " + @contracts.version().to_string())
  }
}