PwSh.Fw.NodeJs.psm1

## @file PwShNode.psm1
## @brief Cross-platform PowerShell module for managing Node.js installations
## @requires PowerShell 7.0+

$Script:RELEASEURL = "https://api.github.com/repos/nodejs/node/releases"
if ($IsWindows -or $PSVersionTable.Platform -eq 'Win32NT') {
    $Script:configDir = Join-Path -Path $env:LOCALAPPDATA -ChildPath 'PwSh.Fw'
} else {
    # Linux/macOS: use XDG_CONFIG_HOME or ~/.config
    $configHome = $env:XDG_CONFIG_HOME
    if ([string]::IsNullOrEmpty($configHome)) {
        $configHome = $HOME
    }
    $Script:configDir = Join-Path -Path $configHome -ChildPath '.config' -AdditionalChildPath 'pwsh.fw'
}
if (!(Test-Path $Script:configDir)) {
    New-Item -ItemType Directory -Path $Script:configDir -Force | Out-Null
}
$Script:ConfigFile = Join-Path -Path $Script:configDir -ChildPath 'pwsh.fw.nodejs.json'

<#
.SYNOPSIS
    Retrieves Node.js version information.
 
.DESCRIPTION
    Queries the GitHub API to retrieve available Node.js versions (with -Remote),
    lists locally installed versions (with -ListAvailable), or gets the latest version
    for a specific channel (LTS or Current).
 
.PARAMETER Channel
    The release channel to filter: 'Current' for the latest version or 'LTS' for long-term support.
    Can be combined with -Remote or -ListAvailable to filter results.
    Default: Returns all versions when used with -Remote or -ListAvailable.
 
.PARAMETER Version
    Filter results to a specific version (e.g., 'v20.11.0').
    Supports wildcards (e.g., 'v20*', '*20.11*').
    Can be combined with -Remote or -ListAvailable.
 
.PARAMETER Remote
    Queries the GitHub API to retrieve all available Node.js versions.
    Returns versions with their installation status (installed/notInstalled).
 
.PARAMETER ListAvailable
    Lists all locally installed Node.js versions.
    Returns only versions that are currently installed on the system.
 
.PARAMETER Latest
    Returns only the latest version from the filtered results.
    Useful with -Remote or -ListAvailable to get the most recent version.
 
.OUTPUTS
    PSCustomObject containing Version, Name, Channel, PublishedAt, Assets, and Status.
 
.EXAMPLE
    Get-PwShNodeVersion
    Retrieves information about the latest LTS version (default behavior).
 
.EXAMPLE
    Get-PwShNodeVersion -Channel Current
    Retrieves information about the latest Current version.
 
.EXAMPLE
    Get-PwShNodeVersion -Remote
    Retrieves all available Node.js versions from GitHub with their installation status.
 
.EXAMPLE
    Get-PwShNodeVersion -Remote -Channel LTS
    Retrieves only LTS versions available on GitHub with their installation status.
 
.EXAMPLE
    Get-PwShNodeVersion -Remote -Version 'v20*'
    Retrieves all v20.x.x versions available on GitHub.
 
.EXAMPLE
    Get-PwShNodeVersion -ListAvailable
    Lists all Node.js versions currently installed on the local system.
 
.EXAMPLE
    Get-PwShNodeVersion -ListAvailable -Channel LTS
    Lists only LTS versions currently installed on the local system.
 
.EXAMPLE
    Get-PwShNodeVersion -ListAvailable -Version 'v20.11.0'
    Checks if version v20.11.0 is installed locally.
 
.EXAMPLE
    Get-PwShNodeVersion -Remote -Version 'v20*' -Latest
    Gets the latest v20.x.x version available on GitHub.
 
.EXAMPLE
    Get-PwShNodeVersion -Remote -Channel Current | Where-Object { $_.Status -eq 'notInstalled' }
    Lists all Current versions that are not yet installed.
 
.NOTES
    The -Remote parameter queries the GitHub API and may be subject to rate limiting.
    Status field indicates: 'installed' (version is installed locally) or 'notInstalled' (version is not installed).
    When Channel is not specified with -Remote or -ListAvailable, all versions are returned.
#>

