source/Private/RJFeatures.ps1

function Get-RJConfiguration {
    [CmdletBinding()]
    param()

    $actualEnvironment = Get-RJCurrentEnvironment
    $configFileName = "RJFeatures.$actualEnvironment.psd1"

    $scriptDir = if ($PSScriptRoot) {
        $PSScriptRoot
    } elseif ($MyInvocation.MyCommand.Path) {
        Split-Path -Parent $MyInvocation.MyCommand.Path
    } else {
        # Fallback: try to resolve relative to current location
        (Resolve-Path ".\source\Private" -ErrorAction SilentlyContinue).Path
    }

    if ($scriptDir) {
        $configPath = Join-Path -Path $scriptDir -ChildPath $configFileName
    } else {
        throw "Could not determine script directory to locate configuration files"
    }

    if (-not (Test-Path $configPath)) {
        throw "Configuration file not found: $configPath"
    }

    $config = Import-PowerShellDataFile -Path $configPath

    return $config
}

function Get-RJFeatureDefinitionList {
    # Load feature definitions from environment-specific PSD1 files
    [CmdletBinding()]
    [OutputType([System.Object[]])]
    param()

    $config = Get-RJConfiguration
    $features = $config.Features | ForEach-Object { [PSCustomObject]$_ }

    # Ensure we always return an array, even for single results
    return ,@($features)
}

function Get-RJFeatureByName {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$FeatureName
    )

    $features = Get-RJFeatureDefinitionList
    return $features | Where-Object { $_.Name -eq $FeatureName }
}

function Get-RJMandatoryFeatureList {
    [CmdletBinding()]
    [OutputType([System.Object[]])]
    param()

    $features = Get-RJFeatureDefinitionList
    $mandatoryFeatures = $features | Where-Object { $_.IsMandatory -eq $true }

    # Ensure we always return an array, even for single results
    return ,@($mandatoryFeatures)
}


function Test-RJFeatureExists {
    [CmdletBinding()]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', 'Test-RJFeatureExists')]
    param(
        [Parameter(Mandatory)]
        [string]$FeatureName
    )

    $feature = Get-RJFeatureByName -FeatureName $FeatureName
    return $null -ne $feature
}

function Resolve-RJFeature {
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param(
        [string[]]$SelectedFeatures,
        [PSCustomObject]$CurrentConfig,
        [string[]]$FeaturesToAdd = @(),
        [string[]]$FeaturesToRemove = @(),
        [switch]$ReadOnly
    )

    # Parameter validation
    $hasSelectedFeatures = $SelectedFeatures -and $SelectedFeatures.Count -gt 0
    $hasIncrementalFeatures = ($FeaturesToAdd -and $FeaturesToAdd.Count -gt 0) -or ($FeaturesToRemove -and $FeaturesToRemove.Count -gt 0)

    # Validate parameter combinations
    if (-not $hasSelectedFeatures -and -not $hasIncrementalFeatures) {
        throw "Either SelectedFeatures or at least one of FeaturesToAdd/FeaturesToRemove must be specified"
    }

    if ($hasSelectedFeatures -and $hasIncrementalFeatures) {
        throw "Cannot use SelectedFeatures together with FeaturesToAdd or FeaturesToRemove"
    }

    if ($hasIncrementalFeatures -and -not $CurrentConfig) {
        throw "CurrentConfig is required when using FeaturesToAdd or FeaturesToRemove"
    }

    $mandatoryFeatures = Get-RJMandatoryFeatureList

    # Determine final feature list based on mode
    if ($hasSelectedFeatures) {
        # Traditional mode: validate and use selected features
        foreach ($featureName in $SelectedFeatures) {
            if (-not (Test-RJFeatureExists -FeatureName $featureName)) {
                $availableFeatures = (Get-RJFeatureDefinitionList).Name
                throw "Feature '$featureName' does not exist. Available features: $($availableFeatures -join ', ')"
            }
        }

        $enabledFeatureNames = @($mandatoryFeatures.Name)
        foreach ($featureName in $SelectedFeatures) {
            if ($featureName -notin $enabledFeatureNames) {
                $enabledFeatureNames += $featureName
            }
        }
    } else {
        # Incremental mode: build from current config + add - remove
        $currentFeatureNames = @($CurrentConfig.ConfiguredFeatures | ForEach-Object { $_.Name })

        # Validate features to add
        foreach ($featureName in $FeaturesToAdd) {
            if (-not (Test-RJFeatureExists -FeatureName $featureName)) {
                $availableFeatures = (Get-RJFeatureDefinitionList).Name
                throw "Feature '$featureName' does not exist. Available features: $($availableFeatures -join ', ')"
            }

            # Check if feature is already configured
            if ($featureName -in $currentFeatureNames) {
                throw "Feature '$featureName' is already configured and cannot be added again"
            }
        }

        # Validate features to remove
        foreach ($featureName in $FeaturesToRemove) {
            if (-not (Test-RJFeatureExists -FeatureName $featureName)) {
                $availableFeatures = (Get-RJFeatureDefinitionList).Name
                throw "Feature '$featureName' does not exist. Available features: $($availableFeatures -join ', ')"
            }

            # Check if feature is currently configured
            if ($featureName -notin $currentFeatureNames) {
                throw "Feature '$featureName' is not currently configured and cannot be removed"
            }

            # Check if feature is mandatory
            if ($featureName -in $mandatoryFeatures.Name) {
                throw "Cannot remove mandatory feature '$featureName'"
            }
        }

        # Calculate final feature state: current + add - remove
        $enabledFeatureNames = @(@($currentFeatureNames) + @($FeaturesToAdd) | Where-Object { $_ -notin $FeaturesToRemove } | Select-Object -Unique)
    }

    return @{
        FeatureNames = $enabledFeatureNames
        ReadOnly = $ReadOnly.IsPresent
        IsIncrementalMode = $hasIncrementalFeatures
        FeaturesToAdd = $FeaturesToAdd
        FeaturesToRemove = $FeaturesToRemove
    }
}