#!/bin/sh
# SPDX-FileCopyrightText: 2024-present Setup Tooling Project
# SPDX-License-Identifier: Apache-2.0

# libstp3-sh: Shell implementation of Setup Tooling Project library v3
#
# This library provides equivalent functionality to libstp3.so for shell scripts.
# All functions from the C library are available in shell-compatible form.
# Interactive UI functions use whiptail, backed by the Newt library.
#
# New in libstp3:
# - Dry-run mode: stp_set_dry_run / stp_get_dry_run / stp_dry_run_command
# - Package manager detection: stp_detect_package_managers
# - GPU detection: stp_detect_gpu
# - Update all packages: stp_update_all_packages
# - Update checks: stp_check_pending_updates, stp_check_dnf_packages_pending_updates
# - Systemd user units: stp_user_systemd_unit_exists, stp_user_systemd_unit_set_enabled
# - Multi-package operations: stp_multi_install_packages, stp_multi_remove_packages
# - Repository setup: stp_setup_rpmfusion, stp_add_copr_repo, stp_add_repo_url, stp_setup_snapd
# - DNF configuration: stp_configure_dnf, stp_configure_dnf_custom
# - Codec setup: stp_setup_codecs
# - System info: stp_show_system_info, stp_show_system_overview
# - Security validation: stp_is_safe_copr_name, stp_is_safe_url
# - Distro detection: stp_is_el_distro_str
# - Screen control: stp_clear_screen
# - Sudo with password: stp_run_sudo_with_password
# - Resource path init: stp_init_resource_paths
# - Ansible integration: stp_run_ansible_playbook, stp_run_ansible_script, stp_run_ansible_command
# - display_special_day(): Supports custom special dates from config files
#   * Default config: /etc/libstp3/special_dates.yaml
#   * Simple config format (no dependencies):
#     [date]
#     month=2
#     day=14
#     day_end=14
#     message=Happy Valentine's Day!
#     message=May your day be filled with love!
#     script=/path/to/optional/script.sh
#   
#   * YAML format (requires 'yq' tool):
#     - month: 2
#       day: 14
#       day_end: 14
#       messages:
#         - "Happy Valentine's Day!"
#         - "May your day be filled with love!"
#       script: "/path/to/optional/script.sh"
#
# Usage:
#   . /path/to/.libstp3-sh  # Source this file
#   stp_set_dry_run 1       # Enable dry-run mode
#   stp_update_all_packages # Update all packages (dry-run: just prints commands)
#   stp_check_pending_updates 1 # Check for pending updates (skip firmware)
#   stp_check_dnf_packages_pending_updates "foo,bar" # Check specific RPM packages

# Error code constants
readonly STP_SUCCESS=0
readonly STP_ERROR_GENERIC=1
# Shell exit statuses cannot be negative.  These are the modulo-256 equivalents
# of the C enum values, so `return "$STP_ERROR_PATH"` works in every POSIX sh.
readonly STP_ERROR_DIR=255
readonly STP_ERROR_PERMISSION=254
readonly STP_ERROR_PATH=253
readonly STP_ERROR_BOUNDS=252
readonly STP_ERROR_EXECUTION=251
readonly STP_ERROR_CLOUD_CONFIG=248
readonly STP_ERROR_VM_DETECTION=247

# Global variables
STP_CONFIG_FILE=""
STP_LOG_PATH=""
PROGRAMS_PATH="/usr/bin/"
STP_DRY_RUN=0  # Global dry-run mode (0=disabled, 1=enabled)

# Convert string to lowercase (POSIX-compliant)
strlwr() {
    printf '%s' "$1" | tr '[:upper:]' '[:lower:]'
}

# Execute command (safer alternative to eval)
runCommand() {
    [ $# -gt 0 ] || return "$STP_ERROR_GENERIC"
    if [ "${STP_DRY_RUN:-0}" -eq 1 ]; then
        printf '[DRY-RUN] Would execute:'
        printf ' %s' "$@"
        printf '\n'
        return 0
    fi
    "$@"
}

# Securely wipe sensitive strings from memory
secureWipeString() {
    var_name="$1"
    [ -z "$var_name" ] && return 1
    printf '%s\n' "$var_name" | grep -Eq '^[A-Za-z_][A-Za-z0-9_]*$' || return 1
    
    # Overwrite variable content multiple times before unsetting
    eval "${var_name}='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'"
    eval "${var_name}='0000000000000000000000000000000000000000000000000000000000000000'"
    eval "${var_name}=''"
    unset "$var_name"
}

# Securely wipe and unset password variable
secureWipeAndFreePassword() {
    password_var="$1"
    [ -z "$password_var" ] && return 1
    secureWipeString "$password_var"
}

# Check if command exists
commandExists() {
    [ -z "$1" ] && return 1
    command -v "$1" >/dev/null 2>&1
}

# Get menu option with validation
getMenuOption() {
    max_option="$1"
    while true; do
        read -r option
        case "$option" in
            ''|*[!0-9]*)
                printf 'Invalid option. Please try again.\n' >&2
                continue
                ;;
        esac
        
        if [ "$option" -ge 1 ] && [ "$option" -le "$max_option" ]; then
            printf '%d\n' "$option"
            return "$STP_SUCCESS"
        fi
        printf 'Invalid option. Please try again.\n' >&2
    done
}

# Extract OS version from /etc/os-release
extractOSVersion() {
    if [ ! -r /etc/os-release ]; then
        printf 'Version not found\n'
        return "$STP_ERROR_GENERIC"
    fi
    
    version=$(grep '^VERSION_ID=' /etc/os-release | cut -d= -f2 | \
              tr -d '"' | cut -d'(' -f1 | cut -d'.' -f1 | \
              sed 's/[[:space:]]*$//')
    
    if [ -z "$version" ]; then
        printf 'Version not found\n'
        return "$STP_ERROR_GENERIC"
    fi
    
    printf '%s\n' "$version"
    return "$STP_SUCCESS"
}

# Extract Linux distribution from /etc/os-release
extractLinuxDistro() {
    if [ ! -r /etc/os-release ]; then
        printf 'Distro not found\n'
        return "$STP_ERROR_GENERIC"
    fi
    
    distro=$(grep '^ID=' /etc/os-release | cut -d= -f2 | tr -d '"')
    
    if [ -z "$distro" ]; then
        printf 'Distro not found\n'
        return "$STP_ERROR_GENERIC"
    fi
    
    printf '%s\n' "$distro"
    return "$STP_SUCCESS"
}

# Validate package name (prevent injection attacks)
isValidPackageName() {
    [ -z "$1" ] && return 1
    
    # Check if starts with dash
    case "$1" in
        -*) return 1 ;;
    esac
    
    # Check for invalid characters (only allow alphanumeric, dot, dash, underscore)
    case "$1" in
        *[!a-zA-Z0-9._-]*) return 1 ;;
    esac
    
    # Check for command injection attempts
    case "$1" in
        *';'*|*'|'*|*'&'*|*'$'*|*'`'*|*'('*|*')'*|*'{'*|*'}'*|*'<'*|*'>'*)
            return 1
            ;;
    esac
    
    return 0
}

# Validate file path (prevent directory traversal)
isValidFilePath() {
    file_path="$1"
    [ -z "$file_path" ] && return 1
    
    # Check for directory traversal attempts
    case "$file_path" in
        *'..'*|*'//'*) return 1 ;;
    esac
    
    # POSIX shell variables cannot contain NUL bytes, so no separate NUL test
    # is needed (attempting one via command substitution breaks under Dash).
    return 0
}

# Fetch config string value
fetchConfigString() {
    config_var="$1"
    [ -z "$STP_CONFIG_FILE" ] && return "$STP_ERROR_GENERIC"
    [ ! -r "$STP_CONFIG_FILE" ] && return "$STP_ERROR_GENERIC"
    
    value=$(grep "^${config_var} = " "$STP_CONFIG_FILE" | \
            head -n 1 | cut -d= -f2- | \
            sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
    
    if [ -n "$value" ]; then
        printf '%s\n' "$value"
        return "$STP_SUCCESS"
    fi
    return "$STP_ERROR_GENERIC"
}

# Save config string value
saveConfigStringValue() {
    config_var="$1"
    value="$2"
    
    [ -z "$STP_CONFIG_FILE" ] && return "$STP_ERROR_GENERIC"
    
    tmpfile=$(mktemp) || return "$STP_ERROR_GENERIC"
    
    found=0
    while IFS= read -r line; do
        case "$line" in
            "${config_var} = "*)
                printf '%s = %s\n' "$config_var" "$value" >> "$tmpfile"
                found=1
                ;;
            *)
                printf '%s\n' "$line" >> "$tmpfile"
                ;;
        esac
    done < "$STP_CONFIG_FILE"
    
    if [ "$found" -eq 0 ]; then
        printf '%s = %s\n' "$config_var" "$value" >> "$tmpfile"
    fi
    
    mv "$tmpfile" "$STP_CONFIG_FILE"
    return "$STP_SUCCESS"
}

# Get secure password (POSIX-compliant with security improvements)
getSecurePasswordSTP() {
    printf 'Please enter your sudo password: ' >&2
    
    # Disable terminal echo
    if stty -echo 2>/dev/null; then
        stty_worked=1
    else
        stty_worked=0
        printf '\nWarning: Could not disable password echo\n' >&2
    fi
    
    read -r password
    
    # Re-enable terminal echo
    if [ "$stty_worked" -eq 1 ]; then
        stty echo 2>/dev/null
    fi
    
    printf '\n' >&2
    
    # Basic validation
    if [ -z "$password" ]; then
        printf 'Error: Password cannot be empty\n' >&2
        return "$STP_ERROR_GENERIC"
    fi
    
    printf '%s\n' "$password"
    return "$STP_SUCCESS"
}

# Get package version using rpm
getVersion() {
    [ -z "$1" ] && return "$STP_ERROR_GENERIC"
    isValidPackageName "$1" || return "$STP_ERROR_GENERIC"
    
    rpm -q "$1" 2>/dev/null
}

# Set log path
setLogPath() {
    STP_LOG_PATH="$1"
}

# Get log path
getLogPath() {
    printf '%s\n' "$STP_LOG_PATH"
}

# Check if log path is set
hasLogPath() {
    [ -n "$STP_LOG_PATH" ]
}

# Get script shell from shebang or extension
getScriptShell() {
    script_path="$1"
    extension="$2"
    
    # Validate inputs
    isValidFilePath "$script_path" || return "$STP_ERROR_PATH"
    
    # Try to read shebang
    if [ -r "$script_path" ]; then
        first_line=$(head -n 1 "$script_path")
        case "$first_line" in
            '#!'*)
                shell=$(printf '%s' "$first_line" | \
                       sed 's/^#!//' | sed 's/^[[:space:]]*//')
                printf '%s\n' "$shell"
                return "$STP_SUCCESS"
                ;;
        esac
    fi
    
    # Use extension
    ext_lower=$(strlwr "$extension")
    case "$ext_lower" in
        .sh)
            for shell in /bin/bash /bin/dash /bin/zsh /bin/ksh \
                        /bin/mksh /bin/fish /bin/sh; do
                [ -x "$shell" ] && printf '%s\n' "$shell" && return "$STP_SUCCESS"
            done
            printf '/bin/sh\n'
            return "$STP_SUCCESS"
            ;;
        .bash) printf '/bin/bash\n' && return "$STP_SUCCESS" ;;
        .zsh) printf '/bin/zsh\n' && return "$STP_SUCCESS" ;;
        .dash) printf '/bin/dash\n' && return "$STP_SUCCESS" ;;
        .ksh) printf '/bin/ksh\n' && return "$STP_SUCCESS" ;;
        .mksh) printf '/bin/mksh\n' && return "$STP_SUCCESS" ;;
        .fish) printf '/bin/fish\n' && return "$STP_SUCCESS" ;;
        .c) return "$STP_ERROR_GENERIC" ;;
        *) return "$STP_ERROR_GENERIC" ;;
    esac
}

# Compile and execute C file
compile_and_execute_c() {
    source_path="$1"
    
    # Validate path
    isValidFilePath "$source_path" || return "$STP_ERROR_PATH"
    
    temp_dir=$(mktemp -d) || return "$STP_ERROR_EXECUTION"
    obj_path="${temp_dir}/program"
    
    # Use direct execution instead of system() equivalent
    gcc -O2 -I/usr/lib64/libstp/2 -L/usr/lib64/libstp/2 \
        /usr/lib64/libstp/2/libstp-c.so -o "$obj_path" \
        "$source_path" -lncurses -Wall -Wextra -Werror \
        -fstack-protector-strong -D_FORTIFY_SOURCE=2 2>/dev/null
    
    compile_status=$?
    
    if [ $compile_status -ne 0 ] || [ ! -x "$obj_path" ]; then
        rm -rf "$temp_dir"
        return "$STP_ERROR_EXECUTION"
    fi
    
    # Execute with restricted PATH
    PATH=/usr/local/bin:/usr/bin:/bin "$obj_path"
    exec_status=$?
    
    rm -rf "$temp_dir"
    return $exec_status
}

STP_SCRIPT_RECORDS=""

_stp_record_script() {
    recorded_path="$1"
    recorded_mtime="$2"
    case "$recorded_path" in *'|'*|*"
"*) return 1 ;; esac
    STP_SCRIPT_RECORDS="${STP_SCRIPT_RECORDS:+$STP_SCRIPT_RECORDS
}${recorded_mtime}|${recorded_path}"
}

_stp_get_recorded_mtime() {
    lookup_path="$1"
    printf '%s\n' "$STP_SCRIPT_RECORDS" | awk -F '|' -v wanted="$lookup_path" '$2 == wanted { value=$1 } END { if (value != "") print value }'
}

_stp_script_metadata() {
    metadata_path="$1"
    metadata_key="$2"
    sed -n "s/^[[:space:]]*#[[:space:]]*@st-${metadata_key}:[[:space:]]*//p" "$metadata_path" 2>/dev/null | head -n 1
}

# List scripts and remember their mtimes for executeScript's TOCTOU check.
# The default output remains one basename per line for compatibility.
listScripts() {
    directory="$1"
    max_scripts="${2:-128}"
    
    [ ! -d "$directory" ] && return "$STP_ERROR_DIR"
    
    # Validate directory path
    isValidFilePath "$directory" || return "$STP_ERROR_PATH"
    
    listing_file=$(mktemp) || return "$STP_ERROR_EXECUTION"
    find "$directory" -maxdepth 1 -type f \
        \( -name "*.sh" -o -name "*.bash" -o -name "*.zsh" \
        -o -name "*.dash" -o -name "*.ksh" -o -name "*.mksh" \
        -o -name "*.fish" -o -name "*.c" \) -print 2>/dev/null > "$listing_file"
    count=0
    while IFS= read -r script; do
        [ "$count" -lt "$max_scripts" ] || break
        resolved_script=$(_stp_realpath "$script") || continue
        script_mtime=$(stat -c '%Y' -- "$resolved_script" 2>/dev/null) || continue
        _stp_record_script "$resolved_script" "$script_mtime" || continue
        basename "$script"
        count=$((count + 1))
    done < "$listing_file"
    rm -f -- "$listing_file"
}

