#
# Copyright 2020 Lawrence Livermore National Security, LLC
# (c.f. AUTHORS, NOTICE.LLNS, COPYING)
#
# This file is part of the Flux resource manager framework.
# For details, see https://github.com/flux-framework.
#
# SPDX-License-Identifier: LGPL-3.0
#
shopt -s extglob

# Lifted from bash_completions:
# For a longopt --foo=bar, set $prev=--foo and $cur=bar.
_flux_split_longopt() {
    if [[ $cur == --?*=* ]]; then
        # Cut also backslash before '=' in case it ended up there
        # for some reason
        prev="${cur%%?(\\)=*}"
        cur="${cur#*=}"
        return 0
    fi
    return 1
}

# return success if first argument is in remaining args
_flux_contains_word() {
    local w word=$1; shift
    for w in "$@"; do
        [[ $w = "$word" ]] && return
    done
}

# Determines the first non-option word of the command line. This
# is usually the command. Returns empty string if first non-option
# word is $cur, since that means we're still completing the command.
#
_flux_get_cmd() {
    local firstword i

    firstword=
    for ((i = 1; i < ${#COMP_WORDS[@]}; ++i)); do
        if [[ ${COMP_WORDS[i]} != -* && ${COMP_WORDS[i]} != $cur ]]; then
            firstword=${COMP_WORDS[i]}
            break
        fi
    done

    echo $firstword
}

# Determines the first non-option word after another given word,
# This is usually a sub-command
# Returns the word if result = $cur since that means we're still
# completing the current subcommand
#
_flux_get_subcmd() {
    local firstword i word=$1; shift

    firstword=$word
    found=0
    for ((i = 1; i < ${#COMP_WORDS[@]} - 1; ++i)); do
        local w="${COMP_WORDS[i]}"
        if [[ $w != -*  && -n $w && $w != $cur ]]; then
            if ((found == 1)); then
                firstword=$w
                break
            fi
            if [[ $w == $word ]]; then
                found=1
            fi
        fi
    done

    echo $firstword
}

# Autocomplete current queues
_flux_complete_queue() {
    local queues=$(flux queue status | sed -n 's/^\(.*\): .*$/\1/p')
    COMPREPLY=( $(compgen -W "$queues" -- "$cur") )
}

# Autocomplete format names for commands that take -o, --format
# usage _flux_complete_format_name COMMAND SUBCOMMAND [ARGS...]
_flux_complete_format_name() {
    local formats=$($@ --format=help | grep -v ^Conf | awk 'NF {print $1}')
    COMPREPLY=( $(compgen -W "$formats" -- "$cur") )
}

#  Get the list of subcommands from FLUX_EXEC_PATH and hard-coded builtins
__get_flux_subcommands() {
    local subcommands
    if [ -z "$FLUX_EXEC_PATH" ]; then
        FLUX_EXEC_PATH=`flux env printenv FLUX_EXEC_PATH`
    fi

    local IFS=":"
    for dir in $FLUX_EXEC_PATH; do
        for op in $dir/flux-*; do
            if [[ -x $op  ]]; then
                op="${op##*flux-}"
                # skip .in template files; strip .py but preserve version
                # suffixes like the .minor in flux-pythonX.Y
                case "$op" in
                    *.in) continue ;;
                    *.py) op="${op%.py}" ;;
                esac
                subcommands+="$op "
            fi
        done
    done

    for builtin in $FLUX_BUILTINS; do
        subcommands+="$builtin "
    done

    echo "$subcommands"
}

# Helper to emit id.f58plain when match begins with `f`
_flux_id_fmt()
{
    local cur=$1
    local fmt="{id}"
    if [[ $cur == f* ]]; then
        fmt="{id.f58plain}"
    fi
    echo "${fmt}"
}

_flux_active_instances()
{
    flux jobs -no "$(_flux_id_fmt $1) {uri}" | awk '!($2 == "None") {print $1}'
}

#  flux-proxy(1) completions
_flux_proxy()
{
    local $cmd=$1
    OPTS="\
        -f --force \
        -n --nohup \
        --reconnect \
    "
    if [[ $cur != -* ]]; then
        #  Attempt to substitute an active jobid
        active_jobs=$(_flux_active_instances $cur)
        COMPREPLY=( $(compgen -W "${active_jobs}" -- "$cur") )
        return 0
    fi
    COMPREPLY=( $(compgen -W "${OPTS} -h --help" -- "$cur") )
    return 0
}

#  flux-cancel(1) completions
_flux_cancel()
{
    local cmd=$1
    local split=false
    OPTS="\
        --all \
        -n --dry-run \
        -q --quiet \
        -u --user= \
        -S --states= \
        -m --message= \
    "
    _flux_split_longopt && split=true
    case $prev in
        --user | -!(-*)u)
            users=$(flux jobs -Ano {username} | uniq)
            COMPREPLY=( $(compgen -W "$users" -- "$cur") )
            return
            ;;
        --states | -!(-*)S)
            states="active depend priority sched run pending running"
            COMPREPLY=( $(compgen -W "$states" -- "$cur") )
            return
            ;;
        --message | -!(-*)m)
            return
            ;;
    esac
    $split && return

    if [[ $cur != -* ]]; then
        #  Attempt to substitute an active jobid
        active_jobs=$(flux jobs -no $(_flux_id_fmt $cur))
        COMPREPLY=( $(compgen -W "${active_jobs}" -- "$cur") )
        return 0
    fi
    COMPREPLY=( $(compgen -W "${OPTS} -h --help" -- "$cur") )
    if [[ "${COMPREPLY[@]}" == *= ]]; then
        # Add space if there is not a '=' in suggestions
        compopt -o nospace
    fi
    return 0
}

#  flux-update(1) completions
_flux_update()
{
    local cmd=$1
    OPTS="\
        -v --verbose \
        --wait \
        -n --dry-run \
    "
    if [[ $cur != -* ]]; then
        #  Attempt to substitute an active jobid
        active_jobs="$(flux jobs -no $(_flux_id_fmt $cur))"
        COMPREPLY=( $(compgen -W "${active_jobs}" -- "$cur") )
        return 0
    fi
    COMPREPLY=( $(compgen -W "${OPTS} -h --help" -- "$cur") )
    if [[ "${COMPREPLY[@]}" == *= ]]; then
        # Add space if there is not a '=' in suggestions
        compopt -o nospace
    fi
    return 0
}

#  flux-hostlist(1) completions
_flux_hostlist()
{
    local cmd=$1 split=false
    OPTS="\
        -e --expand \
        -d --delimiter= \
        -c --count \
        -n --nth= \
        -L --limit= \
        -S --sort \
        -x --exclude= \
        -u --union --unique \
        -i --intersect \
        -m --minus \
        -X --xor \
        -f --fallback \
        -q --quiet \
    "
    _flux_split_longopt && split=true
    case $prev in
        --limit | --exclude | --nth | --delimiter | -!(-*)[lmxn])
            return
            ;;
    esac
    $split && return

    if [[ $cur != -* ]]; then
        # Substitute source name or recent jobid
        jobs="$(flux jobs -ano $(_flux_id_fmt $cur))"
        sources="local instance avail stdin $jobs"
        COMPREPLY=( $(compgen -W "$sources $jobs" -- "$cur") )
        return 0
    fi
    COMPREPLY=( $(compgen -W "${OPTS} -h --help" -- "$cur") )
    if [[ "${COMPREPLY[@]}" == *= ]]; then
        # Add space if there is not a '=' in suggestions
        compopt -o nospace
    fi
    return 0
}

#  flux-shutdown(1) completions
_flux_shutdown()
{
    local subcmd=$1 split=false
    OPTS="\
        --skip-gc \
        --gc \
        --dump= \
        --background \
        --quiet \
        -v --verbose= \
        -y --yes \
        -n --no \
    "
    _flux_split_longopt && split=true
    case $prev in
        --dump)
            COMPREPLY=( $(compgen -f -- "$cur") )
            return
            ;;
    esac
    $split && return

    if [[ $cur != -* ]]; then
        # Substitute recent alloc/batch jobid
        jobs=$(_flux_active_instances $cur)
        COMPREPLY=( $(compgen -W "$jobs" -- "$cur") )
        return 0
    fi
    COMPREPLY=( $(compgen -W "${OPTS} -h --help" -- "$cur") )
    if [[ "${COMPREPLY[@]}" == *= ]]; then
        # Add space if there is not a '=' in suggestions
        compopt -o nospace
    fi
    return 0
}

