# shellcheck    shell=dash

xrc:mod:lib pkg     sphere/gc/recycle   sphere/gc/remove    sphere/gc/safelist


# x env gc --rm python
# x env gc python

# x env gc # interactive

# Autogc:
# 1. exe long unused:
# Provide user a list of package that is : vert old-version + unused:
# Calculating the size of the packages suggested to be removed.

# 1. Remove some packages long unused
# 2. Select some packages to remove

# x env unuse python
# x env unuse java
# x env gc python java

___x_cmd_pkg_sphere_gc(){
    local op="$1"
    case "$op" in
        # Inner function
        --run|--safelist|--exist|--remove|--recycle)
                    shift; ___x_cmd_pkg_sphere_gc_"${op#--}" "$@" ;;
        -h|--help)  ___x_cmd help -m pkg gc; return     ;;
        *)          ___x_cmd_pkg_sphere_gc_run "$@"     ;;
        # *)          N=pkg M="sphere gc not such option '$op'" log:ret:64
    esac
}

___x_cmd_pkg_sphere_gc_safelist(){
    [ "$#" -gt 0 ] || set -- -h
    local op="$1"; shift
    case "$op" in
        add)
            ___x_cmd_pkg_sphere_safelist add --reason "use,gc-safelist" "$@"
            ;;
        ls|exist|rm)
            ___x_cmd_pkg_sphere_safelist "$op" "$@"
            ;;
        *)  N=pkg M="gc safelist not such option '$op'" log:ret:64 ;;
    esac
}

___x_cmd_pkg_sphere_gc_exist(){
    pkg:sphere:parse:option
    local gcfp="$___X_CMD_PKG_ROOT_SPHERE/$sphere_name/.x-cmd/gc/Start"
    [ -f "$gcfp" ] || return 1
    # ! ___x_cmd fsiter --dirempty "$gcdir"
    ! ___x_cmd_pkg_sphere_gc___defuselock "$gcfp"
}


___x_cmd_pkg_sphere_gc___defuselock(){
    local gcfp="$1"
    [ -f "$gcfp" ] || return $?

    local bombpid;
    local fingerprint;
    {
        ___x_cmd_readr bombpid
        ___x_cmd_readr fingerprint
    } < "$gcfp"

    if ___x_cmd_ps fingerprint check "$bombpid" "$fingerprint"; then
        pkg:warn "There is another process [PID=$bombpid] running gc"
        return 1
    else
        ___x_cmd rmrf "$gcfp"
    fi
}

