#!/bin/bash
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
[[ -z ${ARTOOLS_INCLUDE_VERSION_SETUP_SH:-} ]] || return 0
ARTOOLS_INCLUDE_VERSION_SETUP_SH=1

# LIBDIR=${LIBDIR:-/usr/share/artools}
# source "${LIBDIR}"/base/message.sh

# shellcheck source=src/lib/pkg/version/check.sh
source "${LIBDIR}"/pkg/version/check.sh

# source /usr/share/makepkg/util/message.sh
source /usr/share/makepkg/util/source.sh

set -eo pipefail


artixpkg_version_setup_usage() {
    local -r COMMAND=${_ARTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}}
    cat <<- _EOF_
    Usage: ${COMMAND} [OPTIONS] [PKGBASE]...

    Automate the creation of a basic nvchecker configuration file by
    analyzing the source array specified in the PKGBUILD file of a package.

    If no PKGBASE is specified, the command defaults to using the current
    working directory.

    OPTIONS
        -f, --force            Overwrite existing nvchecker configuration
        --prefer-platform-api  Prefer platform specific GitHub/GitLab API for complex cases
        --url URL              Derive check target from URL instead of source array
        --no-check             Do not run version check after setup
        --no-upstream          Setup a blank config for packages without upstream sources
        -h, --help             Show this help text

    EXAMPLES
        $ ${COMMAND} neovim vim
_EOF_
}