#  flux-archive(1) completions
_flux_archive()
{
    local cmd=$1
    local subcmds="create remove extract list"
    local split=false

    local create_OPTS="\
        -h --help \
        -n --name= \
        --no-force-primary \
        -C --directory= \
        -v --verbose= \
        --overwrite \
        --append \
        --preserve \
        --mmap \
    "
    local remove_OPTS="\
        -h --help \
        -n --name= \
        --no-force-primary \
        -f --force \
    "
    local extract_OPTS="\
        -h --help \
        -n --name= \
        -v --verbose= \
        -C --directory= \
        --overwrite \
        --waitcreate= \
        --no-force-primary \
        -t --list-only \
    "
    local list_OPTS="\
        -h --help \
        -n --name= \
        --no-force-primary \
        -l --long \
        --raw \
    "
    if [[ $cmd != "archive" ]]; then

        _flux_split_longopt && split=true
        case $prev in
            --directory | -!(-*)C)
                compopt -o filenames
                COMPREPLY=( $(compgen -d -- "$cur") )
                return
                ;;
            -!(-*)[n])
                return
                ;;
        esac
        $split && return

        if [[ $cmd == "create" && $cur != -* ]]; then
            compopt -o default -o bashdefault -o filenames
            COMPREPLY=( $(compgen -f -c -- "$cur") )
            return
        fi

        var="${cmd//-/_}_OPTS"
        COMPREPLY=( $(compgen -W "${!var}" -- "$cur") )
        if [[ "${COMPREPLY[@]}" == *= ]]; then
            # Add space if there is not a '=' in suggestions
            compopt -o nospace
        fi
    else
        COMPREPLY=( $(compgen -W "${subcmds}" -- "$cur") )
    fi
    return 0
}

#  flux-{run,submit,batch,alloc,bulksubmit}(1) completions
_flux_submit_commands()
{
    local subcmds="run submit batch alloc bulksubmit"
    local cmd=$1
    local split=false

    local COMMON_OPTIONS="\
        -q --queue= \
        -B --bank= \
        -t --time-limit= \
        -o --setopt= \
        -S --setattr= \
        --urgency= \
        --job-name= \
        --dependency= \
        --requires= \
        --begin-time= \
        --env= \
        --env-remove= \
        --env-file \
        --rlimit= \
        --input= \
        --output= \
        --error= \
        -u --unbuffered \
        -l --label-io \
        --flags= \
        --dry-run \
        --quiet \
        -h --help \
        --cwd= \
        --signal= \
        --add-file= \
    "
    local SUBMIT_OPTIONS="\
        -N --nodes= \
        -x --exclusive \
        -n --ntasks= \
        -c --cores-per-task= \
        -g --gpus-per-task= \
        --cores= \
        --tasks-per-node= \
        --tasks-per-core= \
        --gpus-per-node= \
        -v --verbose \
        --taskmap= \
    "
    local BATCH_ALLOC_OPTIONS="\
        -n --nslots= \
        -c --cores-per-slot= \
        -g --gpus-per-slot= \
        -N --nodes= \
        --dump= \
        --conf= \
        --broker-opts= \
        -x --exclusive \
    "
    local SUBMITBULK_OPTIONS="\
        --quiet \
        --cc= \
        --bcc= \
        --wait \
        --wait-event= \
        --watch \
        --progress \
        --log= \
        --log-stderr= \
        --jps \
    "
    local BULKSUBMIT_OPTIONS="\
        --shuffle \
        --sep= \
        --define= \
    "
    local RUN_OPTIONS="\
        --wait-event= \
    "
    local BATCH_OPTIONS="\
        --wrap \
        --quiet \
    "
    local ALLOC_OPTIONS="\
        -v --verbose \
        --bg \
    "
    local bulksubmit_OPTS="\
        $COMMON_OPTIONS \
        $SUBMIT_OPTIONS \
        $SUBMITBULK_OPTIONS \
        $BULKSUBMIT_OPTIONS \
    "
    local run_OPTS="\
        $COMMON_OPTIONS \
        $SUBMIT_OPTIONS \
        $RUN_OPTIONS \
    "
    local submit_OPTS="\
        $COMMON_OPTIONS \
        $SUBMIT_OPTIONS \
        $SUBMITBULK_OPTIONS \
    "
    local batch_OPTS="\
        $COMMON_OPTIONS \
        $BATCH_ALLOC_OPTIONS \
        $BATCH_OPTIONS \
    "
    local alloc_OPTS="\
        $COMMON_OPTIONS \
        $BATCH_ALLOC_OPTIONS \
        $ALLOC_OPTIONS \
    "
    _flux_split_longopt && split=true
    case $prev in
        --queue | -!(-*)q)
             _flux_complete_queue
            return
            ;;
        --urgency)
            COMPREPLY=( $(compgen -W "{0..32} hold default expedite" -- "$cur") )
            return
            ;;
        --add-file | --env-file | --input | --output | --error | \
            --log | --log-stderr)
            compopt -o filenames
            COMPREPLY=( $(compgen -f -- "$cur") $(compgen -d -- "$cur") )
            return
            ;;
        --cwd)
            compopt -o filenames
            COMPREPLY=( $(compgen -d -- "$cur") )
            return
            ;;
        --flags)
            COMPREPLY=( $(compgen -W 'waitable debug novalidate' -- "$cur") )
            return
            ;;
        -!(-*)[tNoncg])
            return
            ;;
    esac
    $split && return

    if [[ $cur != -* ]]; then
        compopt -o bashdefault -o default
    fi

    var="${cmd}_OPTS"
    COMPREPLY=( $(compgen -W "${!var}" -- "$cur") )
    if [[ "${COMPREPLY[@]}" == *= ]]; then
        # no space if suggestions ends with '='
        compopt -o nospace
    fi
    return 0
}

# flux-resource(1) completions
_flux_resource()
{
    local subcmds="drain undrain status list R info reload eventlog"
    local cmd=$1
    local split=false
    local states

    local reload_OPTS="\
        -h --help \
        -x --xml \
        -f --force \
    "
    local info_OPTS="\
        -h --help \
        -s --states= \
        -i --include= \
    "
    local list_OPTS="\
        -h --help \
        -o --format= \
        -s --states= \
        -n --no-header \
        -q --queue= \
        -i --include= \
        --skip-empty \
        --no-skip-empty \
    "
    local status_OPTS="\
        ${list_OPTS} \
    "
    local R_OPTS="\
        -h --help \
        -s --states= \
        -q --queue= \
        -i --include= \
    "
    local undrain_OPTS="\
        -h --help \
        -f --force \
    "
    local drain_OPTS="\
        -h --help \
        -f --force \
        -u --update \
        -o --format= \
        -n --no-header \
        -q --queue= \
        -i --include= \
    "
    local eventlog_OPTS="\
        -h --help \
        -f --format= \
        -T --time-format=
        -L --color= \
        -F --follor \
        -w --wait-event= \
    "

    _flux_split_longopt && split=true
    case $prev in
        --queue | -!(-*)q)
            _flux_complete_queue
            return
            ;;
        --format | -!(-*)o | -!(-*)f)
            case $cmd in
                eventlog)
                    COMPREPLY=( $(compgen -W "text json" -- "$cur") )
                    return
                    ;;
                *)
                    # Only eventlog takes -f as format
                    if test "$prev" != "-f"; then
                        _flux_complete_format_name flux resource $cmd
                        return
                    fi
                    ;;
            esac
            ;;
        --states | -!(-*)s)
            case $cmd in
                list | info | R)
                    states="up down allocated free all"
                    ;;
                status | drain)
                    states="avail exclude draining drained drain offline online"
                    ;;
            esac
            COMPREPLY=( $(compgen -W "$states" -- "$cur") )
            return
            ;;
        -!(-*)i)
            return
            ;;
    esac
    $split && return

    if [[ $cmd != "resource" ]]; then
        if [[ $cur != -* ]]; then
            if [[ $cmd == "reload" ]]; then
                compopt -o filenames
                COMPREPLY=( $(compgen -f -- "$cur") $(compgen -d -- "$cur") )
                return
            fi
        fi
        var="${cmd}_OPTS"
        COMPREPLY=( $(compgen -W "${!var}" -- "$cur") )
        if [[ "${COMPREPLY[@]}" == *= ]]; then
            # Add space if there is not a '=' in suggestions
            compopt -o nospace
        fi
    else
        COMPREPLY=( $(compgen -W "${subcmds}" -- "$cur") )
    fi
    return 0
}

