PAF.psm1

# Function to read JSON configuration file
function Get-PAFConfiguration {
    <#
    .SYNOPSIS
    Reads the JSON configuration file and returns the configuration data.
 
    .DESCRIPTION
    This function reads the JSON configuration file specified by the ConfigFilePath parameter and converts it to a PowerShell object.
    It validates the structure of the configuration file and returns the configuration data.
 
    .PARAMETER ConfigFilePath
    The path to the JSON configuration file. If not specified, the default path is used.
 
    .EXAMPLE
    Get-PAFConfiguration -ConfigFilePath "C:\Path\to\config.json"
    Reads the configuration from the specified file.
 
    .EXAMPLE
    Get-PAFConfiguration
    Reads the configuration from the default file path.
 
    #>

    param (
        [Parameter(Position = 0)]
        [string]$ConfigFilePath = (Join-Path $PSScriptRoot 'config.json')
    )

    # Check if the configuration file exists
    if (-not (Test-Path $ConfigFilePath)) {
        Write-Warning "Configuration file not found at '$ConfigFilePath'. Using default values."
        return Get-PAFDefaultConfiguration
    }

    try {
        # Read the content of the configuration file and convert it to a JSON object
        $ConfigData = Get-Content -Path $ConfigFilePath -Raw | ConvertFrom-Json

        # Validate the configuration file structure
        $RequiredProperties = @(
            "FrameworkName",
            "DefaultModulePath",
            "SnippetsPath",
            "UserSnippetsPath",
            "MaxSnippetsPerPage",
            "ShowBannerOnStartup",
            "FrameworkPrefix"
        )

        $MissingProperties = $RequiredProperties | Where-Object { $ConfigData.PSObject.Properties.Name -notcontains $_ }

        if ($MissingProperties) {
            Write-Warning "Invalid configuration file structure. Missing properties: $MissingProperties. Using default values."
            return Get-PAFDefaultConfiguration
        }

        return $ConfigData
    }
    catch {
        Write-Error "Error reading or parsing the configuration file: $_"
        return $null
    }
}

function Get-PAFDefaultConfiguration {
    return @{
        "FrameworkName"       = "PowerShell Awesome Framework"
        "DefaultModulePath"   = $PSScriptRoot
        "SnippetsPath"        = Join-Path $PSScriptRoot 'snippets\core'
        "UserSnippetsPath"    = Join-Path $PSScriptRoot 'snippets\user'
        "MaxSnippetsPerPage"  = 10
        "ShowBannerOnStartup" = $true
        "FrameworkPrefix"     = "PAF_"
        # Add more configuration options here with their default values
        # For example:
        # "Theme" = "Dark"
        # "LogFilePath" = Join-Path $PSScriptRoot 'logs'
        # "EnableDebugMode" = $false
    }
}

# Function to save updated configuration to JSON file
function Save-PAFConfiguration {
    param (
        [string]$configFilePath,
        [object]$configData
    )

    try {
        # Convert the configuration data to JSON format
        $jsonConfig = $configData | ConvertTo-Json -Depth 10

        # Save the JSON data to the configuration file
        $jsonConfig | Set-Content -Path $configFilePath -Encoding UTF8

        Write-Host "Configuration saved to '$configFilePath'."
    }
    catch {
        Write-Error "Error saving configuration to '$configFilePath': $_"
    }
}

