workflow.psm1

# Workflow module for Delta-Code
# Manages workflow definitions and pipeline execution

# Resolve module dependencies from the sibling directory when available (repository
# and flat-install layouts) and fall back to module-name resolution (PSGallery).
foreach ($dependencyName in @('delta-code', 'satellite', 'workspace')) {
    $dependencyManifest = Join-Path $PSScriptRoot "$dependencyName.psd1"
    if (Test-Path -LiteralPath $dependencyManifest) {
        Import-Module $dependencyManifest -ErrorAction Stop -DisableNameChecking
    }
    else {
        Import-Module $dependencyName -ErrorAction Stop -DisableNameChecking
    }
}
Remove-Variable dependencyName, dependencyManifest -ErrorAction SilentlyContinue

$script:WorkflowRegistryPath = Join-Path (Split-Path $PSScriptRoot) "data\workflows.json"
$script:WorkflowRegistry = @{}

$script:WorkflowHistoryPath = Join-Path (Split-Path $PSScriptRoot) "data\workflow-history.json"
$script:WorkflowHistory = @()

$script:AutomationProfilesPath = Join-Path (Split-Path $PSScriptRoot) "data\automation-profiles.json"
$script:AutomationProfiles = @{}

function Save-WorkflowRegistry {
    $data = @{
        Workflows = $script:WorkflowRegistry.Values
    }
    $data | ConvertTo-Json -Depth 5 | Set-Content -Path $script:WorkflowRegistryPath -Encoding UTF8
}

function Load-WorkflowRegistry {
    if (Test-Path $script:WorkflowRegistryPath) {
        $data = Get-Content $script:WorkflowRegistryPath -Raw | ConvertFrom-Json
        if ($data.Workflows) {
            $script:WorkflowRegistry = @{}
            foreach ($wf in $data.Workflows) {
                $script:WorkflowRegistry[$wf.Name] = $wf
            }
        }
    }
}

function Save-WorkflowHistory {
    $data = @{
        History = $script:WorkflowHistory
    }
    $data | ConvertTo-Json -Depth 5 | Set-Content -Path $script:WorkflowHistoryPath -Encoding UTF8
}

function Load-WorkflowHistory {
    if (Test-Path $script:WorkflowHistoryPath) {
        $data = Get-Content $script:WorkflowHistoryPath -Raw | ConvertFrom-Json
        if ($data.History) {
            $script:WorkflowHistory = $data.History
        }
    }
}

function Save-AutomationProfiles {
    $data = @{
        Profiles = $script:AutomationProfiles.Values
    }
    $data | ConvertTo-Json -Depth 5 | Set-Content -Path $script:AutomationProfilesPath -Encoding UTF8
}

function Load-AutomationProfiles {
    if (Test-Path $script:AutomationProfilesPath) {
        $data = Get-Content $script:AutomationProfilesPath -Raw | ConvertFrom-Json
        if ($data.Profiles) {
            $script:AutomationProfiles = @{}
            foreach ($p in $data.Profiles) {
                $script:AutomationProfiles[$p.Name] = $p
            }
        }
    }
}

# Load all registries on module import
Load-WorkflowRegistry
Load-WorkflowHistory
Load-AutomationProfiles

function Get-DeltaCodeWorkflow {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$Name
    )
    Load-WorkflowRegistry
    if ($script:WorkflowRegistry.ContainsKey($Name)) {
        return $script:WorkflowRegistry[$Name]
    }
    Write-Error "Workflow '$Name' not found in registry"
    return $null
}

function Get-DeltaCodeWorkflows {
    [CmdletBinding()]
    param(
        [string[]]$Filter
    )
    Load-WorkflowRegistry
    $wfs = $script:WorkflowRegistry.Values
    if ($Filter) {
        $wfs = $wfs | Where-Object { $Filter -contains $_.Category }
    }
    return $wfs
}

