///|
async fn drive_input(session : @shell.Session, interactive : Bool) -> Int {
  let mut continuation = false
  for ;; {
    if interactive {
      @stdio.stderr.write(session.prompt(continuation~))
    }
    let line = @shell.read_line(@stdio.stdin)
    guard line is Some(line) else {
      return match session.finish() {
        @shell.Exit(status) | @shell.Completed(status) => status
        @shell.NeedMoreInput => 2
      }
    }
    let result = session.feed(line) catch {
      @shell.ShellError(message) => {
        @stdio.stderr.write("sh: \{message}\n")
        if !interactive {
          return 2
        }
        @shell.Completed(2)
      }
      err => raise err
    }
    match result {
      @shell.NeedMoreInput => continuation = true
      @shell.Completed(_) => continuation = false
      @shell.Exit(status) => return status
    }
  }
}

///|
async fn main {
  let metadata_args = @env.args()[1:]
  if metadata_args.length() == 1 && metadata_args[0] == "--version" {
    @stdio.stdout.write("sh 0.2.1\n")
    return
  }
  if metadata_args.length() == 1 && metadata_args[0] == "--help" {
    @stdio.stdout.write(
      "Usage: sh [-i] [-c SOURCE [NAME [ARG...]] | -s [ARG...] | FILE [ARG...]]\n  -c SOURCE   execute shell source\n  -s          read commands from standard input\n  -i          enable a persistent line-oriented interactive session\n  --help      display this help and exit\n  --version   display version and exit\nSupports a bounded shell language; terminal job control is unavailable.\n",
    )
    return
  }
  let raw = @env.args()
  let arguments = raw[1:]
  let invocation = @path.Path(raw.get(0).unwrap_or("sh")).basename().to_owned()
  let invocation = if invocation.has_suffix(".exe") {
    invocation[:invocation.length() - 4].to_owned()
  } else if invocation.has_suffix(".wasm") {
    invocation[:invocation.length() - 5].to_owned()
  } else {
    invocation
  }
  let mut index = 0
  let mut command : String? = None
  let mut script_path : String? = None
  let mut interactive = false
  let mut stdin_script = false
  let mut options = true
  while index < arguments.length() && command is None && script_path is None {
    let argument = arguments[index]
    if options && argument == "--" {
      options = false
      index += 1
    } else if options && argument == "-i" {
      interactive = true
      index += 1
    } else if options && argument == "-c" {
      if index + 1 >= arguments.length() {
        @stdio.stderr.write("sh: -c requires a command string\n")
        @sys.exit(2)
      }
      command = Some(arguments[index + 1])
      index += 2
    } else if options && argument == "-s" {
      stdin_script = true
      index += 1
      break
    } else if options && argument.has_prefix("-") && argument != "-" {
      @stdio.stderr.write("sh: unsupported option '\{argument}'\n")
      @sys.exit(2)
    } else {
      script_path = Some(argument)
      index += 1
    }
  }
  let name = if command is Some(_) {
    arguments.get(index).unwrap_or("sh")
  } else {
    script_path.unwrap_or(invocation)
  }
  let positional = if command is Some(_) && index < arguments.length() {
    arguments[index + 1:].to_owned()
  } else {
    arguments[index:].to_owned()
  }
  let session = @shell.Session::new(name~, args=positional, interactive~)
  if interactive {
    session.load_startup() catch {
      err => @stdio.stderr.write("sh: startup: \{err}\n")
    }
  }
  let status = try {
    let source = match command {
      Some(source) => Some(source)
      None =>
        match script_path {
          Some(path) if path != "-" => Some(@fs.read_file(path).text())
          _ => None
        }
    }
    match source {
      Some(source) =>
        match session.feed(source) {
          @shell.Completed(status) | @shell.Exit(status) => status
          @shell.NeedMoreInput => {
            ignore(session.finish())
            2
          }
        }
      None => {
        ignore(stdin_script)
        drive_input(session, interactive)
      }
    }
  } catch {
    @shell.ShellError(message) => {
      @stdio.stderr.write("sh: \{message}\n")
      2
    }
    err => {
      @stdio.stderr.write("sh: \{err}\n")
      2
    }
  }
  if status != 0 {
    @sys.exit(status)
  }
}