src/public/Execution/Get-AitherScript.ps1

#Requires -Version 7.0

<#
.SYNOPSIS
    Get automation script information and metadata

.DESCRIPTION
    Discovers and retrieves information about automation scripts in the automation-scripts directory.
    Can list all scripts, search by number or name, get script metadata, and show parameter information.

    This cmdlet is essential for discovering available automation scripts and understanding their
    parameters before execution. It parses script files to extract metadata including parameters,
    descriptions, and help information.

.PARAMETER Script
    Script identifier - can be a number (e.g., '0501') or script name pattern.
    This parameter is required when using Get, Parameters, Help, Path, or Metadata parameter sets.

    Examples:
    - "0501" - Finds script starting with 0501
    - "system" - Finds scripts with "system" in the name
    - "0501_System-Config" - Exact script name match

.PARAMETER List
    List all available automation scripts. This is the default behavior if no parameters are provided.
    Returns a list of all scripts with their numbers, names, descriptions, and paths.

.PARAMETER Search
    Search scripts by number, name, or description. Useful for finding scripts when you only
    remember part of the name or description. The search is case-insensitive.

.PARAMETER ShowParameters
    Show parameters for the specified script. Displays all parameters with their types,
    mandatory status, and help text. Use this to understand what parameters a script accepts
    before running it.

.PARAMETER ShowHelp
    Show full help for the specified script. Displays comprehensive help information including
    synopsis, description, and all parameters with their descriptions.

.PARAMETER Path
    Return only the file path to the script. Useful when you need the full path for other
    operations or when piping to other cmdlets.

.PARAMETER Metadata
    Return script metadata as a PowerShell object. Includes number, name, description, path,
    and all parameters with their details. Useful for programmatic access to script information.

.INPUTS
    System.String
    You can pipe script identifiers (numbers or names) to Get-AitherScript.

.OUTPUTS
    PSCustomObject
    Returns script information objects with properties: Number, Name, Description, Path, FileName, Parameters

    When -Path is used, returns System.String (the file path)

    When -Metadata is used, returns PSCustomObject with detailed metadata

.EXAMPLE
    Get-AitherScript -List

    Lists all available automation scripts with their basic information.

.EXAMPLE
    Get-AitherScript -Script 0501

    Gets information about script 0501, including its path and description.

.EXAMPLE
    Get-AitherScript -Script 0501 -ShowParameters

    Shows all parameters that script 0501 accepts, including which are mandatory.

.EXAMPLE
    Get-AitherScript -Script 0501 -ShowHelp

    Displays full help information for script 0501.

.EXAMPLE
    Get-AitherScript -Search 'system'

    Searches for all scripts containing "system" in their name or description.

.EXAMPLE
    Get-AitherScript -Script 0501 -Metadata

    Returns detailed metadata object for script 0501, useful for programmatic access.

.EXAMPLE
    '0501', '0502' | Get-AitherScript

    Gets information for multiple scripts by piping script identifiers.

.EXAMPLE
    Get-AitherScript -Script 0501 -Path

    Returns only the file path to script 0501.

.NOTES
    Scripts are located in library/automation-scripts/ directory.
    Scripts follow the naming pattern: NNNN_Description.ps1 where NNNN is a 4-digit number.

    This cmdlet uses PowerShell AST (Abstract Syntax Tree) parsing to extract script metadata,
    so it can provide accurate parameter information even without executing the script.

.LINK
    Invoke-AitherScript
    Get-AitherScriptMetadata
#>

