scripts/modules/shared/phase-functions.ps1

# strangeloop Setup - Shared Phase Functions
# Version: 1.0.0


# Phase management and execution functions

function Invoke-Phase {
    <#
    .SYNOPSIS
        Generic phase execution with standardized error handling and result processing
    
    .PARAMETER PhaseName
        The name of the phase to execute
    
    .PARAMETER PhaseModule
        The module that contains the phase function
    
    .PARAMETER Arguments
        Arguments to pass to the phase function
    
    .PARAMETER Context
        The execution context (session settings, results, etc.)
    
    .RETURNS
        Hashtable with execution results
    #>

    param(
        [Parameter(Mandatory)]
        [string]$PhaseName,
        
        [Parameter(Mandatory)]
        [string]$PhaseModule,
        
        [hashtable]$Arguments = @{},
        
        [hashtable]$Context = @{}
    )
    
    Write-Host "═════════════════════════════════════════════════════════════════════════════════" -ForegroundColor Blue
    Write-Host " EXECUTING PHASE: $($PhaseName.ToUpper())" -ForegroundColor Blue
    Write-Host "═════════════════════════════════════════════════════════════════════════════════" -ForegroundColor Blue
    
    try {
        # Import the phase module
        $moduleBasePath = $PSScriptRoot.Replace('\shared', '')
        $modulePath = Join-Path $moduleBasePath $PhaseModule "Invoke-$PhaseName.ps1"
        
        if (-not (Test-Path $modulePath)) {
            throw "Phase module not found: $modulePath"
        }
        
        # Dot source the phase module
        . $modulePath
        
        # Execute the phase function
        $functionName = "Invoke-$PhaseName"
        $phaseResult = & $functionName @Arguments
        
        # Validate result structure
        if ($phaseResult -is [hashtable] -and $phaseResult.ContainsKey('Success')) {
            if ($phaseResult.Success) {
                Write-Host "✓ Phase '$PhaseName' completed successfully" -ForegroundColor Green
                
                # Store result in context for next phases
                if ($Context.ContainsKey('Results')) {
                    $Context.Results[$PhaseName] = $phaseResult
                }
                
                return $phaseResult
            } else {
                Write-Error "Phase '$PhaseName' failed: $($phaseResult.Message)"
                throw "Phase execution failed"
            }
        } else {
            Write-Warning "Phase '$PhaseName' returned unexpected result format"
            return @{ Success = $true; Phase = $PhaseName; Result = $phaseResult }
        }
    } catch {
        Write-Error "Error executing phase '$PhaseName': $($_.Exception.Message)"
        Write-Error "Stack trace: $($_.ScriptStackTrace)"
        
        return @{
            Success = $false
            Phase = $PhaseName
            Error = $_.Exception.Message
            StackTrace = $_.ScriptStackTrace
        }
    }
}

function Test-ShouldRunPhase {
    <#
    .SYNOPSIS
        Tests if a phase should be executed based on targeting parameters
    
    .PARAMETER PhaseName
        The name of the phase to test
    
    .PARAMETER OnlyPhase
        Run only this specific phase
    
    .PARAMETER FromPhase
        Start execution from this phase and continue through the end
    
    .PARAMETER SkipPhases
        Array of phases to skip
    
    .PARAMETER TargetPhases
        Legacy parameter - array of phases to target (empty means all phases)
    
    .RETURNS
        Boolean indicating if the phase should run
    #>

    param(
        [Parameter(Mandatory)]
        [string]$PhaseName,
        
        [string]$OnlyPhase = '',
        
        [string]$FromPhase = '',
        
        [string[]]$SkipPhases = @(),
        
        [string[]]$TargetPhases = @()
    )
    
    # Get all phases for range validation
    $allPhases = @(
        'prerequisites', 'authentication', 'discovery', 'environment', 
        'wsl', 'project', 'git', 'pipelines', 'vscode', 'completion'
    )
    
    # First, check if this phase should be skipped
    if ($SkipPhases -and $SkipPhases.Count -gt 0 -and $PhaseName -in $SkipPhases) {
        return $false
    }
    
    # If only-phase is specified, run only that phase
    if (-not [string]::IsNullOrEmpty($OnlyPhase)) {
        return $PhaseName -eq $OnlyPhase
    }
    
    # If from-phase is specified, run from that phase onwards
    if (-not [string]::IsNullOrEmpty($FromPhase)) {
        $phaseIndex = $allPhases.IndexOf($PhaseName)
        $fromIndex = $allPhases.IndexOf($FromPhase)
        
        if ($phaseIndex -eq -1) {
            Write-Warning "Unknown phase: $PhaseName"
            return $false
        }
        
        if ($fromIndex -eq -1) {
            Write-Warning "Unknown from-phase: $FromPhase"
            return $false
        }
        
        return $phaseIndex -ge $fromIndex
    }
    
    # Legacy support: If specific phases are targeted, only run those
    if ($TargetPhases -and $TargetPhases.Count -gt 0) {
        return $PhaseName -in $TargetPhases
    }
    
    # Default: run all phases (that aren't skipped)
    return $true
}