function Get-PwShNodeVersion {
    [CmdletBinding(DefaultParameterSetName = 'REMOTE')]
    # [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="Remote and ListAvailable are used to set the ParameterSetName")]
    [OutputType([System.Object[]])]
    Param (
        [Parameter(Mandatory = $false)]
        [ValidateSet('Current', 'LTS')]
        [string]$Channel,

        [Parameter(Mandatory = $false)]
        [string]$Version,

        [Parameter(Mandatory = $false, ParameterSetName = 'REMOTE')]
        [switch]$Remote,

        [Parameter(Mandatory = $false, ParameterSetName = 'LOCALLATEST')]
        [Parameter(Mandatory = $false, ParameterSetName = 'LOCALDEFAULT')]
        [switch]$ListAvailable,

        [Parameter(Mandatory = $false, ParameterSetName = 'LOCALLATEST')]
        [Parameter(Mandatory = $false)]
        [switch]$Latest,

        [Parameter(Mandatory = $false, ParameterSetName = 'LOCALDEFAULT')]
        [Parameter(Mandatory = $false)]
        [switch]$Default
    )
    Begin {
        Write-EnterFunction
    }

    Process {
        try {
            switch ($PSCmdlet.ParameterSetName) {
                'REMOTE' { $Remote = $true}
                'LOCALLATEST' { $ListAvailable = $true}
                'LOCALDEFAULT' { $ListAvailable = $true }
            }
            if ($ListAvailable) {
                # List locally installed versions
                Write-Debug "Listing locally installed Node.js versions"

                $installLocation = Get-PwShNodeInstallLocation

                if (-not (Test-Path $installLocation)) {
                    # Write-Debug "Install location does not exist: $installLocation"
                    return @()
                }

                $installedVersions = Get-ChildItem -Path $installLocation -Directory -ErrorAction SilentlyContinue |
                    Where-Object { $_.Name -match '^v\d+\.\d+\.\d+$' } |
                    Sort-Object Name -Descending

                if ($installedVersions.Count -eq 0) {
                    Write-Debug "No installed versions found"
                    return @()
                }

                $results = foreach ($versionDir in $installedVersions) {
                    # Determine if it's LTS by checking if it's an even major version
                    $versionNumber = $versionDir.Name
                    $majorVersion = [int]($versionNumber -replace '^v(\d+)\..*', '$1')
                    $isLTS = ($majorVersion % 2) -eq 0
                    $versionChannel = if ($isLTS) { 'LTS' } else { 'Current' }

                    # Filter by channel if specified
                    if ($Channel -and $versionChannel -ne $Channel) {
                        continue
                    }

                    [PSCustomObject]@{
                        Version = $versionNumber
                        Name = $versionNumber
                        Channel = $versionChannel
                        Path = $versionDir.FullName
                        InstalledAt = $versionDir.CreationTime
                        Status = 'installed'
                    }
                }
            }
            if ($Remote) {
                # Get all available versions from GitHub
                Write-Debug "Fetching all available releases from GitHub API"

                $releases = Invoke-RestMethod -Uri $Script:RELEASEURL

                # Get list of installed versions for status check
                $installLocation = Get-PwShNodeInstallLocation
                $installedVersions = @()

                if (Test-Path $installLocation) {
                    $installedVersions = Get-ChildItem -Path $installLocation -Directory -ErrorAction SilentlyContinue |
                        Where-Object { $_.Name -match '^v\d+\.\d+\.\d+$' } |
                        ForEach-Object { $_.Name }
                }

                $results = foreach ($release in $releases) {
                    $versionNumber = $release.tag_name
                    $isInstalled = $installedVersions -contains $versionNumber
                    # Determine channel from name (LTS releases have "(LTS" in the name)
                    $versionChannel = if ($release.name -like "*(LTS*") { 'LTS' } else { 'Current' }

                    # Filter by channel if specified
                    if ($Channel -and $versionChannel -ne $Channel) {
                        continue
                    }

                    [PSCustomObject]@{
                        Version = $versionNumber
                        Name = $release.name
                        Channel = $versionChannel
                        PublishedAt = $release.published_at
                        Assets = $release.assets.name
                        Status = if ($isInstalled) { 'installed' } else { 'notInstalled' }
                    }
                }
            }
        }
        catch {
            Write-Error "Failed to retrieve Node.js version information"
            Write-Error $_
            throw
        }

        # Apply sorting and Latest filter
        if ($results) {
            # filter on Version
            if ($Version) {
                $results = $results | Where-Object { $_.Version -like $Version }
            }
            # return the latest version
            if ($Latest) {
                $results = $results | Sort-Object -Property Version | Select-Object -Last 1
            } else {
                $results = $results | Sort-Object -Property Version
            }
            if ($Default) {
                $results = $results | Where-Object { $_.Version -like (Get-PwShNodeConfig).DefaultVersion }
            }
            return $results
        }
        return $null
    }

    End {
        Write-LeaveFunction
    }
}

<#
.SYNOPSIS
    Sets the default Node.js version in configuration.
 
.DESCRIPTION
    Saves the specified Node.js version as the default version in the configuration file.
    This does not install the version, only marks it as the default.
 
.PARAMETER Version
    The Node.js version to set as default (e.g., 'v20.11.0').
 
.EXAMPLE
    Set-PwShNodeDefaultVersion -Version 'v20.11.0'
    Sets v20.11.0 as the default Node.js version.
 
.EXAMPLE
    'v18.19.0' | Set-PwShNodeDefaultVersion
    Sets v18.19.0 as the default using pipeline input.
 
.NOTES
    This function creates the configuration directory if it doesn't exist.
#>