xrc ps
___x_cmd_pkg_sphere_gc_run(){
    local XPKG_SPHERE_GC_RUN_PKG_FORCE_RECYCLE="${XPKG_SPHERE_GC_RUN_PKG_FORCE_RECYCLE}"
    local sphere_name=; local osarch=
    while [ "$#" -gt 0 ]; do
        case "$1" in
            --sphere)
                    sphere_name="$2";
                    [ -n "$sphere_name" ] || M="Provide a sphere name" N=pkg log:ret:1
                    shift 2
                    ;;
            --sphereroot)
                    local ___X_CMD_PKG_ROOT_SPHERE="$2"
                    [ -n "$___X_CMD_PKG_ROOT_SPHERE" ] || M="Provide sphere root path" N=pkg log:ret:1
                    shift 2
                    ;;
            --osarch)
                    osarch="$2"; shift 2 ;;
            --force-recycle|--force)
                    shift; XPKG_SPHERE_GC_RUN_PKG_FORCE_RECYCLE=1 ;;
            -h|--help)
                    help:show:ret:0 ;;
            *)      break ;;
        esac
    done
    [ -n "$osarch" ] || { ___x_cmd_pkg_osarch_ || return 1; osarch="$___X_CMD_PKG_OSARCH"; } || N=pkg M="Not found osarch" log:ret:1
    sphere_name="${sphere_name:-"X"}"


    pkg:info "Initiating garbage collection for [sphere=$sphere_name]"

    local gcfp="$___X_CMD_PKG_ROOT_SPHERE/$sphere_name/.x-cmd/gc/Start"
    local gclist="$___X_CMD_PKG_ROOT_SPHERE/$sphere_name/.x-cmd/gc/list"

    (  # Must be a subshell
        trap '___x_cmd rmrf "$gcfp";' EXIT

        if [ -f "$gcfp" ]; then
            ___x_cmd_pkg_sphere_gc___defuselock "$gcfp" || return $?
        fi

        ___x_cmd ensurefp "$gcfp";
        ___x_cmd rmrf "$gclist"

        local x_=;
        ___x_cmd pidofsubshell_;        local spid="$x_"
        ___x_cmd_ps fingerprint get_;   local fingerprint="$x_"
        printf "%s\n" "$spid" "$fingerprint" >"$gcfp"

        ___x_cmd_compat_syncfs "$gcfp"
        sleep 1 # long enough to make sure file written

        local winner_pid
        ___x_cmd_readr winner_pid <"$gcfp";

        if [ ! "$winner_pid" = "$spid" ]; then
            pkg:info "Abort because other process [PID=$winner_pid] win the gc lock."
            return
        fi

        if ___x_cmd_is_stdout2tty && ___x_cmd_runmode_allow_chatty; then
            if [ "$#" -le 0 ]; then ___x_cmd_pkg_sphere_gc_run___interactive_all || return $?
            else                    ___x_cmd_pkg_sphere_gc_run___interactive_pkg "$@" || return $?
            fi
        fi
        ___x_cmd_pkg_sphere_gc_run___inner "$gcfp" "$gclist" || return $?
    ) || return $?

    [ -f "$gclist" ] || return 0
    local name
    local version
    while ___x_cmd_readr name && ___x_cmd_readr version; do
        ___x_cmd_pkg_sphere_path rm --sphere "$sphere_name" --sphereroot "$___X_CMD_PKG_ROOT_SPHERE" \
            --osarch "$osarch" "$name" "$version"
    done < "$gclist"
    ___x_cmd rmrf "$gclist"
}