# Display script menu
displayScriptMenu() {
    directory="$1"
    count=0
    
    printf '\nAvailable scripts:\n'
    printf '%-4s %-30s %-10s %-20s\n' "No." "Name" "Executable" \
           "Last Modified"
    printf '%s\n' "------------------------------------------------------------"
    
    menu_listing=$(mktemp) || return "$STP_ERROR_EXECUTION"
    find "$directory" -maxdepth 1 -type f \
        \( -name "*.sh" -o -name "*.bash" -o -name "*.zsh" \
        -o -name "*.dash" -o -name "*.ksh" -o -name "*.mksh" \
        -o -name "*.fish" -o -name "*.c" \) -print 2>/dev/null | sort > "$menu_listing"
    while IFS= read -r script; do
        count=$((count + 1))
        name=$(basename "$script")
        title=$(_stp_script_metadata "$script" title)
        description=$(_stp_script_metadata "$script" description)
        [ -n "$title" ] && display_name="$title" || display_name="$name"
        
        if [ -x "$script" ]; then
            executable="Yes"
        else
            executable="No"
        fi
        
        modified=$(stat -c '%y' "$script" 2>/dev/null | cut -d. -f1)
        
        resolved_script=$(_stp_realpath "$script")
        script_mtime=$(stat -c '%Y' -- "$resolved_script" 2>/dev/null)
        [ -n "$resolved_script" ] && [ -n "$script_mtime" ] && _stp_record_script "$resolved_script" "$script_mtime"

        printf '%-4d %-30s %-10s %-20s\n' "$count" "$display_name" \
               "$executable" "$modified"
        [ -z "$description" ] || printf '     └─ %s\n' "$description"
    done < "$menu_listing"
    rm -f -- "$menu_listing"
    
    count=$((count + 1))
    printf '%-4d %s\n' "$count" "Back to previous menu"
}