# flux-job(1) completions
_flux_job()
{
    local cmd=$1
    local split=false

    local subcmds="\
        urgency \
        cancel \
        raise \
        kill \
        killall \
        attach \
        status \
        id \
        eventlog \
        wait-event \
        info \
        namespace \
        wait \
        memo \
        taskmap \
        timeleft \
        last \
        hostpids \
    "
    local nojob_subcmds="\
        cancelall \
        raiseall \
        stats \
        submit \
        purge \
    "
    local all_OPTS="\
        -h --help \
    "
    local urgency_OPTS="\
        -v --verbose \
    "
    local cancel_OPTS="\
        -m --message= \
    "
    local cancelall_OPTS="\
        -u --user= \
        -S --states= \
        -f --force \
        -q --quiet \
    "
    local raise_OPTS="\
        -s --severity= \
        -t --type= \
        -m --message= \
    "
    local raiseall_OPTS="\
        -s --severity= \
        -u --user= \
        -S --states= \
        -f --force \
    "
    local kill_OPTS="\
        -s --signal= \
    "
    local killall_OPTS="\
        -s --signal= \
        -u --user= \
        -f --force \
    "
    local attach_OPTS="\
        -E --show-events \
        -X --show-exec \
        -w --wait-event= \
        -l --label-io \
        -v --verbose \
        -q --quiet \
        -r --read-only \
        -i --stdin-ranks= \
        -u --unbuffered \
        --debug \
        --tail= \
    "
    local status_OPTS="\
        -v --verbose \
        -j --json \
        -e --exception-exit-code= \
    "
    local submit_OPTS="\
        -u --urgency= \
        -f --flags= \
    "
    local id_OPTS="\
        -t --to= \
    "
    local eventlog_OPTS="\
        -f --format= \
        -T --time-format= \
        -p --path= \
        -H --human \
        -L --color= \
    "
    local wait_event_OPTS="\
        -f --format= \
        -T --time-format= \
        -t --timeout= \
        -m --match-context= \
        -c --count= \
        -q --quiet \
        -v --verbose \
        -p --path= \
        -W --waitcreate \
        -H --human \
        -L --color= \
    "
    local info_OPTS="\
        -o --original \
        -b --base \
    "
    local wait_OPTS="\
        -a --all \
        -v --verbose \
    "
    local memo_OPTS="\
        --volatile
    "
    local taskmap_OPTS="\
        --taskids= \
        --ntasks= \
        --nodeid= \
        --hostname= \
        --to= \
    "
    local purge_OPTS="\
        --age-limit= \
        --num-limit= \
        -f --force \
        --batch= \
    "
    local timeleft_OPTS="\
        -H --human
    "
    local hostpids_OPTS="\
        -d --delimiter= \
        -t --timeout= \
        -r --ranks= \
    "
    local last_OPTS=""

    #  Handle options for all subcommands in one case statement
    _flux_split_longopt && split=true
    case $prev in
        --path | -!(-*)p)
            # wait-event, eventlog -p, --path=
            COMPREPLY=( $(compgen -W "exec output input" -- "$cur") )
            return
            ;;
        --format | -!(-*)f)
            # wait-event, eventlog -f, --format=
            COMPREPLY=( $(compgen -W "text json" -- "$cur") )
            return
            ;;
        --time-format | -!(-*)T)
            # wait-event, eventlog -T, --time-format=
            COMPREPLY=( $(compgen -W "raw iso offset human" -- "$cur") )
            return
            ;;
        --to | -!(-*)t)
            case $cmd in
                id)
                    values="dec kvs hex dothex words f58 f58plain"
                    ;;
                taskmap)
                    values="pmi raw multiline hosts"
                    ;;
                *)
                    return
                    ;;
            esac
            COMPREPLY=( $(compgen -W "$values" -- "$cur") )
            return
            ;;
        --user | -!(-*)u)
            active_users=$(flux jobs -Ano {username} | uniq)
            COMPREPLY=( $(compgen -W "${active_users}" -- "$cur") )
            return
            ;;
        --flags | -!(-*)f)
            COMPREPLY=( $(compgen -W "debug waitable novalidate" -- "$cur") )
            return
            ;;
        -!(-*)[cmsSwiudr])
            return
            ;;
    esac
    $split && return

    if [[ $cmd != "job" ]]; then
        if [[ $cur != -* ]]; then
           if _flux_contains_word ${cmd} ${subcmds}; then
                #  These commands take active jobids by default:
                active_jobs=$(flux jobs -no $(_flux_id_fmt $cur))
                COMPREPLY=( $(compgen -W "${active_jobs}" -- "$cur") )
                return 0
            fi
        fi
        # Don't substitute longopts if already fully completed
        if [[ $cur == --*= ]]; then
            COMPREPLY=()
            return 0
        fi
        var="${cmd//-/_}_OPTS"
        COMPREPLY=( $(compgen -W "${!var}" -- "$cur") )
        if [[ "${COMPREPLY[@]}" == *= ]]; then
            # no space if suggestions ends with '='
            compopt -o nospace
        fi
    else
        COMPREPLY=( $(compgen -W "${subcmds} ${nojob_subcmds}" -- "$cur") )
    fi
    return 0
}

# flux-queue(1) completions
_flux_queue()
{
    local cmd=$1 split=false
    local subcmds="\
        enable \
        disable \
        start \
        stop \
        status \
        drain \
        list \
        idle
    "
    local enable_OPTS="\
        -h --help \
        -q --queue= \
        -a --all \
        --quiet \
        -v --verbose \
    "
    local disable_OPTS="\
        -h --help \
        -q --queue= \
        -a --all \
        --quiet \
        -v --verbose \
        --nocheckpoint \
        -m --message= \
    "
    local start_OPTS="\
        -h --help \
        -q --queue= \
        -a --all \
        -v --verbose \
        --quiet \
    "
    local stop_OPTS="\
        -h --help \
        -q --queue= \
        -a --all \
        -v --verbose \
        --quiet \
        -m --message= \
    "
    local status_OPTS="\
        -h --help \
        -v --verbose \
        -q --queue= \
    "
    local drain_OPTS="\
        -h --help \
        -t --timeout= \
    "
    local idle_OPTS="\
        -h --help \
        -t --timeout= \
        --quiet \
    "
    local list_OPTS="\
        -q --queue= \
        -o --format= \
        -n --no-header \
    "

    _flux_split_longopt && split=true
    case $prev in
        --queue | -!(-*)q)
            _flux_complete_queue
            return
            ;;
        --format | -!(-*)o)
            _flux_complete_format_name flux queue $cmd
            return
            ;;
        --timeout | -!(-*)t)
            return
            ;;
        --message | -!(-*)m)
            return
            ;;
    esac
    $split && return

    if [[ $cmd != "queue" ]]; then
        var="${cmd//-/_}_OPTS"
        COMPREPLY=( $(compgen -W "${!var}" -- "$cur") )
        if [[ "${COMPREPLY[@]}" == *= ]]; then
            # Add space if there is not a '=' in suggestions
            compopt -o nospace
        fi
        # Don't complete longopt if fully completed
        if [[ $cur == --*= ]]; then
            COMPREPLY=()
            return 0
        fi
    else
        COMPREPLY=( $(compgen -W "${subcmds}" -- "$cur") )
    fi
    return 0
}

_flux_complete_namespace() {
    namespaces=$(flux kvs namespace list 2>/dev/null \
                 | awk '!/^NAMESPACE/ {print $1}')
    COMPREPLY=( $(compgen -W "$namespaces" -- "$cur") )
    return
}

