#!/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.
#
# 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
# - 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)

# Error code constants
readonly STP_SUCCESS=0
readonly STP_ERROR_GENERIC=1
readonly STP_ERROR_DIR=-1
readonly STP_ERROR_PERMISSION=-2
readonly STP_ERROR_PATH=-3
readonly STP_ERROR_BOUNDS=-4
readonly STP_ERROR_EXECUTION=-5
readonly STP_ERROR_CLOUD_CONFIG=-8
readonly STP_ERROR_VM_DETECTION=-9

# 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() {
    "$@"
}

# Securely wipe sensitive strings from memory
secureWipeString() {
    var_name="$1"
    [ -z "$var_name" ] && 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() {
    path="$1"
    [ -z "$path" ] && return 1
    
    # Check for directory traversal attempts
    case "$path" in
        *'..'*|*'//'*) return 1 ;;
    esac
    
    # Check for null bytes
    case "$path" in
        *"$(printf '\0')"*) return 1 ;;
    esac
    
    return 0
}

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

# 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
}

# List scripts in directory
listScripts() {
    directory="$1"
    
    [ ! -d "$directory" ] && return "$STP_ERROR_DIR"
    
    # Validate directory path
    isValidFilePath "$directory" || return "$STP_ERROR_PATH"
    
    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" \) 2>/dev/null | \
    while IFS= read -r script; do
        basename "$script"
    done
}

# 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' "------------------------------------------------------------"
    
    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" \) 2>/dev/null | \
    sort | while IFS= read -r script; do
        count=$((count + 1))
        name=$(basename "$script")
        
        if [ -x "$script" ]; then
            executable="Yes"
        else
            executable="No"
        fi
        
        modified=$(stat -c '%y' "$script" 2>/dev/null | cut -d. -f1)
        
        printf '%-4d %-30s %-10s %-20s\n' "$count" "$name" \
               "$executable" "$modified"
    done
    
    count=$((count + 1))
    printf '%-4d %s\n' "$count" "Back to previous menu"
}