# Execute script with security checks
executeScript() {
    script_path="$1"
    expected_mtime="${2:-}"
    
    # Validate path
    [ ! -f "$script_path" ] && return "$STP_ERROR_PATH"
    isValidFilePath "$script_path" || return "$STP_ERROR_PATH"
    
    resolved_script=$(_stp_realpath "$script_path") || return "$STP_ERROR_PATH"

    # Check if file is within a temporary directory (security)
    case "$resolved_script" in
        /tmp/*|/var/tmp/*)
            printf 'Error: Cannot execute scripts from temporary directories\n' >&2
            return "$STP_ERROR_PERMISSION"
            ;;
    esac

    current_mtime=$(stat -c '%Y' -- "$resolved_script" 2>/dev/null) || return "$STP_ERROR_PATH"
    [ -n "$expected_mtime" ] || expected_mtime=$(_stp_get_recorded_mtime "$resolved_script")
    if [ -n "$expected_mtime" ] && [ "$current_mtime" != "$expected_mtime" ]; then
        printf 'Error: Script changed after it was listed: %s\n' "$resolved_script" >&2
        return "$STP_ERROR_PERMISSION"
    fi
    
    extension="${script_path##*.}"
    extension=".${extension}"
    
    # Special handling for C files
    ext_lower=$(strlwr "$extension")
    if [ "$ext_lower" = ".c" ]; then
        compile_and_execute_c "$resolved_script"
        return $?
    fi
    
    shell=$(getScriptShell "$resolved_script" "$extension")
    [ -z "$shell" ] && return "$STP_ERROR_EXECUTION"
    
    # Execute with restricted PATH
    PATH=/usr/local/bin:/usr/bin:/bin "$shell" "$resolved_script"
}

# Set config file
setConfigFile() {
    STP_CONFIG_FILE="$1"
}

# Get config file
getConfigFile() {
    printf '%s\n' "$STP_CONFIG_FILE"
}

# Check if config file is set
hasConfigFile() {
    [ -n "$STP_CONFIG_FILE" ]
}

# Display default special day messages (fallback)
_display_default_special_day() {
    month="$1"
    day="$2"
    
    case "${month}-${day}" in
        2-14)
            printf 'Happy Valentine'\''s Day!\n'
            printf 'May your day be filled with love and joy!\n'
            printf 'We wish you a nice holiday and hope you enjoy our program.\n\n'
            ;;
        3-8)
            printf 'Happy International Women'\''s Day!\n'
            printf 'Celebrating the achievements and contributions of women worldwide!\n'
            printf 'We wish you a nice holiday and hope you enjoy our program.\n\n'
            ;;
        4-22)
            printf 'Happy Earth Day!\n'
            printf 'Let'\''s protect and preserve our beautiful planet!\n'
            printf 'We wish you a nice holiday and hope you enjoy our program.\n\n'
            ;;
        5-1)
            printf 'Happy international workers day!\n'
            printf 'Stand up for your workers rights!\n'
            printf 'We wish you a nice holiday and hope you enjoy our program.\n\n'
            ;;
        5-4)
            printf 'May the 4th be with you!\n'
            printf 'We wish you a nice holiday and hope you enjoy our program.\n\n'
            ;;
        6-5)
            printf 'Happy World Environment Day!\n'
            printf 'Let'\''s work together for a sustainable future!\n'
            printf 'We wish you a nice holiday and hope you enjoy our program.\n\n'
            ;;
        9-21)
            printf 'Happy International Day of Peace!\n'
            printf 'Let'\''s work together for a more peaceful world!\n'
            printf 'We wish you a nice holiday and hope you enjoy our program.\n\n'
            ;;
        12-24|12-25|12-26)
            printf 'Merry Christmas!\n'
            printf 'We wish you a nice holiday and hope you enjoy our program.\n\n'
            ;;
        12-31)
            printf 'Happy New Year'\''s Eve!\n'
            printf 'We wish you a nice holiday and hope you enjoy our program.\n\n'
            ;;
        1-1)
            printf 'Happy New Year!\n'
            printf 'We wish you a nice holiday and hope you enjoy our program.\n\n'
            ;;
    esac
}

# Parse special dates from config file
# Config format (simple shell-parseable, alternative to YAML):
# [date]
# month=2
# day=14
# day_end=14
# message=Happy Valentine's Day!
# message=Custom message line 2
# script=/path/to/script.sh
#
# For YAML support, install 'yq' and the function will auto-detect it
_parse_special_dates_config() {
    config_path="$1"
    current_month="$2"
    current_day="$3"
    
    [ ! -r "$config_path" ] && return 1
    
    # Try to detect if it's a YAML file and if yq is available
    if command -v yq >/dev/null 2>&1 && head -n 1 "$config_path" | grep -q '^-'; then
        # YAML format detected and yq available
        _parse_yaml_special_dates "$config_path" "$current_month" "$current_day"
        return $?
    fi
    
    # Parse simple config format
    in_date_block=0
    match_found=0
    month="" day="" day_end="" script="" messages=""
    
    while IFS= read -r line || [ -n "$line" ]; do
        # Skip comments and empty lines
        case "$line" in
            ''|'#'*) continue ;;
        esac
        
        # Check for section start
        if printf '%s' "$line" | grep -q '^\[date\]'; then
            # Save previous block if it matched
            if [ "$match_found" -eq 1 ]; then
                # Print messages
                if [ -n "$messages" ]; then
                    printf '%s\n' "$messages"
                    printf '\n'
                fi
                
                # Execute script if specified
                if [ -n "$script" ] && [ -f "$script" ]; then
                    executeScript "$script"
                elif [ -n "$script" ]; then
                    printf 'Warning: Script not found: %s\n' "$script" >&2
                fi
                return 0
            fi
            
            # Reset for new block
            in_date_block=1
            match_found=0
            month="" day="" day_end="" script="" messages=""
            continue
        fi
        
        if [ "$in_date_block" -eq 1 ]; then
            # Parse key=value pairs
            key=$(printf '%s' "$line" | cut -d= -f1 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
            value=$(printf '%s' "$line" | cut -d= -f2- | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
            
            case "$key" in
                month)
                    month="$value"
                    ;;
                day)
                    day="$value"
                    day_end="$value"  # Default to same day
                    ;;
                day_end)
                    day_end="$value"
                    ;;
                message)
                    if [ -z "$messages" ]; then
                        messages="$value"
                    else
                        messages="${messages}
${value}"
                    fi
                    ;;
                script)
                    script="$value"
                    ;;
            esac
            
            # Check if current date matches
            if [ -n "$month" ] && [ -n "$day" ] && [ -n "$day_end" ]; then
                if [ "$month" -eq "$current_month" ] && \
                   [ "$current_day" -ge "$day" ] && \
                   [ "$current_day" -le "$day_end" ]; then
                    match_found=1
                fi
            fi
        fi
    done < "$config_path"
    
    # Check last block
    if [ "$match_found" -eq 1 ]; then
        if [ -n "$messages" ]; then
            printf '%s\n' "$messages"
            printf '\n'
        fi
        
        if [ -n "$script" ] && [ -f "$script" ]; then
            executeScript "$script"
        elif [ -n "$script" ]; then
            printf 'Warning: Script not found: %s\n' "$script" >&2
        fi
        return 0
    fi
    
    return 1
}

# Parse YAML special dates config (requires yq)
_parse_yaml_special_dates() {
    config_path="$1"
    current_month="$2"
    current_day="$3"
    
    command -v yq >/dev/null 2>&1 || return 1
    
    # Get the number of date entries
    date_count=$(yq eval 'length' "$config_path" 2>/dev/null) || return 1
    
    i=0
    while [ "$i" -lt "$date_count" ]; do
        month=$(yq eval ".[$i].month" "$config_path" 2>/dev/null)
        day=$(yq eval ".[$i].day" "$config_path" 2>/dev/null)
        day_end=$(yq eval ".[$i].day_end" "$config_path" 2>/dev/null)
        
        # Default day_end to day if not specified
        [ "$day_end" = "null" ] && day_end="$day"
        
        # Check if current date matches
        if [ "$month" = "$current_month" ] && \
           [ "$current_day" -ge "$day" ] && \
           [ "$current_day" -le "$day_end" ]; then
            
            # Print messages
            msg_count=$(yq eval ".[$i].messages | length" "$config_path" 2>/dev/null)
            if [ "$msg_count" != "null" ] && [ "$msg_count" -gt 0 ]; then
                j=0
                while [ "$j" -lt "$msg_count" ]; do
                    msg=$(yq eval ".[$i].messages[$j]" "$config_path" 2>/dev/null)
                    printf '%s\n' "$msg"
                    j=$((j + 1))
                done
                printf '\n'
            fi
            
            # Execute script if specified
            script=$(yq eval ".[$i].script" "$config_path" 2>/dev/null)
            if [ "$script" != "null" ] && [ -n "$script" ]; then
                if [ -f "$script" ]; then
                    executeScript "$script"
                else
                    printf 'Warning: Script not found: %s\n' "$script" >&2
                fi
            fi
            
            return 0
        fi
        
        i=$((i + 1))
    done
    
    return 1
}

# Display special day with optional config file support
# Supports both simple config format and YAML (if yq is installed)
display_special_day_with_config() {
    config_path="$1"
    month=$(date +%-m)
    day=$(date +%-d)
    
    message_found=0
    
    # If config path provided and file exists, try to load custom dates
    if [ -n "$config_path" ] && stp_file_exists "$config_path"; then
        if _parse_special_dates_config "$config_path" "$month" "$day"; then
            message_found=1
        fi
    fi
    
    # Fallback to default messages if no config or date not found
    if [ "$message_found" -eq 0 ]; then
        _display_default_special_day "$month" "$day"
    fi
}

# Display special day messages
# If config_path is NULL/empty, falls back to /etc/libstp3/special_dates.yaml
display_special_day() {
    config_path="$1"
    
    # Use default path if none provided
    if [ -z "$config_path" ]; then
        config_path="/etc/libstp3/special_dates.yaml"
    fi
    
    # Call the config version if the file exists
    if stp_file_exists "$config_path"; then
        display_special_day_with_config "$config_path"
    else
        # Fallback to default messages
        month=$(date +%-m)
        day=$(date +%-d)
        _display_default_special_day "$month" "$day"
    fi
}

# Create directory (with tilde expansion and validation)
create_dir() {
    directory="$1"
    
    [ -z "$directory" ] && {
        printf 'Result Error: Directory path is NULL.\n' >&2
        return "$STP_ERROR_GENERIC"
    }
    
    # Handle tilde expansion
    case "$directory" in
        '~/'*)
            directory="${HOME}${directory#\~}"
            ;;
        '~')
            directory="$HOME"
            ;;
    esac
    
    # Validate path
    isValidFilePath "$directory" || {
        printf 'Result Error: Invalid directory path.\n' >&2
        return "$STP_ERROR_PATH"
    }
    
    if [ -d "$directory" ]; then
        printf 'Result: Directory already exists: %s\n' "$directory"
        return "$STP_SUCCESS"
    fi
    
    if [ -e "$directory" ]; then
        printf 'Result Error: A file with the same name exists: %s\n' \
               "$directory" >&2
        return "$STP_ERROR_GENERIC"
    fi
    
    mkdir -p "$directory" || {
        printf 'Result Error: mkdir failed\n' >&2
        return "$STP_ERROR_GENERIC"
    }
    
    printf 'Result: Directory created successfully: %s\n' "$directory"
    return "$STP_SUCCESS"
}

# Scan architecture
scan_arch() {
    uname -m
}

# Get program versions (comma-separated list)
program_version() {
    programs_str="$1"
    
    [ -z "$programs_str" ] && {
        printf 'No programs provided.\n'
        return
    }
    
    # Split by comma
    printf '%s\n' "$programs_str" | tr ',' '\n' | while IFS= read -r prog; do
        # Trim whitespace
        prog=$(printf '%s' "$prog" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
        
        [ -z "$prog" ] && continue
        
        # Validate package name before querying
        if ! isValidPackageName "$prog"; then
            printf 'Invalid package name: %s\n\n' "$prog"
            continue
        fi
        
        version=$(getVersion "$prog")
        if [ -n "$version" ]; then
            printf '%s\n\n' "$version"
        else
            printf 'Unable to retrieve version for %s\n\n' "$prog"
        fi
    done
}

# Get real user from utmp (simplified version)
get_real_user() {
    who | head -n 1 | awk '{print $1}' || printf 'unknown\n'
}

# Get architecture string
get_arch_string() {
    arch=$(uname -m)
    case "$arch" in
        x86_64) printf 'x86_64\n' ;;
        aarch64) printf 'aarch64\n' ;;
        armv7l) printf 'armv7l\n' ;;
        armv6l) printf 'armv6l\n' ;;
        i686) printf 'i686\n' ;;
        *) printf 'unknown\n' ;;
    esac
}

# Get ostree variant
get_ostree_variant() {
    if [ ! -r /etc/os-release ]; then
        printf 'unknown\n'
        return
    fi
    
    variant=$(grep '^VARIANT_ID=' /etc/os-release | cut -d= -f2 | tr -d '"')
    
    case "$variant" in
        silverblue) printf 'silverblue\n' ;;
        kinoite) printf 'kinoite\n' ;;
        sway-atomic) printf 'sericea\n' ;;
        budgie-atomic) printf 'onyx\n' ;;
        base) printf 'base\n' ;;
        *) printf 'unknown\n' ;;
    esac
}

# Download file with wget (with validation)
download_file() {
    dload_file_path="$1"
    download_url="$2"
    
    # Validate inputs
    [ -z "$dload_file_path" ] && {
        printf 'Error: File path is required\n' >&2
        return "$STP_ERROR_GENERIC"
    }
    
    [ -z "$download_url" ] && {
        printf 'Error: Download URL is required\n' >&2
        return "$STP_ERROR_GENERIC"
    }
    
    # Validate URL format
    case "$download_url" in
        http://*|https://*)
            # Valid URL
            ;;
        *)
            printf 'Error: Invalid URL format. Must start with http:// or https://\n' >&2
            return "$STP_ERROR_GENERIC"
            ;;
    esac
    
    # Handle tilde expansion
    case "$dload_file_path" in
        '~/'*)
            resolved_path="${HOME}${dload_file_path#\~}"
            ;;
        '~')
            resolved_path="$HOME"
            ;;
        *)
            resolved_path="$dload_file_path"
            ;;
    esac
    
    # Validate path
    isValidFilePath "$resolved_path" || {
        printf 'Error: Invalid file path\n' >&2
        return "$STP_ERROR_PATH"
    }
    
    if [ -f "$resolved_path" ]; then
        printf 'Configuration file already exists at %s\n' "$resolved_path"
        return "$STP_SUCCESS"
    fi
    
    printf 'Downloading configuration file to %s...\n' "$resolved_path"
    
    # Use -- to prevent URL being interpreted as option
    if wget -q -O "$resolved_path" -- "$download_url"; then
        printf 'Configuration file downloaded successfully\n'
        return "$STP_SUCCESS"
    else
        printf 'Failed to download configuration file. Please check your internet connection.\n' >&2
        return "$STP_ERROR_GENERIC"
    fi
}

# Package operation with improved security and expanded package manager support
packageOperation() {
    manager_name="$1"
    operation="$2"
    shift 2
    
    # For update operations, we don't need package names
    if [ "$operation" != "update" ] && [ "$operation" != "upgrade" ]; then
        # Validate all package names
        for pkg in "$@"; do
            if ! isValidPackageName "$pkg"; then
                printf 'Error: Invalid package name: %s\n' "$pkg" >&2
                return "$STP_ERROR_GENERIC"
            fi
        done
    fi

    if [ "$STP_DRY_RUN" -eq 1 ]; then
        printf '[DRY-RUN] packageOperation %s %s' "$manager_name" "$operation"
        [ $# -eq 0 ] || printf ' %s' "$@"
        printf '\n'
        return 0
    fi
    
    case "$manager_name" in
        dnf)
            case "$operation" in
                install) sudo dnf install --refresh -y "$@" ;;
                uninstall) sudo dnf remove -y "$@" ;;
                search) sudo dnf search --refresh "$@" ;;
                update) sudo dnf update --refresh -y ;;
                upgrade) 
                    version="$1"
                    sudo dnf -y system-upgrade download --releasever="$version" --refresh --allowerasing --best
                    ;;
                addrepo)
                    repofile="$1"
                    sudo dnf config-manager addrepo --from-repofile="$repofile"
                    ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        dnf_el)
            case "$operation" in
                install) sudo dnf install --refresh -y "$@" ;;
                uninstall) sudo dnf remove -y "$@" ;;
                search) sudo dnf search --refresh "$@" ;;
                update) sudo dnf update --refresh -y ;;
                addrepo)
                    repo="$1"
                    sudo dnf config-manager --add-repo "$repo"
                    ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        snap)
            case "$operation" in
                install) sudo snap install "$@" ;;
                uninstall) sudo snap remove "$@" ;;
                search) sudo snap find "$@" ;;
                update) sudo snap refresh ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        flatpak)
            case "$operation" in
                install) flatpak install -y "$@" ;;
                uninstall) flatpak uninstall -y "$@" ;;
                search) flatpak search "$@" ;;
                update) flatpak update -y ;;
                addrepo)
                    name="$1"
                    url="$2"
                    flatpak remote-add --if-not-exists "$name" "$url"
                    ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        rpm-ostree)
            case "$operation" in
                install) sudo rpm-ostree install -y "$@" -A ;;
                uninstall) sudo rpm-ostree uninstall -y "$@" -A ;;
                search) sudo rpm-ostree search "$@" ;;
                update) sudo rpm-ostree upgrade ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        zypper)
            case "$operation" in
                install) sudo zypper install -y "$@" ;;
                uninstall) sudo zypper remove -y "$@" ;;
                search) sudo zypper search "$@" ;;
                update) sudo zypper update -y ;;
                upgrade) 
                    sudo zypper in opensuse-migration-tool
                    sudo opensuse-migration-tool
                    ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        pacman)
            case "$operation" in
                install) sudo pacman -S --noconfirm "$@" ;;
                uninstall) sudo pacman -R --noconfirm "$@" ;;
                search) sudo pacman -Ss "$@" ;;
                update) sudo pacman -Syu ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        apt)
            case "$operation" in
                install) sudo apt install -y "$@" ;;
                uninstall) sudo apt remove -y "$@" ;;
                search) sudo apt search "$@" ;;
                update) sudo apt update && sudo apt upgrade -y ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        apt_ubuntu)
            case "$operation" in
                install) sudo apt install -y "$@" ;;
                uninstall) sudo apt remove -y "$@" ;;
                search) sudo apt search "$@" ;;
                update) sudo apt update && sudo apt upgrade -y ;;
                upgrade) sudo do-release-upgrade ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        emerge)
            case "$operation" in
                install) sudo emerge -av "$@" ;;
                uninstall) sudo emerge -C "$@" ;;
                search) sudo emerge -S "$@" ;;
                update) sudo emerge -u world ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        apk)
            case "$operation" in
                install) sudo apk add "$@" ;;
                uninstall) sudo apk del "$@" ;;
                search) sudo apk search "$@" ;;
                update) sudo apk update && sudo apk upgrade ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        brew)
            case "$operation" in
                install) brew install "$@" ;;
                uninstall) brew uninstall "$@" ;;
                search) brew search "$@" ;;
                update) brew update && brew upgrade -y ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        nix)
            case "$operation" in
                install) nix-env -i "$@" ;;
                uninstall) nix-env -e "$@" ;;
                search) nix-env -qa "$@" ;;
                update) nix-channel --update && nix-env -u ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        fwupdmgr)
            case "$operation" in
                install) sudo fwupdmgr install "$@" ;;
                update) sudo fwupdmgr refresh --force && sudo fwupdmgr get-updates && sudo fwupdmgr update -y ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        opkg)
            case "$operation" in
                install) opkg install "$@" ;;
                uninstall) opkg remove "$@" ;;
                search) opkg list | grep "$@" ;;
                update) opkg update && opkg list-upgradable | cut -f 1 -d ' ' | xargs opkg upgrade ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        winget)
            case "$operation" in
                install) winget install --silent --accept-source-agreements --accept-package-agreements "$@" ;;
                uninstall) winget uninstall --silent "$@" ;;
                search) winget search "$@" ;;
                update) winget upgrade --all --silent --accept-source-agreements --accept-package-agreements ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        choco)
            case "$operation" in
                install) choco install -y "$@" ;;
                uninstall) choco uninstall -y "$@" ;;
                search) choco search "$@" ;;
                update) choco upgrade all -y ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        xbps)
            case "$operation" in
                install) sudo xbps-install -y "$@" ;;
                uninstall) sudo xbps-remove -y "$@" ;;
                search) sudo xbps-query -s "$@" ;;
                update) sudo xbps-install -Su ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        pkg)
            case "$operation" in
                install) sudo pkg install -y "$@" ;;
                uninstall) sudo pkg remove -y "$@" ;;
                search) sudo pkg search "$@" ;;
                update) sudo pkg upgrade -y ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        pkg_add)
            case "$operation" in
                install) sudo pkg_add "$@" ;;
                uninstall) sudo pkg_delete "$@" ;;
                search) sudo pkg_info | grep "$@" ;;
                update) sudo pkg_add -u ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        slackpkg)
            case "$operation" in
                install) sudo slackpkg install "$@" ;;
                uninstall) sudo slackpkg remove "$@" ;;
                search) sudo slackpkg search "$@" ;;
                update) sudo slackpkg update && sudo slackpkg upgrade-all ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        swupd)
            case "$operation" in
                install) sudo swupd bundle-add "$@" ;;
                uninstall) sudo swupd bundle-remove "$@" ;;
                search) sudo swupd search "$@" ;;
                update) sudo swupd update ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        guix)
            case "$operation" in
                install) guix install "$@" ;;
                uninstall) guix remove "$@" ;;
                search) guix search "$@" ;;
                update) guix pull && guix upgrade ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        cargo)
            case "$operation" in
                install) cargo install "$@" ;;
                uninstall) cargo uninstall "$@" ;;
                search) cargo search "$@" ;;
                update) 
                    if commandExists cargo-install-update; then
                        cargo install-update -a
                    else
                        printf 'cargo-install-update not found. Install with: cargo install cargo-update\n' >&2
                        return "$STP_ERROR_GENERIC"
                    fi
                    ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        pip)
            case "$operation" in
                install) pip install "$@" ;;
                uninstall) pip uninstall -y "$@" ;;
                search) pip search "$@" 2>/dev/null || printf 'pip search is currently disabled. Use https://pypi.org to search.\n' ;;
                update) pip list --outdated | cut -d ' ' -f1 | xargs -n1 pip install -U ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        pip3)
            case "$operation" in
                install) pip3 install "$@" ;;
                uninstall) pip3 uninstall -y "$@" ;;
                search) pip3 search "$@" 2>/dev/null || printf 'pip search is currently disabled. Use https://pypi.org to search.\n' ;;
                update) pip3 list --outdated | cut -d ' ' -f1 | xargs -n1 pip3 install -U ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        npm)
            case "$operation" in
                install) npm install -g "$@" ;;
                uninstall) npm uninstall -g "$@" ;;
                search) npm search "$@" ;;
                update) npm update -g ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        pnpm)
            case "$operation" in
                install) pnpm add -g "$@" ;;
                uninstall) pnpm remove -g "$@" ;;
                search) pnpm search "$@" ;;
                update) pnpm update -g ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        bun)
            case "$operation" in
                install) bun add -g "$@" ;;
                uninstall) bun remove -g "$@" ;;
                search) bun pm search "$@" ;;
                update) bun update --global ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        yarn)
            case "$operation" in
                install) yarn global add "$@" ;;
                uninstall) yarn global remove "$@" ;;
                search) yarn search "$@" ;;
                update) yarn global upgrade ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        gem)
            case "$operation" in
                install) gem install "$@" ;;
                uninstall) gem uninstall "$@" ;;
                search) gem search "$@" ;;
                update) gem update ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        pkgutil)
            case "$operation" in
                install) sudo pkgutil --install "$@" ;;
                uninstall) sudo pkgutil --uninstall "$@" ;;
                search) pkgutil --search "$@" ;;
                update) sudo pkgutil --update ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        scoop)
            case "$operation" in
                install) scoop install "$@" ;;
                uninstall) scoop uninstall "$@" ;;
                search) scoop search "$@" ;;
                update) scoop update && scoop update '*' ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        pkgng)
            case "$operation" in
                install) sudo pkg install -y "$@" ;;
                uninstall) sudo pkg remove -y "$@" ;;
                search) sudo pkg search "$@" ;;
                update) sudo pkg upgrade -y ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        conda)
            case "$operation" in
                install) conda install -y "$@" ;;
                uninstall) conda remove -y "$@" ;;
                search) conda search "$@" ;;
                update) conda update --all ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        spack)
            case "$operation" in
                install) spack install "$@" ;;
                uninstall) spack uninstall -y "$@" ;;
                search) spack list | grep "$@" ;;
                update) spack update ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        vcpkg)
            case "$operation" in
                install) vcpkg install "$@" ;;
                uninstall) vcpkg remove "$@" ;;
                search) vcpkg search "$@" ;;
                update) vcpkg upgrade ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        ipkg)
            case "$operation" in
                install) ipkg install "$@" ;;
                uninstall) ipkg remove "$@" ;;
                search) ipkg list | grep "$@" ;;
                update) ipkg update && ipkg upgrade ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        pkcon)
            case "$operation" in
                install) _stp_run_pkcon_install "$@" ;;
                uninstall) sudo pkcon remove -y "$@" ;;
                search) sudo pkcon refresh && sudo pkcon search "$@" ;;
                update) sudo pkcon refresh && sudo pkcon update -y ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        portage)
            case "$operation" in
                install) sudo emerge -av "$@" ;;
                uninstall) sudo emerge --deselect "$@" && sudo emerge --depclean ;;
                search) emerge --search "$@" ;;
                update) sudo emerge --sync && sudo emerge -uDN @world ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        paru)
            case "$operation" in
                install) paru -S --noconfirm "$@" ;;
                uninstall) paru -R --noconfirm "$@" ;;
                search) paru -Ss "$@" ;;
                update) paru -Syu ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        yay)
            case "$operation" in
                install) yay -S --noconfirm "$@" ;;
                uninstall) yay -R --noconfirm "$@" ;;
                search) yay -Ss "$@" ;;
                update) yay -Syu ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        yum)
            case "$operation" in
                install) sudo yum install -y "$@" ;;
                uninstall) sudo yum remove -y "$@" ;;
                search) sudo yum search "$@" ;;
                update) sudo yum update -y ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        apk-tools)
            case "$operation" in
                install) sudo apk add "$@" ;;
                uninstall) sudo apk del "$@" ;;
                search) apk search "$@" ;;
                update) sudo apk update && sudo apk upgrade ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        eopkg)
            case "$operation" in
                install) sudo eopkg install -y "$@" ;;
                uninstall) sudo eopkg remove -y "$@" ;;
                search) sudo eopkg search "$@" ;;
                update) sudo eopkg upgrade ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        urpmi)
            case "$operation" in
                install) sudo urpmi --auto "$@" ;;
                uninstall) sudo urpme "$@" ;;
                search) urpmq "$@" ;;
                update) sudo urpmi --auto-update ;;
                *) return "$STP_ERROR_GENERIC" ;;
            esac
            ;;
        *)
            printf 'Error: Unknown package manager: %s\n' "$manager_name" >&2
            printf 'Supported managers: dnf, dnf_el, snap, flatpak, rpm-ostree, zypper, pacman, apt, apt_ubuntu,\n' >&2
            printf '                     emerge, apk, brew, nix, fwupdmgr, opkg, winget, choco, xbps, pkg,\n' >&2
            printf '                     pkg_add, slackpkg, swupd, guix, cargo, pip, pip3, npm, yarn, gem,\n' >&2
            printf '                     pnpm, bun, pkgutil, scoop, pkgng, conda, spack, vcpkg, ipkg, pkcon, portage,\n' >&2
            printf '                     paru, yay, yum, apk-tools, eopkg, urpmi\n' >&2
            return "$STP_ERROR_GENERIC"
            ;;
    esac
}

# Helper function to detect available package manager
detect_package_manager() {
    # Check for package managers in order of preference
    if commandExists dnf && [ -f /etc/fedora-release ]; then
        printf 'dnf\n'
    elif commandExists dnf && [ -f /etc/redhat-release ]; then
        printf 'dnf_el\n'
    elif commandExists apt; then
        if [ -f /etc/debian_version ] || [ -f /etc/lsb-release ]; then
            if grep -q Ubuntu /etc/lsb-release 2>/dev/null || grep -q Ubuntu /etc/os-release 2>/dev/null; then
                printf 'apt_ubuntu\n'
            else
                printf 'apt\n'
            fi
        else
            printf 'apt\n'
        fi
    elif commandExists pacman; then
        printf 'pacman\n'
    elif commandExists zypper; then
        printf 'zypper\n'
    elif commandExists rpm-ostree; then
        printf 'rpm-ostree\n'
    elif commandExists emerge; then
        printf 'emerge\n'
    elif commandExists apk; then
        printf 'apk\n'
    elif commandExists yum; then
        printf 'yum\n'
    elif commandExists brew; then
        printf 'brew\n'
    elif commandExists nix-env; then
        printf 'nix\n'
    elif commandExists xbps-install; then
        printf 'xbps\n'
    elif commandExists pkg; then
        printf 'pkg\n'
    elif commandExists pkg_add; then
        printf 'pkg_add\n'
    elif commandExists slackpkg; then
        printf 'slackpkg\n'
    elif commandExists swupd; then
        printf 'swupd\n'
    elif commandExists guix; then
        printf 'guix\n'
    elif commandExists eopkg; then
        printf 'eopkg\n'
    elif commandExists urpmi; then
        printf 'urpmi\n'
    elif commandExists pkcon; then
        printf 'pkcon\n'
    else
        printf 'unknown\n'
        return "$STP_ERROR_GENERIC"
    fi
}

# Helper function to check if package manager needs sudo
package_manager_needs_sudo() {
    manager="$1"
    case "$manager" in
        dnf|dnf_el|rpm-ostree|zypper|pacman|apt|apt_ubuntu|emerge|apk|yum|xbps|pkg|pkg_add|slackpkg|swupd|pkgutil|pkgng|pkcon|eopkg|urpmi|fwupdmgr)
            return 0  # Needs sudo
            ;;
        snap|flatpak|brew|nix|guix|cargo|pip|pip3|npm|yarn|gem|scoop|conda|spack|vcpkg|ipkg|opkg|paru|yay)
            return 1  # Doesn't need sudo
            ;;
        *)
            return 0  # Default to needing sudo for safety
            ;;
    esac
}

# Auto-detect and install packages
auto_install() {
    manager=$(detect_package_manager)
    if [ "$manager" = "unknown" ]; then
        printf 'Error: Could not detect package manager\n' >&2
        return "$STP_ERROR_GENERIC"
    fi
    
    printf 'Detected package manager: %s\n' "$manager"
    packageOperation "$manager" install "$@"
}

# Auto-detect and remove packages
auto_uninstall() {
    manager=$(detect_package_manager)
    if [ "$manager" = "unknown" ]; then
        printf 'Error: Could not detect package manager\n' >&2
        return "$STP_ERROR_GENERIC"
    fi
    
    printf 'Detected package manager: %s\n' "$manager"
    packageOperation "$manager" uninstall "$@"
}

# Auto-detect and search packages
auto_search() {
    manager=$(detect_package_manager)
    if [ "$manager" = "unknown" ]; then
        printf 'Error: Could not detect package manager\n' >&2
        return "$STP_ERROR_GENERIC"
    fi
    
    printf 'Detected package manager: %s\n' "$manager"
    packageOperation "$manager" search "$@"
}

# Auto-detect and update system
auto_update() {
    manager=$(detect_package_manager)
    if [ "$manager" = "unknown" ]; then
        printf 'Error: Could not detect package manager\n' >&2
        return "$STP_ERROR_GENERIC"
    fi
    
    printf 'Detected package manager: %s\n' "$manager"
    packageOperation "$manager" update
}

# Check if file exists
stp_file_exists() {
    [ -n "$1" ] && [ -f "$1" ]
}

# Export logs
stp_export_logs() {
    tag_name="${1:-}"

    if [ -n "$STP_LOG_PATH" ]; then
        out_path=$(_stp_validate_log_output_path "$STP_LOG_PATH") || {
            printf 'Error: Log path is outside allowed directories or has unsafe components\n' >&2
            return "$STP_ERROR_PATH"
        }
    else
        if [ "$(id -u)" -eq 0 ]; then
            base_dir=/var/log/libstp
            if [ "$STP_DRY_RUN" -ne 1 ]; then
                mkdir -p -- "$base_dir" || return "$STP_ERROR_PERMISSION"
                chmod 700 -- "$base_dir" || return "$STP_ERROR_PERMISSION"
            fi
        else
            base_dir=/tmp
        fi
        out_path="$base_dir/stp-logs-$$-$(date +%s).log"
    fi

    if [ "$STP_DRY_RUN" -eq 1 ]; then
        printf '[DRY-RUN] Would export journal logs to: %s\n' "$out_path"
        return 0
    fi

    if [ -L "$out_path" ] || { [ -e "$out_path" ] && [ ! -f "$out_path" ]; }; then
        printf 'Error: Refusing unsafe log output target: %s\n' "$out_path" >&2
        return "$STP_ERROR_PERMISSION"
    fi
    if [ -f "$out_path" ]; then
        mode=$(stat -c '%a' -- "$out_path" 2>/dev/null) || return "$STP_ERROR_PERMISSION"
        _stp_mode_is_writable "$mode" && return "$STP_ERROR_PERMISSION"
    fi

    parent=$(dirname -- "$out_path") || return "$STP_ERROR_PATH"
    tmp_log=$(mktemp "$parent/.libstp-log.XXXXXX") || return "$STP_ERROR_PERMISSION"
    chmod 600 -- "$tmp_log" || { rm -f -- "$tmp_log"; return "$STP_ERROR_PERMISSION"; }
    if [ -n "$tag_name" ]; then
        sudo journalctl -t "$tag_name" -S today -U now -o short --no-pager > "$tmp_log"
    else
        sudo journalctl "_PID=$$" -S today -U now -o short --no-pager > "$tmp_log"
    fi
    result=$?
    [ "$result" -eq 0 ] || { rm -f -- "$tmp_log"; return "$result"; }
    mv -f -- "$tmp_log" "$out_path" || { rm -f -- "$tmp_log"; return "$STP_ERROR_PERMISSION"; }
    chmod 600 -- "$out_path" || return "$STP_ERROR_PERMISSION"

    printf 'Logs exported to: %s\n' "$out_path"
}

# Usage example for secure password handling
example_secure_password_usage() {
    # Get password
    password=$(getSecurePasswordSTP)
    password_status=$?
    
    if [ $password_status -ne "$STP_SUCCESS" ]; then
        printf 'Failed to get password\n' >&2
        return "$STP_ERROR_GENERIC"
    fi
    
    # Use password
    printf '%s\n' "$password" | sudo -S some_command
    
    # Securely wipe password
    secureWipeString password
    
    return "$STP_SUCCESS"
}

# ============================================================================
# NEW libstp3 FUNCTIONS
# ============================================================================

# Set global dry-run mode
stp_set_dry_run() {
    STP_DRY_RUN="$1"
}

# Get current dry-run mode state
stp_get_dry_run() {
    printf '%d\n' "$STP_DRY_RUN"
}

readonly STP_PKCON_UNTRUSTED_ENV=STP_ALLOW_UNTRUSTED_PKCON

# Internal helper: run command respecting global dry-run mode
_stp_run_cmd() {
    if [ "$STP_DRY_RUN" -eq 1 ]; then
        printf '[DRY-RUN] Would execute: %s\n' "$*"
        return 0
    fi
    "$@"
}

# Internal helper for commands that genuinely require shell features.
_stp_run_shell_cmd() {
    if [ "$STP_DRY_RUN" -eq 1 ]; then
        printf '[DRY-RUN] Would execute: %s\n' "$1"
        return 0
    fi
    sh -c "$1"
}

_stp_is_safe_copr_name() {
    name="$1"
    [ -n "$name" ] || return 1
    [ "${#name}" -le 127 ] || return 1
    printf '%s\n' "$name" | grep -Eq '^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$'
}

_stp_is_safe_url() {
    url="$1"
    [ -n "$url" ] || return 1
    case "$url" in
        http://*|https://*) ;;
        *) return 1 ;;
    esac
    case "$url" in
        *[[:space:][:cntrl:]]*|*[\;\|\&\$\`\"\'\\\(\)\{\}\[\]\<\>\!]*)
            return 1
            ;;
    esac
    return 0
}

_stp_is_safe_repo_filename() {
    filename="$1"
    [ -n "$filename" ] || return 1
    case "$filename" in
        *..*|*/*|*\\*) return 1 ;;
        *[!A-Za-z0-9._-]*) return 1 ;;
    esac
    return 0
}

