src/public/Orchestration/Save-AitherPlaybook.ps1

#Requires -Version 7.0

<#
.SYNOPSIS
    Save a playbook definition to disk

.DESCRIPTION
    Creates or updates a playbook file in the playbooks directory.
    Can create from scratch or save an existing playbook object.

.PARAMETER Name
    Name of the playbook (will be used as filename)

.PARAMETER Description
    Description of the playbook

.PARAMETER Sequence
    Array of script numbers or script definitions to execute

.PARAMETER Variables
    Default variables for the playbook

.PARAMETER Options
    Playbook options (Parallel, MaxConcurrency, etc.)

.PARAMETER Playbook
    Playbook object to save (overrides other parameters)

.PARAMETER Force
    Overwrite existing playbook

.EXAMPLE
    Save-AitherPlaybook -Name 'my-playbook' -Description 'Test playbook' -Sequence @('0407', '0413')

.EXAMPLE
    $playbook = @{
        Name = 'custom-playbook'
        Description = 'Custom validation'
        Sequence = @('0407', '0413', '0402')
        Variables = @{ CI = $true }
        Options = @{ Parallel = $true; MaxConcurrency = 4 }
    }
    Save-AitherPlaybook -Playbook $playbook

.NOTES
    Playbooks are saved as .psd1 files in library/playbooks/
#>

function Save-AitherPlaybook {
    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'FromObject')]
    param(
        [Parameter(Mandatory = $false, ParameterSetName = 'New')]
        [string]$Name,

        [Parameter(ParameterSetName = 'New')]
        [string]$Description = '',

        [Parameter(ParameterSetName = 'New')]
        [object[]]$Sequence = @(),

        [Parameter(ParameterSetName = 'New')]
        [hashtable]$Variables = @{},

        [Parameter(ParameterSetName = 'New')]
        [hashtable]$Options = @{},

        [Parameter(Mandatory = $false, ParameterSetName = 'FromObject', ValueFromPipeline)]
        [hashtable]$Playbook,

        [Parameter()]
        [switch]$Force,

        [switch]$ShowOutput
    )

    begin {
        # Save original log targets
        $originalLogTargets = $script:AitherLogTargets

        # Set log targets based on ShowOutput parameter
        if ($ShowOutput) {
            # Ensure Console is in the log targets
            if ($script:AitherLogTargets -notcontains 'Console') {
                $script:AitherLogTargets += 'Console'
            }
        }
        else {
            # Remove Console from log targets if present (default behavior)
            if ($script:AitherLogTargets -contains 'Console') {
                $script:AitherLogTargets = $script:AitherLogTargets | Where-Object { $_ -ne 'Console' }
            }
        }

        $moduleRoot = Get-AitherModuleRoot
        $playbooksPath = Join-Path $moduleRoot 'library' 'playbooks'

        if (-not (Test-Path $playbooksPath)) {
            New-Item -ItemType Directory -Path $playbooksPath -Force | Out-Null
        }
    }

    process {
        try {
            try {
                # Build playbook object
                if ($PSCmdlet.ParameterSetName -eq 'New') {
                    $Playbook = @{
                        Name        = $Name
                        Description = $Description
                        Version     = '1.0.0'
                        Sequence    = $Sequence
                        Variables   = $Variables
                        Options     = $Options
                    }
                }

                # Ensure required fields
                if (-not $Playbook -or -not $Playbook.Name) {
                    # During module validation, Playbook may be empty - skip validation
                    if ($PSCmdlet.MyInvocation.InvocationName -eq '.') {
                        return
                    }
                    throw "Playbook must have a Name"
                }
                if (-not $Playbook.Version) {
                    $Playbook.Version = '1.0.0'
                }

                # Build file path
                $fileName = "$($Playbook.Name).psd1"
                $filePath = Join-Path $playbooksPath $fileName

                # Check if exists
                if ((Test-Path $filePath) -and -not $Force) {
                    throw "Playbook already exists: $fileName. Use -Force to overwrite."
                }

                # Convert to PowerShell data file format
                $content = "@{`n"
                $content += " Name = '$($Playbook.Name)'`n"
                $content += " Description = '$($Playbook.Description)'`n"
                $content += " Version = '$($Playbook.Version)'`n"
                $content += "`n"

                if ($Playbook.Sequence) {
                    $content += " Sequence = @("
                    if ($Playbook.Sequence[0] -is [string]) {
                        $content += "'$($Playbook.Sequence -join "', '")'"
                    }
                    else {
                        $seqStrings = $Playbook.Sequence | ForEach-Object {
                            if ($_ -is [hashtable]) {
                                $script = $_.Script -replace '\.ps1$', ''
                                "@{ Script = '$script'; Description = '$($_.Description)' }"
                            }
                            else {
                                "'$_'"
                            }
                        }
                        $content += $seqStrings -join ",`n "
                    }
                    $content += ")`n"
                    $content += "`n"
                }
                if ($Playbook.Variables -and $Playbook.Variables.Count -gt 0) {
                    $content += " Variables = @{`n"
                    foreach ($key in $Playbook.Variables.Keys) {
                        $value = $Playbook.Variables[$key]
                        if ($value -is [string]) {
                            $content += " $key = '$value'`n"
                        }
                        elseif ($value -is [bool]) {
                            $content += " $key = `$$value`n"
                        }
                        else {
                            $content += " $key = $value`n"
                        }
                    }
                    $content += " }`n"
                    $content += "`n"
                }
                if ($Playbook.Options -and $Playbook.Options.Count -gt 0) {
                    $content += " Options = @{`n"
                    foreach ($key in $Playbook.Options.Keys) {
                        $value = $Playbook.Options[$key]
                        if ($value -is [string]) {
                            $content += " $key = '$value'`n"
                        }
                        elseif ($value -is [bool]) {
                            $content += " $key = `$$value`n"
                        }
                        else {
                            $content += " $key = $value`n"
                        }
                    }
                    $content += " }`n"
                }

                $content += "}`n"

                # Save file
                if ($PSCmdlet.ShouldProcess($filePath, "Save playbook")) {
                    Set-Content -Path $filePath -Value $content -Encoding UTF8
                    Write-AitherLog -Level Information -Message "Playbook saved: $filePath" -Source $PSCmdlet.MyInvocation.MyCommand.Name
                    return $filePath
                }
            }
            catch {
                Write-AitherLog -Level Error -Message "Failed to save playbook: $_" -Source 'Save-AitherPlaybook' -Exception $_
                throw
            }
        }
        finally {
            # Restore original log targets
            $script:AitherLogTargets = $originalLogTargets
        }
    }

}