function Set-PwShNodeDefaultVersion {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Low')]
    [OutputType([void])]
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ArgumentCompleter({
            [OutputType([System.Management.Automation.CompletionResult])]
            param(
                [string] $CommandName,
                [string] $ParameterName,
                [string] $WordToComplete,
                [System.Management.Automation.Language.CommandAst] $CommandAst,
                [System.Collections.IDictionary] $FakeBoundParameters
            )
            (Get-PwShNodeVersion -ListAvailable).Version | Where-Object { $_ -like "$wordToComplete*" }
        })]
        [ValidateScript(
            {
                if ($_ -in (Get-PwShNodeVersion -ListAvailable).Version) {
                    $true
                } else {
                    Throw [System.Management.Automation.ItemNotFoundException] "Nodejs version '$_' is not installed."
                }
            }
        )]
        [string]$Version
    )
    Begin {
        Write-EnterFunction
    }

    Process {
        try {
            # Prepare the configuration
            $config = @{
                DefaultVersion = $Version
                LastUpdated = (Get-Date).ToString('o')
            }

            # Preserve existing InstallLocation if it exists
            if (Test-Path $Script:ConfigFile) {
                $existingConfig = Get-Content $Script:ConfigFile | ConvertFrom-Json -AsHashtable
                if ($existingConfig.InstallLocation) {
                    $config.InstallLocation = $existingConfig.InstallLocation
                }
            }

            # Prepare the target description for ShouldProcess
            $target = $Script:ConfigFile
            $action = "Set default Node.js version to '$Version'"

            # Execute with ShouldProcess support
            if ($PSCmdlet.ShouldProcess($target, $action)) {
                $config | ConvertTo-Json | Set-Content -Path $Script:ConfigFile
                Write-Verbose "Node.js default version set to: $Version"
                Write-Info "Default Node.js version set to $Version"
            }
        }
        catch {
            Write-Error "Failed to set Node.js version: $_"
            throw
        }
    }

    End {
        Write-LeaveFunction
    }
}

<#
.SYNOPSIS
    Gets the Node.js installation directory path.
 
.DESCRIPTION
    Retrieves the configured installation directory for Node.js versions.
    Returns the platform-specific default location if not explicitly configured.
 
.OUTPUTS
    String containing the installation directory path.
 
.EXAMPLE
    Get-PwShNodeInstallLocation
    Returns the current installation directory path.
 
.EXAMPLE
    $installPath = Get-PwShNodeInstallLocation
    Stores the installation path in a variable.
 
.NOTES
    Default locations:
    - Windows: %LOCALAPPDATA%\PwShNode
    - Linux/macOS: $XDG_DATA_HOME/pwshnode or ~/.local/share/pwshnode
#>

function Get-PwShNodeInstallLocation {
    [CmdletBinding()]
    [OutputType([string])]
    Param ()
    Begin {
        Write-EnterFunction
    }

    Process {
        try {
            if (Test-Path $Script:ConfigFile) {
                $config = Get-Content $Script:ConfigFile | ConvertFrom-Json
                if ($config.InstallLocation) {
                    return $config.InstallLocation
                }
            }

            # Default location based on platform
            $defaultLocation = Get-PwShNodeDefaultInstallLocation
            Write-Verbose "Using default install location: $defaultLocation"
            return $defaultLocation
        }
        catch {
            Write-Error "Failed to get install location: $_"
            throw
        }
    }

    End {
        Write-LeaveFunction
    }
}

<#
.SYNOPSIS
    Sets the Node.js installation directory path.
 
.DESCRIPTION
    Configures the directory where Node.js versions will be installed.
    Creates the directory if it doesn't exist.
    Supports -WhatIf and -Confirm for safe operations.
 
.PARAMETER Path
    The directory path where Node.js versions should be installed.
 
.EXAMPLE
    Set-PwShNodeInstallLocation -Path 'C:\NodeJS'
    Sets the installation directory to C:\NodeJS on Windows.
 
.EXAMPLE
    Set-PwShNodeInstallLocation -Path '/opt/nodejs'
    Sets the installation directory to /opt/nodejs on Linux/macOS.
 
.EXAMPLE
    'C:\Dev\NodeJS' | Set-PwShNodeInstallLocation
    Sets the installation directory using pipeline input.
 
.EXAMPLE
    Set-PwShNodeInstallLocation -Path 'C:\NodeJS' -WhatIf
    Shows what would happen without actually changing the configuration.
 
.EXAMPLE
    Set-PwShNodeInstallLocation -Path 'C:\NodeJS' -Confirm:$false
    Sets the location without confirmation prompt.
 
.NOTES
    The directory will be created if it doesn't exist.
    Supports -WhatIf and -Confirm parameters for safe operations.
#>