_stp_is_safe_dnf_option() {
    option="$1"
    [ -n "$option" ] || return 1
    case "$option" in
        *[[:space:]]*|*[\;\|\&\$\`\"\'\\\(\)\{\}\[\]\<\>\!]*)
            return 1
            ;;
    esac
    return 0
}

_stp_extract_repo_filename() {
    repo_url="$1"
    filename=$(printf '%s' "$repo_url" | sed 's|.*/||')
    [ -n "$filename" ] || filename="custom-repo.repo"
    if ! _stp_is_safe_repo_filename "$filename"; then
        return 1
    fi
    printf '%s\n' "$filename"
}

_stp_allow_untrusted_pkcon() {
    [ "${STP_ALLOW_UNTRUSTED_PKCON:-0}" = "1" ]
}

_stp_run_pkcon_install() {
    if _stp_allow_untrusted_pkcon; then
        if [ "$STP_DRY_RUN" -eq 1 ]; then
            printf '[DRY-RUN] Would execute: %s=1 sudo pkcon install -y --allow-untrusted %s\n' "$STP_PKCON_UNTRUSTED_ENV" "$*"
            return 0
        fi
        STP_ALLOW_UNTRUSTED_PKCON=1 sudo pkcon install -y --allow-untrusted "$@"
        return $?
    fi
    _stp_run_cmd sudo pkcon install -y "$@"
}

# Detect available package managers
# Returns space-separated list of available package managers
stp_detect_package_managers() {
    managers=""
    commandExists dnf && managers="$managers dnf"
    commandExists flatpak && managers="$managers flatpak"
    commandExists snap && managers="$managers snap"
    commandExists brew && managers="$managers brew"
    commandExists rpm-ostree && managers="$managers rpm-ostree"
    rpm -q fwupd >/dev/null 2>&1 && managers="$managers fwupd"
    commandExists zypper && managers="$managers zypper"
    commandExists pacman && managers="$managers pacman"
    commandExists apt && managers="$managers apt"
    printf '%s\n' "$managers" | sed 's/^ //'
}

# Check if specific package manager is available
stp_has_package_manager() {
    # Match C when called without arguments: report whether any supported
    # package manager exists.  Keep the historical named-manager form too.
    manager="${1:-}"
    if [ -z "$manager" ]; then
        for manager in dnf rpm-ostree apt zypper pacman brew npm pnpm bun; do
            commandExists "$manager" && return 0
        done
        return 1
    fi
    case "$manager" in
        dnf) commandExists dnf ;;
        flatpak) commandExists flatpak ;;
        snap) commandExists snap ;;
        brew) commandExists brew ;;
        rpm-ostree) commandExists rpm-ostree ;;
        fwupd) rpm -q fwupd >/dev/null 2>&1 ;;
        zypper) commandExists zypper ;;
        pacman) commandExists pacman ;;
        apt) commandExists apt ;;
        *) return 1 ;;
    esac
}

# Detect GPU vendor(s)
# Returns space-separated list of detected GPU vendors
stp_detect_gpu() {
    gpus=""
    found_sysfs=0
    for device in /sys/bus/pci/devices/*; do
        [ -r "$device/class" ] && [ -r "$device/vendor" ] || continue
        found_sysfs=1
        class=$(sed -n '1p' "$device/class" 2>/dev/null)
        case "$class" in 0x03*) ;; *) continue ;; esac
        vendor=$(strlwr "$(sed -n '1p' "$device/vendor" 2>/dev/null)")
        case "$vendor" in
            0x1002) case " $gpus " in *' amd '*) ;; *) gpus="$gpus amd" ;; esac ;;
            0x10de) case " $gpus " in *' nvidia '*) ;; *) gpus="$gpus nvidia" ;; esac ;;
            0x8086) case " $gpus " in *' intel '*) ;; *) gpus="$gpus intel" ;; esac ;;
            0x14e4) case " $gpus " in *' broadcom '*) ;; *) gpus="$gpus broadcom" ;; esac ;;
        esac
    done
    if [ "$found_sysfs" -eq 0 ] && commandExists lspci; then
        pci_data=$(lspci -Dn 2>/dev/null)
        printf '%s\n' "$pci_data" | grep -Eqi '[[:space:]]03[0-9a-f]{2}:[[:space:]]+1002:' && gpus="$gpus amd"
        printf '%s\n' "$pci_data" | grep -Eqi '[[:space:]]03[0-9a-f]{2}:[[:space:]]+10de:' && gpus="$gpus nvidia"
        printf '%s\n' "$pci_data" | grep -Eqi '[[:space:]]03[0-9a-f]{2}:[[:space:]]+8086:' && gpus="$gpus intel"
        printf '%s\n' "$pci_data" | grep -Eqi '[[:space:]]03[0-9a-f]{2}:[[:space:]]+14e4:' && gpus="$gpus broadcom"
    fi
    case " $gpus " in *' broadcom '*) ;; *)
        for compatible in /proc/device-tree/compatible /sys/firmware/devicetree/base/compatible; do
            grep -Eqi 'raspberrypi|brcm,bcm' "$compatible" 2>/dev/null && { gpus="$gpus broadcom"; break; }
        done
        case " $gpus " in *' broadcom '*) ;; *)
            commandExists vulkaninfo && vulkaninfo --summary 2>/dev/null | grep -Eqi '0?x14e4' && gpus="$gpus broadcom"
        esac
    esac
    printf '%s\n' "$gpus" | sed 's/^ //'
}

# Check if specific GPU vendor is present
stp_has_gpu() {
    vendor="$1"
    case "$vendor" in amd|nvidia|intel|broadcom) ;; *) return 1 ;; esac
    case " $(stp_detect_gpu) " in *" $vendor "*) return 0 ;; *) return 1 ;; esac
}

# Clear stdin input buffer
stp_clear_input_buffer() {
    [ -t 0 ] || return 0
    saved_stty=$(stty -g 2>/dev/null) || return 0
    stty min 0 time 1 2>/dev/null || return 0
    while IFS= read -r _discard 2>/dev/null; do :; done
    stty "$saved_stty" 2>/dev/null
}

# Helper to detect if running on RHEL/CentOS
_stp_is_el_distro() {
    distro=$(extractLinuxDistro)
    distro=$(strlwr "$distro")
    case "$distro" in
        rhel|centos|*rhel*|*centos*|*oracle*) return 0 ;;
        *) return 1 ;;
    esac
}

# Helper to detect if running on Fedora
_stp_is_fedora() {
    distro=$(extractLinuxDistro)
    distro=$(strlwr "$distro")
    [ "$distro" = "fedora" ]
}

# Update all packages using detected package managers
stp_update_all_packages() {
    skip_firmware="${1:-0}"
    
    if stp_has_package_manager dnf; then
        printf 'Updating DNF packages...\n'
        _stp_run_cmd sudo dnf update --refresh -y
    fi
    
    if stp_has_package_manager rpm-ostree; then
        printf 'Updating rpm-ostree packages...\n'
        _stp_run_cmd sudo rpm-ostree upgrade
    fi
    
    if stp_has_package_manager flatpak; then
        printf 'Updating Flatpak packages...\n'
        _stp_run_cmd flatpak update -y
    fi
    
    if stp_has_package_manager snap; then
        printf 'Updating Snap packages...\n'
        _stp_run_cmd sudo snap refresh
    fi
    
    if [ "$skip_firmware" -eq 0 ] && stp_has_package_manager fwupd; then
        printf 'Updating firmware...\n'
        _stp_run_shell_cmd "sudo fwupdmgr refresh --force && sudo fwupdmgr get-updates && sudo fwupdmgr update -y"
    fi
    
    if stp_has_package_manager brew; then
        printf 'Updating Homebrew packages...\n'
        _stp_run_shell_cmd "brew update && brew upgrade"
    fi
    
    if stp_has_package_manager zypper; then
        printf 'Updating zypper packages...\n'
        _stp_run_cmd sudo zypper update -y
    fi
    
    if stp_has_package_manager pacman; then
        printf 'Updating pacman packages...\n'
        _stp_run_cmd sudo pacman -Syu
    fi
    
    if stp_has_package_manager apt; then
        printf 'Updating apt packages...\n'
        _stp_run_shell_cmd "sudo apt update && sudo apt upgrade -y"
    fi
    
    return 0
}

# Internal helper: true when a command produces non-empty stdout/stderr output
_stp_command_output_nonempty() {
    output=$("$@" 2>/dev/null)
    [ -n "$output" ]
}

# Internal helper: true when a shell pipeline produces non-empty output
_stp_shell_output_nonempty() {
    output=$(sh -c "$1" 2>/dev/null)
    [ -n "$output" ]
}

# Internal helper: check one package manager for pending updates
_stp_check_manager_pending_updates() {
    manager_name="$1"
    skip_firmware="${2:-0}"

    case "$manager_name" in
        dnf)
            dnf check-update -q >/dev/null 2>&1
            [ $? -eq 100 ]
            ;;
        rpm-ostree)
            rpm-ostree upgrade --check >/dev/null 2>&1
            [ $? -eq 77 ]
            ;;
        flatpak)
            _stp_shell_output_nonempty "flatpak update --dry-run --assumeno 2>/dev/null | grep -Ev '^(Nothing to do|Info:|Looking for updates)' | grep -q ."
            ;;
        snap)
            # snap refresh --list prints "All snaps up to date." even when nothing is pending
            _stp_shell_output_nonempty "snap refresh --list 2>/dev/null | grep -Ev '^(All snaps up to date\\.|$)' | grep -q ."
            ;;
        brew)
            _stp_shell_output_nonempty "brew outdated --quiet 2>/dev/null"
            ;;
        fwupd|fwupdmgr)
            [ "$skip_firmware" -ne 0 ] && return 1
            sudo fwupdmgr refresh --force >/dev/null 2>&1 || return 1
            _stp_command_output_nonempty sudo fwupdmgr get-updates
            ;;
        zypper)
            zypper --no-refresh list-updates >/dev/null 2>&1
            [ $? -eq 103 ]
            ;;
        pacman)
            if commandExists checkupdates; then
                _stp_command_output_nonempty checkupdates
            else
                _stp_command_output_nonempty pacman -Qu
            fi
            ;;
        apt)
            _stp_shell_output_nonempty "apt list --upgradable 2>/dev/null | grep -v '^Listing'"
            ;;
        *)
            return 1
            ;;
    esac
}

# Check for pending system updates across detected package managers (read-only)
stp_check_pending_updates() {
    skip_firmware="${1:-0}"
    case "$skip_firmware" in
        0|1) ;;
        true|True|yes|Yes) skip_firmware=1 ;;
        *) skip_firmware=0 ;;
    esac

    managers=$(stp_detect_package_managers)
    for manager in $managers; do
        if _stp_check_manager_pending_updates "$manager" "$skip_firmware"; then
            return 1
        fi
    done
    return 0
}

# Check whether comma-separated RPM package names have pending DNF updates
stp_check_dnf_packages_pending_updates() {
    packages="$1"
    [ -n "$packages" ] || return 0
    commandExists dnf || return 0

    old_ifs="$IFS"
    IFS=','
    for pkg in $packages; do
        IFS="$old_ifs"
        pkg=$(printf '%s' "$pkg" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
        [ -n "$pkg" ] || continue
        isValidPackageName "$pkg" || continue
        rpm -q "$pkg" >/dev/null 2>&1 || continue
        dnf check-update -q "$pkg" >/dev/null 2>&1
        [ $? -eq 100 ] && return 1
        IFS=','
    done
    IFS="$old_ifs"
    return 0
}

# Check whether a systemd user unit file exists
stp_user_systemd_unit_exists() {
    unit_name="$1"
    [ -n "$unit_name" ] || return 1
    case "$unit_name" in */*) return 1 ;; esac
    [ -n "${HOME:-}" ] && [ -f "${HOME}/.config/systemd/user/$unit_name" ] && return 0
    [ -f "/usr/lib/systemd/user/$unit_name" ]
}

# Enable or disable a systemd user unit immediately
stp_user_systemd_unit_set_enabled() {
    unit_name="$1"
    enabled="$2"
    case "$enabled" in
        0|false|False|no|No) enabled=0 ;;
        *) enabled=1 ;;
    esac
    [ -n "$unit_name" ] || return 255
    case "$unit_name" in */*) return 255 ;; esac
    commandExists systemctl || {
        printf 'Error: systemctl is not available.\n' >&2
        return 1
    }
    if [ "$enabled" -eq 1 ]; then
        systemctl --user enable --now "$unit_name"
    else
        systemctl --user disable --now "$unit_name"
    fi
}

# Install packages using multiple package managers with fallback
stp_multi_install_packages() {
    packages="$1"
    [ -z "$packages" ] && return 1
    
    # Process comma-separated list
    printf '%s\n' "$packages" | tr ',' '\n' | while IFS= read -r pkg; do
        # Trim whitespace
        pkg=$(printf '%s' "$pkg" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
        [ -z "$pkg" ] && continue
        
        if ! isValidPackageName "$pkg"; then
            printf 'Error: Invalid package name: %s\n' "$pkg" >&2
            continue
        fi
        
        installed=0
        
        # Try flatpak first
        if [ "$installed" -eq 0 ] && stp_has_package_manager flatpak; then
            if _stp_run_cmd flatpak install -y flathub "$pkg"; then
                printf '%s installed successfully using Flatpak.\n' "$pkg"
                installed=1
            fi
        fi
        
        # Try native package managers
        if [ "$installed" -eq 0 ] && stp_has_package_manager dnf; then
            if _stp_run_cmd sudo dnf install --refresh -y "$pkg"; then
                printf '%s installed successfully using DNF.\n' "$pkg"
                installed=1
            fi
        fi
        
        if [ "$installed" -eq 0 ] && stp_has_package_manager rpm-ostree; then
            if _stp_run_cmd sudo rpm-ostree install -y "$pkg" -A; then
                printf '%s installed successfully using rpm-ostree.\n' "$pkg"
                installed=1
            fi
        fi
        
        if [ "$installed" -eq 0 ] && stp_has_package_manager apt; then
            if _stp_run_cmd sudo apt install -y "$pkg"; then
                printf '%s installed successfully using apt.\n' "$pkg"
                installed=1
            fi
        fi
        
        if [ "$installed" -eq 0 ] && stp_has_package_manager zypper; then
            if _stp_run_cmd sudo zypper install -y "$pkg"; then
                printf '%s installed successfully using zypper.\n' "$pkg"
                installed=1
            fi
        fi
        
        if [ "$installed" -eq 0 ] && stp_has_package_manager pacman; then
            if _stp_run_cmd sudo pacman -S --noconfirm "$pkg"; then
                printf '%s installed successfully using pacman.\n' "$pkg"
                installed=1
            fi
        fi
        
        if [ "$installed" -eq 0 ] && stp_has_package_manager brew; then
            if _stp_run_cmd brew install "$pkg"; then
                printf '%s installed successfully using Homebrew.\n' "$pkg"
                installed=1
            fi
        fi
        
        if [ "$installed" -eq 0 ] && stp_has_package_manager snap; then
            if _stp_run_cmd sudo snap install "$pkg"; then
                printf '%s installed successfully using Snap.\n' "$pkg"
                installed=1
            fi
        fi

        if [ "$installed" -eq 0 ] && commandExists npm; then
            if _stp_run_cmd npm install -g "$pkg"; then
                printf '%s installed successfully using npm.\n' "$pkg"
                installed=1
            fi
        fi

        if [ "$installed" -eq 0 ] && commandExists pnpm; then
            if _stp_run_cmd pnpm add -g "$pkg"; then
                printf '%s installed successfully using pnpm.\n' "$pkg"
                installed=1
            fi
        fi

        if [ "$installed" -eq 0 ] && commandExists bun; then
            if _stp_run_cmd bun add -g "$pkg"; then
                printf '%s installed successfully using Bun.\n' "$pkg"
                installed=1
            fi
        fi
        
        if [ "$installed" -eq 0 ]; then
            printf 'Failed to install: %s\n' "$pkg" >&2
        fi
    done
    
    return 0
}

# Remove packages using multiple package managers with fallback
stp_multi_remove_packages() {
    packages="$1"
    [ -z "$packages" ] && return 1
    
    printf '%s\n' "$packages" | tr ',' '\n' | while IFS= read -r pkg; do
        pkg=$(printf '%s' "$pkg" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
        [ -z "$pkg" ] && continue
        
        if ! isValidPackageName "$pkg"; then
            printf 'Error: Invalid package name: %s\n' "$pkg" >&2
            continue
        fi
        
        removed=0
        
        if [ "$removed" -eq 0 ] && stp_has_package_manager flatpak; then
            if _stp_run_cmd flatpak uninstall -y "$pkg"; then
                printf '%s removed successfully using Flatpak.\n' "$pkg"
                removed=1
            fi
        fi
        
        if [ "$removed" -eq 0 ] && stp_has_package_manager dnf; then
            if _stp_run_cmd sudo dnf remove -y "$pkg"; then
                printf '%s removed successfully using DNF.\n' "$pkg"
                removed=1
            fi
        fi
        
        if [ "$removed" -eq 0 ] && stp_has_package_manager rpm-ostree; then
            if _stp_run_cmd sudo rpm-ostree uninstall -y "$pkg" -A; then
                printf '%s removed successfully using rpm-ostree.\n' "$pkg"
                removed=1
            fi
        fi
        
        if [ "$removed" -eq 0 ] && stp_has_package_manager apt; then
            if _stp_run_cmd sudo apt remove -y "$pkg"; then
                printf '%s removed successfully using apt.\n' "$pkg"
                removed=1
            fi
        fi
        
        if [ "$removed" -eq 0 ] && stp_has_package_manager zypper; then
            if _stp_run_cmd sudo zypper remove -y "$pkg"; then
                printf '%s removed successfully using zypper.\n' "$pkg"
                removed=1
            fi
        fi
        
        if [ "$removed" -eq 0 ] && stp_has_package_manager pacman; then
            if _stp_run_cmd sudo pacman -R --noconfirm "$pkg"; then
                printf '%s removed successfully using pacman.\n' "$pkg"
                removed=1
            fi
        fi
        
        if [ "$removed" -eq 0 ] && stp_has_package_manager brew; then
            if _stp_run_cmd brew uninstall "$pkg"; then
                printf '%s removed successfully using Homebrew.\n' "$pkg"
                removed=1
            fi
        fi
        
        if [ "$removed" -eq 0 ] && stp_has_package_manager snap; then
            if _stp_run_cmd sudo snap remove "$pkg"; then
                printf '%s removed successfully using Snap.\n' "$pkg"
                removed=1
            fi
        fi

        if [ "$removed" -eq 0 ] && commandExists npm; then
            if _stp_run_cmd npm uninstall -g "$pkg"; then
                printf '%s removed successfully using npm.\n' "$pkg"
                removed=1
            fi
        fi

        if [ "$removed" -eq 0 ] && commandExists pnpm; then
            if _stp_run_cmd pnpm remove -g "$pkg"; then
                printf '%s removed successfully using pnpm.\n' "$pkg"
                removed=1
            fi
        fi

        if [ "$removed" -eq 0 ] && commandExists bun; then
            if _stp_run_cmd bun remove -g "$pkg"; then
                printf '%s removed successfully using Bun.\n' "$pkg"
                removed=1
            fi
        fi
        
        if [ "$removed" -eq 0 ]; then
            printf 'Failed to remove: %s\n' "$pkg" >&2
        fi
    done
    
    return 0
}

# Search for packages across all available package managers
stp_search_packages() {
    packages="$1"
    [ -z "$packages" ] && return 1
    
    printf 'Searching for: %s\n\n' "$packages"
    
    if stp_has_package_manager flatpak; then
        printf '=== Flatpak Results ===\n'
        flatpak search "$packages"
        printf '\n'
    fi
    
    if stp_has_package_manager dnf; then
        printf '=== DNF Results ===\n'
        dnf search "$packages"
        printf '\n'
    fi
    
    if stp_has_package_manager rpm-ostree; then
        printf '=== rpm-ostree Results ===\n'
        rpm-ostree search "$packages" 2>/dev/null || printf 'Search not supported\n'
        printf '\n'
    fi
    
    if stp_has_package_manager apt; then
        printf '=== apt Results ===\n'
        apt search "$packages"
        printf '\n'
    fi
    
    if stp_has_package_manager zypper; then
        printf '=== zypper Results ===\n'
        zypper search "$packages"
        printf '\n'
    fi
    
    if stp_has_package_manager pacman; then
        printf '=== pacman Results ===\n'
        pacman -Ss "$packages"
        printf '\n'
    fi
    
    if stp_has_package_manager brew; then
        printf '=== Homebrew Results ===\n'
        brew search "$packages"
        printf '\n'
    fi
    
    if stp_has_package_manager snap; then
        printf '=== Snap Results ===\n'
        snap find "$packages"
        printf '\n'
    fi

    if commandExists npm; then
        printf '=== npm Results ===\n'
        npm search "$packages"
        printf '\n'
    fi

    if commandExists pnpm; then
        printf '=== pnpm Results ===\n'
        pnpm search "$packages"
        printf '\n'
    fi

    if commandExists bun; then
        printf '=== Bun Results ===\n'
        bun pm search "$packages"
        printf '\n'
    fi
    
    return 0
}

# Setup RPM Fusion repositories
# type: free, free-tainted, nonfree, nonfree-tainted, all, all-with-tainted
stp_setup_rpmfusion() {
    type="$1"
    
    case "$type" in
        free)
            if _stp_is_fedora; then
                if stp_has_package_manager dnf; then
                    _stp_run_shell_cmd "sudo dnf install -y https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-\$(rpm -E %fedora).noarch.rpm"
                    _stp_run_shell_cmd "sudo dnf config-manager setopt fedora-cisco-openh264.enabled=1"
                    _stp_run_shell_cmd "sudo dnf group upgrade -y core"
                elif stp_has_package_manager rpm-ostree; then
                    _stp_run_shell_cmd "sudo rpm-ostree install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-\$(rpm -E %fedora).noarch.rpm"
                fi
            elif _stp_is_el_distro; then
                _stp_run_shell_cmd "sudo subscription-manager repos --enable \"codeready-builder-for-rhel-\$(rpm -E %{rhel})-\$(uname -m)-rpms\""
                _stp_run_shell_cmd "sudo dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-\$(rpm -E %rhel).noarch.rpm"
                _stp_run_shell_cmd "sudo dnf install -y https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-\$(rpm -E %rhel).noarch.rpm"
                _stp_run_shell_cmd "sudo dnf groupupdate -y core"
            fi
            ;;
        free-tainted)
            if stp_has_package_manager dnf; then
                _stp_run_shell_cmd "sudo dnf install -y rpmfusion-free-release-tainted"
            elif stp_has_package_manager rpm-ostree; then
                _stp_run_shell_cmd "sudo rpm-ostree install -y rpmfusion-free-release-tainted -A"
            fi
            ;;
        nonfree)
            if _stp_is_fedora; then
                if stp_has_package_manager dnf; then
                    _stp_run_shell_cmd "sudo dnf install -y https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-\$(rpm -E %fedora).noarch.rpm"
                    _stp_run_shell_cmd "sudo dnf config-manager setopt fedora-cisco-openh264.enabled=1"
                    _stp_run_shell_cmd "sudo dnf group upgrade -y core"
                elif stp_has_package_manager rpm-ostree; then
                    _stp_run_shell_cmd "sudo rpm-ostree install https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-\$(rpm -E %fedora).noarch.rpm"
                fi
            elif _stp_is_el_distro; then
                _stp_run_shell_cmd "sudo subscription-manager repos --enable \"codeready-builder-for-rhel-\$(rpm -E %{rhel})-\$(uname -m)-rpms\""
                _stp_run_shell_cmd "sudo dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-\$(rpm -E %rhel).noarch.rpm"
                _stp_run_shell_cmd "sudo dnf install -y https://mirrors.rpmfusion.org/nonfree/el/rpmfusion-nonfree-release-\$(rpm -E %rhel).noarch.rpm"
                _stp_run_shell_cmd "sudo dnf groupupdate -y core"
            fi
            ;;
        nonfree-tainted)
            if stp_has_package_manager dnf; then
                _stp_run_shell_cmd "sudo dnf install -y rpmfusion-nonfree-release-tainted"
            elif stp_has_package_manager rpm-ostree; then
                _stp_run_shell_cmd "sudo rpm-ostree install -y rpmfusion-nonfree-release-tainted -A"
            fi
            ;;
        all)
            stp_setup_rpmfusion free
            stp_setup_rpmfusion nonfree
            ;;
        all-with-tainted)
            stp_setup_rpmfusion free
            stp_setup_rpmfusion free-tainted
            stp_setup_rpmfusion nonfree
            stp_setup_rpmfusion nonfree-tainted
            ;;
        *)
            printf 'Error: Unknown RPM Fusion type: %s\n' "$type" >&2
            printf 'Valid types: free, free-tainted, nonfree, nonfree-tainted, all, all-with-tainted\n' >&2
            return 1
            ;;
    esac
    
    return 0
}

# Add a Copr repository
stp_add_copr_repo() {
    copr_name="$1"
    [ -z "$copr_name" ] && return 1
    if ! _stp_is_safe_copr_name "$copr_name"; then
        printf 'Error: Invalid Copr name format. Use: creator/project\n' >&2
        printf 'Only alphanumeric characters, underscores, hyphens, and dots are allowed.\n' >&2
        return 1
    fi
    
    if stp_has_package_manager dnf; then
        _stp_run_cmd sudo dnf copr enable -y "$copr_name"
    elif stp_has_package_manager rpm-ostree; then
        # Extract creator name from copr_name (format: creator/project)
        creator=$(printf '%s' "$copr_name" | cut -d'/' -f1)
        fedora_ver=$(rpm -E '%fedora' 2>/dev/null) || return 1
        copr_repo="https://copr.fedorainfracloud.org/coprs/$copr_name/repo/fedora-$fedora_ver/$creator-fedora-$fedora_ver.repo"
        _stp_run_cmd sudo curl -fsSL -o "/etc/yum.repos.d/$creator.repo" -- "$copr_repo"
    else
        printf 'Error: No compatible package manager found for adding Copr repositories\n' >&2
        return 1
    fi
    
    return 0
}

# Add a repository from URL
stp_add_repo_url() {
    repo_url="$1"
    [ -z "$repo_url" ] && return 1
    if ! _stp_is_safe_url "$repo_url"; then
        printf 'Error: Invalid URL format. Must start with http:// or https://\n' >&2
        return 1
    fi
    
    filename=$(_stp_extract_repo_filename "$repo_url") || {
        printf 'Error: Invalid repository filename in URL\n' >&2
        return 1
    }
    
    if stp_has_package_manager dnf || stp_has_package_manager rpm-ostree; then
        _stp_run_cmd sudo curl -fsSL -o "/etc/yum.repos.d/$filename" -- "$repo_url"
    else
        printf 'Error: No compatible package manager found for adding repositories from URL\n' >&2
        return 1
    fi
    
    return 0
}

# Setup and enable snapd
stp_setup_snapd() {
    if stp_has_package_manager dnf; then
        _stp_run_cmd sudo dnf install -y snapd
    elif stp_has_package_manager rpm-ostree; then
        _stp_run_cmd sudo rpm-ostree install -y snapd -A
    fi
    _stp_run_cmd sudo ln -s /var/lib/snapd/snap /snap
    
    return 0
}

# Configure DNF settings
# option: defaultyes, parallel, all
stp_configure_dnf() {
    option="$1"
    
    case "$option" in
        defaultyes)
            if _stp_is_el_distro; then
                _stp_run_shell_cmd "sudo dnf config-manager --setopt=\"defaultyes=True\" --save"
            else
                _stp_run_shell_cmd "sudo dnf config-manager setopt defaultyes=True"
            fi
            ;;
        parallel)
            if _stp_is_el_distro; then
                _stp_run_shell_cmd "sudo dnf config-manager --setopt=\"max_parallel_downloads=10\" --save"
            else
                _stp_run_shell_cmd "sudo dnf config-manager setopt max_parallel_downloads=10"
            fi
            ;;
        all)
            if _stp_is_el_distro; then
                _stp_run_shell_cmd "sudo dnf config-manager --setopt=\"defaultyes=True\" --setopt=\"max_parallel_downloads=10\" --save"
            else
                _stp_run_shell_cmd "sudo dnf config-manager setopt defaultyes=True max_parallel_downloads=10"
            fi
            ;;
        *)
            printf 'Error: Unknown DNF config option: %s\n' "$option" >&2
            printf 'Valid options: defaultyes, parallel, all\n' >&2
            return 1
            ;;
    esac
    
    return 0
}

# Configure DNF with custom option
stp_configure_dnf_custom() {
    option="$1"
    [ -z "$option" ] && return 1
    if ! _stp_is_safe_dnf_option "$option"; then
        printf 'stp_configure_dnf_custom: invalid option contains unsafe characters\n' >&2
        return 1
    fi
    
    if _stp_is_el_distro; then
        _stp_run_cmd sudo dnf config-manager --setopt "$option" --save
    else
        _stp_run_cmd sudo dnf config-manager setopt "$option"
    fi
    
    return 0
}

# Setup multimedia codecs
# type: general, dvd, gpu
stp_setup_codecs() {
    type="$1"
    
    case "$type" in
        general)
            if stp_has_package_manager dnf; then
                _stp_run_shell_cmd "sudo dnf swap -y ffmpeg-free ffmpeg --allowerasing"
                if _stp_is_el_distro; then
                    _stp_run_shell_cmd "sudo dnf groupupdate -y multimedia --setop=\"install_weak_deps=False\" --exclude=PackageKit-gstreamer-plugin"
                    _stp_run_shell_cmd "sudo dnf groupupdate -y sound-and-video"
                else
                    _stp_run_shell_cmd "sudo dnf group upgrade -y multimedia -x PackageKit-gstreamer-plugin"
                    _stp_run_shell_cmd "sudo dnf group upgrade -y sound-and-video"
                fi
                _stp_run_shell_cmd "sudo dnf --repo=rpmfusion-nonfree-tainted install \"*-firmware\" -y"
            elif stp_has_package_manager rpm-ostree; then
                _stp_run_shell_cmd "sudo rpm-ostree override -y remove ffmpeg-free --install ffmpeg -A"
                _stp_run_shell_cmd "sudo rpm-ostree install -y gstreamer1-plugin-libav gstreamer1-plugins-bad-free-extras gstreamer1-plugins-bad-freeworld gstreamer1-plugins-ugly gstreamer1-vaapi -A"
                _stp_run_shell_cmd "sudo rpm-ostree override -y remove libavcodec-free libavfilter-free libavformat-free libavutil-free libpostproc-free libswresample-free libswscale-free --install ffmpeg -A"
            fi
            ;;
        dvd)
            if stp_has_package_manager dnf; then
                _stp_run_shell_cmd "sudo dnf install -y libdvdcss"
            elif stp_has_package_manager rpm-ostree; then
                _stp_run_shell_cmd "sudo rpm-ostree install -y libdvdcss -A"
            fi
            ;;
        gpu)
            if stp_has_gpu amd; then
                printf 'Installing AMD GPU codecs...\n'
                if stp_has_package_manager dnf; then
                    _stp_run_shell_cmd "sudo dnf swap -y mesa-va-drivers mesa-va-drivers-freeworld"
                    _stp_run_shell_cmd "sudo dnf swap -y mesa-vdpau-drivers mesa-vdpau-drivers-freeworld"
                    _stp_run_shell_cmd "sudo dnf swap -y mesa-va-drivers.i686 mesa-va-drivers-freeworld.i686"
                    _stp_run_shell_cmd "sudo dnf swap -y mesa-vdpau-drivers.i686 mesa-vdpau-drivers-freeworld.i686"
                elif stp_has_package_manager rpm-ostree; then
                    _stp_run_shell_cmd "sudo rpm-ostree override -y remove mesa-va-drivers --install mesa-va-drivers-freeworld -A"
                    _stp_run_shell_cmd "sudo rpm-ostree override -y remove mesa-vdpau-drivers --install mesa-vdpau-drivers-freeworld -A"
                fi
            fi
            if stp_has_gpu nvidia; then
                printf 'Installing NVIDIA GPU codecs...\n'
                if stp_has_package_manager dnf; then
                    _stp_run_shell_cmd "sudo dnf install -y libva-nvidia-driver"
                elif stp_has_package_manager rpm-ostree; then
                    _stp_run_shell_cmd "sudo rpm-ostree install -y libva-nvidia-driver -A"
                fi
            fi
            if stp_has_gpu intel; then
                printf 'Installing Intel GPU codecs...\n'
                if stp_has_package_manager dnf; then
                    _stp_run_shell_cmd "sudo dnf install -y intel-media-driver"
                elif stp_has_package_manager rpm-ostree; then
                    _stp_run_shell_cmd "sudo rpm-ostree install -y intel-media-driver -A"
                fi
            fi
            ;;
        *)
            printf 'Error: Unknown codec type: %s\n' "$type" >&2
            printf 'Valid types: general, dvd, gpu\n' >&2
            return 1
            ;;
    esac
    
    return 0
}

# Show system information
# type: pci, usb, storage
stp_show_system_info() {
    type="$1"
    
    case "$type" in
        pci)
            lspci -nnk
            ;;
        usb)
            lsusb
            ;;
        storage)
            lsblk -fa
            ;;
        *)
            printf 'Error: Unknown system info type: %s\n' "$type" >&2
            printf 'Valid types: pci, usb, storage\n' >&2
            return 1
            ;;
    esac
    
    return 0
}

# Show system overview using specified tool
stp_show_system_overview() {
    tool="$1"
    
    if [ -z "$tool" ]; then
        # Default fallback: try fastfetch, then neofetch, then basic info
        if commandExists fastfetch; then
            FFTS_IGNORE_PARENT=1 fastfetch
        elif commandExists neofetch; then
            neofetch
        else
            # Basic fallback
            printf '=== System Overview ===\n'
            uname -a
            printf '\n'
            cat /etc/os-release
            printf '\n'
            free -h
            printf '\n'
            df -h /
        fi
        return 0
    fi
    
    # Check if specified tool exists
    if ! commandExists "$tool"; then
        printf 'Error: %s is not installed\n' "$tool" >&2
        return 1
    fi
    
    "$tool"
}

# Check if a distro string is RHEL-family (Enterprise Linux)
stp_is_el_distro_str() {
    distro=$(strlwr "$1")
    [ -z "$distro" ] && return 1
    
    case "$distro" in
        rhel|centos|oracle|*rhel*|*centos*|*oracle*)
            return 0
            ;;
        *)
            return 1
            ;;
    esac
}

# Clear terminal screen using ANSI escape sequence (avoids shell process spawn)
stp_clear_screen() {
    printf '\033[H\033[J'
}

# Validate Copr name format (prevents command injection)
# Valid format: alphanumeric, underscore, hyphen, dot, with exactly one slash
stp_is_safe_copr_name() {
    name="$1"
    [ -z "$name" ] && return 1
    
    # Check length
    if [ "${#name}" -gt 127 ]; then
        return 1
    fi
    
    # Count slashes - must have exactly one
    slash_count=$(printf '%s' "$name" | tr -cd '/' | wc -c)
    if [ "$slash_count" -ne 1 ]; then
        return 1
    fi
    
    # Check for invalid characters (only allow alphanumeric, underscore, hyphen, dot, and slash)
    case "$name" in
        *[!a-zA-Z0-9._/-]*)
            return 1
            ;;
    esac
    
    return 0
}

# Validate URL format (basic security check for command injection)
stp_is_safe_url() {
    url="$1"
    [ -z "$url" ] && return 1
    
    # Check for shell metacharacters that could enable injection
    case "$url" in
        *';'*|*'|'*|*'&'*|*'$'*|*'`'*|*'"'*|*"'"*|*'\\'*|*'('*|*')'*|*'{'*|*'}'*|*'['*|*']'*|*'<'*|*'>'*|*'!'*)
            return 1
            ;;
    esac
    
    # Basic URL format check - must start with http:// or https://
    case "$url" in
        http://*|https://*)
            return 0
            ;;
        *)
            return 1
            ;;
    esac
}