artixpkg_version_setup() {
    local pkgbases=()
    local override_url=
    local run_check=1
    local force=0
    local prefer_platform_api=0
    local no_upstream=0

    local path ret
    local checks=()

    while (( $# )); do
        case $1 in
            -h|--help)
                artixpkg_version_setup_usage
                exit 0
            ;;
            -f|--force)
                force=1
                shift
            ;;
            --prefer-platform-api)
                prefer_platform_api=1
                shift
            ;;
            --url)
                (( $# <= 1 )) && die "missing argument for %s" "$1"
                override_url=$2
                shift 2
            ;;
            --no-check)
                run_check=0
                shift
            ;;
            --no-upstream)
                no_upstream=1
                shift
            ;;
            --)
                shift
                break
            ;;
            -*)
                die "invalid argument: %s" "$1"
            ;;
            *)
                pkgbases=("$@")
                break
            ;;
        esac
    done

    # Check if used without pkgbases in a packaging directory
    if (( ${#pkgbases[@]} == 0 )); then
        if [[ -f PKGBUILD ]]; then
            pkgbases=(".")
        else
            artixpkg_version_setup_usage
            exit 1
        fi
    fi

    ret=0
    for path in "${pkgbases[@]}"; do
        # skip paths that are not directories
        if [[ ! -d "${path}" ]]; then
            continue
        fi

        pushd "${path}" >/dev/null
        if nvchecker_setup "${path}" "${force}" "${prefer_platform_api}" "${override_url}" "${no_upstream}"; then
            checks+=("${path}")
        else
            ret=1
        fi
        popd >/dev/null
    done

    # run checks on the setup targets
    if (( run_check )) && (( ${#checks[@]} >= 1 )); then
        echo
        artixpkg_version_check --verbose "${checks[@]}" || true
    fi

    return $ret
}

nvchecker_setup() {
    local path=$1
    local force=$2
    local prefer_platform_api=$3
    local override_url=$4
    local no_upstream=$5
    local pkgbase pkgname source source_url proto domain url_parts section body

    if [[ ! -f PKGBUILD ]]; then
        msg_error "${BOLD}${path}:${ALL_OFF} no PKGBUILD found"
        return 1
    fi

    unset body pkgbase pkgname source url
    # shellcheck source=contrib/makepkg/PKGBUILD.proto
    if ! . ./PKGBUILD; then
        msg_error "${BOLD}${path}:${ALL_OFF} failed to source PKGBUILD"
        return 1
    fi
    pkgbase=${pkgbase:-$pkgname}

    # try to guess from url as last try
    if [[ -n ${url} ]]; then
        source+=("${url}")
    fi

    # handle overwrite of existing config
    if [[ -f .nvchecker.toml ]] && (( ! force )); then
        msg_warn "${BOLD}${pkgbase}:${ALL_OFF} nvchecker already configured"
        return 1
    fi

    # override the source array with a passed URL
    if [[ -n ${override_url} ]]; then
        source=("${override_url}")
    fi

    # skip empty source array
    if (( ${#source[@]} == 0 )) && (( ! no_upstream )); then
        msg_error "${BOLD}${pkgbase}:${ALL_OFF} PKGBUILD has no source array"
        return 1
    fi

    for source_url in "${source[@]}"; do
    # Strips out filename::http for example
    source_url=$(get_url "${source_url}")
    # discard query fragments
    source_url=${source_url%\?*}
    source_url=${source_url%#*}

    # skip patches
    if [[ ${source_url} == *.patch ]]; then
        continue
    fi
    # skip signatures
    if [[ ${source_url} == *.asc ]] || [[ ${source_url} == *.sig ]]; then
        continue
    fi
    # skip local files
    if [[ ${source_url} != *://* ]]; then
        continue
    fi

    # split URL segments while avoiding empty element after protocol and newline at the end
    mapfile -td / url_parts <<< "${source_url/:\/\//\/}/"
    unset "url_parts[-1]"

    # extract protocol and domain to select the configuration type
    proto=${url_parts[0]}
    domain=${url_parts[1]}

        case "${domain}" in
            gitlab.*)
                if (( prefer_platform_api )); then
                    body=$(nvchecker_setup_gitlab "${url_parts[@]}")
                else
                    body=$(nvchecker_setup_git "${url_parts[@]}")
                fi
                break
            ;;
            github.com)
                if (( prefer_platform_api )); then
                    body=$(nvchecker_setup_github "${url_parts[@]}")
                else
                    body=$(nvchecker_setup_git "${url_parts[@]}")
                fi
                break
            ;;
            codeberg.org)
                body=$(nvchecker_setup_git "${url_parts[@]}")
                break
            ;;
            pypi.org|pypi.io|files.pythonhosted.org)
                body=$(nvchecker_setup_pypi "${url_parts[@]}")
                break
            ;;
            hackage.haskell.org)
                body=$(nvchecker_setup_hackage "${url_parts[@]}")
                break
            ;;
            registry.npmjs.org|npmjs.com|www.npmjs.com)
                body=$(nvchecker_setup_npm "${url_parts[@]}")
                break
            ;;
            rubygems.org)
                body=$(nvchecker_setup_rubygems "${url_parts[@]}")
                break
            ;;
            *.cpan.org|*.mcpan.org|*.metacpan.org)
                body=$(nvchecker_setup_cpan "${url_parts[@]}")
                break
            ;;
            crates.io|*.crates.io)
                body=$(nvchecker_setup_crates_io "${url_parts[@]}")
                break
            ;;
            *)
                if [[ ${proto} == git ]] || [[ ${proto} == git+https ]]; then
                    body=$(nvchecker_setup_git "${url_parts[@]}")
                fi
            ;;
        esac
    done

    if (( no_upstream )); then
        body='source = "manual"'
    fi

    if [[ -z "${body}" ]]; then
        msg_error "${BOLD}${pkgbase}:${ALL_OFF} unable to automatically setup nvchecker"
        return 1
    fi

    # escape the section if it contains toml subsection chars
    section="${pkgbase}"
    if [[ ${section} == *.* ]] || [[ ${section} == *+* ]]; then
        section="\"${section}\""
    fi

    msg_success "${BOLD}${pkgbase}:${ALL_OFF} successfully configured nvchecker"
	cat > .nvchecker.toml << EOF
[${section}]
${body}
EOF
}

join_by() {
    local IFS="	"
    local sep=$1
    local split
    shift
    split=$(printf "%s" "$*")
    echo "${split//${IFS}/"${sep}"}"
}

get_git_url_from_parts() {
    local url_parts=("$@")
    local proto=${url_parts[0]#*+}
    local domain=${url_parts[1]}
    local url
    url="${proto}://$(join_by / "${url_parts[@]:1}")"

    case "${domain}" in
        gitlab.*)
            url=${url%/-/*/*}
            [[ ${url} != *.git ]] && url+=.git
        ;;
        github.com|codeberg.org)
            url="${proto}://$(join_by / "${url_parts[@]:1:3}")"
            [[ ${url} != *.git ]] && url+=.git
        ;;
    esac

    printf '%s' "${url}"
}

# PyPI
#
# As Arch python packages don't necessarily match the pypi name, when the
# provided source url comes from pypi.io or pypi.org try to extract the package
# name from the (predictable) tarball download url for example:
#
# https://pypi.io/packages/source/p/pyflakes/pyflakes-3.1.0.tar.gz
# https://pypi.io/packages/source/p/pyflakes
# https://pypi.org/packages/source/b/bleach
# https://files.pythonhosted.org/packages/source/p/pyflakes
# https://pypi.org/project/SQLAlchemy/
nvchecker_setup_pypi() {
    local url_parts=("$@")
    local pypi

    if [[ ${url_parts[2]} == packages ]]; then
        pypi=${url_parts[5]}
    elif [[ ${url_parts[2]} == project ]]; then
        pypi=${url_parts[3]}
    else
        return 1
    fi

    cat << EOF
source = "pypi"
pypi = "${pypi}"
EOF
}

# Git
#
# Set up a generic Git source, while removing the proto specific part from makepkg
#
# git+https://github.com/prometheus/prometheus.git
# https://git.foobar.com/some/path/group/project.git
# https://gitlab.com/sub/group/project/-/archive/8.0.0/packages-8.0.0.tar.gz
nvchecker_setup_git() {
    local url_parts=("$@")
    local url
    url=$(get_git_url_from_parts "${url_parts[@]}")

    cat << EOF
source = "git"
git = "${url}"
EOF

    # best effort check if the tags are prefixed with v
    if git_tags_have_version_prefix "${url}"; then
        echo 'prefix = "v"'
    fi
}

git_tags_have_version_prefix() {
    local url=$1
    # best effort check if the tags are prefixed with v
    if ! grep --max-count=1 --quiet --extended-regex 'refs/tags/v[0-9]+[\.0-9]*$' \
        <(GIT_TERMINAL_PROMPT=0 git ls-remote --quiet --tags "${url}" 2>/dev/null); then
        return 1
    fi
    return 0
}

# Github
#
# We want to know the $org/$project name from the url
#
# https://github.com/prometheus/prometheus/archive/v2.49.1.tar.gz
nvchecker_setup_github() {
    local url_parts=("$@")
    local url project
    if ! url=$(get_git_url_from_parts "${url_parts[@]}"); then
        return 1
    fi
    project=${url#*://*/}
    project=${project%.git}

    cat << EOF
source = "github"
github = "${project}"
use_max_tag = true
EOF

    # best effort check if the tags are prefixed with v
    if git_tags_have_version_prefix "${url}"; then
        echo 'prefix = "v"'
    fi
}

# GitLab
#
# We want to know the $org/$project name from the url
#
# git+https://gitlab.com/inkscape/inkscape.git#tag=091e20ef0f204eb40ecde54436e1ef934a03d894
nvchecker_setup_gitlab() {
    local url_parts=("$@")
    local url project host
    if ! url=$(get_git_url_from_parts "${url_parts[@]}"); then
    return 1
    fi
    project=${url#*://*/}
    project=${project%.git}
    cat << EOF
source = "gitlab"
gitlab = "${project}"
EOF

    host=${url#*://}
    host=${host%%/*}
    if [[ ${host} != gitlab.com ]]; then
        echo "host = \"${host}\""
    fi

    echo "use_max_tag = true"

    # best effort check if the tags are prefixed with v
    if git_tags_have_version_prefix "${url}"; then
        echo 'prefix = "v"'
    fi
}

# Hackage
#
# We want to know the project name
#
# https://hackage.haskell.org/package/xmonad
# https://hackage.haskell.org/package/xmonad-0.18.0/xmonad-0.18.0.tar.gz
# https://hackage.haskell.org/packages/archive/digits/0.3.1/digits-0.3.1.tar.gz
nvchecker_setup_hackage() {
    local url_parts=("$@")
    local hackage

    if [[ ${url_parts[2]} == packages ]]; then
        hackage=${url_parts[4]}
    elif [[ ${url_parts[2]} == package ]] && (( ${#url_parts[@]} == 4 )); then
        hackage=${url_parts[3]}
    elif [[ ${url_parts[2]} == package ]] && (( ${#url_parts[@]} >= 5 )); then
        hackage=${url_parts[3]%-*}
    else
        return 1
    fi

    cat << EOF
source = "hackage"
hackage = "${hackage}"
EOF
}

# NPM
#
# We want to know the project name
#
# https://registry.npmjs.org/eslint_d/-/eslint_d-12.1.0.tgz
# https://www.npmjs.com/package/node-gyp
nvchecker_setup_npm() {
    local url_parts=("$@")
    local npm

    if [[ ${url_parts[1]} == registry.npmjs.org ]]; then
        npm=${url_parts[2]}
    elif [[ ${url_parts[2]} == package ]] && (( ${#url_parts[@]} == 4 )); then
        npm=${url_parts[3]}
    else
        return 1
    fi

    cat << EOF
source = "npm"
npm = "${npm}"
EOF
}

# RubyGems
#
# We want to know the project name
#
# https://rubygems.org/downloads/polyglot-0.3.5.gem
# https://rubygems.org/gems/diff-lcs
nvchecker_setup_rubygems() {
    local url_parts=("$@")
    local gem

    if [[ ${url_parts[2]} == downloads ]]; then
        gem=${url_parts[-1]%-*}
    elif [[ ${url_parts[2]} == gems ]]; then
        gem=${url_parts[3]}
    else
        return 1
    fi

    cat << EOF
source = "gems"
gems = "${gem}"
EOF
}

# CPAN
#
# We want to know the project name
#
# source = https://search.cpan.org/CPAN/authors/id/C/CO/COSIMO/Locale-PO-1.2.3.tar.gz
    nvchecker_setup_cpan() {
    local url_parts=("$@")
    local cpan=${url_parts[-1]}
    cpan=${cpan%-*}

    cat << EOF
source = "cpan"
cpan = "${cpan}"
EOF
}

# crates.io
#
# We want to know the crate name
#
# https://crates.io/api/v1/crates/${pkgname}/${pkgver}/download
# https://static.crates.io/crates/${pkgname}/$pkgname-$pkgver.crate
# https://crates.io/crates/git-smash
nvchecker_setup_crates_io() {
    local url_parts=("$@")
    local crate

    if [[ ${url_parts[2]} == crates ]]; then
        crate=${url_parts[3]}
    elif [[ ${url_parts[4]} == crates ]]; then
        crate=${url_parts[5]}
    else
        return 1
    fi


    for i in "${!url_parts[@]}"; do
        if [[ ${url_parts[i]} == crates ]]; then
            crate=${url_parts[(( i + 1 ))]}
        fi
    done

    cat << EOF
source = "cratesio"
cratesio = "${crate}"
EOF
}
