#Requires -Version 7.4

function Invoke-vSEAL {
    <#
    .SYNOPSIS
        Executes vSEAL operations: seal, engage, analyze, or rollback.

    .DESCRIPTION
        Main entry point for the vSEAL module. Orchestrates golden image sealing
        and VDI activation through a plugin-based execution pipeline.

        Supports three operational modes:

        1. Seal/Engage execution - Runs plugins for the specified phase with
           timeout enforcement, prerequisite checks, baseline capture, and
           streaming event output.

        2. Analyze (dry-run) - Discovers plugins, evaluates conditions, and
           reports potential changes without applying them. Exit code 2 indicates
           changes pending; exit code 0 indicates a clean state.

        3. Rollback - Restores system state from a previously saved baseline,
           performing plugin-by-plugin undo of changes.

    .PARAMETER Phase
        The operational phase to execute. Valid values: Seal, Engage.
        Required unless using -Rollback.

    .PARAMETER Analyze
        Enables dry-run mode. Requires -Phase. Discovers plugins and evaluates
        conditions without applying changes. Exit code 2 indicates pending
        changes; exit code 0 indicates a clean state.

    .PARAMETER Rollback
        Restores system from a saved baseline. Mutually exclusive with -Phase.
        Requires -Baseline and -Force.

    .PARAMETER Config
        Path to TOML configuration file.
        Defaults to $env:ProgramData\vSEAL\vseal.toml.

    .PARAMETER Plugin
        Filters execution to plugins matching the specified name pattern.
        Supports wildcards.

    .PARAMETER PluginPath
        Overrides the default plugin discovery path. Primarily used for testing.

    .PARAMETER Baseline
        The baseline ID to restore from. Required when using -Rollback.

    .PARAMETER Force
        Skips confirmation prompts. Required for -Rollback to prevent
        accidental rollbacks.

    .PARAMETER BackupCurrent
        Creates a forward baseline of current state before performing rollback,
        enabling undo of the rollback itself.

    .EXAMPLE
        Invoke-vSEAL -Phase Seal

        Run the seal phase for standard golden image preparation.

    .EXAMPLE
        Invoke-vSEAL -Phase Engage

        Run the engage phase to activate VDI at user login.

    .EXAMPLE
        Invoke-vSEAL -Phase Seal -Analyze

        Dry-run seal: report what would change without applying.

    .EXAMPLE
        Invoke-vSEAL -Phase Seal -Plugin "defender*"

        Run only plugins matching "defender*" during the seal phase.

    .EXAMPLE
        Invoke-vSEAL -Rollback -Baseline "seal-20260215-143022" -Force

        Rollback to a specific baseline, restoring previous system state.

    .EXAMPLE
        Invoke-vSEAL -Rollback -Baseline "seal-20260215-143022" -Force -BackupCurrent

        Rollback with forward baseline for undo capability.

    .OUTPUTS
        [void]

    .NOTES
        Requires Administrator privileges.
        Configuration is validated before execution.
        Plugins are discovered from built-in, local, and shared paths.
        Results are emitted as structured log events via Write-vSEALLog streaming.
        Use Get-vSEALStatus to inspect results after execution.
    #>
    [CmdletBinding()]
    [OutputType([void])]
    param(
        [Parameter()]
        [ValidateSet('Seal', 'Engage')]
        [string]$Phase,

        [Parameter()]
        [switch]$Analyze,  # Dry-run mode (requires -Phase)

        [Parameter()]
        [switch]$Rollback,  # Restore from baseline (mutually exclusive with -Phase)

        [string]$Config = "$env:ProgramData\vSEAL\vseal.toml",

        [string]$Plugin,

        [string]$PluginPath,  # Optional: Override plugin discovery path for testing

        [Parameter()]
        [string]$Baseline,  # Required for -Rollback

        [Parameter()]
        [switch]$Force,  # Skip confirmation prompts

        [Parameter()]
        [switch]$BackupCurrent  # Create forward baseline before rollback
    )

    # --- Parameter Validation ---
    if ($Rollback) {
        if ($Phase) {
            throw "-Rollback cannot be used with -Phase. Use: Invoke-vSEAL -Rollback -Baseline <id> -Force"
        }
        if ($Analyze) {
            throw "-Rollback cannot be used with -Analyze"
        }
    } else {
        if (-not $Phase) {
            throw "-Phase is required when not using -Rollback. Use: Invoke-vSEAL -Phase Seal [-Analyze]"
        }
    }

    # --- Load Configuration ---
    Write-vSEALLog "Loading configuration from: $Config"
    $configuration = Import-vSEALConfiguration -Path $Config

    # --- Validate Configuration ---
    Write-vSEALLog "Validating configuration schema..." -Level 'DEBUG'
    # Use provided path or default to built-in plugins
    $builtInPluginPath = if ($PluginPath) {
        $PluginPath
    } else {
        Join-Path -Path $PSScriptRoot -ChildPath '..\Plugins'
    }
    # Additional plugin search locations
    $localPluginPath = Join-Path -Path $PSScriptRoot -ChildPath '..\plugins-custom'
    $sharedPluginPath = Join-Path -Path $env:ProgramData -ChildPath 'vSEAL\plugins'

    try {
        Test-vSEALConfiguration -Config $configuration -BuiltInPluginPath $builtInPluginPath -LocalPluginPath $localPluginPath -SharedPluginPath $sharedPluginPath
        Write-vSEALLog "Configuration validation passed" -Level 'DEBUG'
    }
    catch {
        Write-vSEALLog "Configuration validation failed: $($_.Exception.Message)" -Level 'FATAL'
        throw
    }

    # --- Build Globals ---
    $globals = Get-vSEALGlobals -Config $configuration -ConfigPath $Config
    $globals.AnalyzeMode = $Analyze.IsPresent

    # --- Initialize Logger ---
    $logPhase = if ($Rollback) { 'Rollback' } else { $Phase }
    Initialize-vSEALLogger -Phase $logPhase -Configuration $configuration

    # --- Initialize Baseline Context (Seal phase only) ---
    $baselineId = $null
    if ($Phase -eq 'Seal') {
        Initialize-vSEALBaselineContext
    }

    # --- Rollback Phase Validation ---
    $baselineData = $null
    if ($Rollback) {
        if ([string]::IsNullOrEmpty($Baseline)) {
            # List available baselines in error message
            try {
                $available = Get-vSEALBaselineList -Config $configuration
                $ids = if ($available -and $available.Count -gt 0) {
                    ($available | ForEach-Object { $_.Id }) -join ', '
                } else {
                    '(none available)'
                }
            }
            catch {
                $ids = '(unable to list)'
            }
            Write-vSEALLog "Rollback requires -Baseline parameter. Available: $ids" -Level FATAL
            Close-vSEALLogger -Message "Rollback phase failed"
            throw "Rollback requires -Baseline parameter. Available baselines: $ids"
        }

        # Load baseline
        $baselineData = Get-vSEALBaseline -Id $Baseline -Config $configuration
        if ($null -eq $baselineData) {
            Write-vSEALLog "Baseline not found: $Baseline" -Level FATAL
            Close-vSEALLogger -Message "Rollback phase failed"
            throw "Baseline not found: $Baseline"
        }

        # OS version mismatch warning (proceed with warning, don't require -Force)
        try {
            $currentOS = Get-vSEALOperatingSystemInfo
            $currentOSName = $currentOS.Name

            # Try new manifest format first (os.name), fall back to osBuild
            $baselineOSName = $null
            $baselineOSBuild = $null

            if ($baselineData.OS -and $baselineData.OS.name) {
                $baselineOSName = $baselineData.OS.name
                $baselineOSBuild = $baselineData.OS.buildNumber
            } elseif ($baselineData.OSBuild) {
                # Old manifest format
                $baselineOSBuild = $baselineData.OSBuild
            }

            # Compare by name if available, otherwise by build number
            $mismatch = $false
            if ($baselineOSName -and $baselineOSName -ne $currentOSName) {
                $mismatch = $true
                Write-vSEALLog "OS mismatch: Baseline created on '$baselineOSName', current OS is '$currentOSName'" -Level WARNING
            } elseif ($baselineOSBuild -and $baselineOSBuild -ne $currentOS.BuildNumber) {
                $mismatch = $true
                Write-vSEALLog "OS build mismatch: Baseline from build $baselineOSBuild, current is $($currentOS.BuildNumber)" -Level WARNING
            }

            if ($mismatch) {
                Write-vSEALLog "OS differences may affect rollback compatibility. Proceeding anyway." -Level WARNING
                # Note: Per BASE-05, we proceed with warning, not error
            }
        }
        catch {
            Write-vSEALLog "Could not verify OS compatibility: $($_.Exception.Message)" -Level VERBOSE
        }

        # Rollback confirmation (non-interactive: require -Force)
        if (-not $Force) {
            $msg = "Rollback to baseline '$Baseline' (created $($baselineData.Created)) requires -Force flag. This will affect $($baselineData.Plugins.Count) plugin(s)."
            throw $msg
        }
        Write-vSEALLog "Proceeding with rollback (-Force specified)" -Level INFO
    }

    # --- Decision Logic (Engage phase only, skip gate bypassed for -Analyze) ---
    if ($Phase -eq 'Engage' -and -not $Analyze) {
        $skipResult = Test-vSEALShouldSkipEngage -Configuration $configuration
        if ($skipResult.Skip) {
            # Emit operation.start for streaming mode compatibility
            Write-vSEALLog "Engage phase started" -Level INFO `
                -Type 'operation.start' `
                -Data @{
                    phase        = 'engage'
                    computerName = $env:COMPUTERNAME
                    pluginCount  = 0
                }

            Write-vSEALLog "$($skipResult.Reason). Skipping Engage." -Level INFO

            # Emit operation.end with Skipped result for CLI to handle
            Write-vSEALLog "Engage phase skipped" -Level INFO `
                -Type 'operation.end' `
                -Data @{
                    result       = 'Skipped'
                    reason       = $skipResult.Reason
                    successCount = 0
                    failedCount  = 0
                    skippedCount = 0
                    durationMs   = 0
                }

            Close-vSEALLogger -Message "Engage phase skipped" -Silent
            return
        }
    }

    # --- Discover Plugins ---
    Write-vSEALLog "Discovering plugins (local > shared > built-in)"
    $registry = Get-vSEALPluginRegistry -BuiltInPath $builtInPluginPath -LocalPath $localPluginPath -SharedPath $sharedPluginPath

    $displayPhase = if ($Rollback) { 'Rollback' } else { $Phase }

    if ($registry.Count -eq 0 -and -not $Rollback) {
        Write-vSEALLog "No plugins found" -Level 'WARNING'
        Close-vSEALLogger -Message "$displayPhase phase completed (no plugins)"
        return
    }

    Write-vSEALLog "Discovered $($registry.Count) plugin(s)"

    # --- Collect All Plugin Status ---
    $allPluginStatus = @()

    foreach ($pName in $registry.Keys) {
        $pluginEntry = $registry[$pName]

        # v2 registry: fields are directly on entry, no .Metadata wrapper
        # Config key is the plugin ID (matches [plugins.{id}] in TOML)
        $configKey = $pName

        # Check if plugin handles this phase
        # v2 registry stores phases lowercase ("seal", "engage"); $Phase is title-case
        $supportsPhase = $Phase.ToLower() -in $pluginEntry.Phases
        $phaseReason = if (-not $supportsPhase) { "Not in $Phase phase" } else { $null }

        # Check if enabled in config (v2: [plugins.{id}] format)
        $pluginsConfig = $configuration['plugins']
        $pluginConfig = if ($pluginsConfig) { $pluginsConfig[$pName] } else { $null }
        $isEnabled = $pluginConfig -and ($pluginConfig.enabled -eq $true)

        # Check Test-Applicable using ephemeral module
        # Call Test-Applicable for ALL plugins regardless of enabled status
        # This provides "would apply if enabled" visibility in the summary table
        $isApplicable = $false
        $applicableReason = $null

        if ($supportsPhase -and $pluginEntry.HasScript) {
            $testModule = New-Module -Name "vSEAL_Test_$([guid]::NewGuid().ToString('N'))" -ScriptBlock (
                [scriptblock]::Create($pluginEntry.ScriptContent)
            )
            try {
                $applicableResult = & $testModule {
                    param($Globals)
                    Test-Applicable -Globals $Globals
                } -Globals $globals
            }
            finally {
                Remove-Module -ModuleInfo $testModule -Force -ErrorAction SilentlyContinue
            }

            # Handle hashtable return format: @{ Applicable = $bool; Reason = $string }
            $isApplicable = if ($applicableResult -is [hashtable]) {
                $applicableResult.Applicable
            } else {
                # Fallback for legacy boolean return (should not occur after migration)
                $applicableResult
            }
            $applicableReason = if ($applicableResult -is [hashtable]) { $applicableResult.Reason } else { $null }
        }
        elseif ($supportsPhase) {
            # TOML-only plugins (no plugin.ps1) are always applicable
            $isApplicable = $true
        }

        # Determine display reason (phase reason takes precedence)
        $displayReason = if ($phaseReason) { $phaseReason } else { $applicableReason }

        # Remove 'enabled' from config before passing to plugin
        $pluginConfigClean = @{}
        if ($pluginConfig) {
            foreach ($key in $pluginConfig.Keys) {
                if ($key -ne 'enabled') {
                    $pluginConfigClean[$key] = $pluginConfig[$key]
                }
            }
        }

        $allPluginStatus += @{
            Name           = $configKey                   # plugin ID (e.g., "defender")
            DisplayName    = $pluginEntry.Description     # e.g., "Windows Defender"
            Priority       = $pluginEntry.Priority
            Enabled        = $isEnabled
            Applicable     = $isApplicable -and $supportsPhase
            Reason         = $displayReason
            Plugin         = $pluginEntry
            Config         = $pluginConfigClean
            Critical       = $pluginEntry.Critical
            SupportsPhase  = $supportsPhase
        }
    }

    # --- Filter to Runnable Plugins ---
    $runnablePlugins = @($allPluginStatus | Where-Object { $_.Enabled -and $_.Applicable -and $_.SupportsPhase })

    # Apply -Plugin filter if specified
    if ($Plugin) {
        $runnablePlugins = @($runnablePlugins | Where-Object { $_.Name -like "*$Plugin*" })
    }

    # --- Sort by Priority ---
    # Wrap in @() to ensure array type is preserved (single-item arrays unwrap to hashtable after Sort-Object)
    $pluginsToRun = @($runnablePlugins | Sort-Object { $_.Priority })

    # --- Emit plugin.summary structured event ---
    $summaryData = @($allPluginStatus | Sort-Object { $_.Priority } | ForEach-Object {
        @{
            name       = $_.Name
            priority   = $_.Priority
            enabled    = $_.Enabled
            applies    = $_.Applicable -and $_.SupportsPhase
            source     = $_.Plugin.Source
            skipReason = if (-not ($_.Enabled -and $_.Applicable -and $_.SupportsPhase)) {
                if (-not $_.SupportsPhase) { "phase mismatch" }
                elseif (-not $_.Enabled) { "disabled" }
                elseif ($_.Reason) { $_.Reason }
                else { "not applicable" }
            } else { $null }
        }
    })

    $executionOrder = @($pluginsToRun | ForEach-Object { $_.Name })

    Write-vSEALLog "Plugin discovery complete" -Level DEBUG `
        -Type 'plugin.summary' `
        -Data @{
            phase          = $Phase.ToLower()
            totalPlugins   = $allPluginStatus.Count
            executingCount = $pluginsToRun.Count
            executionOrder = $executionOrder
            plugins        = $summaryData
        }

    # --- INFO one-liner summarizing discovery ---
    $skippedNames = @($allPluginStatus | Where-Object {
        -not ($_.Enabled -and $_.Applicable -and $_.SupportsPhase)
    } | ForEach-Object { $_.Name }) -join ', '

    $summaryMsg = "Discovered $($allPluginStatus.Count) plugins, executing $($pluginsToRun.Count)"
    if ($skippedNames) {
        $summaryMsg += " (skipped: $skippedNames)"
    }
    Write-vSEALLog $summaryMsg

    # --- Emit plugin.skipped events for non-runnable plugins (Seal/Engage only) ---
    $pluginsSkipped = 0
    if (-not $Rollback) {
        $skippedPlugins = @($allPluginStatus | Where-Object {
            -not ($_.Enabled -and $_.Applicable -and $_.SupportsPhase)
        })
        foreach ($skipped in $skippedPlugins) {
            $reasonCode = if (-not $skipped.SupportsPhase) { 'phase_mismatch' }
                          elseif (-not $skipped.Enabled) { 'disabled' }
                          else { 'not_applicable' }
            $displayReason = if ([string]::IsNullOrWhiteSpace($skipped.Reason)) { $reasonCode -replace '_', ' ' } else { $skipped.Reason }
            Write-vSEALLog "Skipping $($skipped.Name) ($displayReason)" -Level INFO `
                -Type 'plugin.skipped' `
                -Data @{
                    name       = $skipped.Name
                    reasonCode = $reasonCode
                    reason     = $displayReason
                }
            $pluginsSkipped++
        }
    }

    if ($pluginsToRun.Count -eq 0 -and -not $Rollback) {
        Write-vSEALLog "No applicable plugins to run" -Level 'INFO'
        Close-vSEALLogger -Message "$displayPhase phase completed (no applicable plugins)"
        $global:LASTEXITCODE = 0
        return
    }

    # --- Execute or Analyze ---
    $pluginsRun = 0
    $pluginsFailed = 0

    # --- Rollback: Handle separately from normal plugin execution ---
    if ($Rollback) {
        # Start operation-level timing
        $opStopwatch = [System.Diagnostics.Stopwatch]::StartNew()

        Write-vSEALLog "Rollback phase started" -Level INFO `
            -Type 'operation.start' `
            -Data @{
                phase        = 'rollback'
                computerName = $env:COMPUTERNAME
                pluginCount  = $baselineData.Plugins.Count
            }

        # Start rollback-specific timing
        $rollbackStopwatch = [System.Diagnostics.Stopwatch]::StartNew()

        Write-vSEALLog "Starting rollback from baseline: $Baseline" -Level INFO `
            -Type 'rollback.start' `
            -Data @{
                baselineId = $Baseline
                reason     = 'manual'
            }

        # Check for all-Custom baseline (no rollback-able entries)
        $hasRollbackableEntries = $false
        $totalEntryCount = 0
        foreach ($pluginKey in $baselineData.Plugins.Keys) {
            foreach ($entry in $baselineData.Plugins[$pluginKey]) {
                $totalEntryCount++
                if ($entry.Type -ne 'Custom') {
                    $hasRollbackableEntries = $true
                    break
                }
            }
            if ($hasRollbackableEntries) { break }
        }
        if (-not $hasRollbackableEntries -and $totalEntryCount -gt 0) {
            Write-vSEALLog "Baseline contains only Custom entries - no rollback-able entries" -Level ERROR
            Close-vSEALLogger -Message "Rollback phase failed"
            throw "Baseline '$Baseline' contains only Custom entries. No rollback-able entries found."
        }

        # === Forward Baseline: Capture current state BEFORE rollback ===
        if ($BackupCurrent) {
            $forwardId = "pre-rollback-$Baseline"

            Write-vSEALLog "Creating forward baseline: $forwardId" -Level INFO

            # Check if forward baseline already exists (conflict detection)
            $forwardBasePath = Resolve-vSEALBaselinePath -Config $configuration
            $forwardDir = Join-Path $forwardBasePath $forwardId
            if (Test-Path -Path $forwardDir -PathType Container) {
                Write-vSEALLog "Forward baseline '$forwardId' already exists. Delete it first or omit -BackupCurrent." -Level FATAL
                Close-vSEALLogger -Message "Rollback phase failed"
                throw "Forward baseline '$forwardId' already exists. Delete with: vseal baselines delete $forwardId"
            }

            # Initialize context for forward baseline capture
            Initialize-vSEALBaselineContext

            # Capture current state for each rollback-able entry in the source baseline
            try {
                foreach ($pluginKey in $baselineData.Plugins.Keys) {
                    foreach ($entry in $baselineData.Plugins[$pluginKey]) {
                        # Skip Custom entries -- they have no "current state" to capture
                        if ($entry.Type -eq 'Custom') { continue }

                        # Capture current system state based on entry type
                        $currentValue = $null
                        switch ($entry.Type) {
                            'Registry' {
                                # Read current registry value
                                $parts = $entry.Target -split '\\'
                                $valueName = $parts[-1]
                                $keyPath = ($parts[0..($parts.Count - 2)]) -join '\'
                                $currentValue = Get-vSEALRegistryKey -LiteralPath $keyPath -Name $valueName -ErrorAction SilentlyContinue
                                if ($null -eq $currentValue) {
                                    $currentValue = @{ DidNotExist = $true }
                                }
                            }
                            'Service' {
                                $svc = Get-Service -Name $entry.Target -ErrorAction SilentlyContinue
                                if ($null -ne $svc) {
                                    $currentValue = @{ StartMode = $svc.StartType.ToString() }
                                }
                            }
                            'Task' {
                                $task = Get-ScheduledTask -TaskName $entry.Target -ErrorAction SilentlyContinue
                                if ($null -ne $task) {
                                    $currentValue = @{ State = $task.State.ToString() }
                                }
                            }
                            'Command' {
                                $rollbackCmd = $entry.OriginalValue.RollbackCommand
                                if (-not [string]::IsNullOrEmpty($rollbackCmd)) {
                                    $currentValue = @{ RollbackCommand = $rollbackCmd }
                                }
                            }
                        }

                        if ($null -ne $currentValue) {
                            Add-vSEALBaselineEntry -PluginId $pluginKey `
                                -Type $entry.Type `
                                -Target $entry.Target `
                                -OriginalValue $currentValue
                        }
                    }
                }

                # Save forward baseline with custom ID
                $null = Save-vSEALBaseline -Id $forwardId `
                    -ConfigPath $Config `
                    -Config $configuration `
                    -Phase 'rollback' `
                    -SourceBaseline $Baseline `
                    -Status 'complete'

                Write-vSEALLog "Forward baseline created: $forwardId" -Level INFO `
                    -Type 'baseline.create' `
                    -Data @{
                        baselineId     = $forwardId
                        sourceBaseline = $Baseline
                        phase          = 'rollback'
                    }
            }
            catch {
                # Capture failed -- abort rollback entirely (honor the explicit safety request)
                Clear-vSEALBaselineContext
                Write-vSEALLog "Forward baseline capture failed: $($_.Exception.Message)" -Level FATAL
                Close-vSEALLogger -Message "Rollback phase failed"
                throw "Forward baseline capture failed. Rollback aborted. Error: $($_.Exception.Message)"
            }
        }

        # Track rollback results
        $rollbackResults = @()

        # Process each plugin in the baseline
        foreach ($pluginKey in $baselineData.Plugins.Keys) {
            $pluginEntries = $baselineData.Plugins[$pluginKey]

            if ($null -eq $pluginEntries -or $pluginEntries.Count -eq 0) {
                Write-vSEALLog "No entries for plugin: $pluginKey" -Level VERBOSE
                continue
            }

            Write-vSEALLog "Rolling back: $pluginKey ($($pluginEntries.Count) entries)" -Level INFO
            Push-PluginContext -PluginPath $pluginKey

            # Emit plugin.start event for CLI state tracking
            Write-vSEALLog "Starting rollback for $pluginKey" -Level INFO `
                -Type 'plugin.start' `
                -Data @{
                    name     = $pluginKey
                    entries  = $pluginEntries.Count
                    critical = $false
                }

            # Start per-plugin timing
            $pluginStopwatch = [System.Diagnostics.Stopwatch]::StartNew()

            try {
                # Check if plugin has custom Invoke-Rollback
                # v2 registry: entry key is the plugin name directly
                $pluginEntry = $null
                foreach ($pName in $registry.Keys) {
                    if ($pName -eq $pluginKey) {
                        $pluginEntry = $registry[$pName]
                        break
                    }
                }

                $hasCustomRollback = $false
                if ($pluginEntry) {
                    # Load plugin into dynamic module to check for Invoke-Rollback export
                    $rollbackModule = New-Module -Name "vSEAL_Rollback_$([guid]::NewGuid().ToString('N'))" -ScriptBlock (
                        [scriptblock]::Create($pluginEntry.ScriptContent)
                    )
                    try {
                        # Detect custom Invoke-Rollback via Get-Command
                        $hasCustomRollback = $null -ne (Get-Command -Name 'Invoke-Rollback' -Module $rollbackModule -ErrorAction SilentlyContinue)

                        if ($hasCustomRollback) {
                            # Call plugin's custom rollback
                            $customResult = & $rollbackModule {
                                param($Entries, $PluginId)
                                Invoke-Rollback -Entries $Entries -PluginId $PluginId
                            } -Entries $pluginEntries -PluginId $pluginKey

                            $rollbackResults += [PSCustomObject]@{
                                Plugin = $pluginKey
                                Result = $customResult
                                Custom = $true
                            }
                        }
                    }
                    finally {
                        Remove-Module -ModuleInfo $rollbackModule -Force -ErrorAction SilentlyContinue
                    }
                }

                if (-not $hasCustomRollback) {
                    # Use core rollback for declarative plugins
                    $coreResult = Invoke-vSEALCoreRollback -Entries $pluginEntries -PluginId $pluginKey
                    $rollbackResults += [PSCustomObject]@{
                        Plugin = $pluginKey
                        Result = $coreResult
                        Custom = $false
                    }

                    if ($coreResult.Restored -gt 0) {
                        $pluginsRun++
                        Write-vSEALLog "${pluginKey}: $($coreResult.Restored) restored, $($coreResult.Skipped) skipped" -Level SUCCESS
                    }
                    if ($coreResult.Failed -gt 0) {
                        $pluginsFailed++
                        Write-vSEALLog "${pluginKey}: $($coreResult.Failed) failed" -Level WARNING
                    }
                }

                # Stop per-plugin timing and emit plugin.success event
                $pluginStopwatch.Stop()
                Write-vSEALLog "Rollback complete for $pluginKey" -Level SUCCESS `
                    -Type 'plugin.success' `
                    -DurationMs $pluginStopwatch.ElapsedMilliseconds `
                    -Data @{
                        name       = $pluginKey
                        durationMs = $pluginStopwatch.ElapsedMilliseconds
                    }
            }
            catch {
                # Stop per-plugin timing and emit plugin.failed event
                $pluginStopwatch.Stop()
                Write-vSEALLog "Rollback failed for $pluginKey : $($_.Exception.Message)" -Level ERROR `
                    -Type 'plugin.failed' `
                    -DurationMs $pluginStopwatch.ElapsedMilliseconds `
                    -Data @{
                        name         = $pluginKey
                        durationMs   = $pluginStopwatch.ElapsedMilliseconds
                        errorMessage = $_.Exception.Message
                    }
                $pluginsFailed++
                # Continue with other plugins (best effort)
            }
            finally {
                Pop-PluginContext
            }
        }

        # Aggregate totals
        $totalRestored = ($rollbackResults | ForEach-Object { $_.Result.Restored } | Measure-Object -Sum).Sum
        $totalFailed = ($rollbackResults | ForEach-Object { $_.Result.Failed } | Measure-Object -Sum).Sum
        $totalSkipped = ($rollbackResults | ForEach-Object { $_.Result.Skipped } | Measure-Object -Sum).Sum
        $totalCustomSkipped = ($rollbackResults | ForEach-Object { $_.Result.CustomSkipped } | Measure-Object -Sum).Sum

        # === Per-Plugin Transaction Summary ===
        Write-vSEALLog "Rollback summary" -Level INFO

        $allCustomSkipped = @()

        foreach ($pluginResult in $rollbackResults) {
            $r = $pluginResult.Result
            $pluginName = $pluginResult.Plugin

            if ($r.Failed -gt 0) {
                Write-vSEALLog "FAILED  $pluginName -- $($r.Restored) restored, $($r.Failed) failed, $($r.Skipped) skipped" -Level ERROR
                foreach ($errorMsg in $r.Errors) {
                    Write-vSEALLog "  $errorMsg" -Level ERROR
                }
            }
            else {
                Write-vSEALLog "OK      $pluginName -- $($r.Restored) restored" -Level SUCCESS
            }

            # Collect Custom-skipped entries for summary section
            if ($r.CustomSkipped -gt 0) {
                $allCustomSkipped += @($r.CustomEntries | ForEach-Object {
                    @{ Plugin = $pluginName; Target = $_ }
                })
            }
        }

        # === Custom Skip Warnings (summary section only) ===
        if ($allCustomSkipped.Count -gt 0) {
            Write-vSEALLog "Custom entries skipped" -Level WARNING
            foreach ($skipped in $allCustomSkipped) {
                Write-vSEALLog "  Skipped: $($skipped.Target) ($($skipped.Plugin))" -Level WARNING
            }
            Write-vSEALLog "$($allCustomSkipped.Count) Custom entries skipped (no automatic rollback available)" -Level WARNING
        }

        # Stop rollback timing and emit rollback.end event
        $rollbackStopwatch.Stop()
        $rollbackResult = if ($totalFailed -eq 0) { 'Success' } else { 'Failed' }

        $rollbackEndData = @{
            result            = $rollbackResult
            baselineId        = $Baseline
            durationMs        = $rollbackStopwatch.ElapsedMilliseconds
            customSkipped     = $totalCustomSkipped
            entriesRestored   = $totalRestored
            entriesFailed     = $totalFailed
            entriesSkipped    = $totalSkipped
        }

        if ($totalFailed -gt 0) {
            $rollbackEndData.errorMessage = "Rollback failed for $totalFailed entries"
            $rollbackEndData.errorCategory = 'RollbackFailure'
        }

        if ($totalFailed -eq 0) {
            Write-vSEALLog "Rollback complete: $($rollbackResults.Count) plugins, $totalRestored actions restored, $totalSkipped skipped" -Level SUCCESS `
                -Type 'rollback.end' `
                -DurationMs $rollbackStopwatch.ElapsedMilliseconds `
                -Data $rollbackEndData
        }
        else {
            Write-vSEALLog "Rollback complete with errors: $totalRestored restored, $totalFailed failed, $totalSkipped skipped" -Level WARNING `
                -Type 'rollback.end' `
                -DurationMs $rollbackStopwatch.ElapsedMilliseconds `
                -Data $rollbackEndData
        }

        # Stop operation timing and emit operation.end event
        $opStopwatch.Stop()
        $operationResult = if ($totalFailed -eq 0) { 'Success' } else { 'Failed' }

        Write-vSEALLog "Rollback operation completed" -Level INFO `
            -Type 'operation.end' `
            -DurationMs $opStopwatch.ElapsedMilliseconds `
            -Data @{
                result              = $operationResult
                successCount        = $pluginsRun        # Plugin count for CLI state
                failedCount         = $pluginsFailed     # Plugin count for CLI state
                entriesRestored     = $totalRestored     # Entry-level detail
                entriesFailed       = $totalFailed
                entriesSkipped      = $totalSkipped
                entriesCustomSkipped = $totalCustomSkipped
                skippedCount        = $totalSkipped
                durationMs          = $opStopwatch.ElapsedMilliseconds
            }

        Close-vSEALLogger -Message "Rollback phase completed" -Silent
        return
    }

    # --- Analyze Mode: Event-based handling for CLI consumption ---
    if ($Analyze) {
        $analyzeErrors = 0
        $totalChanges = 0
        $totalPlugins = $pluginsToRun.Count

        # Start operation timing and emit operation.start with mode=analyze
        $opStopwatch = [System.Diagnostics.Stopwatch]::StartNew()

        Write-vSEALLog "$Phase analyze started" -Level INFO `
            -Type 'operation.start' `
            -Data @{
                phase        = $Phase.ToLower()
                mode         = 'analyze'
                computerName = $env:COMPUTERNAME
                pluginCount  = $pluginsToRun.Count
            }

        foreach ($entry in $pluginsToRun) {
            $pluginEntry = $entry.Plugin

            Write-vSEALLog "Analyzing: $($entry.DisplayName)" -Level VERBOSE
            Push-PluginContext -PluginPath $entry.Name

            try {
                $pluginAnalysis = Get-PluginAnalysis -PluginEntry $pluginEntry -Globals $globals -Config $entry.Config

                $totalChanges += $pluginAnalysis.ChangeActions

                Write-vSEALLog "Analyzed: $($pluginAnalysis.Plugin)" -Level INFO `
                    -Type 'analyze.plugin' `
                    -Data @{
                        name            = $pluginAnalysis.Plugin
                        changeActions   = $pluginAnalysis.ChangeActions
                        noOpActions     = $pluginAnalysis.NoOpActions
                        skippedActions  = $pluginAnalysis.SkippedActions
                        rollbackCapable = $pluginAnalysis.RollbackCapable
                        commandCount    = $pluginAnalysis.CommandCount
                        hasTemplate     = $pluginAnalysis.HasTemplate
                        groups          = @($pluginAnalysis.Groups | ForEach-Object {
                            @{
                                name        = $_.Name
                                willExecute = $_.WillExecute
                                skipReason  = $_.SkipReason
                                actions     = @($_.Actions | ForEach-Object {
                                    @{
                                        type       = $_.Type
                                        target     = $_.Target
                                        willChange = $_.WillChange
                                        skipped    = $_.Skipped
                                    }
                                })
                            }
                        })
                    }
            }
            catch {
                Write-vSEALLog "Analysis failed for $($entry.DisplayName): $($_.Exception.Message)" -Level ERROR `
                    -Type 'analyze.plugin' `
                    -Data @{
                        name  = $entry.Name
                        error = $_.Exception.Message
                    }
                $analyzeErrors++
            }
            finally {
                Pop-PluginContext
            }
        }

        # Determine result string for operation.end
        $operationResult = if ($analyzeErrors -eq $totalPlugins) { 'Failed' }
                           elseif ($analyzeErrors -gt 0) { 'AnalyzePartial' }
                           elseif ($totalChanges -gt 0) { 'AnalyzeChanges' }
                           else { 'AnalyzeClean' }

        $opStopwatch.Stop()

        Write-vSEALLog "Analyze completed" -Level INFO `
            -Type 'operation.end' `
            -DurationMs $opStopwatch.ElapsedMilliseconds `
            -Data @{
                result        = $operationResult
                totalChanges  = $totalChanges
                totalPlugins  = $totalPlugins
                analyzeErrors = $analyzeErrors
            }

        Close-vSEALLogger -Message "$Phase analyze completed" -Silent

        # Set terraform-convention exit codes for CLI consumption
        $global:LASTEXITCODE = switch ($operationResult) {
            'Failed'         { 1 }  # All plugins errored
            'AnalyzePartial' { 1 }  # Some plugins errored
            'AnalyzeChanges' { 2 }  # Changes detected, no errors (like terraform plan)
            'AnalyzeClean'   { 0 }  # No changes, no errors
        }

        return
    }

    # --- Seal/Engage Phase: Standard plugin execution with timeout enforcement ---

    # Start operation-level timing and emit operation.start event
    $opStopwatch = [System.Diagnostics.Stopwatch]::StartNew()

    Write-vSEALLog "$Phase phase started" -Level INFO `
        -Type 'operation.start' `
        -Data @{
            phase        = $Phase.ToLower()
            computerName = $env:COMPUTERNAME
            pluginCount  = $pluginsToRun.Count
        }

    # Extract global default timeout (5 minutes = 300 seconds)
    $defaultTimeout = $configuration.vseal.default_plugin_timeout ?? 300

    try {
        # Initialize error collection for status persistence
        $collectedErrors = [System.Collections.ArrayList]::new()

        # Plugin index counter for step tracking
        $pluginIndex = 0

        foreach ($entry in $pluginsToRun) {
            $pluginEntry = $entry.Plugin
            $pluginIndex++

            # Start plugin-level timing
            $pluginStopwatch = [System.Diagnostics.Stopwatch]::StartNew()

            # Emit plugin.start event with step tracking
            Write-vSEALLog "Starting $($entry.Name) [$($entry.Priority)] ($($pluginEntry.Source))" -Level INFO `
                -Type 'plugin.start' `
                -StepCurrent $pluginIndex `
                -StepTotal $pluginsToRun.Count `
                -Data @{
                    name     = $entry.Name
                    priority = $entry.Priority
                    critical = $entry.Critical
                    source   = $pluginEntry.Source
                }

            # Push plugin context for accurate log source detection
            Push-PluginContext -PluginPath $entry.Name

            try {
                # Create ephemeral module for this plugin execution
                $pluginModule = New-Module -Name "vSEAL_$($entry.Name -replace '[/\\]', '_')_$([guid]::NewGuid().ToString('N'))" -ScriptBlock (
                    [scriptblock]::Create($pluginEntry.ScriptContent)
                )

                # Seal/Engage execution with timeout enforcement
                # (Analyze and Rollback phases return early before this loop)

                # Determine timeout for this plugin
                        $timeoutSeconds = $entry.Config.timeout ?? $defaultTimeout
                        $hasTimeout = $timeoutSeconds -gt 0  # 0 = infinite

                        # Create execution scriptblock for ThreadJob
                        # Plugins use $PluginScriptRoot instead of $PSScriptRoot (which is empty in ThreadJob)
                        $executionBlock = {
                            param($PluginContent, $Config, $Globals, $Phase, $PluginPath)

                            # Set $PluginScriptRoot variable that plugins can use (replaces $PSScriptRoot)
                            $script:PluginScriptRoot = Split-Path -Parent $PluginPath

                            $pluginModule = New-Module -Name "vSEAL_Job_$([guid]::NewGuid().ToString('N'))" -ScriptBlock (
                                [scriptblock]::Create($PluginContent)
                            )

                            try {
                                & $pluginModule {
                                    param($Config, $Globals, $Phase, $ScriptRoot)
                                    # Make PluginScriptRoot available as both variable and in PSScriptRoot-like pattern
                                    $script:PluginScriptRoot = $ScriptRoot
                                    $phaseFunctionName = "Invoke-$Phase"
                                    if (Get-Command -Name $phaseFunctionName -ErrorAction SilentlyContinue) {
                                        & $phaseFunctionName -Config $Config -Globals $Globals
                                    } else {
                                        throw "Plugin has no $phaseFunctionName function"
                                    }
                                } -Config $Config -Globals $Globals -Phase $Phase -ScriptRoot $script:PluginScriptRoot

                                # Extract baseline context from ThreadJob runspace before it dies
                                # Context will be null for Engage phase (Initialize-vSEALBaselineContext only runs for Seal)
                                $jobBaselineContext = Get-vSEALBaselineContext
                                if ($null -ne $jobBaselineContext -and $jobBaselineContext.Count -gt 0) {
                                    [PSCustomObject]@{
                                        __vSEALBaseline = $true
                                        Context         = $jobBaselineContext
                                    }
                                }
                            } finally {
                                Remove-Module -ModuleInfo $pluginModule -Force -ErrorAction SilentlyContinue
                            }
                        }

                        # Create InitializationScript to import vSEAL module into ThreadJob runspace
                        # This enables plugins to call exported functions like Write-vSEALLog
                        # For Seal phase, also initialize baseline context in the job's runspace
                        $manifestPath = Join-Path $script:ModuleRoot 'vSEAL.psd1'
                        $initCommands = "Import-Module '$manifestPath' -Force -DisableNameChecking"
                        if ($Phase -eq 'Seal') {
                            $initCommands += "; Initialize-vSEALBaselineContext"
                        }
                        $initScript = [scriptblock]::Create($initCommands)

                        # Start job and wait with timeout
                        $startTime = Get-Date
                        $job = Start-ThreadJob -ScriptBlock $executionBlock `
                            -InitializationScript $initScript `
                            -ArgumentList $pluginEntry.ScriptContent, $entry.Config, $globals, $Phase, (Join-Path $pluginEntry.PluginPath 'plugin.ps1')

                        $timedOut = $false
                        if ($hasTimeout) {
                            $completed = Wait-Job -Job $job -Timeout $timeoutSeconds
                            if (-not $completed) {
                                $timedOut = $true
                            }
                        } else {
                            $null = Wait-Job -Job $job
                        }

                        # Handle timeout case
                        if ($timedOut) {
                            $pluginStopwatch.Stop()
                            Stop-Job -Job $job -ErrorAction SilentlyContinue

                            Write-vSEALLog "$($entry.Name) timed out after $([Math]::Round($pluginStopwatch.ElapsedMilliseconds/1000, 1))s (limit: ${timeoutSeconds}s)" -Level WARNING `
                                -Type 'plugin.timeout' `
                                -DurationMs $pluginStopwatch.ElapsedMilliseconds `
                                -Data @{
                                    name       = $entry.Name
                                    durationMs = $pluginStopwatch.ElapsedMilliseconds
                                }
                            [void]$collectedErrors.Add(@{
                                plugin = $entry.Name
                                message = "Timed out after ${timeoutSeconds}s"
                            })

                            if ($entry.Critical) {
                                Remove-Job -Job $job -Force -ErrorAction SilentlyContinue
                                Write-vSEALLog "Critical plugin failed - aborting" -Level 'FATAL'
                                throw "Critical plugin '$($entry.DisplayName)' timed out"
                            }

                            Remove-Job -Job $job -Force -ErrorAction SilentlyContinue
                            # Capture timeout plugin metadata for baseline (Seal phase only)
                            if ($Phase -eq 'Seal') {
                                $baselinePluginId = $entry.Name -replace '[/\\]', '.'
                                Add-vSEALBaselineEntry -PluginId $baselinePluginId `
                                    -Type 'Custom' `
                                    -Target "plugin:$($entry.Name)" `
                                    -OriginalValue @{ executed = $false } `
                                    -AppliedValue @{
                                        executed = $true
                                        result = 'Timeout'
                                        duration = [Math]::Round($pluginStopwatch.ElapsedMilliseconds / 1000, 3)
                                        error = "Timed out after ${timeoutSeconds}s"
                                    }
                            }

                            $pluginsFailed++
                        }
                        # Handle completion case (check for job errors)
                        else {
                            $pluginStopwatch.Stop()
                            $jobErrors = $null
                            $jobOutput = Receive-Job -Job $job -ErrorAction SilentlyContinue -ErrorVariable jobErrors

                            # Separate baseline payloads from regular plugin output
                            $baselinePayloads = @($jobOutput | Where-Object {
                                $_ -is [PSCustomObject] -and $_.PSObject.Properties['__vSEALBaseline'] -and $_.__vSEALBaseline -eq $true
                            })

                            # Determine plugin result (failed vs success)
                            $pluginFailed = $job.State -eq 'Failed' -or $jobErrors
                            $errorMsg = $null

                            if ($pluginFailed) {
                                $errorMsg = if ($jobErrors) { $jobErrors[0].Exception.Message } else { "Job failed" }
                                $errorCategory = if ($jobErrors) { $jobErrors[0].CategoryInfo.Category.ToString() } else { 'NotSpecified' }
                                Write-vSEALLog "$($entry.Name) failed ($([Math]::Round($pluginStopwatch.ElapsedMilliseconds/1000, 1))s): $errorMsg" -Level ERROR `
                                    -Type 'plugin.failed' `
                                    -DurationMs $pluginStopwatch.ElapsedMilliseconds `
                                    -Data @{
                                        name          = $entry.Name
                                        durationMs    = $pluginStopwatch.ElapsedMilliseconds
                                        errorMessage  = $errorMsg
                                        errorCategory = $errorCategory
                                        wasCritical   = $entry.Critical
                                    }
                                [void]$collectedErrors.Add(@{
                                    plugin = $entry.Name
                                    message = $errorMsg
                                })

                                if ($entry.Critical) {
                                    Remove-Job -Job $job -Force -ErrorAction SilentlyContinue
                                    Write-vSEALLog "Critical plugin failed - aborting" -Level 'FATAL'
                                    throw "Critical plugin '$($entry.DisplayName)' failed: $errorMsg"
                                }
                            }

                            # Capture baseline for failed plugin or successful plugin (Seal phase only)
                            # If fine-grained entries exist, merge them; otherwise create Custom wrapper
                            if ($Phase -eq 'Seal') {
                                $baselinePluginId = $entry.Name -replace '[/\\]', '.'

                                if ($baselinePayloads.Count -gt 0) {
                                    $mergedCount = 0
                                    $byType = @{}
                                    foreach ($payload in $baselinePayloads) {
                                        foreach ($pluginId in $payload.Context.Keys) {
                                            $entries = $payload.Context[$pluginId]
                                            $parentContext = Get-vSEALBaselineContext
                                            if ($null -ne $parentContext) {
                                                $parentContext[$pluginId] = $entries
                                            }
                                            $mergedCount += $entries.Count
                                            foreach ($e in $entries) {
                                                $typeName = $e.Type
                                                if (-not $byType.ContainsKey($typeName)) { $byType[$typeName] = 0 }
                                                $byType[$typeName]++
                                            }
                                        }
                                    }
                                    Write-vSEALLog "Merged $mergedCount plugin entries into baseline for $($entry.Name)" -Level DEBUG
                                    $mergeData = @{
                                        plugin     = $baselinePluginId
                                        entryCount = $mergedCount
                                        byType     = $byType
                                    }
                                    if ($pluginFailed) { $mergeData.result = 'Failed' }
                                    Write-vSEALLog "Baseline entries merged" -Level DEBUG `
                                        -Type 'baseline.merge' `
                                        -Data $mergeData
                                } else {
                                    $appliedValue = @{
                                        executed = $true
                                        duration = [Math]::Round($pluginStopwatch.ElapsedMilliseconds / 1000, 3)
                                    }
                                    if ($pluginFailed) {
                                        $appliedValue.result = 'Failed'
                                        $appliedValue.error = $errorMsg
                                    } else {
                                        $appliedValue.result = 'Success'
                                    }
                                    Add-vSEALBaselineEntry -PluginId $baselinePluginId `
                                        -Type 'Custom' `
                                        -Target "plugin:$($entry.Name)" `
                                        -OriginalValue @{ executed = $false } `
                                        -AppliedValue $appliedValue
                                }
                            }

                            # Update counters and emit success event
                            if ($pluginFailed) {
                                $pluginsFailed++
                            } else {
                                $pluginsRun++
                                Write-vSEALLog "$($entry.Name) completed ($([Math]::Round($pluginStopwatch.ElapsedMilliseconds/1000, 1))s)" -Level SUCCESS `
                                    -Type 'plugin.success' `
                                    -DurationMs $pluginStopwatch.ElapsedMilliseconds `
                                    -Data @{
                                        name       = $entry.Name
                                        durationMs = $pluginStopwatch.ElapsedMilliseconds
                                    }
                            }

                            Remove-Job -Job $job -Force -ErrorAction SilentlyContinue
                        }

                # Clean up ephemeral module
                Remove-Module -ModuleInfo $pluginModule -Force -ErrorAction SilentlyContinue
            }
            finally {
                # Always pop context, even on error
                Pop-PluginContext
            }
        }

    }
    finally {
        # --- Save Baseline (Seal phase only) ---
        # Must happen before Write-vSEALStatus so we have $baselineId for status
        $baselineId = $null
        if ($Phase -eq 'Seal' -and $pluginsRun -gt 0) {
            try {
                $baselineStatus = if ($pluginsFailed -eq 0) { 'complete' } else { 'incomplete' }
                $baselineId = Save-vSEALBaseline -ConfigPath $Config -Config $configuration -Status $baselineStatus -PassThru
            }
            catch {
                Write-vSEALLog "Failed to save baseline: $($_.Exception.Message)" -Level WARNING
            }
            finally {
                Clear-vSEALBaselineContext
            }
        }

        # Write status (before logger close, but only if we executed plugins)
        if ($pluginsToRun.Count -gt 0) {
            # Determine result based on execution outcomes
            $statusResult = if ($pluginsFailed -eq 0 -and $pluginsRun -gt 0) {
                'Success'
            } elseif ($pluginsRun -gt 0 -and $pluginsFailed -gt 0) {
                'PartialSuccess'
            } else {
                'Failed'
            }

            try {
                $statusPath = Resolve-vSEALStatusPath -Phase $Phase -Configuration $configuration
                Write-vSEALStatus -Phase $Phase `
                    -Result $statusResult `
                    -PluginsRun $pluginsRun `
                    -PluginsFailed $pluginsFailed `
                    -Errors @($collectedErrors) `
                    -Version $globals.vSEALVersion `
                    -Path $statusPath `
                    -BaselineId $baselineId
            }
            catch {
                # Status write failure should not fail the phase
                Write-vSEALLog "Failed to write status: $($_.Exception.Message)" -Level WARNING
            }
        }

        # Emit operation.end event before closing logger
        $opStopwatch.Stop()
        $operationResult = if ($pluginsFailed -eq 0 -and $pluginsRun -gt 0) { 'Success' }
                           elseif ($pluginsRun -gt 0) { 'Partial' }
                           else { 'Failed' }

        $completionMessage = if ($pluginsFailed -eq 0) {
            "$Phase complete: $pluginsRun/$($pluginsToRun.Count) succeeded"
        } else {
            "$Phase complete: $pluginsRun/$($pluginsToRun.Count) succeeded ($pluginsFailed failed)"
        }

        Write-vSEALLog $completionMessage -Level INFO `
            -Type 'operation.end' `
            -DurationMs $opStopwatch.ElapsedMilliseconds `
            -Data @{
                result       = $operationResult
                successCount = $pluginsRun
                failedCount  = $pluginsFailed
                skippedCount = $pluginsSkipped
                durationMs   = $opStopwatch.ElapsedMilliseconds
            }

        # Always close logger (Silent: operation.end already emitted completion message)
        Close-vSEALLogger -Message $completionMessage -Silent
    }
}
