# Display general usage
function __to_usage
    echo 'Usage:'
    echo ' to (BOOKMARK|DIR)         Go to BOOKMARK or DIR'
    echo ' to add [BOOKMARK] [DEST]  Create a BOOKMARK for DEST'
    echo '                             Default BOOKMARK: name of current directory'
    echo '                             Default DEST: path to current directory'
    echo ' to add DEST               Create a bookmark for DEST if it is a directory'
    echo ' to ls                     List all bookmarks'
    echo ' to mv OLD NEW             Change the name of a bookmark from OLD to NEW'
    echo ' to rm BOOKMARK            Remove BOOKMARK'
    echo ' to clean                  Remove bookmarks that have a missing destination'
    echo ' to resolve BOOKMARK       Print the destination of a bookmark'
    echo ' to help                   Show this message'
    echo
    echo "Bookmarks are stored in: $TO_DIR"
    echo 'To change, run: set -U TO_DIR <dir>'
    return 1
end

# https://github.com/fish-shell/fish-shell/issues/6173#issuecomment-1067114363
function is_empty_dir
    test -d $argv
    or return 1 # not a directory, so not an empty directory
    # count counts how many arguments it received
    # if this glob doesn't match, it won't get arguments
    # and so it will return 1
    # because we *want* an empty directory, turn that around.
    # the `{.*,*}` ensures it matches hidden files as well.
    not count $argv/{.*,*} >/dev/null
end

function __to_dir
    if test -n $TO_DIR
        echo $TO_DIR
        return
    end

    set -l dir

    if test -d $HOME/.tofish && not is_empty_dir $HOME/.tofish
        set dir $HOME/.tofish
    else if test -n $XDG_DATA_HOME
        set dir $XDG_DATA_HOME/to-fish
    else
        set dir $HOME/.local/share/to-fish
    end

    set -U TO_DIR $dir
    echo $TO_DIR
end

function __to_bm_path
    echo (__to_dir)/$argv
end

function __to_resolve
    readlink (__to_bm_path $argv) 2>/dev/null
end

function __to_print
    __to_resolve $argv | string replace -r "^$HOME" '~' | string replace -r '^~$' $HOME
end

function __to_ls
    for l in (__to_dir)/*
        test -L $l; or continue
        basename $l
    end
end

function __to_rm
    command rm -v (__to_bm_path $argv[1]); or return $status
    __to_update_bookmark_completions
end

function __to_add -a bm dest
    # if there are no arguments
    if test -z $bm
        # use the current directory
        set dest (pwd)
        set bm (basename $dest)
    else
        # if there are two arguments
        if test -n $dest
            # use them as bookmark name and destination
            set dest (realpath $dest)

            # if there is only one argument
        else

            # if the argument is a directory
            if string match -q '*/*' $bm && test -d $bm
                # use it as the destination
                set dest (realpath $bm)
                set bm (basename $dest)
            else
                # if not a directory
                # use it as the bookmark name
                set dest (pwd)
            end
        end
    end

    if __to_resolve $bm >/dev/null
        echo "ERROR: Bookmark exists: $bm -> "(__to_print $bm) >&2
        return 1
    end

    if not test -d $dest
        echo "ERROR: Destination does not exist: $dest" >&2
        return 1
    end

    if string match -q '*/*' $bm
        echo "ERROR: Bookmark name cannot contain '/': $bm" >&2
        return 1
    end

    switch (uname)
        case Darwin
            command ln -s $dest (__to_bm_path $bm); or return $status
        case '*'
            command ln -sT $dest (__to_bm_path $bm); or return $status
    end

    echo $bm "->" (__to_print $bm)

    __to_update_bookmark_completions
end

function __to_complete_directories
    set -l cl (commandline -ct | string split -m 1 /)
    set -l bm $cl[1]
    set -l bmdir (__to_resolve $bm)
    if test -z $bmdir
        __fish_complete_directories
    else
        set -e cl[1]
        if test -z $cl
            __fish_complete_directories $bmdir/ | string replace -r 'Directory$' $bm
        else
            __fish_complete_directories $bmdir/$cl | string replace -r 'Directory$' $bm
        end
    end
