Private/Import-ScriptletCatalog.ps1

function Import-ScriptletCatalog {
    <#
    .SYNOPSIS
        Loads and validates the ScriptletCatalog.json file.
    .DESCRIPTION
        Reads the scriptlet catalog from the module's Scriptlets directory,
        validates each entry against the expected schema, and returns the
        validated catalog array. Invalid entries are logged as warnings
        but do not halt the load.
    .PARAMETER Path
        Optional override path to the catalog JSON. Defaults to the module's
        built-in Scriptlets/ScriptletCatalog.json.
    .OUTPUTS
        [PSCustomObject[]] Array of validated scriptlet objects.
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject[]])]
    param(
        [Parameter(Mandatory = $false)]
        [string]$Path
    )

    if ([string]::IsNullOrWhiteSpace($Path)) {
        $Path = Join-Path -Path $script:IndagoState.ModuleRoot -ChildPath 'Scriptlets\ScriptletCatalog.json'
    }

    if (-not (Test-Path -Path $Path)) {
        Write-Warning "Import-ScriptletCatalog: Catalog not found at: $Path"
        return @()
    }

    try {
        $rawJson = Get-Content -Path $Path -Raw -ErrorAction Stop
        $catalog = ConvertFrom-Json -InputObject $rawJson -ErrorAction Stop
    }
    catch {
        Write-Warning "Import-ScriptletCatalog: Failed to parse JSON. Error: $($_.Exception.Message)"
        return @()
    }

    # Required fields for schema validation
    $requiredFields = @('Id', 'Name', 'DisplayName', 'Category', 'Description', 'ExecutionContext', 'Script', 'Version')
    $validContexts = @('System', 'User', 'Auto')

    $validatedCatalog = [System.Collections.Generic.List[PSCustomObject]]::new()
    $catalogArray = @($catalog)

    foreach ($entry in $catalogArray) {
        $isValid = $true
        $entryId = if ($null -ne $entry.Id) { $entry.Id } else { '(unknown)' }

        #region Check required fields
        foreach ($field in $requiredFields) {
            $value = $entry.$field
            if ($null -eq $value -or ([string]$value).Trim() -eq '') {
                Write-Warning "Import-ScriptletCatalog: Scriptlet $entryId is missing required field: $field"
                $isValid = $false
            }
        }
        #endregion

        #region Validate ExecutionContext
        if ($null -ne $entry.ExecutionContext -and $entry.ExecutionContext -notin $validContexts) {
            Write-Warning "Import-ScriptletCatalog: Scriptlet $entryId has invalid ExecutionContext: $($entry.ExecutionContext). Must be one of: $($validContexts -join ', ')"
            $isValid = $false
        }
        #endregion

        #region Validate Script parses
        if (-not [string]::IsNullOrWhiteSpace($entry.Script)) {
            try {
                $null = [scriptblock]::Create($entry.Script)
            }
            catch {
                Write-Warning "Import-ScriptletCatalog: Scriptlet $entryId has a script syntax error: $($_.Exception.Message)"
                $isValid = $false
            }
        }
        #endregion

        #region Validate Name is alphanumeric (no spaces or special chars)
        if ($null -ne $entry.Name -and $entry.Name -notmatch '^[A-Za-z0-9]+$') {
            Write-Warning "Import-ScriptletCatalog: Scriptlet $entryId Name contains invalid characters: $($entry.Name). Use alphanumeric only."
            $isValid = $false
        }
        #endregion

        if ($isValid) {
            $validatedCatalog.Add($entry)
        }
        else {
            Write-Warning "Import-ScriptletCatalog: Scriptlet $entryId failed validation and will be skipped."
        }
    }

    Write-Verbose "Import-ScriptletCatalog: Validated $($validatedCatalog.Count) of $($catalogArray.Count) scriptlets."
    return $validatedCatalog.ToArray()
}