Modules/IdLE.Core/Private/Resolve-IdleStepRetryParameters.ps1

# Resolves effective retry parameters for a step based on ExecutionOptions and step's RetryProfile.

# Retry parameter limits are defined centrally (e.g., in Assert-IdleExecutionOptions.ps1)

function Resolve-IdleStepRetryParameters {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNull()]
        [object] $Step,

        [Parameter()]
        [AllowNull()]
        [object] $ExecutionOptions
    )

    function Get-StepPropertyValue {
        [CmdletBinding()]
        param(
            [Parameter(Mandatory)]
            [ValidateNotNull()]
            [object] $Step,

            [Parameter(Mandatory)]
            [ValidateNotNullOrEmpty()]
            [string] $PropertyName
        )

        if ($Step -is [System.Collections.IDictionary]) {
            if ($Step.Contains($PropertyName)) {
                return $Step[$PropertyName]
            }
            return $null
        }

        $propNames = @($Step.PSObject.Properties.Name)
        if ($propNames -contains $PropertyName) {
            return $Step.$PropertyName
        }

        return $null
    }

    # Default retry parameters (engine defaults)
    $effectiveParams = @{
        MaxAttempts              = 3
        InitialDelayMilliseconds = 250
        BackoffFactor            = 2.0
        MaxDelayMilliseconds     = 5000
        JitterRatio              = 0.2
    }

    # If no ExecutionOptions provided, return defaults
    if ($null -eq $ExecutionOptions) {
        return $effectiveParams
    }

    if ($ExecutionOptions -isnot [System.Collections.IDictionary]) {
        return $effectiveParams
    }

    # Check if ExecutionOptions has RetryProfiles
    $retryProfiles = $null
    if ($ExecutionOptions.Contains('RetryProfiles')) {
        $retryProfiles = $ExecutionOptions['RetryProfiles']
    }

    # Determine which profile to use
    $profileKey = [string](Get-StepPropertyValue -Step $Step -PropertyName 'RetryProfile')

    # If step specifies a RetryProfile but no profiles are configured, fail
    if (-not [string]::IsNullOrWhiteSpace($profileKey) -and ($null -eq $retryProfiles -or $retryProfiles -isnot [System.Collections.IDictionary])) {
        $stepName = [string](Get-StepPropertyValue -Step $Step -PropertyName 'Name')

        throw [System.ArgumentException]::new(
            "Step '$stepName' references RetryProfile '$profileKey' but ExecutionOptions.RetryProfiles is not configured.",
            'ExecutionOptions'
        )
    }

    # If no RetryProfiles configured and step doesn't specify one, return defaults
    if ($null -eq $retryProfiles -or $retryProfiles -isnot [System.Collections.IDictionary]) {
        return $effectiveParams
    }

    # If step doesn't specify a RetryProfile, use DefaultRetryProfile
    if ([string]::IsNullOrWhiteSpace($profileKey)) {
        if ($ExecutionOptions.Contains('DefaultRetryProfile')) {
            $profileKey = [string]$ExecutionOptions['DefaultRetryProfile']
        }
    }

    # If still no profile key, return defaults
    if ([string]::IsNullOrWhiteSpace($profileKey)) {
        return $effectiveParams
    }

    # Look up the profile
    if (-not $retryProfiles.Contains($profileKey)) {
        # Fail-fast: Unknown RetryProfile key
        $stepName = [string](Get-StepPropertyValue -Step $Step -PropertyName 'Name')

        throw [System.ArgumentException]::new(
            "Step '$stepName' references unknown RetryProfile '$profileKey'. Available profiles: $([string]::Join(', ', $retryProfiles.Keys))",
            'ExecutionOptions'
        )
    }

    $profile = $retryProfiles[$profileKey]

    # Apply profile parameters, preserving defaults for missing values
    if ($profile.Contains('MaxAttempts')) {
        $effectiveParams['MaxAttempts'] = [int]$profile['MaxAttempts']
    }

    if ($profile.Contains('InitialDelayMilliseconds')) {
        $effectiveParams['InitialDelayMilliseconds'] = [int]$profile['InitialDelayMilliseconds']
    }

    if ($profile.Contains('BackoffFactor')) {
        $effectiveParams['BackoffFactor'] = [double]$profile['BackoffFactor']
    }

    if ($profile.Contains('MaxDelayMilliseconds')) {
        $effectiveParams['MaxDelayMilliseconds'] = [int]$profile['MaxDelayMilliseconds']
    }

    if ($profile.Contains('JitterRatio')) {
        $effectiveParams['JitterRatio'] = [double]$profile['JitterRatio']
    }

    return $effectiveParams
}