# Execute script with security checks
executeScript() {
    script_path="$1"
    
    # Validate path
    [ ! -f "$script_path" ] && return "$STP_ERROR_PATH"
    isValidFilePath "$script_path" || return "$STP_ERROR_PATH"
    
    # Check if file is within allowed directory (security)
    case "$script_path" in
        /tmp/*|/var/tmp/*)
            printf 'Error: Cannot execute scripts from temporary directories\n' >&2
            return "$STP_ERROR_PERMISSION"
            ;;
    esac
    
    extension="${script_path##*.}"
    extension=".${extension}"
    
    # Special handling for C files
    ext_lower=$(strlwr "$extension")
    if [ "$ext_lower" = ".c" ]; then
        compile_and_execute_c "$script_path"
        return $?
    fi
    
    shell=$(getScriptShell "$script_path" "$extension")
    [ -z "$shell" ] && return "$STP_ERROR_EXECUTION"
    
    # Execute with restricted PATH
    PATH=/usr/local/bin:/usr/bin:/bin "$shell" "$script_path"
}

# 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
    local in_date_block=0
    local match_found=0
    local 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
    
    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 ;;
                *) 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
            ;;
        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 '                     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_LOG_PATH"
    else
        out_path="/tmp/stp-logs-$$-$(date +%s).log"
    fi
    
    # Validate output path
    isValidFilePath "$out_path" || {
        printf 'Error: Invalid log path\n' >&2
        return "$STP_ERROR_PATH"
    }
    
    if ! _stp_run_shell_cmd "umask 077 && : > \"$out_path\""; then
        printf 'Error: Failed to create log file\n' >&2
        return "$STP_ERROR_GENERIC"
    fi
    chmod 600 "$out_path" 2>/dev/null || :
    
    if [ -n "$tag_name" ]; then
        sudo journalctl -t "$tag_name" -S today -U now > "$out_path"
    else
        sudo journalctl _PID=$$ -S today -U now > "$out_path"
    fi
    chmod 600 "$out_path" 2>/dev/null || :
    
    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() {
    manager="$1"
    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=""
    lspci -nnk 2>/dev/null | grep -EA3 'VGA|3D' | grep -q 'AMD/ATI' && gpus="$gpus amd"
    lspci -nnk 2>/dev/null | grep -EA3 'VGA|3D' | grep -qi 'nvidia' && gpus="$gpus nvidia"
    lspci -nnk 2>/dev/null | grep -EA3 'VGA|3D' | grep -q 'Intel' && gpus="$gpus intel"
    vulkaninfo --summary 2>/dev/null | grep -q 'x14e4' && gpus="$gpus broadcom"
    printf '%s\n' "$gpus" | sed 's/^ //'
}

# Check if specific GPU vendor is present
stp_has_gpu() {
    vendor="$1"
    case "$vendor" in
        amd) lspci -nnk 2>/dev/null | grep -EA3 'VGA|3D' | grep -q 'AMD/ATI' ;;
        nvidia) lspci -nnk 2>/dev/null | grep -EA3 'VGA|3D' | grep -qi 'nvidia' ;;
        intel) lspci -nnk 2>/dev/null | grep -EA3 'VGA|3D' | grep -q 'Intel' ;;
        broadcom) vulkaninfo --summary 2>/dev/null | grep -q 'x14e4' ;;
        *) return 1 ;;
    esac
}

# Clear stdin input buffer
stp_clear_input_buffer() {
    while read -r -t 0.1 _discard 2>/dev/null; do :; done
}

# 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
}

# 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 ]; 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 ]; 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
    
    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="$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() {
    command="$1"
    dry_run="${2:-0}"
    
    if [ "$dry_run" -eq 1 ] || [ "$dry_run" = "true" ]; then
        printf '[DRY-RUN] Would execute: %s\n' "$command"
        return 0
    fi
    
    eval "$command"
}

# ============================================================================
# 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_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
    
    # Initialize result variables
    eval "${result_var}_base_path=''"
    eval "${result_var}_resource_path=''"
    eval "${result_var}_config_path=''"
    eval "${result_var}_found=0"
    
    # 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
                eval "${result_var}_base_path='$search_path'"
                eval "${result_var}_resource_path='$test_path'"
                
                if [ -n "$config_filename" ]; then
                    eval "${result_var}_config_path='${search_path}/${config_filename}'"
                fi
                
                eval "${result_var}_found=1"
                IFS="$old_ifs"
                return 0
            fi
        done
        IFS="$old_ifs"
    fi
    
    # Not found - use default if provided
    if [ -n "$default_base" ]; then
        eval "${result_var}_base_path='$default_base'"
        
        if [ -n "$subdir_name" ]; then
            eval "${result_var}_resource_path='${default_base}/${subdir_name}'"
        else
            eval "${result_var}_resource_path='$default_base'"
        fi
        
        if [ -n "$config_filename" ]; then
            eval "${result_var}_config_path='${default_base}/${config_filename}'"
        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
    
    # Check if playbook exists
    if ! stp_file_exists "$playbook_path"; then
        printf 'Error: Playbook not found: %s\n' "$playbook_path" >&2
        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' "$playbook_path" "$target_hosts"
    
    # Dry-run mode: just print what would happen
    if [ "$STP_DRY_RUN" -eq 1 ]; then
        if [ -n "$config_path" ]; then
            printf "[DRY-RUN] Would execute: ANSIBLE_CONFIG='%s' ansible-playbook %s -e '%s'\n" \
                   "$config_path" "$playbook_path" "$full_extra_vars"
        else
            printf "[DRY-RUN] Would execute: ansible-playbook %s -e '%s'\n" \
                   "$playbook_path" "$full_extra_vars"
        fi
        return 0
    fi
    
    # Build and execute command
    if [ -n "$config_path" ]; then
        if [ "$ask_become_pass" -eq 1 ] || [ "$ask_become_pass" = "true" ]; then
            ANSIBLE_CONFIG="$config_path" ansible-playbook "$playbook_path" -e "$full_extra_vars" -K
        else
            ANSIBLE_CONFIG="$config_path" ansible-playbook "$playbook_path" -e "$full_extra_vars" 2>&1
        fi
    else
        if [ "$ask_become_pass" -eq 1 ] || [ "$ask_become_pass" = "true" ]; then
            ansible-playbook "$playbook_path" -e "$full_extra_vars" -K
        else
            ansible-playbook "$playbook_path" -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
    
    # Check if script exists
    if ! stp_file_exists "$script_path"; then
        printf 'Error: Script not found: %s\n' "$script_path" >&2
        return 1
    fi
    
    printf '\n[Running script: %s on hosts: %s]\n\n' "$script_path" "$target_hosts"
    
    # Dry-run mode: just print what would happen
    if [ "$STP_DRY_RUN" -eq 1 ]; then
        if [ -n "$config_path" ]; then
            printf "[DRY-RUN] Would execute: ANSIBLE_CONFIG='%s' ansible %s -m script -a '%s' -b\n" \
                   "$config_path" "$target_hosts" "$script_path"
        else
            printf "[DRY-RUN] Would execute: ansible %s -m script -a '%s' -b\n" \
                   "$target_hosts" "$script_path"
        fi
        return 0
    fi
    
    # Build and execute command
    if [ -n "$config_path" ]; then
        if [ "$ask_become_pass" -eq 1 ] || [ "$ask_become_pass" = "true" ]; then
            ANSIBLE_CONFIG="$config_path" ansible "$target_hosts" -m script -a "$script_path" -b -K
        else
            ANSIBLE_CONFIG="$config_path" ansible "$target_hosts" -m script -a "$script_path" -b
        fi
    else
        if [ "$ask_become_pass" -eq 1 ] || [ "$ask_become_pass" = "true" ]; then
            ansible "$target_hosts" -m script -a "$script_path" -b -K
        else
            ansible "$target_hosts" -m script -a "$script_path" -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
    
    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 "$config_path" ]; then
            printf "[DRY-RUN] Would execute: ANSIBLE_CONFIG='%s' ansible %s -m raw -a '%s'\n" \
                   "$config_path" "$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 "$config_path" ]; then
        if [ "$ask_become_pass" -eq 1 ] || [ "$ask_become_pass" = "true" ]; then
            ANSIBLE_CONFIG="$config_path" ansible "$target_hosts" -m raw -a "$command" -K
        else
            ANSIBLE_CONFIG="$config_path" 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
}

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