# Run sudo command with password via stdin pipe
# Usage: stp_run_sudo_with_password "password" command arg1 arg2 ...
stp_run_sudo_with_password() {
    password="$1"
    shift
    
    [ -z "$password" ] && return 1
    [ $# -eq 0 ] && return 1
    
    # Use printf to pipe password to sudo -S
    printf '%s\n' "$password" | sudo -S "$@" >/dev/null 2>&1
    return $?
}

# Run command with optional dry-run mode (explicit parameter)
stp_dry_run_command() {
    # Preferred argv-safe form: stp_dry_run_command 0|1 command [arg ...]
    # Legacy form remains supported: stp_dry_run_command "command string" 0|1
    case "${1:-}" in
        0|1|true|false)
            dry_run="$1"
            shift
            [ $# -gt 0 ] || return "$STP_ERROR_GENERIC"
            if [ "$dry_run" = 1 ] || [ "$dry_run" = true ]; then
                printf '[DRY-RUN] Would execute:'
                printf ' %s' "$@"
                printf '\n'
                return 0
            fi
            "$@"
            ;;
        *)
            command="$1"
            dry_run="${2:-0}"
            if [ "$dry_run" = 1 ] || [ "$dry_run" = true ]; then
                printf '[DRY-RUN] Would execute: %s\n' "$command"
                return 0
            fi
            sh -c "$command"
            ;;
    esac
}

