#compdef cargo-nextest

# cargo-nextest is a cargo subcommand: the binary expects `nextest` and then a
# command as its first arguments, or `ntr`, a shortcut for `nextest run`.

_cargo-nextest() {
  local curcontext="$curcontext" state line
  typeset -A opt_args

  # Option groups shared across commands. They are local, and zsh's dynamic
  # scope makes them visible to the helper functions below.
  local -a _nx_early _nx_global _nx_build _nx_filter _nx_runner _nx_run \
    _nx_bench _nx_reporter _nx_report_run _nx_reuse
  local -a _nx_commands _nx_show_config_cmds _nx_self_cmds _nx_schema_kinds

  # Command tables, shared with `help`, which completes the same names.
  _nx_commands=(
    'list:List tests in workspace'
    'run:Build and run tests'
    'bench:Build and run benchmarks (experimental)'
    'archive:Build and archive tests'
    'show-config:Show information about nextest'\''s configuration in this workspace'
    'self:Manage the nextest installation'
    'help:Show help for a command or topic'
  )
  _nx_show_config_cmds=(
    'version:Show version-related configuration'
    'test-groups:Show defined test groups and their associated tests'
  )
  _nx_self_cmds=(
    'schema:Print an embedded JSON Schema'
    'update:This version of nextest does not have self-update enabled'
  )
  _nx_schema_kinds=(
    'repo-config:Print the JSON Schema for .config/nextest.toml'
    'user-config:Print the JSON Schema for the user config file'
  )

  # The binary's own parser takes these before `nextest`, unlike the rest of
  # _nx_global.
  _nx_early=(
    '--color=[Produce color output]:when:(auto always never)'
    '--no-pager[Do not pipe output through a pager]'
    '--user-config-file=[User config file]:file:_cargo_nextest_user_config'
    '(-h --help)'{-h,--help}'[Print help]'
  )

  # Global in clap: accepted before and after the command word.
  _nx_global=(
    "${_nx_early[@]}"
    '(-v --verbose)'{-v,--verbose}'[Verbose output]'
    '--manifest-path=[Path to Cargo.toml]:path:_files'
    '--config-file=[Config file]:file:_files'
    '*--tool-config-file=[Tool-specific config files]:tool\:path:'
    '--override-version-check[Override checks for the minimum version defined in nextest'\''s config]'
    '(-P --profile)'{-P+,--profile=}'[The nextest profile to use]:profile:'
  )

  _nx_build=(
    '*'{-p+,--package=}'[Package to test]:package:'
    '--workspace[Test all packages in the workspace]'
    '*--exclude=[Exclude packages from the test]:package:'
    '--lib[Test only this package'\''s library unit tests]'
    '*--bin=[Test only the specified binary]:bin:'
    '--bins[Test all binaries]'
    '*--example=[Test only the specified example]:example:'
    '--examples[Test all examples]'
    '*--test=[Test only the specified test target]:test:'
    '--tests[Test all targets]'
    '*--bench=[Test only the specified bench target]:bench:'
    '--benches[Test all benches]'
    '--all-targets[Test all targets]'
    '*'{-F+,--features=}'[Space or comma separated list of features to activate]:features:'
    '--all-features[Activate all available features]'
    '--no-default-features[Do not activate the default feature]'
    '--build-jobs=[Number of build jobs to run]:jobs:'
    '(-r --release)'{-r,--release}'[Build artifacts in release mode, with optimizations]'
    '--cargo-profile=[Build artifacts with the specified Cargo profile]:profile:'
    '--target=[Build for the target triple]:triple:'
    '--target-dir=[Directory for all generated artifacts]:dir:_directories'
    '--unit-graph[Output build graph in JSON (unstable)]'
    '--timings=-[Timing output formats (unstable)]::formats:_values -s , format html json'
    '--frozen[Require Cargo.lock and cache are up to date]'
    '--locked[Require Cargo.lock is up to date]'
    '--offline[Run without accessing the network]'
    '*--cargo-message-format=[Cargo message format]:format:_values -s , format human short json json-diagnostic-short json-diagnostic-rendered-ansi json-render-diagnostics'
    '*--cargo-quiet[Do not print cargo log messages (specify twice for no Cargo output at all)]'
    '*--cargo-verbose[Use cargo verbose output (specify twice for very verbose/build.rs output)]'
    '--ignore-rust-version[Ignore rust-version specification in packages]'
    '--future-incompat-report[Outputs a future incompatibility report at the end of the build]'
    '*--config=[Override a Cargo configuration value]:key=value:'
    '*-Z+[Unstable (nightly-only) flags to Cargo]:flag:'
  )

  _nx_filter=(
    '*'{-E+,--filterset=}'[Test filterset]:expr:'
    '--run-ignored=[Run ignored tests]:which:(default only all)'
    '--partition=[Test partition, e.g. hash:1/2, count:2/3, or slice:1/3]:partition:'
    '--ignore-default-filter[Ignore the default filter configured in the profile]'
  )

  _nx_runner=(
    '(--tracer --stress-count --stress-duration --no-run)--debugger=[Debug a single test using a text-based or graphical debugger]:debugger:'
    '(--debugger --stress-count --stress-duration --no-run)--tracer=[Trace a single test using a syscall tracer like strace or truss]:tracer:'
    '(--stress-duration --debugger --tracer)--stress-count=[The number of times to run each test, or infinite to run indefinitely]:count:'
    '(--stress-count --debugger --tracer)--stress-duration=[How long to run stress tests until (e.g. 24h)]:duration:'
  )

  _nx_run=(
    '(-R --rerun)'{-R+,--rerun=}'[Rerun tests that failed or didn'\''t complete in a previous recorded run]:run id or recording:_cargo_nextest_rerun'
    '(--fail-fast --ff --no-fail-fast --nff --max-fail --debugger --tracer)--no-run[Compile, but don'\''t run tests]'
    '(-j --test-threads --jobs)'{-j+,--test-threads=,--jobs=}'[Number of tests to run simultaneously]:n:'
    '--retries=[Number of retries for failing tests]:n:'
    '--flaky-result=[Whether to treat flaky tests as passing or failing]:result:(pass fail)'
    '(--fail-fast --ff --no-run --max-fail)'{--fail-fast,--ff}'[Stop running tests after the first failure]'
    '(--no-fail-fast --nff --no-run --max-fail)'{--no-fail-fast,--nff}'[Run all tests regardless of failure]'
    '(--fail-fast --ff --no-fail-fast --nff --no-run)--max-fail=[Number of failures after which to stop running tests]:max failures:'
    '--no-tests=[Behavior if there are no tests to run]:action:(auto pass warn fail)'
    '--no-capture[Run tests serially and do not capture output]'
  )

  _nx_bench=(
    '(--fail-fast --ff --no-fail-fast --nff --max-fail --debugger --tracer)--no-run[Compile, but don'\''t run benchmarks]'
    '(--fail-fast --ff --no-run --max-fail)'{--fail-fast,--ff}'[Stop running benchmarks after the first failure]'
    '(--no-fail-fast --nff --no-run --max-fail)'{--no-fail-fast,--nff}'[Run all benchmarks regardless of failure]'
    '(--fail-fast --ff --no-fail-fast --nff --no-run)--max-fail=[Number of failures after which to stop running benchmarks]:max failures:'
    '--no-tests=[Behavior if there are no benchmarks to run]:action:(auto pass warn fail)'
    '--no-capture[Run benchmarks serially and do not capture output (always enabled)]'
  )

  _nx_reporter=(
    '--show-progress=[Show nextest progress in the specified manner]:show:(auto none bar counter only)'
    '--no-input-handler[Disable handling of input keys from the terminal]'
  )

  _nx_report_run=(
    '--failure-output=[When to display output for failed tests]:when:(immediate immediate-final final never)'
    '--success-output=[When to display output for successful tests]:when:(immediate immediate-final final never)'
    '--status-level=[Level of status information to display during test runs]:level:(none fail retry slow leak pass skip all)'
    '--final-status-level=[Level of status information to display in the final summary]:level:(none fail flaky slow skip pass all)'
    '--no-output-indent[Do not indent captured test output]'
    '--max-progress-running=[Maximum number of running tests to list in the progress bar]:n:'
    '--message-format=[Format to use for test results (experimental)]:format:(human libtest-json libtest-json-plus)'
    '--message-format-version=[Version of structured message-format to use (experimental)]:version:'
  )

  _nx_reuse=(
    '--archive-file=[Path to nextest archive]:file:_files'
    '--archive-format=[Archive format]:format:(auto tar-zst)'
    '--extract-to=[Destination directory to extract archive to]:dir:_directories'
    '--extract-overwrite[Overwrite files in destination directory while extracting archive]'
    '--persist-extract-tempdir[Persist extracted temporary directory]'
    '--cargo-metadata=[Path to cargo metadata JSON]:file:_files'
    '--workspace-remap=[Remapping for the workspace root]:dir:_directories'
    '--binaries-metadata=[Path to binaries-metadata JSON]:file:_files'
    '--target-dir-remap=[Remapping for the target directory]:dir:_directories'
    '--build-dir-remap=[Remapping for the build directory]:dir:_directories'
  )

  # For `cargo nextest …`, _cargo calls us with `nextest` still in words[1].
  if [[ $words[1] == nextest ]]; then
    _cargo_nextest_cmd cargo
    return
  fi

  _arguments -C -A '-*' \
    "${_nx_early[@]}" \
    '(-V --version)'{-V,--version}'[Print version]' \
    '1:nextest subcommand:->subcommand' \
    '*::args:->args'

  local -a subcommands=(
    'nextest:A next-generation test runner for Rust'
    'ntr:Build and run tests: a shortcut for cargo nextest run'
    'help:Print this message or the help of the given subcommand(s)'
  )

  case $state in
    subcommand)
      _describe -t commands 'cargo-nextest subcommand' subcommands
      ;;
    args)
      case $line[1] in
        nextest) _cargo_nextest_cmd ;;
        ntr)     _arguments "${_nx_global[@]}" "${_nx_build[@]}" \
                   "${_nx_filter[@]}" "${_nx_runner[@]}" "${_nx_run[@]}" \
                   "${_nx_reporter[@]}" "${_nx_report_run[@]}" \
                   "${_nx_reuse[@]}" '*::test filter:' ;;
        help)
          (( CURRENT == 2 )) &&
            _describe -t commands 'cargo-nextest subcommand' subcommands
          ;;
      esac
      ;;
  esac
}

