#!/bin/sh

set -eu

GITHUB_BASE="${GITHUB_BASE:-cockpit-project/cockpit}"
GITHUB_REPOSITORY="${GITHUB_BASE%/*}/node-cache"
HTTPS_REMOTE="https://github.com/${GITHUB_REPOSITORY}"

CACHE_DIR="${XDG_CACHE_HOME-${HOME}/.cache}/cockpit-dev/${GITHUB_REPOSITORY}.git"

message() {
    [ "${V-}" != 0 ] || printf "  %-8s %s\n" "$1" "$2"
}

get_index_gitlink() {
    if ! git ls-files -s "$1" | egrep -o '\<[[:xdigit:]]{40}\>'; then
        echo "*** couldn't read gitlink for file $1 from the index" >&2
        exit 1
    fi
}

cmd_remove() {
    # if we did this for ourselves the rm is enough, but it might be the case
    # that someone actually used git-submodule to fetch this, so clean up after
    # that as well.  NB: deinit nicely recreates the empty directory for us.
    message REMOVE node_modules
    rm -rf node_modules
    git submodule deinit node_modules
    rm -rf "$(git rev-parse --absolute-git-dir)/modules/node_modules"
}

cmd_checkout() {
    # we default to check out the node_modules corresponding to the gitlink in the index
    local sha="${1-$(get_index_gitlink node_modules)}"
    local tag="sha-${sha}"

    if [ ! -d "${CACHE_DIR}" ]; then
        message INIT "${CACHE_DIR}"
        mkdir -p "${CACHE_DIR}"
        git init --bare ${quiet} "${CACHE_DIR}"
        git --git-dir "${CACHE_DIR}" remote add origin "${HTTPS_REMOTE}"
    fi

    # fetch the tag if we don't already have it
    if ! git --git-dir "${CACHE_DIR}" rev-parse --verify --quiet "${tag}" >/dev/null; then
        message FETCH "node_modules  [sha: ${sha}]"
        # fetch by sha to prevent us from downloading something we don't want
        git --git-dir "${CACHE_DIR}" fetch ${quiet} origin --no-tags "${sha}"
        # create the tag locally for ourselves
        git --git-dir "${CACHE_DIR}" tag "sha-${sha}" "${sha}"
    fi

    # paranoid: double check that the tag is what it claims to be
    test "$(git --git-dir "${CACHE_DIR}" rev-parse "${tag}")" = "${sha}"

    # verify that our package.json is equal to the one the cached node_modules
    # was created with, unless --force is given
    if [ "${1-}" != "--force" ]; then
        local our_package_json_sha="$(cat package.json | sha256sum)"
        local their_package_json_sha="$(git --git-dir "${CACHE_DIR}" cat-file -p "${sha}":.package.json | sha256sum)"
        if [ "${our_package_json_sha}" != "${their_package_json_sha}" ]; then
            cat >&2 <<EOF

*** node_modules ${sha} doesn't match our package.json
*** refusing to automatically check out node_modules

Options:

    - tools/node-modules checkout --force     # disable this check

    - tools/node-modules rebuild              # npm install with our package.json

$0: *** aborting

EOF
            exit 1
        fi
    fi

    # we're actually going to do this; let's remove the old one
    cmd_remove

    # and check out the new one
    # we need to use the tag name here, unfortunately
    message UNPACK "node_modules  [sha: ${sha}]"
    git -c advice.detachedHead=false clone ${quiet} "${CACHE_DIR}" -b "${tag}" node_modules
}

cmd_rebuild() {
    for file in npm-shrinkwrap.json package-lock.json yarn.lock; do
        if [ -e "${top_srcdir}/${file}" ]; then
            echo "Please delete ${file} from ${top_srcdir} and start again"
            exit 1
        fi
    done

    cmd_remove

    npm install

    # try to keep this vaguely in sync with what .github/workflows/node_modules-* do
    cp package.json node_modules/.package.json
    cp package-lock.json node_modules/.package-lock.json
    echo /.cache > node_modules/.gitignore

    git -C node_modules init -b main
    git -C node_modules add .
    git -C node_modules -c gc.autoDetach=false commit -a -m "$(sha256sum package.json)"

    cat <<EOF
Next steps:

  - git add node_modules && git commit

EOF
}

# called from tools/test-static-code
cmd_test_static_code() {
    node_modules_update=$(git log --format=%H -n 1 node_modules)
    if ! git --no-pager log --stat --exit-code "${node_modules_update}".. package.json; then
        cat <<EOF
#
# *** The above commits modify package.json without updating node_modules
#
# Try running:
#
#   tools/node-modules rebuild
#
EOF
        exit 1
    fi
}

# called from Makefile.am
cmd_make_package_lock_json() {
    # Run from make to ensure package-lock.json is up to date

    # package-lock.json is used as the stamp file for all things that use
    # node_modules (mostly webpack), so this is the main bit of glue that
    # drives the entire process

    # We try our best not to touch package-lock.json unless it actually changes

    # This isn't going to work for a tarball, but as long as
    # package-lock.json is already there, and newer than package.json,
    # we're OK
    if [ ! -e .git ]; then
        if [ package-lock.json -nt package.json ]; then
            exit 0
        fi

        echo "*** Can't update node modules unless running from git" >&2
        exit 1
    fi

    # Otherwise, our main goal is to ensure that the node_modules from
    # the index is the one that we actually have.
    local sha="$(get_index_gitlink node_modules)"
    if [ ! -e node_modules/.git ]; then
        # nothing there yet...
        cmd_checkout
    elif [ "$(git -C node_modules rev-parse HEAD)" != "${sha}" ]; then
        # wrong thing there...
        cmd_checkout
    fi

    # This check is more about catching local changes to package.json than
    # about validating something we just checked out:
    if ! cmp -s node_modules/.package.json package.json; then
        cat 2>&1 <<EOF
*** package.json is out of sync with node_modules
*** If you modified package.json, please run:
***
***    tools/node-modules rebuild
***
*** and add the result to the index.
EOF
        exit 1
    fi

    # Only copy the package-lock.json if it differs from the one we have
    if ! cmp -s node_modules/.package-lock.json package-lock.json; then
        message COPY package-lock.json
        cp node_modules/.package-lock.json package-lock.json
    fi

    # We're now in a situation where:
    #  - the checked out node_modules is equal to the gitlink in the index
    #  - the package.json in the tree is equal to the one in node_modules
    #  - ditto package-lock.json
    exit 0
}

main() {
    local top_srcdir="$(realpath -m "$0"/../..)"
    cd "${top_srcdir}"

    local cmd="${1-}"
    local quiet=''

    if [ -z "${cmd}" ]; then
        # don't list the "private" ones
        echo 'This command requires a subcommand: remove checkout rebuild'
        exit 1
    elif ! type -t "cmd_${cmd}" | grep -q function; then
        echo "Unknown subcommand ${cmd}"
        exit 1
    fi

    shift
    [ "${V-}" != 0 ] || quiet='--quiet'
    [ "${V-}" = 0 ] || set -x
    "cmd_$cmd" "$@"
}

main "$@"
