///|
pub(all) suberror CliError {
  HelpRequested
  MissingSpec
  MissingUninstallName
  UnknownFlag(String)
  MissingFlagValue(String)
  InvalidTarget(String)
  UnexpectedArg(String)
} derive(Eq, Show)

///|
pub struct InstallArgs {
  spec : String
  name : String?
  target : Target
} derive(Eq, Show)

///|
pub enum Command {
  Install(InstallArgs)
  Uninstall(String)
} derive(Eq, Show)

///|
pub fn parse_cli_args(args : Array[String]) -> Result[Command, CliError] {
  if args.length() == 0 {
    return Err(CliError::MissingSpec)
  }
  if args[0] == "--help" || args[0] == "-h" {
    return Err(CliError::HelpRequested)
  }
  if args[0] == "uninstall" {
    if args.length() == 1 {
      return Err(CliError::MissingUninstallName)
    }
    if args[1] == "--help" || args[1] == "-h" {
      return Err(CliError::HelpRequested)
    }
    if args[1].has_prefix("-") {
      return Err(CliError::UnknownFlag(args[1]))
    }
    if args.length() > 2 {
      return Err(CliError::UnexpectedArg(args[2]))
    }
    return Ok(Command::Uninstall(args[1]))
  }
  for i = 0, spec = "", name = None, target = Target::Native; i < args.length(); {
    let arg = args[i]
    if arg == "--help" || arg == "-h" {
      return Err(CliError::HelpRequested)
    }
    match strip_prefix(arg, "--name=") {
      Some(value) => continue i + 1, spec, Some(value), target
      None => ()
    }
    match strip_prefix(arg, "--target=") {
      Some(value) =>
        match Target::from_string(value) {
          Some(t) => continue i + 1, spec, name, t
          None => return Err(CliError::InvalidTarget(value))
        }
      None => ()
    }
    if arg == "--name" {
      if i + 1 >= args.length() {
        return Err(CliError::MissingFlagValue("--name"))
      }
      continue i + 2, spec, Some(args[i + 1]), target
    }
    if arg == "--target" {
      if i + 1 >= args.length() {
        return Err(CliError::MissingFlagValue("--target"))
      }
      let value = args[i + 1]
      match Target::from_string(value) {
        Some(t) => continue i + 2, spec, name, t
        None => return Err(CliError::InvalidTarget(value))
      }
    }
    if arg.has_prefix("-") {
      return Err(CliError::UnknownFlag(arg))
    }
    if spec.length() == 0 {
      continue i + 1, arg, name, target
    }
    return Err(CliError::UnexpectedArg(arg))
  } else {
    if spec.length() == 0 {
      Err(CliError::MissingSpec)
    } else {
      Ok(Command::Install({ spec, name, target }))
    }
  }
}

///|
fn strip_prefix(s : String, prefix : StringView) -> String? {
  if s.has_prefix(prefix) {
    let prefix_len = prefix.length()
    try s[prefix_len:] catch {
      _ => None
    } noraise {
      view => Some(view.to_string())
    }
  } else {
    None
  }
}