function Set-PwShNodeInstallLocation {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [OutputType([void])]
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]$Path
    )
    Begin {
        Write-EnterFunction
    }

    Process {
        try {
            # Create directory if it doesn't exist (with ShouldProcess)
            if (!(Test-Path $Path)) {
                if ($PSCmdlet.ShouldProcess($Path, "Create installation directory")) {
                    New-Item -ItemType Directory -Path $Path -Force | Out-Null
                    Write-Verbose "Created install directory: $Path"
                }
            }

            # Prepare the configuration
            $config = @{
                InstallLocation = $Path
                LastUpdated = (Get-Date).ToString('o')
            }

            # Preserve existing DefaultVersion if it exists
            if (Test-Path $Script:ConfigFile) {
                $existingConfig = Get-Content $Script:ConfigFile | ConvertFrom-Json -AsHashtable
                if ($existingConfig.DefaultVersion) {
                    $config.DefaultVersion = $existingConfig.DefaultVersion
                }
            }

            # Prepare the target description for ShouldProcess
            $target = $Script:ConfigFile
            $action = "Set Node.js installation location to '$Path'"

            # Execute with ShouldProcess support
            if ($PSCmdlet.ShouldProcess($target, $action)) {
                $config | ConvertTo-Json | Set-Content -Path $Script:ConfigFile
                Write-Verbose "Install location set to: $Path"
                Write-Info "Installation location set to $Path"
            }
        }
        catch {
            Write-Error "Failed to set install location: $_"
            throw
        }
    }

    End {
        Write-LeaveFunction
    }
}

<#
.SYNOPSIS
    Installs a specific Node.js version.
 
.DESCRIPTION
    Downloads and installs a Node.js version for the current or specified platform and architecture.
    Supports automatic platform/architecture detection, caching, and setting as default version.
 
.PARAMETER Version
    The Node.js version to install (e.g., 'v20.11.0').
    If not specified, installs the latest version from the specified Channel.
 
.PARAMETER Channel
    The release channel: 'Current' for latest or 'LTS' for long-term support.
    Only used when Version is not specified.
    Default: 'LTS'
 
.PARAMETER Architecture
    The target architecture: 'x64', 'x86', 'arm64', or 'armv7l'.
    If not specified, auto-detects the current system architecture.
 
.PARAMETER Platform
    The target platform: 'win', 'linux', or 'darwin'.
    If not specified, auto-detects the current platform.
 
.PARAMETER CacheDir
    Directory for caching downloaded archives.
    If not specified, uses a 'cache' subdirectory in the installation location.
 
.PARAMETER Force
    Forces reinstallation even if the version is already installed.
 
.OUTPUTS
    PSCustomObject containing Version, Path, Platform, Architecture, and Installed status.
 
.EXAMPLE
    Install-PwShNodeVersion
    Installs the latest LTS version for the current platform.
 
.EXAMPLE
    Install-PwShNodeVersion -Version 'v20.11.0'
    Installs a specific Node.js version.
 
.EXAMPLE
    Install-PwShNodeVersion -Channel Current -Force
    Installs the latest Current version, forcing reinstallation.
 
.EXAMPLE
    Install-PwShNodeVersion -Platform 'linux' -Architecture 'arm64'
    Installs for a specific platform and architecture.
 
.NOTES
    Downloaded files are cached to avoid re-downloading.
    On Unix systems, executable permissions are automatically set.
#>