function Get-AitherScript {
    [OutputType([PSCustomObject], [System.String])]
    [CmdletBinding(DefaultParameterSetName = 'List')]
    param(
        [Parameter(ParameterSetName = 'Get', Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName, HelpMessage = "Script identifier (number or name).")]
        [Parameter(ParameterSetName = 'Parameters', Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName, HelpMessage = "Script identifier (number or name).")]
        [Parameter(ParameterSetName = 'Help', Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName, HelpMessage = "Script identifier (number or name).")]
        [Parameter(ParameterSetName = 'Path', Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName, HelpMessage = "Script identifier (number or name).")]
        [Parameter(ParameterSetName = 'Metadata', Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName, HelpMessage = "Script identifier (number or name).")]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter({
                param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
                if (Get-Command Get-AitherScript -ErrorAction SilentlyContinue) {
                    Get-AitherScript -List | Where-Object { $_.Name -like "$wordToComplete*" -or $_.Number -like "$wordToComplete*" } | ForEach-Object {
                        [System.Management.Automation.CompletionResult]::new($_.Number, "$($_.Number) - $($_.Name)", 'ParameterValue', $_.Description)
                    }
                }
            })]
        [string]$Script,

        [Parameter(ParameterSetName = 'List', HelpMessage = "List all available automation scripts.")]
        [switch]$List,

        [Parameter(ParameterSetName = 'Search', HelpMessage = "Search scripts by number, name, or description.")]
        [string]$Search,

        [Parameter(ParameterSetName = 'Parameters', Mandatory = $true, HelpMessage = "Show parameters for the specified script.")]
        [switch]$ShowParameters,

        [Parameter(ParameterSetName = 'Help', Mandatory = $true, HelpMessage = "Show full help for the specified script.")]
        [switch]$ShowHelp,

        [Parameter(ParameterSetName = 'Path', Mandatory = $true, HelpMessage = "Return only the file path to the script.")]
        [switch]$Path,

        [Parameter(ParameterSetName = 'Metadata', Mandatory = $true, HelpMessage = "Return script metadata as a PowerShell object.")]
        [switch]$Metadata
    )

    begin {
        # Get scripts directory using robust discovery
        try {
            $scriptsPath = Get-AitherScriptsPath
        }
        catch {
            Write-AitherLog -Level Warning -Message "Could not resolve scripts path: $($_.Exception.Message)" -Source 'Get-AitherScript'
            $scriptsPath = $null
        }

        # Use shared helper function

        function Get-ScriptMetadata {
            param([System.IO.FileInfo]$ScriptFile)

            $metadata = @{
                Number      = $null
                Name        = $null
                FullName    = $ScriptFile.Name
                Path        = $ScriptFile.FullName
                Description = ''
                Parameters  = @{}
                Synopsis    = ''
                Help        = ''
                Stage       = 'Unknown'
            }

            # Extract number from filename
            if ($ScriptFile.Name -match '^(\d{4})_') {
                $metadata.Number = $matches[1]
                $metadata.Name = $ScriptFile.BaseName -replace '^\d{4}_', ''
            }
            else {
                $metadata.Name = $ScriptFile.BaseName
            }

            try {
                # Parse script AST for metadata
                $tokens = $null
                $ast = [System.Management.Automation.Language.Parser]::ParseFile(
                    $ScriptFile.FullName,
                    [ref]$tokens,
                    [ref]$null
                )

                # Extract comment-based help
                $helpComments = $tokens | Where-Object { $_.Kind -eq 'Comment' }
                $synopsis = $helpComments | Where-Object { $_.Text -match '\.SYNOPSIS' } | Select-Object -First 1
                if ($synopsis) {
                    $lines = $synopsis.Text -split "`n"
                    $inSynopsis = $false
                    $synopsisText = @()
                    foreach ($line in $lines) {
                        if ($line -match '\.SYNOPSIS') {
                            $inSynopsis = $true
                            continue
                        }
                        if ($inSynopsis -and $line -match '^\s*\.') {
                            break
                        }
                        if ($inSynopsis) {
                            $synopsisText += $line.Trim()
                        }
                    }
                    $metadata.Synopsis = ($synopsisText -join ' ').Trim()
                    $metadata.Description = $metadata.Synopsis
                }

                # Extract description
                $description = $helpComments | Where-Object { $_.Text -match '\.DESCRIPTION' } | Select-Object -First 1
                if ($description) {
                    $lines = $description.Text -split "`n"
                    $inDescription = $false
                    $descText = @()
                    foreach ($line in $lines) {
                        if ($line -match '\.DESCRIPTION') {
                            $inDescription = $true
                            continue
                        }
                        if ($inDescription -and $line -match '^\s*\.') {
                            break
                        }
                        if ($inDescription) {
                            $descText += $line.Trim()
                        }
                    }
                    if ($descText.Count -gt 0) {
                        $metadata.Description = ($descText -join ' ').Trim()
                    }
                }

                # Extract NOTES for Stage
                $notes = $helpComments | Where-Object { $_.Text -match '\.NOTES' } | Select-Object -First 1
                if ($notes) {
                    Write-Verbose "Found NOTES length: $($notes.Text.Length)"
                    if ($notes.Text -match 'Stage:\s*([a-zA-Z0-9-]+)') {
                        $metadata.Stage = $matches[1]
                    }
                    else {
                        Write-Verbose "Regex did not match for Stage in NOTES"
                    }
                }
                else {
                    Write-Verbose "No NOTES found"
                }

                # Extract parameters
                $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.ParameterAst] }, $true) |
                ForEach-Object {
                    $paramName = $_.Name.VariablePath.UserPath
                    $paramInfo = @{
                        Name      = $paramName
                        Type      = if ($_.TypeName) { $_.TypeName.FullName } else { 'object' }
                        Mandatory = $false
                        Help      = ''
                    }

                    # Check if mandatory
                    $paramAttr = $_.Attributes.Where({ $_.TypeName.Name -eq 'Parameter' })
                    if ($paramAttr) {
                        $mandatoryAttr = $paramAttr.Attributes.Where({
                                $_.NamedArguments.ArgumentName -eq 'Mandatory'
                            })
                        if ($mandatoryAttr) {
                            $paramInfo.Mandatory = $mandatoryAttr.Argument.Value -eq $true
                        }
                    }

                    # Get parameter help
                    $paramHelp = $helpComments | Where-Object {
                        $_.Text -match "\.PARAMETER\s+$paramName"
                    } | Select-Object -First 1

                    if ($paramHelp) {
                        $lines = $paramHelp.Text -split "`n"
                        $inParam = $false
                        $helpText = @()
                        foreach ($line in $lines) {
                            if ($line -match "\.PARAMETER\s+$paramName") {
                                $inParam = $true
                                continue
                            }
                            if ($inParam -and $line -match '^\s*\.') {
                                break
                            }
                            if ($inParam) {
                                $helpText += $line.Trim()
                            }
                        }
                        $paramInfo.Help = ($helpText -join ' ').Trim()
                    }

                    $metadata.Parameters[$paramName] = $paramInfo
                }
            }
            catch {
                Write-AitherLog -Level Warning -Message "Failed to parse script metadata: $_" -Source 'Get-AitherScript' -Exception $_
                return $metadata
            }

            return $metadata
        }
    }

    process {
        try {
            if (-not (Test-Path $scriptsPath)) {
                Write-AitherLog -Level Warning -Message "Automation scripts directory not found: $scriptsPath" -Source 'Get-AitherScript'
                return @()
            }

            # List all scripts
            if ($List -or ($PSCmdlet.ParameterSetName -eq 'List' -and -not $Script)) {
                $scripts = Get-ChildItem -Path $scriptsPath -Filter '*.ps1' -ErrorAction SilentlyContinue |
                Where-Object { $_.Name -match '^\d{4}_' } |
                Sort-Object Name

                return $scripts | ForEach-Object {
                    $meta = Get-ScriptMetadata -ScriptFile $_
                    [PSCustomObject]@{
                        Number       = $meta.Number
                        ScriptNumber = $meta.Number
                        Name         = $meta.Name
                        Description  = $meta.Synopsis
                        Path         = $meta.Path
                        FileName     = $meta.FullName
                    }
                }
            }

            # Search scripts
            if ($Search) {
                $scripts = Get-ChildItem -Path $scriptsPath -Filter '*.ps1' -ErrorAction SilentlyContinue |
                Where-Object { $_.Name -match '^\d{4}_' }

                return $scripts | ForEach-Object {
                    $meta = Get-ScriptMetadata -ScriptFile $_
                    $searchText = "$($meta.Number) $($meta.Name) $($meta.Description)" -replace '-', ' '
                    if ($searchText -match $Search) {
                        [PSCustomObject]@{
                            Number       = $meta.Number
                            ScriptNumber = $meta.Number
                            Name         = $meta.Name
                            Description  = $meta.Synopsis
                            Path         = $meta.Path
                            FileName     = $meta.FullName
                        }
                    }
                } | Where-Object { $_ }
            }

            # Get specific script
            if ($Script) {
                $scriptFile = Find-AitherScriptFile -ScriptId $Script -ScriptsPath $scriptsPath

                if (-not $scriptFile) {
                    Invoke-AitherErrorHandler -ErrorRecord ([System.Management.Automation.ErrorRecord]::new(
                            [System.Exception]::new("Script not found: $Script"),
                            "ScriptNotFound",
                            [System.Management.Automation.ErrorCategory]::ObjectNotFound,
                            $Script
                        )) -Operation "Getting script: $Script" -Parameters $PSBoundParameters -ErrorAction Continue
                    return $null
                }

                $meta = Get-ScriptMetadata -ScriptFile $scriptFile

                # Return path only
                if ($Path) {
                    return $meta.Path
                }

                # Show parameters
                if ($ShowParameters) {
                    Write-AitherLog -Level Information -Message "Script: $($meta.Number) - $($meta.Name)" -Source 'Get-AitherScript'
                    Write-AitherLog -Level Information -Message "Path: $($meta.Path)" -Source 'Get-AitherScript'

                    if ($meta.Parameters.Count -eq 0) {
                        Write-AitherLog -Level Warning -Message "No parameters found." -Source 'Get-AitherScript'
                        return
                    }

                    Write-AitherLog -Level Information -Message "Parameters:" -Source 'Get-AitherScript'
                    Write-AitherLog -Level Information -Message ("=" * 50) -Source 'Get-AitherScript'

                    foreach ($param in $meta.Parameters.Values | Sort-Object Name) {
                        $mandatory = if ($param.Mandatory) { "[MANDATORY] " } else { "" }
                        $paramLine = "-$($param.Name) $mandatory($($param.Type))"
                        Write-AitherLog -Level Information -Message $paramLine -Source 'Get-AitherScript'

                        if ($param.Help) {
                            Write-AitherLog -Level Information -Message " $($param.Help)" -Source 'Get-AitherScript'
                        }
                    }

                    return
                }

                # Show full help
                if ($ShowHelp) {
                    Write-AitherLog -Level Information -Message "=== Script Help ===" -Source 'Get-AitherScript'
                    Write-AitherLog -Level Information -Message "Number: $($meta.Number)" -Source 'Get-AitherScript'
                    Write-AitherLog -Level Information -Message "Name: $($meta.Name)" -Source 'Get-AitherScript'
                    Write-AitherLog -Level Information -Message "Path: $($meta.Path)" -Source 'Get-AitherScript'

                    if ($meta.Synopsis) {
                        Write-AitherLog -Level Information -Message "SYNOPSIS" -Source 'Get-AitherScript'
                        Write-AitherLog -Level Information -Message $meta.Synopsis -Source 'Get-AitherScript'
                    }
                    if ($meta.Description -and $meta.Description -ne $meta.Synopsis) {
                        Write-AitherLog -Level Information -Message "DESCRIPTION" -Source 'Get-AitherScript'
                        Write-AitherLog -Level Information -Message $meta.Description -Source 'Get-AitherScript'
                    }
                    if ($meta.Parameters.Count -gt 0) {
                        Write-AitherLog -Level Information -Message "PARAMETERS" -Source 'Get-AitherScript'
                        foreach ($param in $meta.Parameters.Values | Sort-Object Name) {
                            $mandatory = if ($param.Mandatory) { "[MANDATORY] " } else { "" }
                            $paramLine = " -$($param.Name) $mandatory($($param.Type))"
                            Write-AitherLog -Level Information -Message $paramLine -Source 'Get-AitherScript'

                            if ($param.Help) {
                                Write-AitherLog -Level Information -Message " $($param.Help)" -Source 'Get-AitherScript'
                            }
                        }
                        return
                    }
                }

                # Return metadata object
                if ($Metadata) {
                    return [PSCustomObject]$meta
                }

                # Default: return script info object
                return [PSCustomObject]@{
                    Number      = $meta.Number
                    Name        = $meta.Name
                    Description = $meta.Synopsis
                    Stage       = $meta.Stage
                    Path        = $meta.Path
                    FileName    = $meta.FullName
                    Parameters  = $meta.Parameters.Keys
                }
            }
        }
        catch {
            Invoke-AitherErrorHandler -ErrorRecord $_ -Operation "Getting script information" -Parameters $PSBoundParameters -ThrowOnError
        }
    }
}