# shellcheck disable=2120
___x_cmd_pkg_sphere_gc_run___interactive_all(){
    local l=; local osarch=; local name=; local version=;
    local list; list="$( ___x_cmd_pkg_sphere_safelist___ls_human --sphere "$sphere_name" --sphereroot "$___X_CMD_PKG_ROOT_SPHERE" )"
    if [ -n "$list" ]; then
        local x_=; ___x_cmd storerat_ ___x_cmd pick --width 65 --col 3 --limit no-limit --ask "Using Tab to select the pkg you want to remove from the safelist." printf "%s\n" "$list"
        printf "%s\n" "$x_" | while ___x_cmd_readr l; do
            [ -n "$l" ] || continue
            [ "${l%%" ("*}" = "$l" ] || l="${l%%" ("*}"
            osarch="${l%% *}"; l="${l#* }"
            name="${l%% *}"; l="${l#* }"
            version="${l% *}"

            ___x_cmd_pkg_sphere_gc_run___interactive_unit_ || return $?
        done
    fi
}

___x_cmd_pkg_sphere_gc_run___interactive_pkg(){
    [ "$#" -gt 0 ] || N=pkg M="Please provide the pkg name you want to delete" log:ret:64
    local l=; local osarch=; local name=; local version=;
    local data; data="$( ___x_cmd_pkg_sphere_safelist___parse_arg "$@" )"
    [ -n "$data" ] || {
        pkg:info "Not found $* from safelist"
        return
    }
    printf "%s\n" "$data" | while ___x_cmd_readr l; do
        name="${l%%/*}"; l="${l#*/}"
        version="${l%%/*}";
        osarch="${l#*/}"

        ___x_cmd_pkg_sphere_gc_run___interactive_unit_ || return $?
    done
}

___x_cmd_pkg_sphere_gc_run___interactive_unit_(){
    local force_recycle="${XPKG_SPHERE_GC_RUN_PKG_FORCE_RECYCLE}"

    if ___x_cmd_pkg_sphere_link exist       --sphere "$sphere_name" --sphereroot "$___X_CMD_PKG_ROOT_SPHERE"  \
        --osarch "$osarch" "$name" "$version" && \
        ___x_cmd_pkg_sphere_safelist exist  --sphere "$sphere_name" --sphereroot "$___X_CMD_PKG_ROOT_SPHERE"  \
        --osarch "$osarch" "${name}=${version}"; then
        if [ "$force_recycle" = 1 ]; then
            pkg:info "Force unuse [name=$name] [version=$version] [osarch=$osarch] and remove it from the safelist in [sphere=$sphere_name]"
        else
            ___x_cmd ui yesno "Do you want to unuse [name=$name] [version=$version] [osarch=$osarch] and remove it from the safelist in [sphere=$sphere_name]?" || return $?
        fi

        pkg:debug "Unlinked [name=$name] [version=$version] [osarch=$osarch]"
        ___x_cmd_pkg_sphere_link rm     \
            --sphere "$sphere_name" --sphereroot "$___X_CMD_PKG_ROOT_SPHERE" \
            --osarch "$osarch" "$name" "$version" || return $?

        pkg:debug "Removed safelist [name=$name] [version=$version] [osarch=$osarch]"
        ___x_cmd_pkg_sphere_safelist rm     \
            --sphere "$sphere_name" --sphereroot "$___X_CMD_PKG_ROOT_SPHERE" \
            --osarch "$osarch" "${name}=${version}"
        return
    fi

    if ___x_cmd_pkg_sphere_link exist       --sphere "$sphere_name" --sphereroot "$___X_CMD_PKG_ROOT_SPHERE"  \
        --osarch "$osarch" "$name" "$version"; then
        if [ "$force_recycle" = 1 ]; then
            pkg:info "Force unuse [name=$name] [version=$version] [osarch=$osarch]"
        else
            ___x_cmd ui yesno "Do you want to unuse [name=$name] [version=$version] [osarch=$osarch]?" || return $?
        fi

        pkg:debug "Unlinked [name=$name] [version=$version] [osarch=$osarch]"
        ___x_cmd_pkg_sphere_link rm     \
            --sphere "$sphere_name" --sphereroot "$___X_CMD_PKG_ROOT_SPHERE" \
            --osarch "$osarch" "$name" "$version" || return $?
    fi

    if ___x_cmd_pkg_sphere_safelist exist   --sphere "$sphere_name" --sphereroot "$___X_CMD_PKG_ROOT_SPHERE"  \
        --osarch "$osarch" "${name}=${version}"; then
        if [ "$force_recycle" = 1 ]; then
            pkg:info "Force remove [name=$name] [version=$version] from the safelist in [sphere=$sphere_name]"
        else
            ___x_cmd ui yesno "Do you want to remove [name=$name] [version=$version] from the safelist in [sphere=$sphere_name]?" || return $?
        fi

        pkg:debug "Removed safelist [name=$name] [version=$version] [osarch=$osarch]"
        ___x_cmd_pkg_sphere_safelist rm     \
            --sphere "$sphere_name" --sphereroot "$___X_CMD_PKG_ROOT_SPHERE" \
            --osarch "$osarch" "${name}=${version}"
    fi

    ___x_cmd_pkg_sphere_gc_recycle___set_confirmed "$osarch" "$name" "$version"
}

# TODO: A = ALL packages
# TODO: B = islink + safelist + DEPEENCYLIST
# TODO: C = candidates = A - B
___x_cmd_pkg_sphere_gc_run___inner(){
    local gcfp="$1"
    local gclist="$2"
    local gc_loop_continue=1; local l=; local name=; local version=;
    while [ -n "$gc_loop_continue" ]; do
        gc_loop_continue=

        while ___x_cmd_readr l; do
            [ -n "$l" ] || continue
            name="${l%%/*}"
            version="${l#*/}"

            ___x_cmd_pkg_sphere_gc_recycle \
                --sphere "$sphere_name" --sphereroot "$___X_CMD_PKG_ROOT_SPHERE" \
                --osarch "$osarch" "$name" "$version" || continue

            pkg:info "gc ==> Trying to remove $name $version"
            ___x_cmd_pkg_sphere_gc_remove silent \
                --sphere "$sphere_name" --sphereroot "$___X_CMD_PKG_ROOT_SPHERE" \
                --osarch "$osarch" "$name" "$version" "$gcfp" || {
                pkg:warn "gc ==> failed to remove $name $version"
                continue
            }

            ___x_cmd ensurefp "$gclist"
            printf "%s\n" "$name" "$version" >> "$gclist"

            gc_loop_continue=1
        done <<A
$(___x_cmd_pkg_ls___installed)
A
    done
}