function Install-PwShNodeVersion {
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    Param (
        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [string]$Version,

        [Parameter(Mandatory = $false)]
        [ValidateSet('Current', 'LTS')]
        [string]$Channel = 'LTS',

        [Parameter(Mandatory = $false)]
        [string]$Architecture = (Get-PwShNodeArchitecture),

        [Parameter(Mandatory = $false)]
        [string]$Platform = (Get-PwShNodePlatform),

        [Parameter(Mandatory = $false)]
        [string]$CacheDir,

        [Parameter(Mandatory = $false)]
        [switch]$Force
    )
    Begin {
        Write-EnterFunction
    }

    Process {
        try {
            # Get version if not specified
            if ([string]::IsNullOrEmpty($Version)) {
                $versionInfo = Get-PwShNodeVersion -Channel $Channel -Latest
            } else {
                $versionInfo = Get-PwShNodeVersion -Version $Version -Latest
            }
            if ($null -eq $versionInfo) {
                Write-Fatal "No Node.js version found for channel $Channel and version $Version"
            }
            $Version = $versionInfo.Version
            Write-Verbose "Retrieved version: $Version"

            if (($versionInfo.Status -eq 'installed') -and !$Force) {
                Write-Warning "Version $Version already installed at $versionPath. Use -Force to reinstall."
                return $versionInfo
            }

            # Determine install location
            $installLocation = Get-PwShNodeInstallLocation
            $versionPath = Join-Path $installLocation $Version

            # Determine filename and extension
            $extension = if ($Platform -eq 'linux' -or $Platform -eq 'darwin') { 'tar.gz' } else { 'zip' }
            $filename = "node-$Version-$Platform-$Architecture.$extension"
            $url = "https://nodejs.org/dist/$Version/$filename"

            # Setup cache directory
            if ([string]::IsNullOrEmpty($CacheDir)) {
                $CacheDir = Join-Path $installLocation 'cache'
            }
            if (!(Test-Path $CacheDir)) {
                New-Item -ItemType Directory -Path $CacheDir -Force | Out-Null
            }

            $cacheFile = Join-Path $CacheDir $filename

            # Download if not cached
            if (!(Test-Path $cacheFile)) {
                Write-Verbose "Downloading $filename from $url"
                Write-Info "Downloading Node.js $Version..."
                Invoke-WebRequest -Uri $url -OutFile $cacheFile -UseBasicParsing
            } else {
                Write-Verbose "Using cached file: $cacheFile"
            }

            # Extract based on platform
            Write-Verbose "Extracting to $installLocation"
            $extractedFolder = Join-Path $installLocation "node-$Version-$Platform-$Architecture"

            if ($extension -eq 'zip') {
                # Windows
                Expand-Archive -Path $cacheFile -DestinationPath $installLocation -Force
            } else {
                # Linux/macOS - using tar
                $tarArgs = @('-xzf', $cacheFile, '-C', $installLocation)
                $tarProcess = Start-Process -FilePath 'tar' -ArgumentList $tarArgs -NoNewWindow -Wait -PassThru

                if ($tarProcess.ExitCode -ne 0) {
                    throw "tar extraction failed with exit code $($tarProcess.ExitCode)"
                }
            }

            # Rename extracted folder to version
            if (Test-Path $extractedFolder) {
                if (Test-Path $versionPath) {
                    Remove-Item $versionPath -Recurse -Force
                }
                Move-Item -Path $extractedFolder -Destination $versionPath
            } else {
                throw "Extraction failed: expected folder not found at $extractedFolder"
            }

            # Set executable permissions on Unix-like systems
            if ($Platform -eq 'linux' -or $Platform -eq 'darwin') {
                $binPath = Join-Path $versionPath 'bin'
                if (Test-Path $binPath) {
                    Get-ChildItem -Path $binPath -File | ForEach-Object {
                        chmod +x $_.FullName
                    }
                }
            }

            # Set as default version
            Set-PwShNodeDefaultVersion -Version $Version

            Write-Info "Node.js $Version installed successfully at $versionPath"

            return [PSCustomObject]@{
                Version = $Version
                Path = $versionPath
                Platform = $Platform
                Architecture = $Architecture
                Installed = $true
            }
        }
        catch {
            Write-Error "Failed to install Node.js version ${Version}: $_"
        }
    }

    End {
        Write-LeaveFunction
    }
}

<#
.SYNOPSIS
    Removes an installed Node.js version.
 
.DESCRIPTION
    Uninstalls a specific Node.js version by removing its installation directory.
    Supports -WhatIf and -Confirm for safe operations.
 
.PARAMETER Version
    The Node.js version to remove (e.g., 'v20.11.0').
 
.PARAMETER Force
    Bypasses confirmation prompts (use with -Confirm:$false).
 
.EXAMPLE
    Remove-PwShNodeVersion -Version 'v18.19.0'
    Removes the specified version with confirmation prompt.
 
.EXAMPLE
    Remove-PwShNodeVersion -Version 'v18.19.0' -Confirm:$false
    Removes the version without confirmation.
 
.EXAMPLE
    'v16.20.0' | Remove-PwShNodeVersion
    Removes the version using pipeline input.
 
.EXAMPLE
    Remove-PwShNodeVersion -Version 'v20.11.0' -WhatIf
    Shows what would be removed without actually removing it.
 
.NOTES
    This operation cannot be undone. Use -WhatIf to preview changes.
#>

function Remove-PwShNodeVersion {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    [OutputType([void])]
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ArgumentCompleter({
            [OutputType([System.Management.Automation.CompletionResult])]
            param(
                [string] $CommandName,
                [string] $ParameterName,
                [string] $WordToComplete,
                [System.Management.Automation.Language.CommandAst] $CommandAst,
                [System.Collections.IDictionary] $FakeBoundParameters
            )
            (Get-PwShNodeVersion -ListAvailable).Version | Where-Object { $_ -like "$wordToComplete*" }
        })]
        [ValidateScript(
            {
                if ($_ -in (Get-PwShNodeVersion -ListAvailable).Version) {
                    $true
                } else {
                    Throw [System.Management.Automation.ItemNotFoundException] "Nodejs version '$_' is not installed."
                }
            }
        )]
        [string]$Version,

        [Parameter(Mandatory = $false)]
        [switch]$Force
    )
    Begin {
        Write-EnterFunction
    }

    Process {
        try {
            $installLocation = Get-PwShNodeInstallLocation
            $versionPath = Join-Path $installLocation $Version

            if (!(Test-Path $versionPath)) {
                Write-Warning "Version $Version is not installed at $versionPath"
                return
            }

            if ($PSCmdlet.ShouldProcess($versionPath, "Remove Node.js version")) {
                Remove-Item -Path $versionPath -Recurse -Force:$Force
                Write-Verbose "Removed Node.js version $Version from $versionPath"
                Write-Info "Node.js $Version removed successfully"
            }
        }
        catch {
            Write-Error "Failed to remove Node.js version ${Version}: $_"
            throw
        }
    }

    End {
        Write-LeaveFunction
    }
}

# Private helper functions

<#
.SYNOPSIS
    Detects the current operating system platform.
 