function Register-DeltaCodeWorkflow {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$Name,
        [Parameter(Mandatory = $true, Position = 1)]
        [string]$Workspace,
        [Parameter(Mandatory = $false)]
        [string]$Category = 'General',
        [Parameter(Mandatory = $false)]
        [string]$Description = '',
        [Parameter(Mandatory = $true)]
        [array]$Steps,
        [Parameter(Mandatory = $false)]
        [hashtable]$Metadata = @{}
    )
    Load-WorkflowRegistry
    Initialize-DeltaCodeSatellites

    $ws = Get-DeltaCodeWorkspace -Name $Workspace
    if (-not $ws) {
        Write-Error "Referenced workspace '$Workspace' not found"
        return $false
    }

    foreach ($step in $Steps) {
        if ($step.Type -eq 'satellite') {
            if (-not $step.Satellite) {
                Write-Error "Step in workflow '$Name' missing Satellite property"
                return $false
            }
            $sat = Get-DeltaCodeSatellite -Name $step.Satellite
            if (-not $sat) {
                Write-Error "Workflow '$Name' references unknown satellite: $($step.Satellite)"
                return $false
            }
        }
        elseif ($step.Type -eq 'builtin') {
            $validBuiltins = @('Diff', 'WorkspaceList', 'WorkspaceShow', 'WorkspaceUse')
            if (-not ($validBuiltins -contains $step.Command)) {
                Write-Error "Unknown builtin command: $($step.Command). Valid: $($validBuiltins -join ', ')"
                return $false
            }
        }
        else {
            Write-Error "Unknown step type: $($step.Type). Valid: satellite, builtin"
            return $false
        }
    }

    $workflow = [PSCustomObject]@{
        Name        = $Name
        Workspace   = $Workspace
        Category    = $Category
        Description = $Description
        Steps       = $Steps
        Metadata    = $Metadata
        Registered  = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
    }

    $script:WorkflowRegistry[$Name] = $workflow
    Save-WorkflowRegistry
    Write-Verbose "Registered workflow '$Name' with $($Steps.Count) steps"
    return $true
}

function Test-DeltaCodeWorkflow {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$Name
    )
    Load-WorkflowRegistry
    $wf = Get-DeltaCodeWorkflow -Name $Name
    if (-not $wf) { return $false }

    $valid = $true
    $issues = @()

    $ws = Get-DeltaCodeWorkspace -Name $wf.Workspace
    if (-not $ws) {
        $issues += "Workspace '$($wf.Workspace)' no longer exists"
        $valid = $false
    }

    foreach ($step in $wf.Steps) {
        if ($step.Type -eq 'satellite') {
            $sat = Get-DeltaCodeSatellite -Name $step.Satellite
            if (-not $sat) {
                $issues += "Satellite '$($step.Satellite)' not found"
                $valid = $false
            }
        }
        elseif ($step.Type -eq 'builtin') {
            $validBuiltins = @('Diff', 'WorkspaceList', 'WorkspaceShow', 'WorkspaceUse')
            if (-not ($validBuiltins -contains $step.Command)) {
                $issues += "Unknown builtin command: $($step.Command)"
                $valid = $false
            }
        }
    }

    if (-not $valid) {
        Write-Warning "Workflow '$Name' validation failed: $($issues -join '; ')"
    }
    else {
        Write-Verbose "Workflow '$Name' validated successfully"
    }
    return $valid
}

function Initialize-DeltaCodeWorkflows {
    [CmdletBinding()]
    param(
        [string[]]$SearchPaths
    )
    Load-WorkflowRegistry
    if (-not $SearchPaths) {
        $SearchPaths = @(
            "$PSScriptRoot\workflows",
            "$PSScriptRoot\..\workflows",
            (Join-Path $env:USERPROFILE ".delta-code\workflows")
        )
    }

    foreach ($basePath in $SearchPaths) {
        if (Test-Path $basePath) {
            $files = Get-ChildItem -Path $basePath -Filter "*.json" -File
            foreach ($file in $files) {
                $config = Get-Content $file.FullName -Raw | ConvertFrom-Json
                if ($config.Name -and $config.Workspace -and $config.Steps) {
                    Register-DeltaCodeWorkflow -Name $config.Name -Workspace $config.Workspace -Category $config.Category -Description $config.Description -Steps $config.Steps -Metadata $config.Metadata
                }
            }
        }
    }

    $names = @($script:WorkflowRegistry.Keys)
    foreach ($name in $names) {
        Test-DeltaCodeWorkflow -Name $name
    }
}