# flux-kvs(1) eventlog completions
_flux_kvs_eventlog()
{
    local cmd=$1
    local subcmds="append get wait-event"
    local split=false

    local append_OPTS="\
        -N --namespace= \
        -t --timestamp= \
    "
    local get_OPTS="\
        -N --namespace= \
        -W --waitcreate \
        -w --watch \
        -c --count= \
        -u --unformatted \
        -H --human \
        -L --color= \
        -S --stream \
    "
    local wait_event_OPTS="\
        -N --namespace= \
        -W --waitcreate \
        -u --unformatted \
        -q --quiet \
        -v --verbose \
        -H --human \
        -L --color= \
    "
    _flux_split_longopt && split=true
    case $prev in
        --namespace | -!(-*)N)
            _flux_complete_namespace
            return
            ;;
        -!(-*)[tc])
            return
            ;;
    esac
    $split && return

    if [[ $cmd != "eventlog" ]]; then
        var="${cmd//-/_}_OPTS"
        COMPREPLY=( $(compgen -W "${!var}" -- "$cur") )
        if [[ "${COMPREPLY[@]}" == *= ]]; then
            # Add space if there is not a '=' in suggestions
            compopt -o nospace
        fi
        if [[ "$cur" != -* ]]; then
            compopt -o default
        fi
    else
        COMPREPLY=( $(compgen -W "${subcmds}" -- "$cur") )
    fi
    return 0
}

# flux-kvs(1) completions
_flux_kvs()
{
    local cmd=$1
    local split=false
    local subcmds="\
        namespace
        get
        put
        dir
        ls
        unlink
        link
        readlink
        mkdir
        copy
        move
        version
        wait
        getroot
        eventlog
    "
    get_OPTS="\
        -N --namespace= \
        -r --raw \
        -t --treeobj \
        -a --at= \
        -l --label \
        -W --waitcreate \
        -w --watch \
        -u --uniq \
        -A --append \
        -f --full \
        -c --count= \
        -S --stream \
    "
    put_OPTS="\
        -N --namespace= \
        -O --treeobj-root \
        -b --blobref \
        -s --sequence \
        -r --raw \
        -t --treeobj \
        -n --no-merge \
        -A --append \
        -S --sync \
    "
    dir_OPTS="\
        -N --namespace= \
        -R --recursive \
        -d --directory \
        -w --width= \
        -a --at \
    "
    ls_OPTS="\
        -N --namespace= \
        -R --recursive \
        -d --directory \
        -w --width= \
        -1 --1 \
        -F --classify \
    "
    unlink_OPTS="\
        -N --namespace= \
        -O --treeobj-root \
        -b --blobref \
        -s --sequence \
        -R --recursive \
        -f --force \
    "
    link_OPTS="\
        -N --namespace= \
        -O --treeobj-root \
        -b --blobref \
        -s --sequence \
    "
    readlink_OPTS="\
        -N --namespace= \
        -a --at= \
        -o --namespace-only \
        -k --key-only \
    "
    mkdir_OPTS="\
        -N --namespace= \
        -O --treeobj-root \
        -b --blobref \
        -s --sequence \
    "
    copy_OPTS="\
        -S --src-namespace= \
        -D --dst-namespace= \
    "
    move_OPTS="\
        ${copy_OPTS}
    "
    version_OPTS="\
        -N --namespace= \
    "
    wait_OPTS="\
        -N --namespace= \
    "
    getroot_OPTS="\
        -N --namespace= \
        -s --sequence \
        -o --owner \
        -b --blobref \
    "

    if [[ $cmd != "kvs" ]]; then

        if _flux_contains_word "eventlog" ${COMP_WORDS[*]}; then
            _flux_kvs_eventlog $(_flux_get_subcmd $cmd)
            return 0
        fi

        _flux_split_longopt && split=true
        case $prev in
            --src-namespace | --dst-namespace | --namespace | -!(-*)[SDN])
                _flux_complete_namespace
                return
                ;;
            -!(-*)[awc])
                return
                ;;
        esac
        $split && return

        var="${cmd//-/_}_OPTS"
        COMPREPLY=( $(compgen -W "${!var}" -- "$cur") )
        if [[ "${COMPREPLY[@]}" == *= ]]; then
            # Add space if there is not a '=' in suggestions
            compopt -o nospace
        fi
        # Don't complete longopt if fully completed
        if [[ $cur == --*= ]]; then
            COMPREPLY=()
            return 0
        fi
    else
        COMPREPLY=( $(compgen -W "${subcmds}" -- "$cur") )
    fi
}

# flux-overlay(1) completions
_flux_overlay()
{
    local cmd=$1
    local subcmds="status lookup parentof disconnect trace errors"
    local split=false

    local status_OPTS="\
        -h --help \
        -r --rank= \
        -v --verbose= \
        -t --timeout= \
        --summary \
        --down \
        --no-pretty \
        --no-ghost \
        -L --color= \
        -H --highlight= \
        -w --wait= \
    "
    local lookup_OPTS="\
        -h --help \
    "
    local parentof_OPTS="\
        -h --help \
    "
    local disconnect_OPTS="\
        -h --help \
        -r --parent= \
    "
    local trace_OPTS="\
        -h --help \
        -r --rank= \
        -t --type= \
        -L --color= \
        -H --human \
        -d --delta \
        -f --full \
    "
    local errors_OPTS="\
        -h --help \
        -t --timeout= \
    "
    _flux_split_longopt && split=true
    case $prev in
        --wait | -!(-*)w)
            COMPREPLY=( $(compgen -W 'full partial offline degraded lost' -- "$cur") )
            return
            ;;
        -!(-*)[rtH])
            return
            ;;
    esac
    $split && return

    if [[ $cmd != "overlay" ]]; then
        var="${cmd//-/_}_OPTS"
        COMPREPLY=( $(compgen -W "${!var}" -- "$cur") )
        if [[ "${COMPREPLY[@]}" == *= ]]; then
            # Add space if there is not a '=' in suggestions
            compopt -o nospace
        fi
        # Don't complete longopt if fully completed
        if [[ $cur == --*= ]]; then
            COMPREPLY=()
            return 0
        fi
    else
        COMPREPLY=( $(compgen -W "${subcmds}" -- "$cur") )
    fi
    return 0
}

# flux-config(1) completions
_flux_config()
{
    local cmd=$1
    local subcmds="reload get builtin load"
    local split=false

    local get_OPTS="\
        -t --type= \
        -q --quiet \
        -d --default= \
        -c --config-path \
    "

    if [[ $cmd != "config" ]]; then

        if _flux_contains_word "load" ${COMP_WORDS[*]}; then
            COMPREPLY=( $(compgen -f -- "$cur") $(compgen -d -- "$cur") )
            return 0
        fi

        _flux_split_longopt && split=true
        case $prev in
            --config-path | -!(-*)c)
                compopt -o filenames
                COMPREPLY=( $(compgen -f -- "$cur") $(compgen -d -- "$cur") )
                return
                ;;
            --type | -!(-*)t)
                values="string integer real boolean object array fsd fsd-integer fsd-real"
                COMPREPLY=( $(compgen -W "$values" -- "$cur") )
                return
                ;;
            -!(-*)d)
                return
                ;;
        esac
        $split && return

        COMPREPLY=( $(compgen -W "${!var} -h --help" -- "$cur") )
        if [[ "${COMPREPLY[@]}" == *= ]]; then
            # Add space if there is not a '=' in suggestions
            compopt -o nospace
        fi
    else
        COMPREPLY=( $(compgen -W "${subcmds}" -- "$cur") )
    fi
    return 0
}

# flux-admin(8) completions
_flux_admin()
{
    local cmd=$1
    local subcmds="cleanup-push system-scripts"

    local system_scripts_OPTS="\
        -v \
        --verbose \
        --color= \
    "

    if [[ $cmd != "admin" ]]; then
        var="${cmd//-/_}_OPTS"
        COMPREPLY=( $(compgen -W "${!var} -h --help" -- "$cur") )
        if [[ "${COMPREPLY[@]}" == *= ]]; then
            # Add space if there is not a '=' in suggestions
            compopt -o nospace
        fi
    else
        COMPREPLY=( $(compgen -W "${subcmds}" -- "$cur") )
    fi
    return 0
}