.DESCRIPTION
    Returns the platform identifier used by Node.js: 'win', 'linux', or 'darwin'.
 
.OUTPUTS
    String containing the platform identifier.
 
.NOTES
    This is an internal helper function.
#>

function Get-PwShNodePlatform {
    [CmdletBinding()]
    [OutputType([string])]
    Param()

    if ($IsWindows -or $PSVersionTable.Platform -eq 'Win32NT') {
        return 'win'
    }
    elseif ($IsLinux) {
        return 'linux'
    }
    elseif ($IsMacOS) {
        return 'darwin'
    }
    else {
        throw "Unsupported platform"
    }
}

<#
.SYNOPSIS
    Detects the current system architecture.
 
.DESCRIPTION
    Returns the architecture identifier used by Node.js: 'x64', 'x86', 'arm64', or 'armv7l'.
 
.OUTPUTS
    String containing the architecture identifier.
 
.NOTES
    This is an internal helper function.
#>

function Get-PwShNodeArchitecture {
    [CmdletBinding()]
    [OutputType([string])]
    Param()

    $arch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture

    switch ($arch) {
        'X64' { return 'x64' }
        'X86' { return 'x86' }
        'Arm64' { return 'arm64' }
        'Arm' { return 'armv7l' }
        default { throw "Unsupported architecture: $arch" }
    }
}

<#
.SYNOPSIS
    Gets the platform-specific default installation directory.
 
.DESCRIPTION
    Returns the default installation directory path, respecting platform conventions:
    - Windows: %LOCALAPPDATA%\PwShNode
    - Linux/macOS: $XDG_DATA_HOME/pwshnode or ~/.local/share/pwshnode
 
.OUTPUTS
    String containing the default installation directory path.
 
.NOTES
    This is an internal helper function.
#>

function Get-PwShNodeDefaultInstallLocation {
    [CmdletBinding()]
    [OutputType([string])]
    Param()

    if ($IsWindows -or $PSVersionTable.Platform -eq 'Win32NT') {
        return Join-Path -Path $env:LOCALAPPDATA -ChildPath 'PwShNode'
    }
    else {
        # Linux/macOS: use XDG_DATA_HOME or ~/.local/share
        $dataHome = $env:XDG_DATA_HOME
        if ([string]::IsNullOrEmpty($dataHome)) {
            $dataHome = $env:HOME
        }
        return Join-Path -Path $dataHome -ChildPath '.local' -AdditionalChildPath 'share', 'pwshnode'
    }
}

<#
.SYNOPSIS
    Clears the Node.js download cache.
 
.DESCRIPTION
    Removes all cached Node.js installation archives from the cache directory.
    This frees up disk space but will require re-downloading files for future installations.
 
.PARAMETER Force
    Bypasses confirmation prompts (use with -Confirm:$false).
 
.EXAMPLE
    Clear-PwShNodeCache
    Clears the cache with confirmation prompt.
 
.EXAMPLE
    Clear-PwShNodeCache -Confirm:$false
    Clears the cache without confirmation.
 
.EXAMPLE
    Clear-PwShNodeCache -WhatIf
    Shows what would be removed without actually removing it.
 
.NOTES
    This operation cannot be undone. Cached files will need to be re-downloaded.
#>

function Clear-PwShNodeCache {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [OutputType([void])]
    Param (
        [Parameter(Mandatory = $false)]
        [switch]$Force
    )
    Begin {
        Write-EnterFunction
    }

    Process {
        try {
            $installLocation = Get-PwShNodeInstallLocation
            $cacheDir = Join-Path -Path $installLocation -ChildPath 'cache'

            if (!(Test-Path $cacheDir)) {
                Write-Warning "Cache directory does not exist at $cacheDir"
                return
            }

            $cacheFiles = Get-ChildItem -Path $cacheDir -File
            $cacheSize = ($cacheFiles | Measure-Object -Property Length -Sum).Sum
            $cacheSizeMB = [math]::Round($cacheSize / 1MB, 2)
            $fileCount = $cacheFiles.Count

            if ($fileCount -eq 0) {
                Write-Info "Cache is already empty"
                return
            }

            $message = "Remove $fileCount cached file(s) ($cacheSizeMB MB) from $cacheDir"

            if ($PSCmdlet.ShouldProcess($cacheDir, $message)) {
                Remove-Item -Path $cacheDir -Recurse -Force:$Force
                New-Item -ItemType Directory -Path $cacheDir -Force:$Force | Out-Null
                Write-Verbose "Cleared cache directory: $cacheDir"
                Write-Info "Cache cleared successfully ($cacheSizeMB MB freed)"
            }
        }
        catch {
            Write-Error "Failed to clear cache: $_"
            throw
        }
    }

    End {
        Write-LeaveFunction
    }
}

<#
.SYNOPSIS
    Retrieves the current Node.js module configuration.
 
.DESCRIPTION
    Gets the configuration settings for the PwSh.Fw.NodeJs module, including
    default version, installation location, and last update timestamp.
    If no configuration file exists, returns default values.
 
