#!/bin/sh
# Sourced by langs/*.sh. Provides run(), run_install(), check_cmd(), lang_skip(), lang_ok().
# When sourced from langs/foo.sh, $0 is the lang script: LANG_NAME and SCRIPT_DIR are set from it.
# Requires ENV_NAME, EPKG_BIN (and optionally OS, ENV_ROOT). From run.sh we export ENV_NAME, ENV_ROOT, OS.
# ENV_ROOT is used by run_ebin/run_ebin_if to exercise ebin/<tool> (exposed binaries). Standalone:
#   ENV_NAME=dev-alpine ENV_ROOT=$HOME/.epkg/envs/dev-alpine OS=alpine EPKG_BIN=/path/to/epkg ./langs/c.sh

SCRIPT_DIR="${SCRIPT_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
LANG_NAME="${LANG_NAME:-$(basename "$0" .sh)}"

[ -n "$ENV_NAME" ] && [ -n "$EPKG_BIN" ] || {
    echo "[dev-projects] Set ENV_NAME and EPKG_BIN (e.g. run from run.sh or: ENV_NAME=... OS=... EPKG_BIN=... $0)" >&2
    exit 1
}

# Log epkg command to stderr. Use color if GREEN/NC exported from run.sh.
_log_cmd() {
    printf '%b\n' "${GREEN:-}[${OS:-?}/${LANG_NAME}]${NC:-} \$ $*" >&2
}

# Fail with clear debug info if log contains Error:, Warning:, or WARN
_check_log_and_fail() {
    local log_file="$1"
    local cmd_display="$2"
    if [ ! -f "$log_file" ]; then
        return 0
    fi

    # Note on "no_exit=true, continuing" logs: scriptlet failures explicitly allowed,
    # since they are mostly non-fatal in end users POV, but our tests shall be
    # more strict, trying to catch & fix them all
    # Use " WARN " pattern to match log format [timestamp WARN module]
    # Avoid matching file paths like "OSSL_CMP_LOG_WARNING" or "error_logger_file_h.erl"
    local error_pattern='Error:|Warning:| WARN |exited with code'
    # Filter out harmless warnings/errors from package managers and known-safe patterns
    if grep -E "$error_pattern" "$log_file" \
        | grep -v -E 'WARNING: (Running pip as the|The directory.*pip)' \
        | grep -v 'Error: no test specified' \
        | grep -v 'Transaction file conflict:' \
        | grep -v 'OSSL_CMP_LOG_WARN' \
        | grep -v 'timed out after .* seconds' \
        | grep -v 'Hook.*failed:.*timed out' \
        | grep -v "exited with code -\?[0-9]* (no_exit=true, continuing)" \
        | grep -v -E 'Unknown (user|group): .* \( ?continuing with special' \
        | grep -v 'ensure_mount_propagation_private: failed with EPERM' \
        | grep -v -E 'Error::[A-Z]' \
        | grep -v 'useradd: Warning:' \
        | grep -v 'Dependency resolution failed' \
        | grep -v -E '^Error: $' \
        | grep -v 'ACPI Warning:.*AcpiEnable failed' \
        | grep -q .; then
        echo "" >&2
        echo "Reproduce command: $cmd_display" >&2
        echo "Log file: $log_file" >&2
        echo "<<<<<<<<<<<<<<<<<<<" >&2
        grep -n -B2 -A3 -E "$error_pattern" "$log_file" >&2
        echo ">>>>>>>>>>>>>>>>>>>" >&2
        exit 1
    fi
}

# Per-run log file counter (fresh per lang script process)
RUN_COUNT=${RUN_COUNT:-0}

# Create and mount a dedicated temp directory for VM guest.
# On macOS/Windows, /tmp in VM guest is ephemeral (tmpfs), so we mount
# a host temp dir to preserve files across multiple epkg run invocations.
# The mount is added automatically when running tests via libkrun VM.
# Tests should use $TEST_TMP instead of /tmp for persistent storage.
TEST_TMP="/tmp/epkg-test-${LANG_NAME:-?}"
mkdir -p "$TEST_TMP" 2>/dev/null || TEST_TMP="/tmp"
export TEST_TMP

# Shared: log to file, check for Error/Warning/WARN, cat log, return exit code. Used by run() and run_install().
_run_logged() {
    tag=$1
    cmd_display=$2
    shift 2
    RUN_COUNT=$((RUN_COUNT + 1))
    log_dir="${LOG_DIR:-/tmp}"
    log_file="$log_dir/epkg-dev-projects-${OS:-?}-${LANG_NAME:-?}-${tag}-${RUN_COUNT}.log"
    if ! mkdir -p "$log_dir" 2>/dev/null; then
        log_dir="/tmp"
        log_file="$log_dir/epkg-dev-projects-${OS:-?}-${LANG_NAME:-?}-${tag}-${RUN_COUNT}.log"
    fi
    _log_cmd "$cmd_display"
    "$EPKG_BIN" -e "$ENV_NAME" "$@" > "$log_file"
    r=$?
    _check_log_and_fail "$log_file" "$cmd_display"
    cat "$log_file"
    # Exit on non-zero so callers don't need '|| exit 1' after every run()/run_install().
    # Piped invocations (run ... | grep ...) are unaffected — exit only aborts the pipe subshell.
    [ $r -eq 0 ] || exit $r
}