# flux-module(1) completions
_flux_module()
{
    local cmd=$1
    local subcmds_module_arg="remove reload stats debug trace"
    local subcmds="list load ${subcmds_module_arg}"
    local split=false

    local load_OPTS="\
        --name= \
        --exec \
    "
    local remove_OPTS="\
        -f --force \
    "
    local reload_OPTS="\
        ${remove_OPTS} \
        ${load_OPTS} \
    "
    local stats_OPTS="\
        -p --parse= \
        -s --scale=N \
        -t --type= \
        -R --rusage \
        -c --clear \
        -C --clear-all \
    "
    local debug_OPTS="\
        -C --clear \
        -S --set \
        -s --setbit \
        -c --clearbit \
    "
    local list_OPTS="\
        -l --long \
    "
    local trace_OPTS="\
        -h --help \
        -T --topic= \
        -t --type= \
        -L --color= \
        -H --human \
        -d --delta \
        -f --full \
    "
    if [[ $cmd != "module" ]]; then

        _flux_split_longopt && split=true
        case $prev in
            -!(-*)[pst])
                return
                ;;
        esac
        $split && return

        if [[ $cur != -* ]]; then
            if _flux_contains_word ${cmd} ${subcmds_module_arg}; then
                #  These commands take loaded module as args
                modules=$(flux module list | grep -v Module | awk '{print $1}')
                COMPREPLY=( $(compgen -W "${modules}" -- "$cur") )
                return 0
            elif _flux_contains_word ${cmd} "load"; then
                compopt -o default -o bashdefault
            fi
        fi

        var="${cmd//-/_}_OPTS"
        COMPREPLY=( $(compgen -W "${!var} -h --help" -- "$cur") )
        if [[ "${COMPREPLY[@]}" == *= ]]; then
            # Add space if there is not a '=' in suggestions
            compopt -o nospace
        fi
        # Don't complete longopt if fully completed
        if [[ $cur == --*= ]]; then
            COMPREPLY=()
            return 0
        fi
    else
        COMPREPLY=( $(compgen -W "${subcmds}" -- "$cur") )
    fi
    return 0
}

# flux-jobtap(1) completions
_flux_jobtap()
{
    local cmd=$1
    local subcmds_plugin_arg="remove query"
    local subcmds="load list ${subcmds_plugin_arg}"

    load_OPTS="\
        -r --remove= \
    "
    list_OPTS="\
        -a --all \
    "
    if [[ $cmd != "jobtap" ]]; then

        if [[ $cur != -* ]]; then
            if _flux_contains_word ${cmd} ${subcmds_plugin_arg}; then
                #  These commands take loaded plugins as args
                plugins=$(flux jobtap list 2>/dev/null)
                COMPREPLY=( $(compgen -W "${plugins}" -- "$cur") )
                return 0
            elif _flux_contains_word ${cmd} "load"; then
                compopt -o bashdefault -o default
            fi
        fi

        var="${cmd//-/_}_OPTS"
        COMPREPLY=( $(compgen -W "${!var} -h --help" -- "$cur") )
        if [[ "${COMPREPLY[@]}" == *= ]]; then
            # Add space if there is not a '=' in suggestions
            compopt -o nospace
        fi
        # Don't complete longopt if fully completed
        if [[ $cur == --*= ]]; then
            COMPREPLY=()
            return 0
        fi
    else
        COMPREPLY=( $(compgen -W "${subcmds}" -- "$cur") )
    fi
    return 0
}

# flux-jobs(1) completions
_flux_jobs()
{
    local cmd=$1
    local split=false
    local OPTS="\
        -a \
        -A \
        -c --count=N \
        -f --filter= \
        -i --include= \
        --since= \
        -n --no-header \
        -u --user= \
        --name= \
        -q --queue= \
        -o --format= \
        --color= \
        -R --recursive \
        -L --level= \
        --recurse-all \
        --threads= \
        --stats \
        --stats-only \
        --sort= \
        -w --width= \
    "

    _flux_split_longopt && split=true
    case $prev in
        --queue | -!(-*)q)
            _flux_complete_queue
            return
            ;;
        --format | -!(-*)o)
            _flux_complete_format_name flux jobs
            return
            ;;
        --user | -!(-*)u)
            active_users=$(flux jobs -Ano {username} | uniq)
            COMPREPLY=( $(compgen -W "$active_users" -- "$cur") )
            return
            ;;
        --sort | -!(-*)[Lcf])
            return
            ;;
    esac
    $split && return

    # Don't complete longopt if fully completed
    if [[ $cur == --*= ]]; then
        return
    fi
    COMPREPLY=( $(compgen -W "${OPTS} -h --help" -- "$cur") )
    if [[ "${COMPREPLY[@]}" == *= ]]; then
        # Add space if there is not a '=' in suggestions
        compopt -o nospace
    fi
    return 0
}

# flux-pgrep(1) completions
_flux_pgrep()
{
    local cmd=$1
    local split=false
    local OPTS="\
        -a \
        -A \
        -c --count= \
        --max-entries= \
        -f --filter= \
        -n --no-header \
        -u --user= \
        -q --queue= \
        -o --format= \
    "
    _flux_split_longopt && split=true
    case $prev in
        --queue | -!(-*)q)
            _flux_complete_queue
            return
            ;;
        --format | -!(-*)o)
            # flux-prgrep uses flux-jobs named formats
            _flux_complete_format_name flux jobs
            return
            ;;
        --user | -!(-*)u)
            active_users=$(flux jobs -Ano {username} | uniq)
            COMPREPLY=( $(compgen -W "$active_users" -- "$cur") )
            return
            ;;
        -!(-*)[cf])
            return
            ;;
    esac
    $split && return

    COMPREPLY=( $(compgen -W "${OPTS} -h --help" -- "$cur") )
    if [[ "${COMPREPLY[@]}" == *= ]]; then
        # Add space if there is not a '=' in suggestions
        compopt -o nospace
    fi
    return 0
}

# flux-pkill(1) completions
_flux_pkill()
{
    local cmd=$1
    local split=false
    local OPTS="\
        -A \
        -c --count= \
        --max-entries= \
        -f --filter= \
        -u --user= \
        -q --queue= \
        --wait \
    "
    _flux_split_longopt && split=true
    case $prev in
        --queue | -!(-*)q)
            _flux_complete_queue
            return
            ;;
        --user | -!(-*)u)
            active_users=$(flux jobs -Ano {username} | uniq)
            COMPREPLY=( $(compgen -W "$active_users" -- "$cur") )
            return
            ;;
        -!(-*)[cf])
            return
            ;;
    esac
    $split && return

    COMPREPLY=( $(compgen -W "${OPTS} -h --help" -- "$cur") )
    if [[ "${COMPREPLY[@]}" == *= ]]; then
        # Add space if there is not a '=' in suggestions
        compopt -o nospace
    fi
    return 0
}

# flux-pstree(1) completions
_flux_pstree()
{
    local cmd=$1
    local split=false

    local OPTS="\
        -a --all \
        -c --count= \
        -f --filter= \
        -x --extended \
        -l --long \
        -L --level= \
        -p --parent-ids \
        -n --no-header \
        -X --no-combine \
        -o --label= \
        --parent-label= \
        --detail-format= \
        -d --details= \
        -C --compact \
        --ascii \
        --skip-root \
    "

    _flux_split_longopt && split=true
    case $prev in
        --details | -!(-*)d)
            COMPREPLY=( $(compgen -W 'default resources progress stats' -- "$cur") )
            return
            ;;
        -!(-*)[cfLo])
            return
            ;;
    esac
    $split && return

    if [[ $cur != -* ]]; then
        active_jobs=$(flux jobs -no $(_flux_id_fmt $cur))
        COMPREPLY=( $(compgen -W "${active_jobs}" -- "$cur") )
        return 0
    fi
    COMPREPLY=( $(compgen -W "${OPTS} -h --help" -- "$cur") )
    if [[ "${COMPREPLY[@]}" == *= ]]; then
        # Add space if there is not a '=' in suggestions
        compopt -o nospace
    fi
    return 0
}

# flux-watch(1) completions
_flux_watch()
{
    local cmd=$1
    local split=false
    local OPTS="\
        -a --active \
        -A --all \
        -c --count= \
        -f --filter= \
        -l --label-io \
        --since= \
        --progress \
        --jps \
        -v --verbose
    "
    if [[ $cur != -* ]]; then
        #  Attempt to substitute an active jobid
        active_jobs=$(flux jobs -no $(_flux_id_fmt $cur))
        COMPREPLY=( $(compgen -W "${active_jobs}" -- "$cur") )
        return 0
    fi

    _flux_split_longopt && split=true
    case $prev in
        -!(-*)[cf])
            return
            ;;
    esac
    $split && return

    COMPREPLY=( $(compgen -W "${OPTS} -h --help" -- "$cur") )
    if [[ "${COMPREPLY[@]}" == *= ]]; then
        # Add space if there is not a '=' in suggestions
        compopt -o nospace
    fi
    return 0
}