# ============================================================================
# Resource Path and Ansible Functions
# ============================================================================

# Initialize resource paths by searching multiple directories for a subdirectory
# Mirrors the pattern from stp_init_resource_paths() in C library
#
# Usage: stp_init_resource_paths result_var "path1:path2:path3" "subdir" "config.cfg" "default_base"
# Result is stored in variables: ${result_var}_base_path, ${result_var}_resource_path, 
#                                ${result_var}_config_path, ${result_var}_found
_stp_set_named_value() {
    variable_name="$1"
    variable_value="$2"
    printf '%s\n' "$variable_name" | grep -Eq '^[A-Za-z_][A-Za-z0-9_]*$' || return 1
    escaped_value=$(printf '%s' "$variable_value" | sed "s/'/'\\\\''/g")
    eval "$variable_name='$escaped_value'"
}

stp_init_resource_paths() {
    result_var="$1"
    search_paths="$2"    # colon-separated list of paths to search
    subdir_name="$3"     # subdirectory name to look for
    config_filename="$4" # config file name (optional)
    default_base="$5"    # default base path if not found
    
    printf '%s\n' "$result_var" | grep -Eq '^[A-Za-z_][A-Za-z0-9_]*$' || return 1

    # Initialize result variables without evaluating caller-controlled values.
    _stp_set_named_value "${result_var}_base_path" "" || return 1
    _stp_set_named_value "${result_var}_resource_path" "" || return 1
    _stp_set_named_value "${result_var}_config_path" "" || return 1
    _stp_set_named_value "${result_var}_found" 0 || return 1
    
    # Search through provided paths
    if [ -n "$search_paths" ] && [ -n "$subdir_name" ]; then
        # Save and restore IFS
        old_ifs="$IFS"
        IFS=':'
        for search_path in $search_paths; do
            IFS="$old_ifs"
            [ -z "$search_path" ] && continue
            
            test_path="${search_path}/${subdir_name}"
            if [ -d "$test_path" ]; then
                # Found the subdirectory
                _stp_set_named_value "${result_var}_base_path" "$search_path" || return 1
                _stp_set_named_value "${result_var}_resource_path" "$test_path" || return 1
                
                if [ -n "$config_filename" ]; then
                    _stp_set_named_value "${result_var}_config_path" "${search_path}/${config_filename}" || return 1
                fi
                
                _stp_set_named_value "${result_var}_found" 1 || return 1
                IFS="$old_ifs"
                return 0
            fi
        done
        IFS="$old_ifs"
    fi
    
    # Not found - use default if provided
    if [ -n "$default_base" ]; then
        _stp_set_named_value "${result_var}_base_path" "$default_base" || return 1
        
        if [ -n "$subdir_name" ]; then
            _stp_set_named_value "${result_var}_resource_path" "${default_base}/${subdir_name}" || return 1
        else
            _stp_set_named_value "${result_var}_resource_path" "$default_base" || return 1
        fi
        
        if [ -n "$config_filename" ]; then
            _stp_set_named_value "${result_var}_config_path" "${default_base}/${config_filename}" || return 1
        fi
    fi
    
    return 1
}