end

function __to_update_bookmark_completions
    complete -e -c to
    complete -c to -k -x -s h -l help -d 'Show help'

    # Subcommands
    complete -c to -k -n __fish_use_subcommand -f -a help -d 'Show help'
    complete -c to -k -n __fish_use_subcommand -x -a resolve -d 'Print bookmark destination'
    complete -c to -k -n __fish_use_subcommand -x -a clean -d 'Remove bad bookmarks'
    complete -c to -k -n __fish_use_subcommand -x -a mv -d 'Rename bookmark'
    complete -c to -k -n __fish_use_subcommand -x -a rm -d 'Remove bookmark'
    complete -c to -k -n __fish_use_subcommand -f -a ls -d 'List bookmarks'
    complete -c to -k -n __fish_use_subcommand -x -a add -d 'Create bookmark'

    # Directories
    complete -c to -k -n __fish_use_subcommand -r -a '(__to_complete_directories)'

    # Bookmarks
    for bm in (__to_ls | sort -r)
        if test -z $bm
            continue
        end
        set -l desc (__to_print $bm)
        if test -z $desc
            set desc '(broken)'
        end

        complete -c to -k -n '__fish_use_subcommand; or __fish_seen_subcommand_from rm mv resolve' -r -a (echo $bm | string escape) -d $desc
    end
end

function to -d 'Bookmarking tool'
    set -l dir (__to_dir)

    # Create tofish directory
    if not test -d $dir
        if command mkdir $dir
            echo "Created bookmark directory: $dir"
        else
            echo "Failed to Create bookmark directory: $dir"
            return 1
        end
    end

    # Catch usage errors
    set -l cmd $argv[1]
    set -l numargs (count $argv)
    switch $cmd
        # subcommands that don't take an argument
        case ls help clean
            if not test $numargs -eq 1
                echo "Usage: to $cmd"
                return 1
            end

            # subcommands that require an argument
        case rm resolve
            if not test $numargs -eq 2
                echo "Usage: to $cmd BOOKMARK"
                return 1
            end

            # add has 2 optional arguments
        case add
            if not test $numargs -ge 1 -a $numargs -le 3
                echo 'Usage: to add [BOOKMARK] [DEST]'
                echo '       to add DEST'
                return 1
            end

            # subcommands that require 2 arguments
        case mv
            if not test $numargs -eq 3
                echo 'Usage: to mv OLD NEW'
                return 1
            end
    end

    switch $cmd
        # Add a bookmark
        case add
            __to_add $argv[2..-1]
            return $status

            # Remove a bookmark
        case rm
            __to_rm $argv[2]
            return $status

            # List all bookmarks
        case ls
            for bm in (__to_ls)
                echo "$bm -> "(__to_print $bm)
            end
            return 0

            # Rename a bookmark
        case mv
            set -l old $argv[2]
            if not __to_resolve $old >/dev/null
                echo "ERROR: Bookmark not found: $old"
                return 1
            end

            set -l new $argv[3]
            __to_add $new (__to_resolve $old); or return $status
            __to_rm $old; or return $status

            return 0

            # Clean
        case clean
            for bm in (__to_ls)
                if not test -d (__to_resolve $bm)
                    __to_rm $bm
                end
            end
            return 0

            # Resolve
        case resolve
            __to_resolve $argv[2]
            return $status

            # Help
        case -h --help help
            __to_usage
            return 0

            # Default
        case '*'
            set -l name $argv[1]
            if test -z $name
                __to_usage
                return 1
            end

            set -l dest (__to_resolve $name)
            if test -z $dest
                if test -d $name
                    echo cd (string escape $name) | source -
                else
                    echo "to: No such bookmark “$name”" >&2
                    return 1
                end
            else if test -d $dest
                echo cd (string escape $dest) | source -
            else
                echo "to: Destination for bookmark “$name” does not exist: $dest" >&2
                return 1
            end
    end
end