#  flux-dump(1) completions
_flux_dump()
{
    local cmd=$1
    local OPTS="\
        -v --verbose \
        -q --quiet \
        --checkpoint \
        --no-cache \
        --ignore-failed-read \
    "
    compopt -o default -o bashdefault

    COMPREPLY=( $(compgen -W "${OPTS} -h --help" -- "$cur") )
    if [[ "${COMPREPLY[@]}" == *= ]]; then
        # Add space if there is not a '=' in suggestions
        compopt -o nospace
    fi
    return 0
}

#  flux-restore(1) completions
_flux_restore()
{
    local cmd=$1
    local OPTS="\
        -v --verbose \
        -q --quiet \
        --checkpoint \
        --key= \
        --no-cache \
    "
    compopt -o default -o bashdefault
    COMPREPLY=( $(compgen -W "${OPTS} -h --help" -- "$cur") )
    if [[ "${COMPREPLY[@]}" == *= ]]; then
        # Add space if there is not a '=' in suggestions
        compopt -o nospace
    fi
    return 0
}

# flux-top(1) completions
_flux_top()
{
    local cmd=$1
    local split=false
    local OPTS="\
        --color= \
        -q --queue= \
    "
    _flux_split_longopt && split=true
    case $prev in
        --queue | -!(-*)q)
            _flux_complete_queue
            return
            ;;
    esac
    $split && return

    #  flux-top(1) can target jobids that are also instances
    local jobs=$(flux jobs -no {uri}:$(_flux_id_fmt $cur) | grep -v ^None: \
                 | sed -n 's/.*://p')
    COMPREPLY=( $(compgen -W "${OPTS} $jobs" -- "$cur") )
    if [[ "${COMPREPLY[@]}" == *= ]]; then
        # Add space if there is not a '=' in suggestions
        compopt -o nospace
    fi
    return 0
}

# flux-logger(1) completions
_flux_logger()
{
    local severity_levels="emerg alert crit err warning notice info debug"
    OPTS="\
        -s --severity= \
        -n --appname= \
    "
    case "$prev" in
        --severity|-s)
            COMPREPLY=( $(compgen -W "$severity_levels" -- "$cur") )
            return
            ;;
    esac
    if [[ $cur == --severity=* ]]; then
        COMPREPLY=( $(compgen -W "$severity_levels" -- "${cur#--severity=}") )
        COMPREPLY=( "${COMPREPLY[@]/#/--severity=}" )
        return
    fi
    if [[ $cur == --*= ]]; then
        COMPREPLY=()
        return
    fi
    COMPREPLY=( $(compgen -W "${OPTS} -h --help" -- "$cur") )
    if [[ "${COMPREPLY[@]}" == *= ]]; then
        compopt -o nospace
    fi
    return 0
}

# flux-post-job-event(1) completions
_flux_post_job_event()
{
    # Count non-option words after 'post-job-event' to determine position
    local npos=0 i
    for ((i = 1; i < COMP_CWORD; ++i)); do
        [[ ${COMP_WORDS[i]} != -* ]] && ((npos++))
    done
    # npos==1: completing jobid, npos>=2: name or context (no completions)
    if ((npos == 1)); then
        active_jobs=$(flux jobs -no $(_flux_id_fmt $cur))
        COMPREPLY=( $(compgen -W "${active_jobs}" -- "$cur") )
        return 0
    fi
    return 0
}

# flux-keygen(1) completions
_flux_keygen()
{
    OPTS="\
        -n --name= \
        --meta= \
    "
    if [[ $cur == --*= ]]; then
        compopt -o default
        COMPREPLY=()
        return
    fi
    COMPREPLY=( $(compgen -W "${OPTS} -h --help" -- "$cur") )
    if [[ "${COMPREPLY[@]}" == *= ]]; then
        compopt -o nospace
    fi
    return 0
}

# flux-dmesg(1) completions
_flux_dmesg()
{
    local cmd=$1
    OPTS="\
        -C --clear \
        -c --read-clear \
        -f --follow \
        -n --new \
        -H --human \
        -d --delta \
        -L --color= \
    "
    if [[ $cur == --*= ]]; then
        return
    fi
    COMPREPLY=( $(compgen -W "${OPTS} -h --help" -- "$cur") )
    if [[ "${COMPREPLY[@]}" == *= ]]; then
        # Add space if there is not a '=' in suggestions
        compopt -o nospace
    fi
    return 0
}

# flux-cron(1) completions
_flux_cron()
{
    local cmd=$1
    local split=false
    local subcmds="\
        help \
        interval \
        event \
        tab \
        at \
        list \
    "
    local cron_entry_subcmds="\
        dump \
        delete \
        stop \
        start \
        sync \
    "
    local interval_OPTS="\
        -c --count= \
        -N --name= \
        -a --after= \
        -o --options= \
        -E --preserve-env \
        -d --working-dir= \
    "
    local event_OPTS="\
        -n --nth= \
        -a --after= \
        -i --min-interval= \
        -c --count= \
        -o --options= \
        -N --name= \
        -E --preserve-env \
        -d --working-dir= \
    "
    local tab_OPTS="\
        -o --options= \
        -E --preserve-env \
        -d --working-dir= \
    "
    local at_OPTS="\
        ${tab_OPTS} \
    "
    local dump_OPTS="\
        -k --key= \
    "
    local delete_OPTS="\
        -k --kill \
    "
    local sync_OPTS="\
        -d --disable \
        -e --epsilon= \
    "
    if [[ $cmd != "cron" ]]; then

        if [[ $cur != -* ]]; then
            if _flux_contains_word ${cmd} ${cron_entry_subcmds}; then
                #  These commands take cron entries as args
                entries=$(flux cron list 2>/dev/null \
                          | grep -v ID | awk '{print $1}')
                COMPREPLY=( $(compgen -W "${entries}" -- "$cur") )
                return 0
            fi
        fi

        _flux_split_longopt && split=true
        case $prev in
            --working-dir | -!(-*)d)
                compopt -o filenames
                COMPREPLY=( $(compgen -d -- "$cur") )
                return
                ;;
            -!(-*)k)
                [[ $cmd == "dump" ]] && return
                ;;
            -!(-*)[cNaonie])
                return
                ;;
        esac
        $split && return

        var="${cmd//-/_}_OPTS"
        COMPREPLY=( $(compgen -W "${!var} -h --help" -- "$cur") )
        if [[ "${COMPREPLY[@]}" == *= ]]; then
            # Add space if there is not a '=' in suggestions
            compopt -o nospace
        fi
    else
        COMPREPLY=( $(compgen -W "${subcmds} ${cron_entry_subcmds}" -- "$cur") )
    fi
}

# flux-R(1) completions
_flux_R()
{
    local cmd=$1
    local split=false

    local subcmds="\
        encode \
        append \
        diff \
        intersect \
        remap \
        rerank \
        decode \
        verify \
        set-property \
        parse-config \
    "
    local encode_OPTS="\
        -r --ranks= \
        -c --cores= \
        -g --gpus= \
        -H --hosts= \
        -p --property= \
        -l --local \
        -f --xml \
    "
    local decode_OPTS="\
        -s --short \
        -n --nodelist \
        -r --ranks \
        -c --count= \
        -i --include= \
        -x --exclude= \
        -p --properties= \
    "
    if [[ $cmd != "R" ]]; then
        # Don't complete longopt if fully completed
        if [[ $cur == --*= ]]; then
            COMPREPLY=()
            return 0
        fi

        _flux_split_longopt && split=true
        case $prev in
            -!(-*)[ixcgHp])
                return
                ;;
        esac
        $split && return

        var="${cmd//-/_}_OPTS"
        COMPREPLY=( $(compgen -W "${!var} -h --help" -- "$cur") )
        if [[ "${COMPREPLY[@]}" == *= ]]; then
            # Add space if there is not a '=' in suggestions
            compopt -o nospace
        fi
    else
        COMPREPLY=( $(compgen -W "${subcmds}" -- "$cur") )
    fi
}