_cargo_nextest_cmd() {
  local curcontext="$curcontext" state line
  typeset -A opt_args
  local -a top=("${_nx_global[@]}")

  # Right after `cargo nextest`, _cargo takes -v and --color as its own options
  # and stops calling us, so offering them there leads nowhere. Later words
  # still need their specs, so that `--color <when>` parses.
  [[ $1 == cargo ]] && (( CURRENT == 2 )) &&
    top=("${(@)top:#(--color=*|\(-v --verbose\)*)}")

  # -A keeps the top-level -V from completing after the command word.
  _arguments -C -A '-*' \
    "${top[@]}" \
    '(-V --version)'{-V,--version}'[Print version]' \
    '1:command:->command' \
    '*::args:->cmdargs'

  case $state in
    command)
      _describe -t commands 'cargo nextest command' _nx_commands
      ;;
    cmdargs)
      case $line[1] in
        list)
          _arguments "${_nx_global[@]}" "${_nx_build[@]}" \
            "${_nx_filter[@]}" "${_nx_reuse[@]}" \
            '(-T --message-format)'{-T+,--message-format=}'[Output format]:format:(auto human oneline json json-pretty)' \
            '--list-type=[Type of listing]:type:(full binaries-only)' \
            '--show-progress=[Show list progress in the specified manner]:show:(auto none bar counter only)' \
            '*::test filter:'
          ;;
        run|r)
          _arguments "${_nx_global[@]}" "${_nx_build[@]}" \
            "${_nx_filter[@]}" "${_nx_runner[@]}" "${_nx_run[@]}" \
            "${_nx_reporter[@]}" "${_nx_report_run[@]}" "${_nx_reuse[@]}" \
            '*::test filter:'
          ;;
        bench|b)
          _arguments "${_nx_global[@]}" "${_nx_build[@]}" \
            "${_nx_filter[@]}" "${_nx_runner[@]}" "${_nx_bench[@]}" \
            "${_nx_reporter[@]}" '*::benchmark filter:'
          ;;
        archive)
          _arguments "${_nx_global[@]}" "${_nx_build[@]}" \
            '*'{-E+,--filterset=}'[Archive filterset]:expr:' \
            '--archive-file=[File to write archive to]:file:_files' \
            '--archive-format=[Archive format]:format:(auto tar-zst)' \
            '--zstd-level=[Zstandard compression level (-7 to 22, higher is more compressed + slower)]:level:'
          ;;
        show-config) _cargo_nextest_show_config ;;
        self)        _cargo_nextest_self ;;
        help)        _cargo_nextest_help ;;
      esac
      ;;
  esac
}