function Invoke-DeltaCodeWorkflow {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$Name,
        [Parameter(Mandatory = $false)]
        [hashtable]$InputParams = @{},
        [Parameter(Mandatory = $false)]
        [switch]$DryRun
    )
    Load-WorkflowRegistry
    Initialize-DeltaCodeWorkspaces

    $wf = Get-DeltaCodeWorkflow -Name $Name
    if (-not $wf) {
        Write-Error "Workflow '$Name' not found"
        return $false
    }

    $wsResult = Set-DeltaCodeWorkspace -Name $wf.Workspace
    if (-not $wsResult) {
        Write-Error "Failed to activate workspace '$($wf.Workspace)' for workflow '$Name'"
        return $false
    }

    $activeWs = Get-DeltaCodeActiveWorkspace
    Write-Host "Executing workflow '$Name' in workspace '$($activeWs.Name)' ($($activeWs.Root))"

    $stepResults = @()
    $context = @{}
    $context.Input = $InputParams

    foreach ($step in $wf.Steps) {
        $stepName = if ($step.Name) { $step.Name } else { "$($step.Type):$($step.Satellite -join ' ')$($step.Command)" }
        Write-Host " Step: $stepName" -ForegroundColor Cyan

        if ($DryRun) {
            Write-Host " [DRY RUN] Would execute: $($step.Type) $($step.Satellite -join ' ') $($step.Command)" -ForegroundColor Yellow
            $stepResults += [PSCustomObject]@{ Name = $stepName; Status = 'DryRun'; Result = $null }
            continue
        }

        try {
            $result = $null
            if ($step.Type -eq 'satellite') {
                $result = Invoke-DeltaCodeSatellite -Name $step.Satellite -Command $step.Command -Args $step.Args
            }
            elseif ($step.Type -eq 'builtin') {
                switch ($step.Command) {
                    'Diff' {
                        $from = if ($step.From) { $step.From } else { $activeWs.Root }
                        $to = if ($step.To) { $step.To } else { $activeWs.Root }
                        $result = Get-DeltaCodeDiff -From $from -To $to -IgnoreWhitespace $true
                    }
                    'WorkspaceList' {
                        $result = Get-DeltaCodeWorkspaces | Format-Table Name, Root, Category, Active
                    }
                    'WorkspaceShow' {
                        $result = Get-DeltaCodeActiveWorkspace | Format-List
                    }
                    'WorkspaceUse' {
                        $result = Set-DeltaCodeWorkspace -Name $step.Workspace
                    }
                }
            }

            $stepResults += [PSCustomObject]@{ Name = $stepName; Status = 'Success'; Result = $result }
            $context[$stepName] = $result
            Write-Host " ✓ Success" -ForegroundColor Green
        }
        catch {
            $stepResults += [PSCustomObject]@{ Name = $stepName; Status = 'Failed'; Error = $_.Exception.Message }
            Write-Host " ✗ Failed: $($_.Exception.Message)" -ForegroundColor Red
            if (-not $step.ContinueOnError) {
                Write-Error "Workflow '$Name' aborted at step '$stepName'"
                return $false
            }
        }
    }

    $failed = $stepResults | Where-Object { $_.Status -eq 'Failed' }
    if ($failed.Count -gt 0) {
        Write-Host "Workflow '$Name' completed with $($failed.Count) failed step(s)" -ForegroundColor Red
        return $false
    }
    else {
        Write-Host "Workflow '$Name' completed successfully" -ForegroundColor Green
        return $true
    }
}

function Get-DeltaCodeWorkflowStep {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$WorkflowName,
        [Parameter(Mandatory = $true, Position = 1)]
        [int]$StepIndex
    )
    Load-WorkflowRegistry
    $wf = Get-DeltaCodeWorkflow -Name $WorkflowName
    if (-not $wf -or $StepIndex -lt 0 -or $StepIndex -ge $wf.Steps.Count) {
        return $null
    }
    return $wf.Steps[$StepIndex]
}