#  flux-ping(1) completions
_flux_ping()
{
    local cmd=$1
    local split=false
    OPTS="\
        -r --rank= \
        -p --pad= \
        -i --interval= \
        -c --count= \
        -b --batch \
        -u --userid \
    "
    _flux_split_longopt && split=true
    case $prev in
        -!(-*)[rpic])
            return
            ;;
    esac
    $split && return

    COMPREPLY=( $(compgen -W "${OPTS} -h --help" -- "$cur") )
    if [[ "${COMPREPLY[@]}" == *= ]]; then
        # Add space if there is not a '=' in suggestions
        compopt -o nospace
    fi
    return 0
}

# flux-exec(1) completions
_flux_exec()
{
    local cmd=$1
    local split=false
    OPTS="\
        -r --rank= \
        -x --exclude= \
        -d --dir= \
        -l --label-io \
        -n --noinput \
        -v --verbose \
        -q --quiet \
        --label= \
        --bg \
        --waitable \
        --with-imp \
        -j --jobid= \
    "

    _flux_split_longopt && split=true
    case $prev in
        --jobid | -!(-*)j)
            active_jobs=$(flux jobs -no $(_flux_id_fmt $cur))
            COMPREPLY=( $(compgen -W "${active_jobs}" -- "$cur") )
            return
            ;;
        --dir | -!(-*)d)
            compopt -o filenames
            COMPREPLY=( $(compgen -d -- "$cur") )
            return
            ;;
        -!(-*)[rx])
            return
            ;;
    esac
    $split && return

    COMPREPLY=( $(compgen -W "${OPTS} -h --help" -- "$cur") )
    if [[ "${COMPREPLY[@]}" == *= ]]; then
        # Add space if there is not a '=' in suggestions
        compopt -o nospace
    else
        # Reset to default completions for path/executable
        compopt -o bashdefault -o default
    fi
    return 0
}

# flux-getattr(1), flux-setattr(1), flux-lsattr(1) completions
_flux_attr()
{
    local cmd=$1
    local lsattr_OPTS="\
        -v --values \
    "
    if [[ $cmd != "flux" ]]; then
        if [[ $cur != -* && ( $cmd == "getattr" || $cmd == "setattr" ) ]]; then
            COMPREPLY=( $(compgen -W "$(flux lsattr)" -- "$cur") )
            return 0
        fi
        var="${cmd//-/_}_OPTS"
        COMPREPLY=( $(compgen -W "${!var} -h --help" -- "$cur") )
    else
        COMPREPLY=( $(compgen -W "${subcmds}" -- "$cur") )
    fi
}

# flux-content(1) completions
_flux_content()
{
    local cmd=$1
    local subcmds="load store flush dropcache"
    load_OPTS="\
        -b --bypass-cache \
    "
    store_OPTS="\
        -b --bypass-cache \
        --chunksize=
    "
    if [[ $cmd != "content" ]]; then
        var="${cmd//-/_}_OPTS"
        COMPREPLY=( $(compgen -W "${!var} -h --help" -- "$cur") )
    else
        COMPREPLY=( $(compgen -W "${subcmds}" -- "$cur") )
    fi
}

# flux-start(1) completions
_flux_start()
{
    local cmd=$1
    local split=false

    #  Most test-only options left off on purpose here
    OPTS="\
        -v --verbose \
        -X --noexec \
        -o --broker-opts= \
        --wrap= \
        -s --test-size= \
        -c --config-path = \
        --conf= \
        -S --setattr= \
    "
    _flux_split_longopt && split=true
    case $prev in
        -!(-*)[osS])
            return
            ;;
        --config-path | -!(-*)c)
            compopt -o filenames
            COMPREPLY=( $(compgen -f -- "$cur") $(compgen -d -- "$cur") )
            return
            ;;
    esac
    $split && return

    if [[ $cur != -* ]]; then
        compopt -o default -o bashdefault -o filenames
        COMPREPLY=( $(compgen -f -c -- "$cur") )
        return
    fi
    COMPREPLY=( $(compgen -W "${OPTS} -h --help" -- "$cur") )
    if [[ "${COMPREPLY[@]}" == *= ]]; then
        # Add space if there is not a '=' in suggestions
        compopt -o nospace
    fi
    return 0
}

# flux-broker(1) completions
_flux_broker()
{
    local cmd=$1
    local split=false

    OPTS="\
        -h --help \
        -v --verbose= \
        -S --setattr= \
        -c --config-path= \
        --conf= \
    "
    _flux_split_longopt && split=true
    case $prev in
        --config-path | -!(-*)c)
            compopt -o filenames
            COMPREPLY=( $(compgen -f -- "$cur") $(compgen -d -- "$cur") )
            return
            ;;
        -!(-*)[S])
            return
            ;;
    esac
    $split && return

    if [[ $cur != -* ]]; then
        compopt -o default -o bashdefault -o filenames
        COMPREPLY=( $(compgen -f -c -- "$cur") )
        return
    fi
    COMPREPLY=( $(compgen -W "${OPTS} -h --help" -- "$cur") )
    if [[ "${COMPREPLY[@]}" == *= ]]; then
        # Add space if there is not a '=' in suggestions
        compopt -o nospace
    fi
    return 0
}

# flux-housekeeping(1) completions
_flux_housekeeping()
{
    local cmd=$1
    local subcmds="list kill"
    local split=false
    list_OPTS="\
        -i --include= \
        -o --format= \
        -n --no-header \
    "
    kill_OPTS="\
        -s --signal= \
        -t --targets= \
    "
    _flux_split_longopt && split=true
    case $prev in
        --format | -!(-*)o)
            _flux_complete_format_name flux housekeeping list
            return
            ;;
    esac
    if [[ $cmd != "housekeeping" ]]; then
        if [[ $cur != -* ]]; then
            if _flux_contains_word ${cmd} "kill"; then
                hk_active=$(flux housekeeping list -no $(_flux_id_fmt $cur))
                COMPREPLY=( $(compgen -W "${hk_active}" -- "$cur") )
                return 0
            fi
        fi
        var="${cmd//-/_}_OPTS"
        COMPREPLY=( $(compgen -W "${!var}" -- "$cur") )
        if [[ "${COMPREPLY[@]}" == *= ]]; then
            # no space if suggestions ends with '='
            compopt -o nospace
        fi
    else
        COMPREPLY=( $(compgen -W "${subcmds}" -- "$cur") )
    fi
}

# flux-uri(1) completions
_flux_uri()
{
    local cmd=$1
    local split=false
    local OPTS="\
        --remote \
        --local \
        --wait
    "
    if [[ $cur != -* ]]; then
        #  Attempt to substitute an active jobid
        active_jobs=$(flux jobs -no $(_flux_id_fmt $cur))
        COMPREPLY=( $(compgen -W "${active_jobs}" -- "$cur") )
        return 0
    fi

    COMPREPLY=( $(compgen -W "${OPTS} -h --help" -- "$cur") )
    if [[ "${COMPREPLY[@]}" == *= ]]; then
        # Add space if there is not a '=' in suggestions
        compopt -o nospace
    fi
    return 0
}

# flux-modprobe(1) completions
_flux_modprobe()
{
    local cmd=$1
    local subcmds="load remove run rc1 rc3 list-dependencies show"
    local split=false
    load_OPTS="\
        --dry-run \
    "
    remove_OPTS="\
        --dry-run \
    "
    run_OPTS="\
        -v --verbose \
        --show-deps \
        --dry-run \
        --confdir= \
    "
    rc1_OPTS="\
        -v --verbose \
        --show-deps \
        --dry-run \
        --confdir= \
        --timing \
    "
    rc3_OPTS="\
        -v --verbose \
        --show-deps \
        --dry-run \
        --confdir= \
    "
    list_dependencies_OPTS="\
        -S, --set-alternative= \
        -D, --disable= \
        -f, --full \
    "
    show_OPTS="\
        -S, --set-alternative= \
        -D, --disable= \
    "

    _flux_split_longopt && split=true
    if [[ $cmd != "modprobe" ]]; then
        if [[ $cur != -* ]]; then
            if _flux_contains_word ${cmd} "remove"; then
                modules=$(flux module list | grep -v Module | awk '{print $1}')
                COMPREPLY=( $(compgen -W "all ${modules}" -- "$cur") )
                return 0
            fi
        fi
        var="${cmd//-/_}_OPTS"
        COMPREPLY=( $(compgen -W "${!var}" -- "$cur") )
        if [[ "${COMPREPLY[@]}" == *= ]]; then
            # no space if suggestions ends with '='
            compopt -o nospace
        fi
    else
        COMPREPLY=( $(compgen -W "${subcmds}" -- "$cur") )
    fi
}