.OUTPUTS
    PSCustomObject containing DefaultVersion, InstallLocation, ConfigPath, and LastUpdated.
 
.EXAMPLE
    Get-PwShNodeConfig
    Retrieves the current configuration settings.
 
.EXAMPLE
    $config = Get-PwShNodeConfig
    Write-Host "Default version: $($config.DefaultVersion)"
    Write-Host "Install location: $($config.InstallLocation)"
 
.EXAMPLE
    Get-PwShNodeConfig | Format-List
    Displays configuration in list format.
 
.EXAMPLE
    Get-PwShNodeConfig | ConvertTo-Json
    Exports configuration as JSON.
 
.NOTES
    Returns default values if configuration file does not exist.
    Default values:
    - DefaultVersion: null (not set)
    - InstallLocation: Platform-specific default location
    - ConfigPath: Location of the configuration file
    - LastUpdated: null (never updated)
#>

function Get-PwShNodeConfig {
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    Param ()

    Begin {
        Write-EnterFunction
    }

    Process {
        try {
            # If config file exists, load it
            if (Test-Path $Script:ConfigFile) {
                Write-Debug "Loading configuration from: $Script:ConfigFile"
                $config = Get-Content $Script:ConfigFile -Raw | ConvertFrom-Json
                Write-Devel -InputObject ($config | ConvertTo-Hashtable)
            }
            else {
                Write-Verbose "Configuration file does not exist"
            }
            return $config
        }
        catch {
            Write-Error "Failed to retrieve Node.js configuration: $_"
            return $null
        }
    }

    End {
        Write-LeaveFunction
    }
}

<#
.SYNOPSIS
    Activates a specific Node.js version by creating aliases.
 
.DESCRIPTION
    Creates PowerShell aliases for 'node' and 'npm' pointing to a specific Node.js version.
    This allows you to quickly switch between installed Node.js versions without modifying PATH.
    Supports selecting by explicit version, default version, or latest installed version.
 
.PARAMETER Version
    Specific Node.js version to activate (e.g., 'v20.11.0').
    Cannot be combined with -Default or -Latest.
 
.PARAMETER Default
    Use the default version configured via Set-PwShNodeDefaultVersion.
    Cannot be combined with -Version or -Latest.
 
.PARAMETER Latest
    Use the latest installed version.
    Can be combined with -Channel to select latest LTS or Current.
    Cannot be combined with -Version or -Default.
 
.PARAMETER Channel
    Filter by channel when using -Latest: 'LTS' or 'Current'.
    Only valid when -Latest is specified.
 
.PARAMETER SetDefault
    Also set this version as the default in configuration.
    Works with any parameter combination.
 
.OUTPUTS
    PSCustomObject containing Version, NodePath, NpmPath, and IsDefault status.
 
.EXAMPLE
    Use-PwShNodeVersion -Version 'v20.11.0'
    Activates version v20.11.0 by creating node and npm aliases.
 
.EXAMPLE
    Use-PwShNodeVersion -Default
    Activates the default version configured in settings.
 
.EXAMPLE
    Use-PwShNodeVersion -Latest
    Activates the latest installed version (any channel).
 
.EXAMPLE
    Use-PwShNodeVersion -Latest -Channel LTS
    Activates the latest installed LTS version.
 
.EXAMPLE
    Use-PwShNodeVersion -Latest -Channel Current -SetDefault
    Activates the latest Current version and sets it as default.
 
.EXAMPLE
    Use-PwShNodeVersion -Version 'v20.11.0' -SetDefault
    Activates v20.11.0 and sets it as the default version.
 
.NOTES
    Aliases are created in the current session by default.
    Node.js must be installed via Install-PwShNodeVersion before using this function.
    On Unix systems, the aliases point to files in the 'bin' directory.
    On Windows, the aliases point to .exe files in the version root directory.
#>

