# gpaste-client(1) completion                              -*- shell-script -*-
#
# SPDX-FileCopyrightText: 2010-2026 Marc-Antoine Perennou <Marc-Antoine@Perennou.com>
# SPDX-License-Identifier: BSD-2-Clause

# Is a daemon already there to answer us? Completion must never be what
# activates one: D-Bus would start it behind the user's back and block the shell
# until it finished loading the history.
_gpaste_client_daemon_running()
{
    gdbus call --session --dest org.freedesktop.DBus \
                         --object-path /org/freedesktop/DBus \
                         --method org.freedesktop.DBus.NameHasOwner org.gnome.GPaste 2>/dev/null \
        | grep -q true
}

# One file per history, named <history>.<extension>. Listing them off disk keeps
# history completion working (and instant) with no daemon running at all.
_gpaste_client_histories()
{
    local dir=${XDG_DATA_HOME:-${HOME}/.local/share}
    local f name

    # The daemon stores under PACKAGE_NAME ("GPaste") when that directory
    # already exists, and under PACKAGE ("gpaste") otherwise.
    if [[ -d ${dir}/GPaste ]]; then
        dir=${dir}/GPaste
    else
        dir=${dir}/gpaste
    fi

    # De-duplicate in the shell rather than forking sort(1): the same history
    # can have a file per flavour, but this is the one completion that promises
    # to be instant because it asks no daemon, and a fork per tab is not that.
    local -A seen=()

    for f in "${dir}"/*.xml "${dir}"/*.xmls "${dir}"/*.db "${dir}"/*.dbs; do
        [[ -f ${f} ]] || continue
        name=${f##*/}
        name=${name%.*}
        [[ -v seen[${name}] ]] && continue
        seen[${name}]=1
        printf '%s\n' "${name}"
    done
}

# Both of these ask the daemon for the history and keep only its first column.
# gpaste-client reads stdin whenever it is not a tty (that is how "foo |
# gpaste-client" works), so feed it /dev/null: completion is not always run with
# a terminal on stdin, and it would otherwise block waiting for EOF.
_gpaste_client_uuids()
{
    _gpaste_client_daemon_running || return

    gpaste-client history --oneline </dev/null 2>/dev/null | cut -d: -f1
}

# With --use-index the item arguments are indexes into the history, not uuids.
_gpaste_client_indexes()
{
    _gpaste_client_daemon_running || return

    gpaste-client history --oneline --use-index </dev/null 2>/dev/null | cut -d: -f1
}

_gpaste_client_options()
{
    local common="--help -h"

    case ${1} in
        # No verb at all: the history is printed, and --version is still to come.
        '')
            printf '%s' "${common} --version -v --oneline -o --raw -r --reverse -e --zero -z --use-index -i"
            ;;
        history|h|search)
            printf '%s' "${common} --oneline -o --raw -r --reverse -e --zero -z --use-index -i --favourites -f"
            ;;
        get|g)
            printf '%s' "${common} --use-index -i"
            ;;
        select|set|s|delete|del|d|remove|rm|replace|upload|u|favourite|fav|unfavourite|unfav)
            printf '%s' "${common} --use-index -i"
            ;;
        make-password|mp)
            printf '%s' "${common} --use-index -i --timeout -t"
            ;;
        add-password|ap)
            printf '%s' "${common} --timeout -t"
            ;;
        merge|m)
            printf '%s' "${common} --decoration -d --separator -s"
            ;;
        *)
            printf '%s' "${common}"
            ;;
    esac
}

_gpaste-client()
{
    local cur prev verb items
    local -i i argpos=0 use_index=0

    cur=${COMP_WORDS[COMP_CWORD]}
    prev=${COMP_WORDS[COMP_CWORD-1]}
    COMPREPLY=()

    # Both take a free-form value we have no way to guess.
    case ${prev} in
        -d|--decoration|-s|--separator|-t|--timeout)
            return
            ;;
    esac

    # Options may appear anywhere (getopt_long permutes), so walk the whole line:
    # the first bare word is the verb, the ones after it are its arguments, and
    # @argpos ends up being the index of the argument now being completed.
    for ((i = 1; i < COMP_CWORD; ++i)); do
        case ${COMP_WORDS[i]} in
            -d|--decoration|-s|--separator|-t|--timeout)
                ((++i))
                ;;
            -i|--use-index)
                use_index=1
                ;;
            -*)
                ;;
            *)
                if [[ -z ${verb} ]]; then
                    verb=${COMP_WORDS[i]}
                else
                    ((++argpos))
                fi
                ;;
        esac
    done

    if [[ ${cur} == -* ]]; then
        COMPREPLY=($(compgen -W "$(_gpaste_client_options "${verb}")" -- "${cur}"))
        return
    fi

    if [[ -z ${verb} ]]; then
        # Every verb the dispatch table accepts, aliases included: the three
        # completions have to agree on what the CLI takes, and zsh and fish
        # offer the short forms too.
        local verbs="a about add add-password ap backup-history bh change-passphrase d
                     daemon daemon-reexec daemon-version del delete delete-history
                     delete-password dh dp dr dv e empty f fav favourite file g get
                     get-history gh h help history history-size hs lh list-histories m
                     make-password merge migrate mp p preferences q quit remove replace
                     rm s search select set settings sh show-history start stop
                     switch-history u ui unfav unfavourite upload v version"

        COMPREPLY=($(compgen -W "${verbs}" -- "${cur}"))
        return
    fi

    # Which of the two an item argument is spelled as depends on --use-index.
    if ((use_index)); then
        items=_gpaste_client_indexes
    else
        items=_gpaste_client_uuids
    fi

    case ${verb} in
        empty|e|delete-history|dh|switch-history|sh|backup-history|bh)
            # backup-history takes <history> <backup name>, the history being
            # optional; either way only its first argument names a history.
            ((argpos == 0)) && COMPREPLY=($(compgen -W "$(_gpaste_client_histories)" -- "${cur}"))
            ;;
        file|f)
            COMPREPLY=($(compgen -f -- "${cur}"))
            compopt -o filenames 2>/dev/null
            ;;
        get|g|select|set|s|delete|del|d|remove|rm|upload|u|replace|make-password|mp|favourite|fav|unfavourite|unfav)
            ((argpos == 0)) && COMPREPLY=($(compgen -W "$(${items})" -- "${cur}"))
            ;;
        merge|m)
            COMPREPLY=($(compgen -W "$(${items})" -- "${cur}"))
            ;;
    esac
}

complete -F _gpaste-client gpaste-client
