functions/public/Invoke-KlippyGcode.ps1

function Invoke-KlippyGcode {
    <#
    .SYNOPSIS
        Executes G-code commands on a Klipper printer.

    .DESCRIPTION
        Sends G-code commands to the printer for execution.
        Supports single commands, multiple commands, and script files.

    .PARAMETER Id
        The unique identifier of the printer.

    .PARAMETER PrinterName
        The friendly name of the printer.

    .PARAMETER InputObject
        A printer object from pipeline input.

    .PARAMETER Gcode
        The G-code command(s) to execute. Can be a single command,
        multiple commands separated by newlines, or an array.

    .PARAMETER Path
        Path to a G-code script file to execute.

    .EXAMPLE
        Invoke-KlippyGcode -Gcode "G28"
        Homes all axes on the default printer.

    .EXAMPLE
        Invoke-KlippyGcode -PrinterName "voronv2" -Gcode "M104 S200", "M140 S60"
        Sets extruder to 200C and bed to 60C.

    .EXAMPLE
        Invoke-KlippyGcode -Gcode @"
        G28
        G1 X150 Y150 Z50 F3000
        "@
        Executes multiple G-code commands.

    .EXAMPLE
        "G28", "G1 Z10 F600" | Invoke-KlippyGcode -PrinterName "voronv2"
        Pipes G-code commands to the printer.

    .EXAMPLE
        Invoke-KlippyGcode -Path "/path/to/macro.gcode" -PrinterName "voronv2"
        Executes commands from a file.

    .OUTPUTS
        PSCustomObject with execution result.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$Id,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$PrinterName,

        [Parameter()]
        [PSCustomObject]$InputObject,

        [Parameter(Position = 0, ValueFromPipeline = $true)]
        [string[]]$Gcode,

        [Parameter()]
        [Alias('Script')]
        [string]$Path
    )

    begin {
        $allCommands = [System.Collections.Generic.List[string]]::new()

        # Resolve printer once at the start
        $resolveParams = @{}
        if ($Id) { $resolveParams['Id'] = $Id }
        elseif ($PrinterName) { $resolveParams['PrinterName'] = $PrinterName }
        elseif ($InputObject) { $resolveParams['InputObject'] = $InputObject }

        $printer = Resolve-KlippyPrinterTarget @resolveParams

        # Load from file if Path specified
        if ($Path) {
            if (-not (Test-Path -Path $Path -PathType Leaf)) {
                throw "Script file not found: $Path"
            }
            $scriptContent = Get-Content -Path $Path -Raw
            $lines = $scriptContent -split "`r?`n" | Where-Object {
                $line = $_.Trim()
                $line -and -not $line.StartsWith(';') -and -not $line.StartsWith('#')
            }
            foreach ($line in $lines) {
                $allCommands.Add($line.Trim())
            }
        }
    }

    process {
        if (-not $Path -and $Gcode) {
            foreach ($cmd in $Gcode) {
                # Split multi-line strings
                $lines = $cmd -split "`r?`n" | Where-Object {
                    $line = $_.Trim()
                    $line -and -not $line.StartsWith(';') -and -not $line.StartsWith('#')
                }
                foreach ($line in $lines) {
                    $allCommands.Add($line.Trim())
                }
            }
        }
    }

    end {
        if ($allCommands.Count -eq 0) {
            Write-Warning "No G-code commands to execute. Provide -Gcode or -Path."
            return
        }

        # Join all commands with newline for batch execution
        $gcodeScript = $allCommands -join "`n"

        $commandPreview = if ($allCommands.Count -le 3) {
            $allCommands -join "; "
        }
        else {
            "$($allCommands[0]); $($allCommands[1]); ... ($($allCommands.Count) commands)"
        }

        if ($PSCmdlet.ShouldProcess($printer.PrinterName, "Execute G-code: $commandPreview")) {
            try {
                Write-Verbose "[$($printer.PrinterName)] Executing $($allCommands.Count) G-code command(s)"

                foreach ($cmd in $allCommands) {
                    Write-Verbose " > $cmd"
                }

                # URL encode the script
                $encodedScript = [System.Uri]::EscapeDataString($gcodeScript)
                $endpoint = "printer/gcode/script?script=$encodedScript"

                $null = Invoke-KlippyJsonRpc -Printer $printer -Method $endpoint

                Write-Verbose "[$($printer.PrinterName)] G-code executed successfully"

                # Return result object
                [PSCustomObject]@{
                    PSTypeName   = 'KlippyCLI.GcodeResult'
                    PrinterId    = $printer.Id
                    PrinterName  = $printer.PrinterName
                    CommandCount = $allCommands.Count
                    Commands     = $allCommands.ToArray()
                    Success      = $true
                }
            }
            catch {
                Write-Error "Failed to execute G-code on '$($printer.PrinterName)': $_"

                [PSCustomObject]@{
                    PSTypeName   = 'KlippyCLI.GcodeResult'
                    PrinterId    = $printer.Id
                    PrinterName  = $printer.PrinterName
                    CommandCount = $allCommands.Count
                    Commands     = $allCommands.ToArray()
                    Success      = $false
                    Error        = $_.Exception.Message
                }
            }
        }
    }
}