function Use-PwShNodeVersion {
    [CmdletBinding(DefaultParameterSetName = 'Version')]
    [OutputType([PSCustomObject])]
    Param (
        [Parameter(Mandatory = $true, ParameterSetName = 'Version', ValueFromPipeline = $true)]
        [ArgumentCompleter({
            [OutputType([System.Management.Automation.CompletionResult])]
            param(
                [string] $CommandName,
                [string] $ParameterName,
                [string] $WordToComplete,
                [System.Management.Automation.Language.CommandAst] $CommandAst,
                [System.Collections.IDictionary] $FakeBoundParameters
            )
            (Get-PwShNodeVersion -ListAvailable).Version | Where-Object { $_ -like "$wordToComplete*" }
        })]
        [ValidateScript(
            {
                if ($_ -in (Get-PwShNodeVersion -ListAvailable).Version) {
                    $true
                } else {
                    Throw [System.Management.Automation.ItemNotFoundException] "Nodejs version '$_' is not installed."
                }
            }
        )]
        [string]$Version,

        [Parameter(Mandatory = $true, ParameterSetName = 'Default')]
        [switch]$Default,

        [Parameter(Mandatory = $true, ParameterSetName = 'Latest')]
        [switch]$Latest,

        [Parameter(Mandatory = $false, ParameterSetName = 'Latest')]
        [ValidateSet('Current', 'LTS')]
        [string]$Channel,

        [Parameter(Mandatory = $false)]
        [switch]$SetDefault
    )

    Begin {
        Write-EnterFunction
    }

    Process {
        try {
            $selectedVersion = $null

            # Determine which version to use
            switch ($PSCmdlet.ParameterSetName) {
                'Version' {
                    Write-Verbose "Using specified version: $Version"
                    $selectedVersion = $Version
                }
                'Default' {
                    Write-Verbose "Using default version from configuration"
                    $config = Get-PwShNodeConfig

                    if (-not $config.DefaultVersion) {
                        Write-Fatal "No default version configured. Use Set-PwShNodeDefaultVersion to set a default version."
                    }

                    $selectedVersion = $config.DefaultVersion
                    Write-Verbose "Default version: $selectedVersion"
                }
                'Latest' {
                    Write-Verbose "Finding latest installed version"

                    $params = @{
                        ListAvailable = $true
                        Latest = $true
                    }

                    if ($Channel) {
                        $params.Channel = $Channel
                        Write-Verbose "Filtering by channel: $Channel"
                    }

                    $latestVersion = Get-PwShNodeVersion @params

                    if (-not $latestVersion) {
                        $channelMsg = if ($Channel) { " ($Channel)" } else { "" }
                        throw "No installed Node.js versions found$channelMsg. Install a version first using Install-PwShNodeVersion."
                    }

                    $selectedVersion = $latestVersion.Version
                    Write-Verbose "Latest version: $selectedVersion"
                }
            }

            # Verify the version is installed
            $installLocation = Get-PwShNodeInstallLocation
            $versionPath = Join-Path -Path $installLocation -ChildPath $selectedVersion

            if (-not (Test-Path $versionPath)) {
                throw "Version $selectedVersion is not installed at $versionPath. Install it first using Install-PwShNodeVersion."
            }

            # Determine paths based on platform
            $platform = Get-PwShNodePlatform

            if ($platform -eq 'win') {
                # Windows: executables are in the root directory
                $nodePath = Join-Path $versionPath 'node.exe'
                $npmPath = Join-Path $versionPath 'npm.cmd'
                $npxPath = Join-Path $versionPath 'npx.cmd'
            }
            else {
                # Linux/macOS: executables are in the bin directory
                $binPath = Join-Path $versionPath 'bin'
                $nodePath = Join-Path $binPath 'node'
                $npmPath = Join-Path $binPath 'npm'
                $npxPath = Join-Path $binPath 'npx'
            }

            # Verify node executable exists
            if (-not (Test-Path $nodePath)) {
                throw "Node executable not found at $nodePath. The installation may be corrupted."
            }

            # Create aliases
            $aliasScope = if ($Global) { 'Global' } else { 'Local' }

            Write-Verbose "Creating aliases with scope: $aliasScope"

            # Remove existing aliases if they exist
            Remove-Item Alias:node -ErrorAction SilentlyContinue
            Remove-Item Alias:npm -ErrorAction SilentlyContinue
            Remove-Item Alias:npx -ErrorAction SilentlyContinue

            New-Alias -Name node -Value $nodePath -Scope Global -Force
            if (Test-Path $npmPath) {
                New-Alias -Name npm -Value $npmPath -Scope Global -Force
            }
            if (Test-Path $npxPath) {
                New-Alias -Name npx -Value $npxPath -Scope Global -Force
            }
            Write-Verbose "Aliases created successfully"

            # Set as default if requested
            $isNowDefault = $false
            if ($SetDefault) {
                Write-Verbose "Setting $selectedVersion as default version"
                Set-PwShNodeDefaultVersion -Version $selectedVersion -Confirm:$false
                $isNowDefault = $true
            }

            # Get actual node version for verification
            $nodeVersionOutput = & $nodePath --version 2>&1

            Write-Info "✓ Activated Node.js $selectedVersion"
            Write-Devel " Node: $nodeVersionOutput"

            if (Test-Path $npmPath) {
                $npmVersionOutput = & $npmPath --version 2>&1
                Write-Devel " npm: v$npmVersionOutput"
            }

            # Return information object
            $result = [PSCustomObject]@{
                Version = $selectedVersion
                NodePath = $nodePath
                NpmPath = if (Test-Path $npmPath) { $npmPath } else { $null }
                NpxPath = if (Test-Path $npxPath) { $npxPath } else { $null }
                NodeVersion = $nodeVersionOutput
                NpmVersion = if (Test-Path $npmPath) { (& $npmPath --version 2>&1) } else { $null }
                AliasScope = $aliasScope
                IsDefault = $isNowDefault
                ActivatedAt = Get-Date
            }

            return $result
        }
        catch {
            Write-Error "Failed to activate Node.js version: $_"
            throw
        }
    }

    End {
        Write-LeaveFunction
    }
}