# Run an Ansible playbook with optional extra variables
# Mirrors stp_run_ansible_playbook() from C library
stp_run_ansible_playbook() {
    playbook_path="$1"
    extra_vars="$2"
    config_path="$3"
    target_hosts="${4:-localhost}"
    ask_become_pass="${5:-0}"
    
    if [ -z "$playbook_path" ]; then
        printf 'Error: Playbook path is required\n' >&2
        return 1
    fi
    
    resolved_playbook=$(_stp_resolve_and_validate_path "$playbook_path") || return 1
    _stp_is_safe_ansible_arg "$extra_vars" || {
        printf 'Error: Extra variables contain unsafe characters\n' >&2
        return 1
    }
    _stp_is_safe_hostname "$target_hosts" || {
        printf 'Error: Invalid target hosts\n' >&2
        return 1
    }
    resolved_config=""
    if [ -n "$config_path" ]; then
        resolved_config=$(_stp_resolve_and_validate_path "$config_path") || return 1
    fi
    
    # Build extra vars with target_hosts included
    if [ -n "$extra_vars" ]; then
        full_extra_vars="target_hosts=$target_hosts $extra_vars"
    else
        full_extra_vars="target_hosts=$target_hosts"
    fi
    
    printf '\n[Running Ansible playbook: %s on hosts: %s]\n\n' "$resolved_playbook" "$target_hosts"
    
    # Dry-run mode: just print what would happen
    if [ "$STP_DRY_RUN" -eq 1 ]; then
        if [ -n "$resolved_config" ]; then
            printf "[DRY-RUN] Would execute: ANSIBLE_CONFIG='%s' ansible-playbook %s -e '%s'\n" \
                   "$resolved_config" "$resolved_playbook" "$full_extra_vars"
        else
            printf "[DRY-RUN] Would execute: ansible-playbook %s -e '%s'\n" \
                   "$resolved_playbook" "$full_extra_vars"
        fi
        return 0
    fi
    
    # Build and execute command
    if [ -n "$resolved_config" ]; then
        if [ "$ask_become_pass" -eq 1 ] || [ "$ask_become_pass" = "true" ]; then
            ANSIBLE_CONFIG="$resolved_config" ansible-playbook "$resolved_playbook" -e "$full_extra_vars" -K
        else
            ANSIBLE_CONFIG="$resolved_config" ansible-playbook "$resolved_playbook" -e "$full_extra_vars" 2>&1
        fi
    else
        if [ "$ask_become_pass" -eq 1 ] || [ "$ask_become_pass" = "true" ]; then
            ansible-playbook "$resolved_playbook" -e "$full_extra_vars" -K
        else
            ansible-playbook "$resolved_playbook" -e "$full_extra_vars" 2>&1
        fi
    fi
    result=$?
    printf '\n'
    
    return $result
}

# Run a local script on remote hosts via Ansible's script module
# Mirrors stp_run_ansible_script() from C library
stp_run_ansible_script() {
    script_path="$1"
    config_path="$2"
    target_hosts="${3:-localhost}"
    ask_become_pass="${4:-0}"
    
    if [ -z "$script_path" ]; then
        printf 'Error: Script path is required\n' >&2
        return 1
    fi
    
    resolved_script=$(_stp_resolve_and_validate_path "$script_path") || return 1
    _stp_is_safe_hostname "$target_hosts" || {
        printf 'Error: Invalid target hosts\n' >&2
        return 1
    }
    resolved_config=""
    if [ -n "$config_path" ]; then
        resolved_config=$(_stp_resolve_and_validate_path "$config_path") || return 1
    fi
    
    printf '\n[Running script: %s on hosts: %s]\n\n' "$resolved_script" "$target_hosts"
    
    # Dry-run mode: just print what would happen
    if [ "$STP_DRY_RUN" -eq 1 ]; then
        if [ -n "$resolved_config" ]; then
            printf "[DRY-RUN] Would execute: ANSIBLE_CONFIG='%s' ansible %s -m script -a '%s' -b\n" \
                   "$resolved_config" "$target_hosts" "$resolved_script"
        else
            printf "[DRY-RUN] Would execute: ansible %s -m script -a '%s' -b\n" \
                   "$target_hosts" "$resolved_script"
        fi
        return 0
    fi
    
    # Build and execute command
    if [ -n "$resolved_config" ]; then
        if [ "$ask_become_pass" -eq 1 ] || [ "$ask_become_pass" = "true" ]; then
            ANSIBLE_CONFIG="$resolved_config" ansible "$target_hosts" -m script -a "$resolved_script" -b -K
        else
            ANSIBLE_CONFIG="$resolved_config" ansible "$target_hosts" -m script -a "$resolved_script" -b
        fi
    else
        if [ "$ask_become_pass" -eq 1 ] || [ "$ask_become_pass" = "true" ]; then
            ansible "$target_hosts" -m script -a "$resolved_script" -b -K
        else
            ansible "$target_hosts" -m script -a "$resolved_script" -b
        fi
    fi
    result=$?
    printf '\n'
    
    return $result
}

# Run an ad-hoc command on remote hosts via Ansible
# Mirrors stp_run_ansible_command() from C library
stp_run_ansible_command() {
    command="$1"
    config_path="$2"
    target_hosts="${3:-localhost}"
    ask_become_pass="${4:-0}"
    
    if [ -z "$command" ]; then
        printf 'Error: Command is required\n' >&2
        return 1
    fi
    _stp_is_safe_ansible_arg "$command" || {
        printf 'Error: Command contains unsafe characters\n' >&2
        return 1
    }
    _stp_is_safe_hostname "$target_hosts" || {
        printf 'Error: Invalid target hosts\n' >&2
        return 1
    }
    resolved_config=""
    if [ -n "$config_path" ]; then
        resolved_config=$(_stp_resolve_and_validate_path "$config_path") || return 1
    fi
    
    printf '\n[Running command on hosts: %s]\n\n' "$target_hosts"
    
    # Dry-run mode: just print what would happen
    if [ "$STP_DRY_RUN" -eq 1 ]; then
        if [ -n "$resolved_config" ]; then
            printf "[DRY-RUN] Would execute: ANSIBLE_CONFIG='%s' ansible %s -m raw -a '%s'\n" \
                   "$resolved_config" "$target_hosts" "$command"
        else
            printf "[DRY-RUN] Would execute: ansible %s -m raw -a '%s'\n" \
                   "$target_hosts" "$command"
        fi
        return 0
    fi
    
    # Build and execute command
    if [ -n "$resolved_config" ]; then
        if [ "$ask_become_pass" -eq 1 ] || [ "$ask_become_pass" = "true" ]; then
            ANSIBLE_CONFIG="$resolved_config" ansible "$target_hosts" -m raw -a "$command" -K
        else
            ANSIBLE_CONFIG="$resolved_config" ansible "$target_hosts" -m raw -a "$command"
        fi
    else
        if [ "$ask_become_pass" -eq 1 ] || [ "$ask_become_pass" = "true" ]; then
            ansible "$target_hosts" -m raw -a "$command" -K
        else
            ansible "$target_hosts" -m raw -a "$command"
        fi
    fi
    result=$?
    printf '\n'
    
    return $result
}

# ============================================================================
# Extended libstp3 public API parity
# ============================================================================

# Execute a command with one NAME=value environment override.
# Usage: runCommandEnv "NAME=value" command [arg ...]
runCommandEnv() {
    env_var="$1"
    shift
    [ $# -gt 0 ] || return "$STP_ERROR_GENERIC"
    case "$env_var" in
        *=*) ;;
        *) return "$STP_ERROR_GENERIC" ;;
    esac
    if [ "$STP_DRY_RUN" -eq 1 ]; then
        printf '[DRY-RUN] Would execute: %s %s\n' "$env_var" "$*"
        return 0
    fi
    env "$env_var" "$@"
}

# Shell callers already pass a flat argv, so passthrough execution is simply
# direct execution with the same dry-run contract as the C helper.
stp_exec_with_passthrough() {
    [ $# -gt 0 ] || return "$STP_ERROR_GENERIC"
    _stp_run_cmd "$@"
}

stp_is_safe_arg() {
    arg="$1"
    [ -n "$arg" ] || return 1
    case "$arg" in
        *';'*|*'|'*|*'&'*|*'$'*|*'`'*|*'"'*|*"'"*|*'\'*|*'('*|*')'*|*'{'*|*'}'*|*'['*|*']'*|*'<'*|*'>'*|*'!'*|*"
"*|*"
"*) return 1 ;;
    esac
    return 0
}

stp_is_safe_flatpak_remote_name() {
    name="$1"
    [ -n "$name" ] || return 1
    case "$name" in *[!A-Za-z0-9._-]*) return 1 ;; esac
    return 0
}

stp_validate_commands() {
    required_cmds="$1"
    optional_cmds="${2:-}"
    missing=0
    old_ifs="$IFS"
    IFS=','
    for cmd in $required_cmds; do
        IFS="$old_ifs"
        cmd=$(printf '%s' "$cmd" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
        [ -z "$cmd" ] && continue
        if ! commandExists "$cmd"; then
            printf "Error: Required command '%s' not found.\n" "$cmd" >&2
            missing=1
        fi
        IFS=','
    done
    IFS="$old_ifs"
    [ "$missing" -eq 0 ] || {
        printf '\nPlease install missing dependencies and try again.\n' >&2
        return "$STP_ERROR_GENERIC"
    }
    IFS=','
    for cmd in $optional_cmds; do
        IFS="$old_ifs"
        cmd=$(printf '%s' "$cmd" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
        [ -z "$cmd" ] || commandExists "$cmd" || stp_log_info "Optional command '$cmd' not available"
        IFS=','
    done
    IFS="$old_ifs"
    return 0
}

stp_check_binary_conflicts() {
    [ -n "$1" ] && [ -n "$2" ] || return 0
    commandExists "$1" && commandExists "$2"
}

stp_check_package_version() {
    package_name="$1"
    min_major="$2"
    min_minor="${3:-0}"
    [ -n "$package_name" ] && [ "$min_major" -gt 0 ] 2>/dev/null || return "$STP_ERROR_GENERIC"
    version=$(getVersion "$package_name") || return "$STP_ERROR_GENERIC"
    major=$(printf '%s' "$version" | sed 's/^[^0-9]*//;s/[^0-9].*$//')
    rest=$(printf '%s' "$version" | sed 's/^[^0-9]*[0-9]*\.//')
    minor=$(printf '%s' "$rest" | sed 's/[^0-9].*$//')
    [ -n "$major" ] || return "$STP_ERROR_GENERIC"
    [ -n "$minor" ] || minor=0
    [ "$major" -gt "$min_major" ] || { [ "$major" -eq "$min_major" ] && [ "$minor" -ge "$min_minor" ]; }
}

# Enhanced config API. Values are printed to stdout, following the established
# shell convention used by fetchConfigString.
stp_config_get_string() { fetchConfigString "$1"; }
stp_config_get_bool() {
    value=$(fetchConfigString "$1") || return 1
    case $(strlwr "$value") in true|yes|1) return 0 ;; *) return 1 ;; esac
}
stp_config_get_int() {
    value=$(fetchConfigString "$1") || { printf '0\n'; return 1; }
    case "$value" in ''|*[!0-9-]*) printf '0\n'; return 1 ;; esac
    printf '%d\n' "$value"
}
stp_config_set() { saveConfigStringValue "$1" "$2"; }

# syslog-backed logging, matching the C implementation's facility and levels.
STP_LOG_PROGRAM=""
stp_log_init() { STP_LOG_PROGRAM="${1:-libstp3}"; }
_stp_log() {
    priority="$1"
    shift
    commandExists logger || return 0
    logger -p "local1.$priority" -t "${STP_LOG_PROGRAM:-libstp3}" -- "$*"
}
stp_log_info() { _stp_log info "$@"; }
stp_log_error() { _stp_log err "$@"; }
stp_log_warning() { _stp_log warning "$@"; }
stp_log_debug() { [ "${STP_DEBUG:-0}" = 1 ] && _stp_log debug "$@"; return 0; }
stp_log_close() { STP_LOG_PROGRAM=""; }

STP_DEFAULT_ALLOWED_SCRIPT_DIRS='/usr/share/
/opt/
/etc/
/var/lib/'
STP_DEFAULT_ALLOWED_LOG_DIRS='/var/log/libstp/
/tmp/'
STP_ALLOWED_SCRIPT_DIRS=""
STP_ALLOWED_LOG_DIRS=""

_stp_add_allowed_dir() {
    list_var="$1"
    dir_path="$2"
    [ -n "$dir_path" ] || return 1
    case "$dir_path" in /*) ;; *) printf 'Error: Allowed directory must be an absolute path: %s\n' "$dir_path" >&2; return 1 ;; esac
    case "$dir_path" in */) ;; *) dir_path="$dir_path/" ;; esac
    case "$list_var" in
        STP_ALLOWED_SCRIPT_DIRS) current="$STP_ALLOWED_SCRIPT_DIRS" ;;
        STP_ALLOWED_LOG_DIRS) current="$STP_ALLOWED_LOG_DIRS" ;;
        *) return 1 ;;
    esac
    count=0
    while IFS= read -r _dir; do
        [ -n "$_dir" ] && count=$((count + 1))
    done <<EOF
$current
EOF
    [ "$count" -lt 32 ] || return 1
    case "$list_var" in
        STP_ALLOWED_SCRIPT_DIRS) STP_ALLOWED_SCRIPT_DIRS="${current:+$current
}$dir_path" ;;
        STP_ALLOWED_LOG_DIRS) STP_ALLOWED_LOG_DIRS="${current:+$current
}$dir_path" ;;
    esac
}
stp_add_allowed_script_dir() { _stp_add_allowed_dir STP_ALLOWED_SCRIPT_DIRS "$1"; }
stp_clear_allowed_script_dirs() { STP_ALLOWED_SCRIPT_DIRS=""; }
stp_get_allowed_script_dirs() { printf '%s\n' "${STP_ALLOWED_SCRIPT_DIRS:-$STP_DEFAULT_ALLOWED_SCRIPT_DIRS}"; }
stp_get_allowed_script_dir_count() {
    count=0
    active_dirs=${STP_ALLOWED_SCRIPT_DIRS:-$STP_DEFAULT_ALLOWED_SCRIPT_DIRS}
    while IFS= read -r _dir; do
        [ -n "$_dir" ] && count=$((count + 1))
    done <<EOF
$active_dirs
EOF
    printf '%d\n' "$count"
}
stp_add_allowed_log_dir() { _stp_add_allowed_dir STP_ALLOWED_LOG_DIRS "$1"; }
stp_clear_allowed_log_dirs() { STP_ALLOWED_LOG_DIRS=""; }
stp_get_allowed_log_dirs() { printf '%s\n' "${STP_ALLOWED_LOG_DIRS:-$STP_DEFAULT_ALLOWED_LOG_DIRS}"; }

_stp_realpath() {
    [ -n "$1" ] || return 1
    if commandExists realpath; then
        realpath -- "$1" 2>/dev/null
    elif commandExists readlink; then
        readlink -f -- "$1" 2>/dev/null
    else
        return 1
    fi
}

