#!/bin/bash

# Pre-flight checker for app installs.
#
# Each app declares its prerequisites in its .config as
#   CFG_<APP>_REQUIRES="<csv>"
# where <csv> is a comma-separated list of requirement keys: the special types
#   domain  — at least one CFG_DOMAIN_N is set
#   mail    — global mail is enabled
# or the name of any other app/service that must be installed first. The service
# case is data-driven — any real container name works with NO code change here,
# just list it. Examples:
#   CFG_AUTHELIA_REQUIRES="domain,traefik"
#   CFG_BOOKSTACK_REQUIRES="domain,traefik"
#
# Call this as the first step inside an app's install lifecycle:
#
#   if ! appInstallCheckRequirements "$app_name" "$CFG_AUTHELIA_REQUIRES"; then
#       return 1
#   fi
#
# Returns 0 if all requirements are met, 1 if any are missing. On
# failure, prints one line per missing requirement so the user can
# resolve them in order.
appInstallCheckRequirements()
{
    local app_name="$1"
    local reqs_csv="$2"

    [[ -z "$reqs_csv" ]] && return 0

    local missing=()
    local req
    local IFS=','
    for req in $reqs_csv; do
        # Trim whitespace.
        req="${req#"${req%%[![:space:]]*}"}"
        req="${req%"${req##*[![:space:]]}"}"
        [[ -z "$req" ]] && continue

        case "$req" in
            "domain")
                if ! _appReqHasDomain; then
                    missing+=("Set at least one CFG_DOMAIN_N (General → Network) before installing $app_name.")
                fi
                ;;
            "mail")
                if [[ "$CFG_MAIL_ENABLED" != "true" ]]; then
                    missing+=("Configure global mail (CFG_MAIL_ENABLED=true under General → Mail) first.")
                fi
                ;;
            *)
                # Any other requirement names a service/app that must be installed.
                # Data-driven: if it's a real container it's a service prerequisite —
                # adding a new one needs NO code here, just list it in the app's
                # CFG_<APP>_REQUIRES. A name that's not a known app is a typo → ignore.
                if [[ -d "${install_containers_dir}${req}" ]]; then
                    if ! _appReqServiceInstalled "$req"; then
                        missing+=("$(_appReqServiceMsg "$req" "$app_name")")
                    fi
                else
                    isNotice "Unknown requirement '$req' declared by $app_name — ignoring."
                fi
                ;;
        esac
    done
    unset IFS

    if [[ ${#missing[@]} -gt 0 ]]; then
        isError "Cannot install $app_name — prerequisites are not met:"
        local m
        for m in "${missing[@]}"; do
            isError "  • $m"
        done
        return 1
    fi
    return 0
}

# Human-friendly "install X first" line for a service prerequisite. Known services
# get a reason; anything else falls back to a generic message — so a NEW service
# requirement works with no code change (add a reason here only if you want flavor).
_appReqServiceMsg()
{
    local svc="$1" app="$2" reason=""
    case "$svc" in
        traefik)    reason="a reverse proxy to publish itself" ;;
        gluetun)    reason="a VPN gateway to route through" ;;
        authelia)   reason="its auth integration" ;;
        prometheus) reason="something to query" ;;
    esac
    local disp="$(tr '[:lower:]' '[:upper:]' <<< ${svc:0:1})${svc:1}"
    if [[ -n "$reason" ]]; then
        echo "Install $disp first — $app needs $reason."
    else
        echo "Install $disp first — required by $app."
    fi
}

# True if any CFG_DOMAIN_<n> is set to a non-empty value.
_appReqHasDomain()
{
    local i var val
    for i in 1 2 3 4 5 6 7 8 9; do
        var="CFG_DOMAIN_$i"
        val="${!var}"
        [[ -n "$val" ]] && return 0
    done
    return 1
}

# Thin wrapper so we can use either dockerCheckAppInstalled or whatever
# convenience helper a future refactor introduces, without rewriting the
# call sites in here.
_appReqServiceInstalled()
{
    local svc="$1"
    if declare -f checkServiceInstalled >/dev/null 2>&1; then
        checkServiceInstalled "$svc" && return 0 || return 1
    fi
    if declare -f dockerCheckAppInstalled >/dev/null 2>&1; then
        local status
        status=$(dockerCheckAppInstalled "$svc" "docker" 2>/dev/null)
        [[ "$status" == "installed" ]] && return 0 || return 1
    fi
    # Fallback: ask docker directly (mode-aware: rootless hits the rootless socket).
    runFileOp docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "^${svc}-service$"
}
