///|
priv enum OutputFormat { Human; MachineJson }

///|
priv enum CommandRequest { Help; Run(@moonx.Command) }

///|
priv struct LaunchOptions {
  format : OutputFormat
  request : CommandRequest
}

///|
priv enum UsageProblem {
  UnknownOption(String)
  MissingExecutable
  Executable(@moonx.ExecutableError)
  Arguments(@moonx.ArgumentsError)
}

///|
suberror CliUsageError {
  InvalidLaunchOptions(format~ : OutputFormat, problem~ : UsageProblem)
}

///|
/// Launcher options end at `--` or the first executable. Child options after
/// that boundary are opaque. A later leading --json still formats an earlier
/// unknown launcher option; child --json never selects parent reporting.
fn parse_options(args : Array[String]) -> LaunchOptions raise CliUsageError {
  let mut format = Human
  let mut help = false
  let mut unknown : String? = None
  let mut index = 0
  while index < args.length() {
    let input = args[index]
    if input == "--" { index += 1; break }
    if input == "--json" { format = MachineJson }
    else if input == "--help" || input == "-h" { help = true }
    else if input.has_prefix("-") {
      if unknown is None { unknown = Some(input) }
    } else { break }
    index += 1
  }
  if unknown is Some(input) { raise InvalidLaunchOptions(format~, problem=UnknownOption(input)) }
  if help { return { format, request: Help } }
  guard index < args.length() else { raise InvalidLaunchOptions(format~, problem=MissingExecutable) }
  let program = @moonx.Executable::parse(args[index]) catch {
    cause => raise InvalidLaunchOptions(format~, problem=Executable(cause))
  }
  let arguments = @moonx.Arguments::parse(args[index + 1:].to_owned()) catch {
    cause => raise InvalidLaunchOptions(format~, problem=Arguments(cause))
  }
  { format, request: Run(@moonx.Command::new(program, arguments)) }
}

///|
let usage = "Usage: moonx moonrockz/moon-lander-moonx/launch@VERSION [--json] [--] EXECUTABLE [ARG...]\n       moonx moonrockz/moon-lander-moonx/launch@VERSION --help [--json]"

///|
fn help_document() -> Json {
  { "schema_version": 1, "command": "moon-lander-moonx/launch", "package_version": package_version.text(), "kind": "help",
    "exit_status": 0, "usage": usage,
    "stdout_ownership": "Child output is opaque after startup; parent JSON reports only help, usage errors or a child that never started." }
}

///|
fn UsageProblem::document(self : UsageProblem) -> Json {
  match self {
    UnknownOption(input) => { "kind": "unknown-option", "input": input }
    MissingExecutable => { "kind": "missing-executable" }
    Executable(@moonx.ExecutableError::InvalidExecutable(input~, rule~)) => {
      "kind": "invalid-executable", "input": input,
      "rule": match rule { Empty => "empty"; ContainsNul => "contains-nul" },
    }
    Arguments(@moonx.ArgumentsError::ArgumentContainsNul(input~, index~)) => {
      "kind": "invalid-argument", "input": input, "index": index, "rule": "contains-nul",
    }
  }
}

///|
fn usage_document(problem : UsageProblem) -> Json {
  { "schema_version": 1, "command": "moon-lander-moonx/launch", "package_version": package_version.text(), "kind": "usage-error",
    "exit_status": 2, "reason": problem.document() }
}

///|
fn signal_name(signal : @moonx.LauncherSignal) -> String {
  match signal { Interrupt => "SIGINT"; Terminate => "SIGTERM"; Hangup => "SIGHUP"; Break => "SIGBREAK" }
}

///|
fn cancellation_document(cause : @moonx.CancellationCause) -> Json {
  match cause {
    Signal(signal) => { "kind": "signal", "signal": signal_name(signal) }
    Requested => { "kind": "requested" }
  }
}

///|
/// Only an outcome that proves no child started can produce a parent document.
fn startup_document(outcome : @moonx.LaunchOutcome) -> Json? {
  let reason = match outcome.result() {
    Started(_) => return None
    NotStarted(Cancelled(cause)) => cancellation_document(cause)
    NotStarted(Failed(failure)) => match failure {
      Materialization(error) => { "kind": "materialization-failed", "message": error.to_string() }
      NodeMissing(executable~, cause~) => {
        "kind": "node-missing", "executable": executable.to_string(), "message": cause.to_string(),
      }
      ProcessStart(error) => { "kind": "process-start-failed", "message": error.to_string() }
    }
  }
  Some({ "schema_version": 1, "command": "moon-lander-moonx/launch", "package_version": package_version.text(), "kind": "not-started",
    "exit_status": Json(outcome.selected_status().bits()), "reason": reason,
    "finalization_failure_count": outcome.finalization_failures().length() })
}