_cargo_nextest_show_config() {
  local curcontext="$curcontext" state line
  typeset -A opt_args

  _arguments -C \
    "${_nx_global[@]}" \
    '1:show-config command:->command' \
    '*::args:->subargs'

  case $state in
    command)
      _describe -t commands 'show-config command' _nx_show_config_cmds
      ;;
    subargs)
      case $line[1] in
        version)
          _arguments "${_nx_global[@]}"
          ;;
        test-groups)
          _arguments "${_nx_global[@]}" "${_nx_build[@]}" \
            "${_nx_filter[@]}" "${_nx_reuse[@]}" \
            '--show-default[Show default test groups]' \
            '*--groups=[Show only the named groups]:group:' \
            '*::test filter:'
          ;;
      esac
      ;;
  esac
}

_cargo_nextest_self() {
  local curcontext="$curcontext" state line
  typeset -A opt_args

  _arguments -C \
    "${_nx_global[@]}" \
    '1:self command:->command' \
    '*::args:->subargs'

  case $state in
    command)
      _describe -t commands 'self command' _nx_self_cmds
      ;;
    subargs)
      case $line[1] in
        schema)
          _cargo_nextest_self_schema
          ;;
        update)
          _arguments "${_nx_global[@]}" \
            '(--beta --rc)--version=[Version or version range to download]:version:' \
            '(--version --rc)--beta[Update to the latest version, including prereleases]' \
            '(--version --beta)--rc[Update to the latest RC or stable version]' \
            '(-n --check -y --yes)'{-n,--check}'[Check for updates rather than downloading them]' \
            '(-n --check -y --yes)'{-y,--yes}'[Do not prompt for confirmation]' \
            '(-f --force)'{-f,--force}'[Force downgrades and reinstalls]' \
            '--releases-url=[URL or path to fetch releases.json from]:url:_files'
          ;;
      esac
      ;;
  esac
}

