///|
fn cli_positive_int(value : String, field : String) -> Int32 raise ConfigError {
  if value.is_empty() {
    raise InvalidValue(field)
  }
  let mut result = 0
  for c in value {
    if c < '0' || c > '9' {
      raise InvalidValue(field)
    }
    let digit = c.to_int() - 48
    if result > (2147483647 - digit) / 10 {
      raise InvalidValue(field)
    }
    result = result * 10 + digit
  }
  if result <= 0 {
    raise InvalidValue(field)
  }
  result
}

///|
fn csv_patterns(value : String) -> Array[String] raise ConfigError {
  let result : Array[String] = []
  for item in value.split(",") {
    let pattern = item.to_owned().trim().to_owned()
    validate_pattern(pattern)
    result.push(pattern)
  }
  result
}

///|
fn set_action(current : Action, next : Action) -> Action raise ConfigError {
  if current != Pack && current != next {
    raise Conflict("action")
  }
  next
}

///|
/// Arguments exclude argv[0]. Supports --key=value and -- to end option parsing.
pub fn parse_cli(args : Array[String]) -> Cli raise ConfigError {
  let patch = Patch::empty()
  let mut directory : String? = None
  let mut config_path : String? = None
  let mut action = Pack
  let mut stdout = false
  let mut quiet = false
  let mut verbose = false
  let mut positional_only = false
  let mut index = 0
  while index < args.length() {
    let arg = args[index]
    index += 1
    if !positional_only && arg == "--" {
      positional_only = true
      continue
    }
    if positional_only || !arg.has_prefix("-") || arg == "-" {
      if directory is Some(_) {
        raise MultipleDirectories
      }
      validate_path(arg, "directory")
      directory = Some(arg)
      continue
    }
    let (key, inline) = match arg.split_once("=") {
      Some((key, value)) => (key.to_owned(), Some(value.to_owned()))
      None => (arg, None)
    }
    let takes_value = match key {
      "-o"
      | "--output"
      | "--style"
      | "--include"
      | "-i"
      | "--ignore"
      | "--token-count-encoding"
      | "--token-budget"
      | "-c"
      | "--config"
      | "--tokenizer-path" => true
      _ => false
    }
    if takes_value {
      let value = match inline {
        Some(value) => value
        None => {
          if index >= args.length() || args[index].has_prefix("-") {
            raise MissingValue(key)
          }
          let value = args[index]
          index += 1
          value
        }
      }
      match key {
        "-o" | "--output" => {
          validate_path(value, "output.filePath")
          patch.output = Some(value)
        }
        "--style" => patch.style = Some(parse_style(value))
        "--include" =>
          patch.include_patterns = append_unique(
            patch.include_patterns,
            csv_patterns(value),
          )
        "-i" | "--ignore" =>
          patch.ignore = append_unique(patch.ignore, csv_patterns(value))
        "--token-count-encoding" => {
          if value != "o200k_base" {
            raise InvalidValue("tokenCount.encoding")
          }
          patch.encoding = Some(value)
        }
        "--token-budget" =>
          patch.token_budget = Some(
            cli_positive_int(value, "output.tokenBudget"),
          )
        "-c" | "--config" => {
          validate_path(value, "config")
          config_path = Some(value)
        }
        "--tokenizer-path" => {
          validate_path(value, "tokenizerPath")
          patch.tokenizer_path = Some(value)
        }
        _ => raise UnknownOption(key)
      }
    } else {
      // Boolean and action flags do not accept an attached value.
      if inline is Some(_) {
        raise InvalidValue(key)
      }
      match key {
        "--stdout" => stdout = true
        "--quiet" => quiet = true
        "--verbose" => verbose = true
        "--no-gitignore" => patch.use_gitignore = Some(false)
        "--no-dot-ignore" => patch.use_dot_ignore = Some(false)
        "--no-default-patterns" => patch.use_default_patterns = Some(false)
        "--no-security-check" => patch.security_check = Some(false)
        "--parsable-style" => ()
        "--init" => action = set_action(action, Init)
        "-h" | "--help" => action = set_action(action, Help)
        "-v" | "--version" => action = set_action(action, Version)
        _ => raise UnknownOption(key)
      }
    }
  }
  if quiet && verbose {
    raise Conflict("quiet/verbose")
  }
  if stdout && patch.output is Some(_) {
    raise Conflict("output/stdout")
  }
  validate_patch(patch)
  {
    directory: directory.unwrap_or("."),
    config_path,
    action,
    stdout,
    quiet,
    verbose,
    patch,
  }
}