# "run --" so options like ruby -e / node -e are not parsed as epkg run options.
# Add --mount for VM guest to access persistent temp directory.
run() {
    _run_logged "run" "epkg -e $ENV_NAME run --mount $TEST_TMP -- $*" run --mount "$TEST_TMP" -- "$@"
}

run_install() {
    _run_logged "install" "epkg -e $ENV_NAME --assume-yes install --ignore-missing $*" --assume-yes install --ignore-missing "$@"
}

run_update() {
    _run_logged "update" "epkg -e $ENV_NAME update" update
}

# Quiet: no log file, no grep, no stop. Use with || lang_skip or || run_install ... for graceful fallback.
check_cmd() {
    "$EPKG_BIN" -e "$ENV_NAME" run -- "$@" 2>/dev/null
}

# Check if we should use 'epkg run' instead of direct ebin execution.
# For Linux distros running on non-Linux hosts (macOS/Windows), we cannot create
# elf-loader based ebin/ wrappers since they need heavy VM execution.
# In these cases, we use 'epkg run' to execute binaries properly.
# For native Windows/macOS distros (msys2/conda/homebrew), ebin/ wrappers or
# symlinks are still created and can be used directly.
_should_use_epkg_run() {
    local host_os
    host_os=$(uname -s)
    # Only applies when OS is a Linux distro (not native msys2/conda/brew)
    case "$OS" in
        conda|msys2|brew)
            return 1  # Native host distros can use ebin/ directly
            ;;
    esac
    # WSL2 detection (testing Windows epkg.exe from WSL2)
    [ -n "${WSL_DISTRO_NAME:-}" ] && return 0
    [ -n "${WSL_INTEROP:-}" ] && return 0
    # macOS host running Linux distro
    [ "$host_os" = "Darwin" ] && return 0
    return 1
}

# Direct run the exposed binary at env ebin/<name> (exercises ebin wrappers). No-op if ENV_ROOT unset.
# Note: conda/msys2 environments have different library layouts (bin/ instead of usr/bin/, no /lib64/ld-linux-x86-64.so.2)
# so run_ebin is skipped for conda/msys2. Use "run" instead for conda/msys2 tests.
# On macOS (Darwin), Linux binaries can't run directly - they need the libkrun VM.
# So we skip run_ebin on macOS for non-native distros (Linux-based).
# Exception: brew is native macOS packages (Mach-O), can run directly on macOS.
# On non-Linux hosts (macOS/Windows), we don't create elf-loader based ebin/ wrappers.
# ebin/ contains only text script wrappers. We use 'epkg run' instead of direct execution
# for consistency and proper environment setup.
run_ebin() {
    [ -z "${ENV_ROOT:-}" ] && return 0
    [ "$OS" = "conda" ] || [ "$OS" = "msys2" ] && return 0
    # Skip on macOS for non-native distros (Linux-based), but NOT for brew (native macOS packages)
    [ "$(uname -s)" = "Darwin" ] && [ "$OS" != "brew" ] && return 0
    bin=$1
    shift
    # On non-Linux hosts, use 'epkg run' instead of direct execution
    # because we don't create elf-loader based ebin/ wrappers for Windows/macOS.
    # Text script wrappers could still be created, but 'epkg run' ensures
    # proper environment setup and consistency.
    if _should_use_epkg_run; then
        "$EPKG_BIN" -e "$ENV_NAME" run -- "$bin" "$@" || exit
    else
        "$ENV_ROOT/ebin/$bin" "$@" || exit
    fi
}

# Run ebin binary only if it exists (for optional names, e.g. pip3 vs pip).
run_ebin_if() {
    [ -z "${ENV_ROOT:-}" ] && return 0
    [ "$OS" = "conda" ] || [ "$OS" = "msys2" ] && return 0
    # Skip on macOS for non-native distros (Linux-based), but NOT for brew (native macOS packages)
    [ "$(uname -s)" = "Darwin" ] && [ "$OS" != "brew" ] && return 0
    bin=$1
    [ ! -x "$ENV_ROOT/ebin/$bin" ] && return 0
    shift
    # On non-Linux hosts, use 'epkg run' instead of direct execution
    if _should_use_epkg_run; then
        "$EPKG_BIN" -e "$ENV_NAME" run -- "$bin" "$@" || exit
    else
        "$ENV_ROOT/ebin/$bin" "$@" || exit
    fi
}

# Call when this language is not available on this OS (exit 0)
lang_skip() {
    printf '%b\n' "${YELLOW:-}[${OS:-?}/${LANG_NAME}]${NC:-} $*, skip" >&2
    exit 0
}

# Call on success
lang_ok() {
    printf '%b\n' "${GREEN:-}[${OS:-?}/${LANG_NAME}]${NC:-} OK"
}