_cargo_nextest_self_schema() {
  local curcontext="$curcontext" state line
  typeset -A opt_args

  _arguments -C \
    "${_nx_global[@]}" \
    '1:schema:->kind' \
    '*::args:->kindargs'

  case $state in
    kind)
      _describe -t schemas 'schema' _nx_schema_kinds
      ;;
    kindargs)
      _arguments "${_nx_global[@]}" \
        '(-o --output)'{-o+,--output=}'[Output file path]:file:_files'
      ;;
  esac
}

_cargo_nextest_help() {
  local curcontext="$curcontext" state line
  typeset -A opt_args

  _arguments -C \
    "${_nx_global[@]}" \
    '*:command or topic:->path'

  [[ $state == path ]] || return

  local -a candidates
  case ${(j: :)line[1,-2]} in
    '')
      candidates=(
        "${_nx_commands[@]}"
        'filterset:the filterset DSL: predicates, operators, and matchers'
        'repo-config:repository configuration reference'
        'user-config:user configuration reference'
      )
      ;;
    show-config)   candidates=("${_nx_show_config_cmds[@]}") ;;
    self)          candidates=("${_nx_self_cmds[@]}") ;;
    'self schema') candidates=("${_nx_schema_kinds[@]}") ;;
  esac
  (( $#candidates )) && _describe -t commands 'command or topic' candidates
}

# A word ending in .zip or holding a path separator is a recording, any other
# word a run id.
_cargo_nextest_rerun() {
  _alternative 'ids:run id:((latest\:"the most recent recorded run"))' \
    'files:recording:_files -g "*.zip(-.)"'
}

_cargo_nextest_user_config() {
  _alternative 'files:file:_files' 'none:special value:((none\:"skip loading user config"))'
}

_cargo-nextest "$@"
