///|
/// CLI argument errors with the process exit code the frontend should use.
priv suberror CliError {
  Usage(message~ : String, exit_code~ : Int)
} derive(Debug)

///|
/// Parsed command-line options for one scan target.
priv struct CliOptions {
  rules_root : String?
  patterns : Array[String]
  scan_root : String
  verbose : Bool
}

///|
fn runtime_cli_args(argv : Array[String]) -> Array[String] {
  let args : Array[String] = []
  for index in 1.. String? {
  match values.get(name) {
    Some(items) if items.length() > 0 => Some(items[items.length() - 1])
    _ => None
  }
}

///|
fn all_cli_values(
  values : Map[String, Array[String]],
  name : String,
) -> Array[String] {
  match values.get(name) {
    Some(items) => items
    None => []
  }
}

///|
fn cli_flag_enabled(flags : Map[String, Bool], name : String) -> Bool {
  match flags.get(name) {
    Some(true) => true
    _ => false
  }
}

///|
fn docs_cli_command(
  docs_matches : @argparse.Matches,
) -> (String, CliOptions?, String?) raise {
  let list_requested = cli_flag_enabled(docs_matches.flags, "list")
  let doc_name = last_cli_value(docs_matches.values, "doc-name")
  match (list_requested, doc_name) {
    (true, None) => ("docs-list", None, None)
    (false, Some(name)) => ("docs-show", None, Some(name))
    (true, Some(_)) =>
      raise CliError::Usage(
        message="docs --list cannot be combined with a document name",
        exit_code=2,
      )
    (false, None) =>
      raise CliError::Usage(
        message="missing docs --list or document name",
        exit_code=2,
      )
  }
}

///|
fn scan_cli_options(scan_matches : @argparse.Matches) -> CliOptions raise {
  let verbose = cli_flag_enabled(scan_matches.flags, "verbose")
  let rules_root = last_cli_value(scan_matches.values, "rules")
  let patterns = all_cli_values(scan_matches.values, "pattern")
  if rules_root is None && patterns.is_empty() {
    raise CliError::Usage(
      message="missing required --rules or --pattern",
      exit_code=2,
    )
  }
  let scan_root = match last_cli_value(scan_matches.values, "scan-root") {
    Some(path) => path
    None => "."
  }
  { rules_root, patterns, scan_root, verbose }
}

///|
/// Parses command-line arguments after the executable name has been removed.
///
/// `scan` is required after the executable name. Within the `scan` subcommand,
/// `--rules `, `--rules=`, `-r `, or at
/// least one `--pattern ` is required, and `--verbose` is optional. One
/// optional scan-root positional may appear in the argument list. If the rules
/// option appears multiple times, the last value wins. Repeated patterns are
/// appended in order. Missing subcommands, required arguments, unknown options,
/// or more than one scan root raise `CliError::Usage` with exit code 2.
fn parse_cli_command(
  argv : Array[String],
) -> (String, CliOptions?, String?) raise {
  let matches = moongrep_command.parse(argv~, env={}) catch {
    err => {
      let message = "\{err}"
      raise CliError::Usage(message~, exit_code=2)
    }
  }
  match matches.subcommand {
    Some(("scan", scan_matches)) =>
      ("scan", Some(scan_cli_options(scan_matches)), None)
    Some(("docs", docs_matches)) => docs_cli_command(docs_matches)
    _ => raise CliError::Usage(message="missing required command", exit_code=2)
  }
}