# flux-sproc(1) completions
_flux_sproc()
{
    local cmd=$1
    local subcmds="ps kill wait"
    local split=false
    common_OPTS="\
        -r --rank= \
        -s --service= \
    "
    ps_OPTS="\
        $common_OPTS \
        -o --format= \
        -n --no-header \
    "
    kill_OPTS="\
        $common_OPTS \
        -w --wait \
    "
    wait_OPTS="\
        $common_OPTS
    "

    _flux_split_longopt && split=true
    if [[ $cmd != "sproc" ]]; then
        # Don't substitute longopts if already fully completed
        if [[ $cur == --*= ]]; then
            COMPREPLY=()
            return 0
        fi
        var="${cmd//-/_}_OPTS"
        COMPREPLY=( $(compgen -W "${!var}" -- "$cur") )
        if [[ "${COMPREPLY[@]}" == *= ]]; then
            # no space if suggestions ends with '='
            compopt -o nospace
        fi
    else
        COMPREPLY=( $(compgen -W "${subcmds}" -- "$cur") )
    fi
    return 0
}

# flux-fsck(1) completions
_flux_fsck()
{
    local cmd=$1
    local split=false
    local OPTS="\
        -v --verbose \
        -q --quiet \
        -r --rootref= \
        -R --repair \
        -j --job-aware \
    "
    _flux_split_longopt && split=true
    case $prev in
        --rootref | -!(-*)r)
            return
            ;;
    esac
    $split && return

    COMPREPLY=( $(compgen -W "${OPTS} -h --help" -- "$cur") )
    if [[ "${COMPREPLY[@]}" == *= ]]; then
        compopt -o nospace
    fi
    return 0
}

# flux-startlog(1) completions
_flux_startlog()
{
    local cmd=$1
    local OPTS="\
        --check \
        --quiet \
        -v --show-version \
    "
    COMPREPLY=( $(compgen -W "${OPTS} -h --help" -- "$cur") )
    return 0
}

# flux-pmi(1) completions
_flux_pmi()
{
    local cmd=$1
    local subcmds="barrier get exchange"
    local split=false

    local general_OPTS="\
        --method= \
        --libpmi-noflux \
        --libpmi2-cray \
        -v --verbose= \
        -t --timeout= \
    "
    local barrier_OPTS="\
        --test-count= \
        --test-abort= \
        --test-timing \
    "
    local get_OPTS="\
        --ranks= \
    "
    local exchange_OPTS="\
        --count= \
    "

    _flux_split_longopt && split=true
    case $prev in
        --method | --timeout | --test-count | --test-abort | --count | \
            --ranks | -!(-*)[vt])
            return
            ;;
    esac
    $split && return

    if [[ $cur == --*= ]]; then
        COMPREPLY=()
        return 0
    fi
    if [[ $cmd != "pmi" ]]; then
        var="${cmd//-/_}_OPTS"
        COMPREPLY=( $(compgen -W "${!var} -h --help" -- "$cur") )
    else
        COMPREPLY=( $(compgen -W "${general_OPTS} ${subcmds} -h --help" -- "$cur") )
    fi
    if [[ "${COMPREPLY[@]}" == *= ]]; then
        compopt -o nospace
    fi
    return 0
}

# flux-pythonX.Y(1) and flux-python-version(1) completions
# Both are versioned python wrappers that accept --get-path
_flux_python3()
{
    local split=false
    _flux_split_longopt && split=true
    case $prev in
        --get-path) return ;;
    esac
    $split && return
    if [[ $cur != -* ]]; then
        compopt -o default -o bashdefault
        return 0
    fi
    COMPREPLY=( $(compgen -W "--get-path -h --help" -- "$cur") )
    return 0
}

_flux_core()
{
    FLUX_OPTS="-v --verbose -V --version -p --parent -r --root"
    local cur prev cmd subcmd matched
    local cmds=$(__get_flux_subcommands)
    cmd=$(_flux_get_cmd)
    subcmd=$(_flux_get_subcmd $cmd)

    #  If available, use bash-completion _get_comp_words_by_ref()
    #  This better handles not splitting on `=` for long options.
    if type _get_comp_words_by_ref >/dev/null 2>&1; then
        _get_comp_words_by_ref -n = cur prev
    else
        cur=${COMP_WORDS[COMP_CWORD]}
        prev=${COMP_WORDS[COMP_CWORD-1]}
    fi

    matched=t
    case "${cmd}" in
    submit|run|alloc|batch|bulksubmit)
        _flux_submit_commands $cmd
        ;;
    proxy)
        _flux_proxy $subcmd
        ;;
    cancel)
        _flux_cancel $subcmd
        ;;
    resource)
        _flux_resource $subcmd
        ;;
    job)
        _flux_job $subcmd
        ;;
    kvs)
        _flux_kvs $subcmd
        ;;
    queue)
        _flux_queue $subcmd
        ;;
    overlay)
        _flux_overlay $subcmd
        ;;
    config)
        _flux_config $subcmd
        ;;
    admin)
        _flux_admin $subcmd
        ;;
    module)
        _flux_module $subcmd
        ;;
    jobtap)
        _flux_jobtap $subcmd
        ;;
    jobs)
        _flux_jobs $subcmd
        ;;
    pgrep)
        _flux_pgrep $subcmd
        ;;
    pkill)
        _flux_pkill $subcmd
        ;;
    pstree)
        _flux_pstree $subcmd
        ;;
    dump)
        _flux_dump $subcmd
        ;;
    restore)
        _flux_restore $subcmd
        ;;
    top)
        _flux_top $subcmd
        ;;
    dmesg)
        _flux_dmesg $subcmd
        ;;
    keygen)
        _flux_keygen $subcmd
        ;;
    logger)
        _flux_logger $subcmd
        ;;
    post-job-event)
        _flux_post_job_event
        ;;
    fsck)
        _flux_fsck $subcmd
        ;;
    startlog)
        _flux_startlog $subcmd
        ;;
    pmi)
        _flux_pmi $subcmd
        ;;
    ping)
        _flux_ping $subcmd
        ;;
    exec)
        _flux_exec $subcmd
        ;;
    R)
        _flux_R $subcmd
        ;;
    cron)
        _flux_cron $subcmd
        ;;
    *attr)
        _flux_attr $subcmd
        ;;
    content)
        _flux_content $subcmd
        ;;
    start)
        _flux_start $subcmd
        ;;
    watch)
        _flux_watch $subcmd
        ;;
    update)
        _flux_update $subcmd
        ;;
    hostlist)
        _flux_hostlist $subcmd
        ;;
    shutdown)
        _flux_shutdown $subcmd
        ;;
    archive)
        _flux_archive $subcmd
        ;;
    broker)
        _flux_broker $subcmd
        ;;
    python)
        # reset to defaults and let readline take over
        compopt -o default
        COMPREPLY=()
        ;;
    python[0-9]*.[0-9]*|python-version)
        _flux_python3 $subcmd
        ;;
    housekeeping)
        _flux_housekeeping $subcmd
        ;;
    uri)
        _flux_uri $subcmd
        ;;
    modprobe)
        _flux_modprobe $subcmd
        ;;
    sproc)
        _flux_sproc $subcmd
        ;;
    help)
        COMPREPLY=( $(compgen -W "${cmds}" -- "$cur") )
        ;;
    *)
        # return with no suggestions for unknown subcommands:
        [[ "$prev" != "flux" && "$prev" != -* ]] && return

        matched=f
        COMPREPLY=( $(compgen -W "${FLUX_OPTS} ${cmds}" -- "$cur") )
        ;;
    esac

    # If there was an exact command match, and current == command and
    # there are no other completion possibilities, return it so it gets
    # completed by a tab, e.g. `flux top<tab>` adds a space: `flux top `.
    if test "$matched" = "t" -a -z "$COMPREPLY" -a "$cur" = "$cmd"; then
        COMPREPLY="$cur"
    fi

    return 0
}

complete -F _flux_core flux

# vi: ts=4 sw=4 expandtab
FLUX_BUILTINS=archive:cgroup:config:content:dmesg:dump:env:fsck:heaptrace:help:imp_exec_helper:lptest:overlay:pmi:proxy:python:relay:restore:shutdown:startlog:uptime:version:setattr:getattr:lsattr:
