functions/public/Save-KlippyConfigFile.ps1

function Save-KlippyConfigFile {
    <#
    .SYNOPSIS
        Downloads a configuration file from a Klipper printer.

    .DESCRIPTION
        Downloads a configuration file from the printer's config directory
        to local storage. Supports pipeline input from Get-KlippyConfigFile.
        Can optionally add timestamps to filenames for backup purposes.

    .PARAMETER Id
        The unique identifier of the printer.

    .PARAMETER PrinterName
        The friendly name of the printer.

    .PARAMETER InputObject
        A ConfigFile object from pipeline input (from Get-KlippyConfigFile).

    .PARAMETER Path
        Path of the config file on the printer to download.

    .PARAMETER Destination
        Local destination path. Can be a directory or full file path.
        If a directory, the original filename is preserved.
        Defaults to current directory.

    .PARAMETER Timestamp
        Add a timestamp to the filename for backup purposes.
        Format: filename_YYYYMMDD_HHmmss.ext

    .PARAMETER Force
        Overwrite existing local file without prompting.

    .PARAMETER PassThru
        Return the local FileInfo object.

    .EXAMPLE
        Save-KlippyConfigFile -Path "printer.cfg"
        Downloads printer.cfg to current directory.

    .EXAMPLE
        Save-KlippyConfigFile -Path "printer.cfg" -Destination "C:\backups\" -Timestamp
        Downloads with timestamp: printer_20250115_143022.cfg

    .EXAMPLE
        Get-KlippyConfigFile -Path "*.cfg" | Save-KlippyConfigFile -Destination "./backup/"
        Downloads all .cfg files via pipeline.

    .OUTPUTS
        System.IO.FileInfo (when -PassThru is used).
    #>

    [CmdletBinding(DefaultParameterSetName = 'Default', SupportsShouldProcess = $true)]
    [OutputType([System.IO.FileInfo])]
    param(
        [Parameter(Mandatory = $true, ParameterSetName = 'ById')]
        [ValidateNotNullOrEmpty()]
        [string]$Id,

        [Parameter(Mandatory = $true, ParameterSetName = 'ByName')]
        [ValidateNotNullOrEmpty()]
        [string]$PrinterName,

        [Parameter(Mandatory = $true, ParameterSetName = 'ByObject', ValueFromPipeline = $true)]
        [PSCustomObject]$InputObject,

        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'Default')]
        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'ById')]
        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'ByName')]
        [ValidateNotNullOrEmpty()]
        [string]$Path,

        [Parameter(Position = 1)]
        [string]$Destination = ".",

        [Parameter()]
        [switch]$Timestamp,

        [Parameter()]
        [switch]$Force,

        [Parameter()]
        [switch]$PassThru
    )

    process {
        # Handle pipeline input (ConfigFile object)
        if ($PSCmdlet.ParameterSetName -eq 'ByObject') {
            $printer = Resolve-KlippyPrinterTarget -Id $InputObject.PrinterId
            $sourcePath = $InputObject.Path
        }
        else {
            # Resolve printer
            $resolveParams = @{}
            switch ($PSCmdlet.ParameterSetName) {
                'ById' { $resolveParams['Id'] = $Id }
                'ByName' { $resolveParams['PrinterName'] = $PrinterName }
            }

            $printer = Resolve-KlippyPrinterTarget @resolveParams
            $sourcePath = $Path.TrimStart('/')
        }

        # Determine local destination
        $sourceFilename = Split-Path $sourcePath -Leaf

        # Add timestamp if requested
        if ($Timestamp) {
            $ts = Get-Date -Format "yyyyMMdd_HHmmss"
            $baseName = [System.IO.Path]::GetFileNameWithoutExtension($sourceFilename)
            $extension = [System.IO.Path]::GetExtension($sourceFilename)
            $sourceFilename = "${baseName}_${ts}${extension}"
        }

        $resolvedDest = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Destination)

        if (Test-Path -Path $resolvedDest -PathType Container) {
            # Destination is a directory
            $localPath = Join-Path $resolvedDest $sourceFilename
        }
        elseif ($Destination.EndsWith([System.IO.Path]::DirectorySeparatorChar) -or $Destination.EndsWith('/')) {
            # Destination looks like a directory that doesn't exist yet
            $null = New-Item -Path $resolvedDest -ItemType Directory -Force
            $localPath = Join-Path $resolvedDest $sourceFilename
        }
        else {
            # Destination is a file path
            $localPath = $resolvedDest
            $parentDir = Split-Path $localPath -Parent
            if ($parentDir -and -not (Test-Path $parentDir)) {
                $null = New-Item -Path $parentDir -ItemType Directory -Force
            }
        }

        # Check if file exists
        if ((Test-Path $localPath) -and -not $Force) {
            if (-not $PSCmdlet.ShouldProcess($localPath, "Overwrite existing file")) {
                Write-Warning "File '$localPath' already exists. Use -Force to overwrite."
                return
            }
        }

        if ($PSCmdlet.ShouldProcess("$($printer.PrinterName):config/$sourcePath -> $localPath", "Download config file")) {
            try {
                Write-Verbose "[$($printer.PrinterName)] Downloading config: $sourcePath -> $localPath"

                # Build URI (URL encode the path)
                $encodedPath = [System.Uri]::EscapeDataString($sourcePath)
                $uri = "$($printer.Uri)/server/files/config/$encodedPath"

                # Build headers
                $headers = @{}
                if ($printer.ApiKey) {
                    $headers['X-Api-Key'] = $printer.ApiKey
                }

                # Download file
                Invoke-WebRequest -Uri $uri -Method GET -Headers $headers -OutFile $localPath -TimeoutSec 60

                Write-Verbose "[$($printer.PrinterName)] Download complete: $localPath"

                if ($PassThru) {
                    Get-Item -Path $localPath
                }
            }
            catch {
                Write-Error "[$($printer.PrinterName)] Failed to download config '$sourcePath': $_"
            }
        }
    }
}