function Get-PhaseOrder {
    <#
    .SYNOPSIS
        Returns the ordered list of all phases
    
    .RETURNS
        Array of phase names in execution order
    #>

    
    return @(
        'prerequisites',
        'authentication', 
        'discovery',
        'environment',
        'wsl',
        'project',
        'git',
        'pipelines',
        'vscode',
        'completion'
    )
}

function Get-PhaseInfo {
    <#
    .SYNOPSIS
        Gets information about all phases
    
    .RETURNS
        Hashtable with phase information
    #>

    
    return @{
        'prerequisites' = @{
            Order = 1
            Description = 'Check and install required tools and dependencies'
            RequiredFor = @('All subsequent phases')
            Dependencies = @()
        }
        'authentication' = @{
            Order = 2
            Description = 'Set up authentication for Git, Azure DevOps, and other services'
            RequiredFor = @('git', 'pipelines')
            Dependencies = @('prerequisites')
        }
        'discovery' = @{
            Order = 3
            Description = 'Discover available loops and determine environment requirements'
            RequiredFor = @('environment', 'wsl', 'project')
            Dependencies = @('prerequisites')
        }
        'environment' = @{
            Order = 4
            Description = 'Set up environment variables and configuration'
            RequiredFor = @('wsl', 'project', 'git', 'pipelines', 'vscode')
            Dependencies = @('discovery')
        }
        'wsl' = @{
            Order = 5
            Description = 'Set up Windows Subsystem for Linux if required'
            RequiredFor = @('project')
            Dependencies = @('discovery', 'environment')
        }
        'project' = @{
            Order = 6
            Description = 'Create project structure and initialize loop'
            RequiredFor = @('git', 'pipelines', 'vscode')
            Dependencies = @('discovery', 'environment')
        }
        'git' = @{
            Order = 7
            Description = 'Initialize Git repository and set up remote'
            RequiredFor = @('pipelines')
            Dependencies = @('authentication', 'project')
        }
        'pipelines' = @{
            Order = 8
            Description = 'Set up CI/CD pipelines'
            RequiredFor = @()
            Dependencies = @('authentication', 'git')
        }
        'vscode' = @{
            Order = 9
            Description = 'Configure VS Code workspace and extensions'
            RequiredFor = @()
            Dependencies = @('project')
        }
        'completion' = @{
            Order = 10
            Description = 'Final setup steps and validation'
            RequiredFor = @()
            Dependencies = @('All previous phases')
        }
    }
}

function Test-PhaseExists {
    <#
    .SYNOPSIS
        Tests if a phase name is valid
    
    .PARAMETER PhaseName
        The phase name to validate
    
    .RETURNS
        Boolean indicating if the phase exists
    #>

    param(
        [Parameter(Mandatory)]
        [string]$PhaseName
    )
    
    $validPhases = Get-PhaseOrder
    return $PhaseName -in $validPhases
}

function Get-PhaseDependencies {
    <#
    .SYNOPSIS
        Gets the dependencies for a specific phase
    
    .PARAMETER PhaseName
        The phase to get dependencies for
    
    .RETURNS
        Array of dependency phase names
    #>

    param(
        [Parameter(Mandatory)]
        [string]$PhaseName
    )
    
    $phaseInfo = Get-PhaseInfo
    if ($phaseInfo.ContainsKey($PhaseName)) {
        return $phaseInfo[$PhaseName].Dependencies
    }
    
    return @()
}

function Test-PhaseDependenciesCompleted {
    <#
    .SYNOPSIS
        Tests if all dependencies for a phase have been completed
    
    .PARAMETER PhaseName
        The phase to check dependencies for
    
    .PARAMETER CompletedPhases
        Array of completed phase names
    
    .RETURNS
        Boolean indicating if all dependencies are met
    #>

    param(
        [Parameter(Mandatory)]
        [string]$PhaseName,
        
        [string[]]$CompletedPhases = @()
    )
    
    $dependencies = Get-PhaseDependencies -PhaseName $PhaseName
    
    foreach ($dependency in $dependencies) {
        if ($dependency -notin $CompletedPhases) {
            return $false
        }
    }
    
    return $true
}