///|
/// One immutable command constructed from one REPL input.
priv trait ReplCommand {
  async fn execute(Self, context : ReplContext) -> CommandResult
}

///|
/// The command name and remaining input after the REPL has split one line.
priv struct CommandInvocation {
  name : String
  arguments : String
}

///|
fn CommandInvocation::new(
  name : StringView,
  arguments : StringView,
) -> CommandInvocation {
  { name: name.to_owned(), arguments: arguments.to_owned(), }
}

///|
fn command_word_end(input : StringView) -> Int {
  for offset, character in input.iter2() {
    if character == ' ' || character == '\t' {
      return offset
    }
  }
  input.length()
}

///|
fn parse_command_invocation(line : StringView) -> CommandInvocation? {
  let input = line.trim_start(chars=" \t")
  guard input != "" else { return None }
  let word_end = command_word_end(input)
  CommandInvocation::new(
    input[:word_end],
    input[word_end:].trim(char_set=" \t"),
  )
  |> Some
}

///|
/// Result of the parser/factory owned by one CommandSpec.
priv enum CommandParseResult {
  ParsedCommand(&ReplCommand)
  InvalidCommand(String)
}

///|
fn[C : ReplCommand] parsed_command(command : C) -> CommandParseResult {
  ParsedCommand(command)
}

///|
fn invalid_command(message : String) -> CommandParseResult {
  InvalidCommand(message)
}

///|
test "command invocation splits one exact word after leading whitespace" {
  assert_true(
    parse_command_invocation("  break main.mbt:4")
    is Some({ name: "break", arguments: "main.mbt:4", }),
  )
  assert_true(
    parse_command_invocation("\trun\t") is Some({ name: "run", arguments: "", }),
  )
}

///|
test "blank input has no command invocation" {
  assert_true(parse_command_invocation("") is None)
  assert_true(parse_command_invocation(" \t ") is None)
}