ModuleMerging.ps1

function Join-EnvironmentModuleInfos([EnvironmentModuleCore.EnvironmentModuleInfo] $Base, [EnvironmentModuleCore.EnvironmentModuleInfo] $Other) {
    <#
    .SYNOPSIS
    Merges the paths, parameters and dependencies of the specified module into a new module object.
    .PARAMETER Base
    The base module to consider for merging.
    .PARAMETER Other
    The module defining the values to merge. Values of this module have a higher priority than the values of the base module.
    .OUTPUTS
    The merged module info object.
    #>

    $result = [EnvironmentModuleCore.EnvironmentModuleInfo]::new($Base)
    $result.Dependencies = @()

    # Merge all dependencies
    $allDependencies = [ordered] @{}

    foreach($dependency in $Base.Dependencies) {
        $nameParts = Split-EnvironmentModuleName $dependency.ModuleFullName
        if($null -eq $nameParts) {
            continue
        }

        $allDependencies[$nameParts.Name] = @($dependency)
    }

    foreach($dependency in $Other.Dependencies) {
        # Check if the name is correctly formated
        $nameParts = Split-EnvironmentModuleName $dependency.ModuleFullName
        if($null -eq $nameParts) {
            continue
        }

        $allDependencies = Select-MergeEnvironmentModule -Dependency $dependency -KnownDependencies $allDependencies
    }

    # Set all dependencies
    foreach($dependencies in $allDependencies) {
        foreach($subDependency in $dependencies.Values) {
            $result.Dependencies += $subDependency
        }
    }

    # Merge all parameters
    foreach($parameterKey in $Other.Parameters.Keys) {
        $result.Parameters[$parameterKey] = $Other.Parameters[$parameterKey]
    }

    # Merge all path manipulations
    foreach($pathDefinition in $Other.Paths) {
        $result.AddPath($pathDefinition) | Out-Null
    }

    return $result
}

function Select-MergeEnvironmentModule([EnvironmentModuleCore.DependencyInfo] $Dependency, [hashtable] $KnownDependencies) {
    $moduleNameParts = Split-EnvironmentModuleName $Dependency.ModuleFullName
    $name = $moduleNameParts.Name

    if(-not $knownDependencies.ContainsKey($name)) {
        # No module with the same name was part of the dependencies
        $KnownDependencies[$name] = @($Dependency)
        return $KnownDependencies
    }

    $firstDependency = $KnownDependencies[$name][0]
    if($firstDependency.Priority -lt $Dependency.Priority) {
        Write-Verbose "The dependency to $($Dependency.ModuleFullName) has a higher priority than the dependency to $($firstDependency.ModuleFullName)"
        $orderedDependencies = $KnownDependencies[$name] + @($Dependency) | Sort-Object -Descending {$_.Priority}
        $firstDependency = $orderedDependencies

        if($Dependency.IsOptional) {
            $orderedDependencies = $KnownDependencies[$name].Sort()
            $KnownDependencies[$name] = $orderedDependencies

            foreach($subDependency in $orderedDependencies) {
                $subDependency.IsOptional = $true
            }
            
            return $KnownDependencies
        }
        else {
            $KnownDependencies[$name] = @($Dependency)
            return $KnownDependencies
        }
    }

    if($firstDependency.IsOptional) {
        # The dependendency is optional -> all dependencies of the same modules are marked as optional as well
        $KnownDependencies[$name] = $KnownDependencies[$name] + @([EnvironmentModuleCore.DependencyInfo]::new($Dependency.ModuleFullName, $true, $Dependency.Priority))
        return $KnownDependencies
    }

    if($true -eq (Test-ConflictModule $Dependency.ModuleFullName $firstDependency.ModuleFullName)) {
        return $KnownDependencies
    }

    $KnownDependencies[$name] = @($Dependency)
    return $KnownDependencies
}