# Function to load snippets from the main path
function Get-PAFSnippets {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string]$snippetsPath,
        [string]$frameworkPrefix
    )

    try {
        # Get the snippets path from the configuration
        Write-Verbose ($snippetsPath | Out-String)

        # Ensure the snippets path is valid
        if (-not (Test-Path -Path $snippetsPath -PathType Container)) {
            Write-Error "Snippets path '$snippetsPath' not found or invalid."
            return
        }

        # Get all snippet files in the snippets directory
        $snippetFiles = Get-ChildItem -Path $snippetsPath -Filter "${frameworkPrefix}*.ps1" -File

        # Initialize an array to store snippet metadata
        $snippetsMetadata = @()

        # Loop through each snippet file and extract metadata
        foreach ($file in $snippetFiles) {
            Try {
                $functionScriptBlock = Get-Content -Path $file.FullName -Raw
                # Extract information from the script block using a custom function
                $functionName = Get-PAFScriptBlockInfo -ScriptBlock $functionScriptBlock -InfoType FunctionName
                $category = Get-PAFScriptBlockInfo -ScriptBlock $functionScriptBlock -InfoType Category
                if ($null -eq $category) {
                    $category = "No category"
                }

                $metadata = Get-Help -Name $file.FullName -Full | `
                    Select-Object -Property Synopsis, `
                @{l = "Description"; e = { $_.Description.Text } }, `
                @{l = "Category"; e = { $category } }

                $snippetMetadata = [PSCustomObject]@{
                    'name'        = $functionName
                    'synopsis'    = $metadata.Synopsis
                    'description' = $metadata.Description.ToString()
                    'category'    = $metadata.Category.ToString()
                    'path'        = $file.FullName
                }

                $snippetsMetadata += $snippetMetadata
            }
            Catch {
                Write-Error -Message "Failed to import file $($file.FullName): $_"
            }
        }

        return $snippetsMetadata
    }
    catch {
        Write-Error "An error occurred while retrieving snippets: $_"
        return $null
    }
}


# Main menu function with snippet categories

function Show-PAFSnippetMenu {
    param (
        [string]$searchKeywords = $null,
        [string]$category = $null,
        [string]$usersnippetsPath = $null,
        [string]$systemsnippetsPath = $null,
        [string]$frameworkPrefix
    )

    try {
        # Caching snippets to avoid repeated file I/O
        if (-not $script:cachedSnippets) {
            $script:cachedSnippets = @()
            $script:cachedSnippets += Get-PAFSnippets -snippetsPath $usersnippetsPath -frameworkPrefix $frameworkPrefix
            $script:cachedSnippets += Get-PAFSnippets -snippetsPath $systemsnippetsPath -frameworkPrefix $frameworkPrefix
        }

        $allSnippets = $script:cachedSnippets

        # Filter snippets based on search keywords
        if ($searchKeywords) {
            $allSnippets = $allSnippets | Where-Object {
                $_.Name -like "*$searchKeywords*" -or $_.Synopsis -like "*$searchKeywords*" -or $_.Description -like "*$searchKeywords*"
            }
        }

        # Display categories if no category specified, or display snippets for the chosen category
        if (-not $category) {
            $categories = $allSnippets | Select-Object -ExpandProperty Category -Unique
            if ($categories.Count -eq 0) {
                Write-Host "No categories found. Continuing without category selection."
            }
            else {
                Write-Host "Available Categories:"
                $categoriesWithNumbers = $categories | ForEach-Object -Begin { $count = 1 } -Process {
                    Write-Host "${count}. $_"
                    $count++
                }

                # Prompt user for category selection
                $selection = Read-Host "Enter the number of the category you want to browse (press Enter to continue without search)"

                if ($selection -ge 1 -and $selection -le $categories.Count) {
                    $category = $categories[$selection - 1]
                }
            }
        }

        if ($category) {
            # Filter snippets based on the chosen category
            $categorySnippets = @()
            $categorySnippets += $allSnippets | Where-Object {
                $_.Category -eq $category
            }

            if ($categorySnippets.Count -eq 0) {
                Write-Host "No snippets found in the '$category' category."
            }
            else {
                Write-Output "Snippets in '$category' category:"
                $SnippetsWithNumbers = $categorySnippets | ForEach-Object -Begin { $count = 1 } -Process {
                    Write-Host "${count}. $($_.Name)"
                    $count++
                }

                # Prompt user to choose a snippet to execute
                $executeSnippet = Read-Host "Enter the number of the snippet you want to execute (press Enter to continue without execution)"

                if ($executeSnippet -ge 1 -and $executeSnippet -le $categorySnippets.Count) {
                    $selectedSnippet = $categorySnippets[$executeSnippet - 1]
                    Write-Host "Executing snippet function: $($selectedSnippet.Name) ($($selectedSnippet.Path))..."
                    Invoke-Expression "& { . $($selectedSnippet.Path) }"
                }
                else {
                    Write-Host "Invalid snippet number or no snippet execution requested."
                }
            }
        }
    }
    catch {
        Write-Error "Error in Show-PAFSnippetMenu: $_"
    }
}


# Gets name of category and function from snippet; must be definied by user in script
function Get-PAFScriptBlockInfo {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string]$ScriptBlock,

        [Parameter(Mandatory = $true)]
        [ValidateSet("FunctionName", "Category")]
        [string]$InfoType
    )

    try {
        # Convert the script block to a string
        $scriptText = $ScriptBlock.ToString()

        # Define simplified regex patterns to match the :NAME and :CATEGORY tags
        $nameRegex = '.*:NAME\s+(.+)'
        $categoryRegex = '.*:CATEGORY\s+(.+)'

        # Initialize variables to hold extracted values
        $functionName = $null
        $category = $null

        # Attempt to find the :NAME and :CATEGORY tags in the script text
        $nameMatch = $scriptText | Select-String -Pattern $nameRegex -AllMatches
        $categoryMatch = $scriptText | Select-String -Pattern $categoryRegex -AllMatches

        # Extract the main function name value
        if ($nameMatch.Matches.Count -gt 0) {
            $functionName = $nameMatch.Matches[0].Groups[1].Value
        }

        # Extract the category value
        if ($categoryMatch.Matches.Count -gt 0) {
            $category = $categoryMatch.Matches[0].Groups[1].Value
        }

        # Determine the requested information based on $InfoType
        switch ($InfoType) {
            "FunctionName" {
                return $functionName
            }
            "Category" {
                return $category
            }
            default {
                Write-Warning "Invalid value for InfoType. Use 'FunctionName' or 'Category'."
                return $null
            }
        }
    }
    catch {
        Write-Warning "An error occurred while extracting the script block information: $_"
    }

    return $null
}

function Start-PAF {
    try {
        $configData = Get-PAFConfiguration
        if ($null -eq $configData) {
            Write-Error "Failed to load configuration. Exiting PAF."
            return
        }

        $usersnippetsPath = $configData.UserSnippetsPath
        $systemsnippetsPath = $configData.SnippetsPath
        $frameworkPrefix = $configData.FrameworkPrefix

        if ($configData.ShowBannerOnStartup -and $null -eq $bannerShowed ) {
            get-banner
            $bannerShowed = $true
        }

        while ($true) {
            
            # Caching snippets to avoid repeated file I/O
            $script:cachedSnippets = @()
            $script:cachedSnippets += (Get-PAFSnippets -snippetsPath $usersnippetsPath -frameworkPrefix $frameworkPrefix)

            $script:cachedSnippets += (Get-PAFSnippets -snippetsPath $systemsnippetsPath -frameworkPrefix $frameworkPrefix)

            Show-PAFSnippetMenu -usersnippetsPath $usersnippetsPath -systemsnippetsPath $systemsnippetsPath -frameworkPrefix $frameworkPrefix
        }
    }
    catch {
        Write-Error "Error in Start-PAF: $_"
    }
}


function Get-Banner {
    param (
        
    )
    
    $banner = get-content -Path "${PSScriptRoot}\images\banner.txt"
    Write-Output $banner
    return

}

# Save the current TLS security protocol to restore it later
$oldProtocol = [Net.ServicePointManager]::SecurityProtocol

# Switch to using TLS 1.2
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# Get the name of the current module
$ModuleName = "PAF"

# Get the installed version of the module
$ModuleVersion = [version]"0.1.3"

# Find the latest version of the module in the PSGallery repository
$LatestModule = Find-Module -Name $ModuleName -Repository PSGallery

try {
    if ($ModuleVersion -lt $LatestModule.Version) {
        Write-Host "An update is available for $($ModuleName). Installed version: $($ModuleVersion). Latest version: $($LatestModule.Version)." -ForegroundColor Red
    } 
<# else {
        Write-Host "The $($ModuleName) module is up-to-date."
    }
 #>
}
catch {
    Write-Error "An error occurred while checking for updates: $_"
}

# Restore the original TLS security protocol
[Net.ServicePointManager]::SecurityProtocol = $oldProtocol

# Create fingerprint
#..\helpers\moduleFingerprint.ps1