function Remove-DeltaCodeWorkflow {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$Name
    )
    Load-WorkflowRegistry
    if ($script:WorkflowRegistry.ContainsKey($Name)) {
        $script:WorkflowRegistry.Remove($Name)
        Save-WorkflowRegistry
        Write-Verbose "Removed workflow '$Name'"
        return $true
    }
    return $false
}

function Record-WorkflowExecution {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Workflow,
        [Parameter(Mandatory = $true)]
        [string]$Workspace,
        [Parameter(Mandatory = $true)]
        [string]$Status,
        [Parameter(Mandatory = $true)]
        [long]$DurationMs,
        [Parameter(Mandatory = $true)]
        [array]$Steps,
        [string[]]$FailedSteps = @()
    )
    Load-WorkflowHistory
    $record = [PSCustomObject]@{
        Workflow    = $Workflow
        Workspace   = $Workspace
        Started     = (Get-Date).AddMilliseconds(-$DurationMs).ToString('o')
        DurationMs  = $DurationMs
        Status      = $Status
        Steps       = $Steps
        FailedSteps = $FailedSteps
        Timestamp   = (Get-Date).ToString('o')
    }
    $script:WorkflowHistory += $record
    if ($script:WorkflowHistory.Count -gt 1000) {
        $script:WorkflowHistory = $script:WorkflowHistory[-1000..-1]
    }
    Save-WorkflowHistory
    return $record
}

function Get-DeltaCodeWorkflowHistory {
    [CmdletBinding()]
    param(
        [string]$Workflow,
        [string]$Workspace,
        [int]$Limit = 50
    )
    Load-WorkflowHistory
    $history = $script:WorkflowHistory
    if ($Workflow) {
        $history = $history | Where-Object { $_.Workflow -eq $Workflow }
    }
    if ($Workspace) {
        $history = $history | Where-Object { $_.Workspace -eq $Workspace }
    }
    $history = $history | Sort-Object { [DateTime]::Parse($_.Timestamp) } -Descending
    if ($Limit -gt 0) {
        $history = $history | Select-Object -First $Limit
    }
    return $history
}

function Get-DeltaCodeWorkflowMetrics {
    [CmdletBinding()]
    param(
        [string]$Workflow,
        [string]$Workspace,
        [int]$Limit = 100
    )
    $history = Get-DeltaCodeWorkflowHistory -Workflow $Workflow -Workspace $Workspace -Limit $Limit
    if (-not $history -or $history.Count -eq 0) {
        return @{
            Workflow       = $Workflow
            Workspace      = $Workspace
            TotalRuns      = 0
            SuccessRate    = 0
            AvgDurationMs  = 0
            MinDurationMs  = 0
            MaxDurationMs  = 0
            StepMetrics    = @{}
        }
    }
    $successful = $history | Where-Object { $_.Status -eq 'Success' }
    $failed = $history | Where-Object { $_.Status -eq 'Failed' }
    $durations = $history.DurationMs
    $stepMetrics = @{}
    foreach ($run in $history) {
        foreach ($step in $run.Steps) {
            $stepName = $step.Name
            if (-not $stepMetrics.ContainsKey($stepName)) {
                $stepMetrics[$stepName] = @{
                    Count      = 0
                    Successes  = 0
                    Failures   = 0
                    TotalMs    = 0
                }
            }
            $stepMetrics[$stepName].Count++
            if ($run.Status -eq 'Success') {
                $stepMetrics[$stepName].Successes++
            }
            else {
                $stepMetrics[$stepName].Failures++
            }
            if ($step.DurationMs) {
                $stepMetrics[$stepName].TotalMs += $step.DurationMs
            }
        }
    }
    return @{
        Workflow       = $Workflow
        Workspace      = $Workspace
        TotalRuns      = $history.Count
        SuccessRuns    = $successful.Count
        FailedRuns     = $failed.Count
        SuccessRate    = if ($history.Count -gt 0) { [math]::Round($successful.Count / $history.Count * 100, 1) } else { 0 }
        AvgDurationMs  = if ($durations.Count -gt 0) { [math]::Round(($durations | Measure-Object -Average).Average, 0) } else { 0 }
        MinDurationMs  = if ($durations.Count -gt 0) { ($durations | Measure-Object -Minimum).Minimum } else { 0 }
        MaxDurationMs  = if ($durations.Count -gt 0) { ($durations | Measure-Object -Maximum).Maximum } else { 0 }
        StepMetrics    = $stepMetrics
    }
}