_stp_mode_is_writable() {
    mode="$1"
    group_digit=$(printf '%s' "$mode" | sed 's/.*\(.\).$/\1/')
    world_digit=$(printf '%s' "$mode" | sed 's/.*\(.\)$/\1/')
    case "$world_digit" in 2|3|6|7) return 0 ;; esac
    case "$group_digit" in 2|3|6|7) return 0 ;; esac
    return 1
}

_stp_directory_is_safe() {
    directory="$1"
    allow_sticky="${2:-0}"
    [ -d "$directory" ] || return 1
    mode=$(stat -c '%a' -- "$directory" 2>/dev/null) || return 1
    owner=$(stat -c '%u' -- "$directory" 2>/dev/null) || return 1
    group_digit=$(printf '%s' "$mode" | sed 's/.*\(.\).$/\1/')
    world_digit=$(printf '%s' "$mode" | sed 's/.*\(.\)$/\1/')
    special_digit=$(printf '%s' "$mode" | sed 's/^\(.\)...$/\1/;t;s/.*/0/')
    case "$world_digit" in
        2|3|6|7)
            [ "$allow_sticky" -eq 1 ] || return 1
            case "$special_digit" in 1|3|5|7) ;; *) return 1 ;; esac
            ;;
    esac
    case "$group_digit" in
        2|3|6|7) [ "$owner" -eq 0 ] || [ "$allow_sticky" -eq 1 ] || return 1 ;;
    esac
    return 0
}

_stp_path_under_root() {
    checked_path="$1"
    checked_root=${2%/}
    [ "$checked_path" = "$checked_root" ] && return 0
    case "$checked_path" in "$checked_root"/*) return 0 ;; *) return 1 ;; esac
}

_stp_validate_parent_chain() {
    resolved="$1"
    allowed_root=${2%/}
    parent=$(dirname -- "$resolved") || return 1
    current="$parent"
    while :; do
        _stp_directory_is_safe "$current" 0 || return 1
        [ "$current" = "$allowed_root" ] && return 0
        _stp_path_under_root "$current" "$allowed_root" || return 1
        next=$(dirname -- "$current") || return 1
        [ "$next" != "$current" ] || return 1
        current="$next"
    done
}

_stp_validate_log_output_path() {
    output_path="$1"
    case "$output_path" in /*/*) ;; *) return 1 ;; esac
    leaf=$(basename -- "$output_path") || return 1
    case "$leaf" in ''|.|..|*[!A-Za-z0-9._-]*) return 1 ;; esac
    parent=$(dirname -- "$output_path") || return 1
    resolved_parent=$(_stp_realpath "$parent") || return 1

    allowed_dirs=${STP_ALLOWED_LOG_DIRS:-$STP_DEFAULT_ALLOWED_LOG_DIRS}
    matched_root=""
    allow_sticky=0
    while IFS= read -r allowed_dir; do
        [ -n "$allowed_dir" ] || continue
        canonical_root=$(_stp_realpath "${allowed_dir%/}") || continue
        if _stp_path_under_root "$resolved_parent" "$canonical_root"; then
            matched_root="$canonical_root"
            [ "$canonical_root" = /tmp ] && allow_sticky=1
            break
        fi
    done <<EOF
$allowed_dirs
EOF
    [ -n "$matched_root" ] || return 1

    current="$resolved_parent"
    while :; do
        _stp_directory_is_safe "$current" "$allow_sticky" || return 1
        [ "$current" = "$matched_root" ] && break
        _stp_path_under_root "$current" "$matched_root" || return 1
        next=$(dirname -- "$current") || return 1
        [ "$next" != "$current" ] || return 1
        current="$next"
    done
    printf '%s/%s\n' "$resolved_parent" "$leaf"
}

_stp_resolve_and_validate_path() {
    input_path="$1"
    [ -n "$input_path" ] || return 1
    resolved=$(_stp_realpath "$input_path") || {
        printf 'Error: Cannot resolve path: %s\n' "$input_path" >&2
        return 1
    }
    [ -r "$resolved" ] && [ -f "$resolved" ] || {
        printf 'Error: File is not readable or regular: %s\n' "$resolved" >&2
        return 1
    }

    allowed_dirs=${STP_ALLOWED_SCRIPT_DIRS:-$STP_DEFAULT_ALLOWED_SCRIPT_DIRS}
    matched_root=""
    while IFS= read -r allowed_dir; do
        [ -n "$allowed_dir" ] || continue
        canonical_root=$(_stp_realpath "${allowed_dir%/}") || continue
        if _stp_path_under_root "$resolved" "$canonical_root"; then
            matched_root="$canonical_root"
            break
        fi
    done <<EOF
$allowed_dirs
EOF
    [ -n "$matched_root" ] || {
        printf 'Error: Path not in an allowed directory: %s\n' "$resolved" >&2
        return 1
    }
    _stp_validate_parent_chain "$resolved" "$matched_root" || {
        printf 'Error: Unsafe parent directory permissions: %s\n' "$resolved" >&2
        return 1
    }
    mode=$(stat -c '%a' -- "$resolved" 2>/dev/null) || return 1
    owner=$(stat -c '%u' -- "$resolved" 2>/dev/null) || return 1
    euid=$(id -u)
    if _stp_mode_is_writable "$mode" || { [ "$euid" -eq 0 ] && [ "$owner" -ne 0 ]; } || { [ "$euid" -ne 0 ] && [ "$owner" -ne 0 ] && [ "$owner" -ne "$euid" ]; }; then
        printf 'Error: Untrusted file permissions or ownership: %s\n' "$resolved" >&2
        return 1
    fi
    printf '%s\n' "$resolved"
}

_stp_is_safe_ansible_arg() {
    case "${1:-}" in *[!A-Za-z0-9_.,:=/\ -]*) return 1 ;; *) return 0 ;; esac
}

_stp_is_safe_hostname() {
    case "${1:-}" in *[!A-Za-z0-9_.,:\[\]-]*) return 1 ;; *) return 0 ;; esac
}

stp_add_apt_source() {
    source_name="$1"; source_line="$2"
    stp_is_safe_flatpak_remote_name "$source_name" || return 1
    case "$source_line" in 'deb '*|'deb-src '*) ;; *) return 1 ;; esac
    case "$source_line" in *"
"*|*"
"*) return 1 ;; esac
    stp_has_package_manager apt || return 1
    if [ "$STP_DRY_RUN" -eq 1 ]; then
        printf '[DRY-RUN] Would write /etc/apt/sources.list.d/%s.list with contents:\n%s\n' "$source_name" "$source_line"
        return 0
    fi
    tmpfile=$(mktemp) || return 1
    printf '%s\n' "$source_line" > "$tmpfile"
    sudo install -D -m 0644 "$tmpfile" "/etc/apt/sources.list.d/$source_name.list"
    result=$?
    rm -f "$tmpfile"
    return "$result"
}

stp_add_zypper_repo() {
    repo_name="$1"; repo_url="$2"; auto_refresh="${3:-0}"
    stp_has_package_manager zypper || return 1
    stp_is_safe_flatpak_remote_name "$repo_name" && stp_is_safe_url "$repo_url" || return 1
    if [ "$auto_refresh" = 1 ] || [ "$auto_refresh" = true ]; then
        _stp_run_cmd sudo zypper addrepo --refresh "$repo_url" "$repo_name"
    else
        _stp_run_cmd sudo zypper addrepo "$repo_url" "$repo_name"
    fi
}

stp_add_pacman_repo() {
    repo_name="$1"; server_url="$2"; siglevel="${3:-}"
    stp_has_package_manager pacman || return 1
    stp_is_safe_flatpak_remote_name "$repo_name" || return 1
    case "$server_url" in ''|*"
"*|*"
"*) return 1 ;; esac
    case "$siglevel" in *[!A-Za-z0-9_\ -]*) return 1 ;; esac
    grep -F "[$repo_name]" /etc/pacman.conf >/dev/null 2>&1 && return 0
    if [ "$STP_DRY_RUN" -eq 1 ]; then
        printf '[DRY-RUN] Would append repository %s to /etc/pacman.conf\n' "$repo_name"
        return 0
    fi
    tmpfile=$(mktemp) || return 1
    cp /etc/pacman.conf "$tmpfile" || { rm -f "$tmpfile"; return 1; }
    { printf '\n[%s]\n' "$repo_name"; [ -z "$siglevel" ] || printf 'SigLevel = %s\n' "$siglevel"; printf 'Server = %s\n' "$server_url"; } >> "$tmpfile"
    sudo install -m 0644 "$tmpfile" /etc/pacman.conf
    result=$?
    rm -f "$tmpfile"
    return "$result"
}

stp_add_flatpak_remote() {
    remote_name="$1"; repo_url="$2"
    stp_has_package_manager flatpak || return 1
    stp_is_safe_flatpak_remote_name "$remote_name" && stp_is_safe_url "$repo_url" || return 1
    _stp_run_cmd flatpak remote-add --if-not-exists "$remote_name" "$repo_url"
}

# System introspection functions print the string that the C API returns.
stp_get_hostname() { hostname 2>/dev/null || uname -n; }
stp_get_kernel_version() { uname -r; }
stp_get_uptime() {
    uptime_seconds=$(cut -d. -f1 /proc/uptime 2>/dev/null) || uptime_seconds=0
    days=$((uptime_seconds / 86400)); hours=$(((uptime_seconds % 86400) / 3600)); mins=$(((uptime_seconds % 3600) / 60))
    printf '%d days, %d:%02d\n' "$days" "$hours" "$mins"
}
stp_get_shell() { basename "${SHELL:-unknown}"; }
stp_get_desktop_environment() { printf '%s\n' "${XDG_CURRENT_DESKTOP:-${DESKTOP_SESSION:-unknown}}"; }
stp_is_virtual_machine() {
    product_name=$(cat /sys/class/dmi/id/product_name 2>/dev/null || true)
    case $(strlwr "$product_name") in *vmware*|*virtualbox*|*qemu*|*kvm*|*xen*|*microsoft\ corporation*|*parallels*) return 0 ;; esac
    commandExists systemd-detect-virt && systemd-detect-virt -q
}
stp_get_cpu_info() {
    cpu=$(sed -n 's/^model name[[:space:]]*:[[:space:]]*//p' /proc/cpuinfo 2>/dev/null | head -n 1)
    printf '%s\n' "${cpu:-unknown}"
}
stp_get_memory_info() {
    total=$(awk '/^MemTotal:/ { print int($2 / 1024) }' /proc/meminfo 2>/dev/null)
    free_mb=$(awk '/^MemFree:/ { print int($2 / 1024) }' /proc/meminfo 2>/dev/null)
    [ -n "$total" ] || { printf '\n'; return 1; }
    [ -n "$free_mb" ] || free_mb=0
    printf '%d MB / %d MB\n' "$((total - free_mb))" "$total"
}
stp_get_pretty_name() {
    pretty=$(sed -n 's/^PRETTY_NAME=//p' /etc/os-release 2>/dev/null | head -n 1)
    pretty=$(printf '%s' "$pretty" | sed 's/^"//;s/"$//')
    printf '%s\n' "${pretty:-Linux}"
}

# Newt/whiptail implementation of the C library's ncurses-facing API.  The
# historical function names remain so callers can share names with libstp3.
STP_TUI_BACKEND=""
STP_TUI_TITLE="Setup Tooling Project"

init_ncurses() {
    if commandExists whiptail; then
        STP_TUI_BACKEND=whiptail
        return 0
    fi
    printf 'Error: whiptail (Newt) is required for the shell TUI\n' >&2
    return "$STP_ERROR_EXECUTION"
}

cleanup_ncurses() {
    STP_TUI_BACKEND=""
    return 0
}

_stp_require_tui() {
    [ "$STP_TUI_BACKEND" = whiptail ] || init_ncurses
}

# Usage: nshow_message "message" [is_error] [title]
nshow_message() {
    message="$1"
    is_error="${2:-0}"
    title="${3:-$STP_TUI_TITLE}"
    _stp_require_tui || return $?
    [ "$is_error" = 1 ] || [ "$is_error" = true ] || is_error=0
    if [ "$is_error" = 0 ]; then
        whiptail --title "$title" --msgbox "$message" 10 70
    else
        whiptail --title "Error - $title" --msgbox "$message" 10 70
    fi
}

# Shell menu contract:
#   ndraw_menu "title" "prompt" "tag1" "description1" [tag2 description2 ...]
# The selected tag is printed to stdout. Cancel/Escape returns non-zero.
ndraw_menu() {
    title="$1"
    prompt="$2"
    shift 2
    [ $# -ge 2 ] && [ $(( $# % 2 )) -eq 0 ] || {
        printf 'ndraw_menu: expected tag/description pairs\n' >&2
        return "$STP_ERROR_BOUNDS"
    }
    _stp_require_tui || return $?
    menu_height=$(( $# / 2 ))
    [ "$menu_height" -lt 1 ] && menu_height=1
    [ "$menu_height" -gt 15 ] && menu_height=15
    choice=$(whiptail --title "$title" --menu "$prompt" 20 78 "$menu_height" "$@" 3>&1 1>&2 2>&3)
    result=$?
    [ "$result" -eq 0 ] || return "$result"
    printf '%s\n' "$choice"
}
stp_color_name_to_ncurses() {
    case $(strlwr "$1") in black) printf '0\n';; red) printf '1\n';; green) printf '2\n';; yellow) printf '3\n';; blue) printf '4\n';; magenta) printf '5\n';; cyan) printf '6\n';; white) printf '7\n';; *) printf '%s\n' '-1'; return 1;; esac
}
STP_CURRENT_THEME_NAME=Default
stp_load_theme() {
    theme=$(strlwr "$1")
    case "$theme" in
        default)
            STP_CURRENT_THEME_NAME='Default'
            NEWT_COLORS='root=white,blue;window=white,black;title=white,blue;button=black,cyan;actbutton=white,blue;entry=white,black;label=white,black;listbox=white,black;actlistbox=black,cyan;textbox=white,black;acttextbox=black,cyan'
            ;;
        high_contrast|high-contrast)
            STP_CURRENT_THEME_NAME='High Contrast'
            NEWT_COLORS='root=black,white;window=black,white;title=black,white;button=black,yellow;actbutton=white,red;entry=black,white;label=black,white;listbox=black,white;actlistbox=black,yellow;textbox=black,white;acttextbox=black,yellow'
            ;;
        solarized_dark|solarized-dark)
            STP_CURRENT_THEME_NAME='Solarized Dark'
            NEWT_COLORS='root=cyan,black;window=cyan,black;title=cyan,black;button=white,blue;actbutton=black,cyan;entry=white,black;label=cyan,black;listbox=cyan,black;actlistbox=white,blue;textbox=cyan,black;acttextbox=white,blue'
            ;;
        *)
            if [ -r "/opt/setup-tool/themes/$1.yaml" ] || [ -r "/etc/libstp3/themes/$1.yaml" ]; then
                STP_CURRENT_THEME_NAME="$1"
                NEWT_COLORS="${NEWT_COLORS:-}"
            else
                return 1
            fi
            ;;
    esac
    export NEWT_COLORS
    return 0
}
stp_get_current_theme_name() { printf '%s\n' "$STP_CURRENT_THEME_NAME"; }
stp_get_current_theme() { stp_get_current_theme_name; }
stp_runtime_cleanup() {
    cleanup_ncurses
    stp_log_close
    setConfigFile ""
    setLogPath ""
    stp_clear_allowed_script_dirs
    stp_clear_allowed_log_dirs
    stp_set_dry_run 0
    STP_SCRIPT_RECORDS=""
    STP_CURRENT_THEME_NAME=Default
    unset NEWT_COLORS
}

# Export all functions when sourced
# This allows the library to be used by sourcing this file