function Register-AutomationProfile {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$Name,
        [Parameter(Mandatory = $true, Position = 1)]
        [string]$Workflow,
        [Parameter(Mandatory = $false)]
        [switch]$DryRun = $false,
        [Parameter(Mandatory = $false)]
        [int]$RetryCount = 0,
        [Parameter(Mandatory = $false)]
        [switch]$ContinueOnError = $false,
        [Parameter(Mandatory = $false)]
        [hashtable]$InputParams = @{},
        [Parameter(Mandatory = $false)]
        [string]$Description = ''
    )
    Load-AutomationProfiles
    $profile = [PSCustomObject]@{
        Name          = $Name
        Workflow      = $Workflow
        DryRun        = $DryRun
        RetryCount    = $RetryCount
        ContinueOnError = $ContinueOnError
        InputParams   = $InputParams
        Description   = $Description
        Created       = (Get-Date).ToString('o')
    }
    $script:AutomationProfiles[$Name] = $profile
    Save-AutomationProfiles
    Write-Verbose "Registered automation profile '$Name' for workflow '$Workflow'"
    return $true
}

function Get-AutomationProfile {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$Name
    )
    Load-AutomationProfiles
    if ($script:AutomationProfiles.ContainsKey($Name)) {
        return $script:AutomationProfiles[$Name]
    }
    Write-Error "Automation profile '$Name' not found"
    return $null
}

function Get-AutomationProfiles {
    [CmdletBinding()]
    param()
    Load-AutomationProfiles
    return $script:AutomationProfiles.Values
}

function Invoke-DeltaCodeProfile {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$Name,
        [switch]$DryRun
    )
    Load-AutomationProfiles
    $profile = Get-AutomationProfile -Name $Name
    if (-not $profile) { return $false }

    $result = $true
    $attempt = 0
    $maxAttempts = $profile.RetryCount + 1

    while ($attempt -lt $maxAttempts) {
        $attempt++
        if ($attempt -gt 1) {
            Write-Host "Retry attempt $attempt of $maxAttempts for profile '$Name'" -ForegroundColor Yellow
            Start-Sleep -Seconds 2
        }

        if ($DryRun -or $profile.DryRun) {
            $result = Invoke-DeltaCodeWorkflow -Name $profile.Workflow -InputParams $profile.InputParams -DryRun
        }
        else {
            $result = Invoke-DeltaCodeWorkflow -Name $profile.Workflow -InputParams $profile.InputParams
        }

        if ($result) {
            Write-Host "Profile '$Name' executed successfully" -ForegroundColor Green
            return $true
        }
        elseif (-not $profile.ContinueOnError) {
            Write-Error "Profile '$Name' failed and ContinueOnError is not set"
            return $false
        }
    }
    Write-Error "Profile '$Name' failed after $maxAttempts attempts"
    return $false
}

Export-ModuleMember -Function @(
    'Get-DeltaCodeWorkflow'
    'Get-DeltaCodeWorkflows'
    'Register-DeltaCodeWorkflow'
    'Test-DeltaCodeWorkflow'
    'Initialize-DeltaCodeWorkflows'
    'Invoke-DeltaCodeWorkflow'
    'Get-DeltaCodeWorkflowStep'
    'Remove-DeltaCodeWorkflow'
    'Record-WorkflowExecution'
    'Get-DeltaCodeWorkflowHistory'
    'Get-DeltaCodeWorkflowMetrics'
    'Register-AutomationProfile'
    'Get-AutomationProfile'
    'Get-AutomationProfiles'
    'Invoke-DeltaCodeProfile'
)