#Requires -Version 7.4
# =============================================================================
# Bloatware Removal Plugin (Hybrid Pattern)
# =============================================================================
#
# This plugin removes AppX provisioned packages, sets registry policies, and
# disables services related to Xbox, Maps, and consumer bloatware during VDI
# golden image sealing.
#
# Pattern: Hybrid (TOML template for data + custom PowerShell for AppX logic)
# Phases: Seal (removal). Analyze mode is handled by the orchestrator's -Analyze switch.
#
# KEY OPTIMIZATION:
# The TOML template contains package patterns, not commands. This plugin:
#   1. Queries Get-AppxProvisionedPackage ONCE (single system call)
#   2. Matches patterns against cached results (O(n*m) string matching)
#   3. Removes matched packages with error aggregation
#
# This replaces 51+ separate pwsh process spawns with one query + batch remove.
#
# =============================================================================

#region Helper Functions

function Test-GroupEnabled {
    <#
    .SYNOPSIS
        Checks if a group is enabled, respecting config overrides.

    .DESCRIPTION
        Returns true if the group should be processed based on:
        1. Default from template (enabled = true/false)
        2. Override from Config.groups['GroupName']

        Config overrides take precedence over template defaults.

    .PARAMETER Group
        The group hashtable from the template.

    .PARAMETER Config
        Plugin configuration from vseal.toml [plugins.bloatware] section.
    #>
    param(
        [Parameter(Mandatory)]
        [hashtable]$Group,

        [Parameter()]
        [hashtable]$Config
    )

    # Default from template (true unless explicitly false)
    $enabled = $Group.enabled -ne $false

    # Config override from [plugins.bloatware.groups]
    if ($Config -and $Config.groups -and $Config.groups.ContainsKey($Group.name)) {
        $enabled = [bool]$Config.groups[$Group.name]
    }

    return $enabled
}

function Test-GroupConditions {
    <#
    .SYNOPSIS
        Evaluates all conditions for a group.

    .DESCRIPTION
        Returns true only if ALL conditions pass. A group with no conditions
        always passes. Uses Test-vSEALCondition for each condition evaluation.

    .PARAMETER Group
        The group hashtable containing conditions array.

    .PARAMETER Globals
        OS and Platform information for condition evaluation.
    #>
    param(
        [Parameter(Mandatory)]
        [hashtable]$Group,

        [Parameter(Mandatory)]
        [hashtable]$Globals
    )

    # No conditions = always pass
    if (-not $Group.conditions -or @($Group.conditions).Count -eq 0) {
        return $true
    }

    # All conditions must pass
    foreach ($condition in $Group.conditions) {
        $result = Test-vSEALCondition -Condition $condition -Globals $Globals
        if (-not $result) {
            return $false
        }
    }

    return $true
}

#endregion

#region Plugin Contract Functions

function Get-PluginConfigSchema {
    <#
    .SYNOPSIS
        Returns the configuration schema for this plugin.

    .DESCRIPTION
        Configuration for bloatware plugin is defined in vseal.toml under
        [plugins.bloatware]. Groups can be selectively enabled/disabled via
        the [plugins.bloatware.groups] section.

    .EXAMPLE
        [plugins.bloatware]
        enabled = true

        [plugins.bloatware.groups]
        "System Infrastructure" = true  # Opt-in to dangerous group
        "System Utilities" = false      # Disable medium risk group
    #>
    @{
        groups = @{
            Type        = 'hashtable'
            Default     = @{}
            Required    = $false
            Description = 'Group enable/disable overrides (group name -> bool)'
        }
    }
}

function Test-Applicable {
    <#
    .SYNOPSIS
        Determines whether this plugin should run on the current system.

    .DESCRIPTION
        Bloatware removal targets Windows 11 (build 22000+) and Windows Server 2022+.
        AppX provisioned packages are not present on older Windows versions in the
        same way, and the package names differ significantly.

    .PARAMETER Globals
        Hashtable containing OS and Platform information.
    #>
    param(
        [Parameter(Mandatory)]
        [hashtable]$Globals
    )

    # Require Windows 11 (22000+) or Windows Server 2022
    if ($Globals.OS.Build -lt 22000) {
        return @{
            Applicable = $false
            Reason     = 'Requires Windows 11 or Windows Server 2022+ (build 22000+)'
        }
    }

    return @{
        Applicable = $true
        Reason     = $null
    }
}

function Get-PlannedActions {
    <#
    .SYNOPSIS
        Returns human-readable descriptions of actions this plugin will perform.

    .DESCRIPTION
        This function is called during the planning phase to show users what
        will happen before execution.

    .PARAMETER Config
        Plugin configuration from vseal.toml.

    .PARAMETER Globals
        OS and Platform information hashtable.

    .PARAMETER Phase
        The phase being planned: Seal or Engage.

    .OUTPUTS
        Array of strings describing planned actions.
    #>
    param(
        [Parameter()]
        [hashtable]$Config,

        [Parameter()]
        [hashtable]$Globals,

        [Parameter()]
        [ValidateSet('Seal', 'Engage')]
        [string]$Phase
    )

    # Only Seal phase is supported
    if ($Phase -ne 'Seal') {
        return @("Bloatware removal only runs during Seal phase")
    }

    # Load the template to extract group info
    $templatePath = Join-Path $script:PluginScriptRoot 'bloatware.toml'

    if (-not (Test-Path $templatePath)) {
        return @("Template file not found: $templatePath")
    }

    try {
        $template = Import-vSEALTemplate -Path $templatePath

        $actions = @()

        foreach ($group in $template.Groups) {
            $groupName = $group.name ?? 'Unnamed group'

            # Check if group is enabled
            $enabled = Test-GroupEnabled -Group $group -Config $Config
            $enabledStr = if ($enabled) { '' } else { ' (disabled)' }

            # Count packages and actions
            $pkgCount = if ($group.packages) { @($group.packages).Count } else { 0 }
            $actCount = if ($group.actions) { @($group.actions).Count } else { 0 }

            if ($pkgCount -gt 0) {
                $actions += "[Group: $groupName] $pkgCount package pattern(s)$enabledStr"
            }
            if ($actCount -gt 0) {
                $actions += "[Group: $groupName] $actCount action(s)$enabledStr"
            }
        }

        if ($actions.Count -eq 0) {
            $actions = @("No actions defined in template for $Phase phase")
        }

        return $actions
    }
    catch {
        return @("Failed to parse template: $_")
    }
}

#endregion

#region Phase Functions

function Invoke-Seal {
    <#
    .SYNOPSIS
        Executes the Seal phase with single-query AppX pattern matching.

    .DESCRIPTION
        Removes AppX provisioned packages, sets registry policies, and disables
        services related to Xbox, Maps, and consumer bloatware.

        OPTIMIZATION: Instead of spawning 51+ pwsh processes (one per package),
        this implementation:
        1. Queries Get-AppxProvisionedPackage -Online ONCE
        2. Matches patterns against the cached list
        3. Removes matched packages with error aggregation

        Groups are processed in template order:
        - Consumer Entertainment: Low risk (Xbox, media, games)
        - Consumer Productivity: Low-medium risk (OneNote, Outlook, etc.)
        - System Utilities: Medium risk (Calculator, Notepad, etc.)
        - System Infrastructure: HIGH risk (Store, Winget - breaks UWP deployment)
        - Registry Policies: GameDVR, Maps, WindowsInkWorkspace
        - Service Disabling: MapsBroker, Xbox services

    .PARAMETER Config
        Plugin configuration from vseal.toml.

    .PARAMETER Globals
        OS and Platform information for condition evaluation.

    .NOTES
        IMPORTANT: AppX package removal is NOT reversible. Once removed, packages
        cannot be restored without a full OS image reset.
    #>
    param(
        [Parameter()]
        [hashtable]$Config,

        [Parameter()]
        [hashtable]$Globals
    )

    # Resolve path to adjacent TOML template
    $templatePath = Join-Path $script:PluginScriptRoot 'bloatware.toml'

    Write-vSEALLog -Message "Loading bloatware template: $templatePath" -Level VERBOSE

    # Load and validate the template
    $template = Import-vSEALTemplate -Path $templatePath

    Write-vSEALLog -Message "Template '$($template.Metadata.Name)' v$($template.Metadata.Version) loaded with $($template.Groups.Count) groups" -Level INFO

    # Single query: cache all provisioned packages
    Write-vSEALLog -Message "Querying provisioned packages..." -Level VERBOSE `
        -Type "query_start" -Data @{ target = 'AppxProvisionedPackage' }

    $queryStart = [DateTime]::UtcNow
    try {
        $allPackages = @(Get-AppxProvisionedPackage -Online -ErrorAction Stop)
        $queryDuration = ([DateTime]::UtcNow - $queryStart).TotalMilliseconds

        Write-vSEALLog -Message "Found $($allPackages.Count) provisioned packages" -Level INFO `
            -Type "query_complete" -Data @{ count = $allPackages.Count } -DurationMs $queryDuration
    }
    catch {
        Write-vSEALLog -Message "Failed to query provisioned packages: $_" -Level ERROR `
            -Type "query_error" -Data @{ error = $_.Exception.Message }
        throw
    }

    # Track overall statistics
    $totalRemoved = 0
    $totalFailed = 0
    $totalSkipped = 0
    $allErrors = [System.Collections.ArrayList]::new()

    # Process each group in the template
    foreach ($group in $template.Groups) {
        $groupName = $group.name ?? 'Unnamed group'
        $hasPackages = $group.packages -and @($group.packages).Count -gt 0
        $hasActions = $group.actions -and @($group.actions).Count -gt 0

        # Check if group is enabled
        if (-not (Test-GroupEnabled -Group $group -Config $Config)) {
            Write-vSEALLog -Message "[$groupName] Skipped (disabled in config)" -Level INFO `
                -Type "group_skip" -Data @{ group = $groupName; reason = 'disabled' }
            continue
        }

        # Check group conditions
        if (-not (Test-GroupConditions -Group $group -Globals $Globals)) {
            Write-vSEALLog -Message "[$groupName] Skipped (conditions not met)" -Level INFO `
                -Type "group_skip" -Data @{ group = $groupName; reason = 'conditions' }
            continue
        }

        # Process package patterns (AppX groups)
        if ($hasPackages) {
            $groupStart = [DateTime]::UtcNow
            $pkgCount = @($group.packages).Count

            Write-vSEALLog -Message "[$groupName] Processing $pkgCount package patterns..." -Level INFO `
                -Type "group_start" -Data @{ group = $groupName; patterns = $pkgCount }

            $groupRemoved = 0
            $groupFailed = 0
            $groupSkipped = 0
            $groupErrors = [System.Collections.ArrayList]::new()

            foreach ($pkgPattern in $group.packages) {
                $pattern = $pkgPattern.pattern
                $description = $pkgPattern.description ?? $pattern

                # Match pattern against cached packages
                $matches = @($allPackages | Where-Object { $_.PackageName -like $pattern })

                if ($matches.Count -eq 0) {
                    Write-vSEALLog -Message "  Skipped: $pattern - not provisioned" -Level VERBOSE `
                        -Type "package_skip" -Data @{ pattern = $pattern; reason = 'not_found' }
                    $groupSkipped++
                    continue
                }

                # Remove each matched package
                foreach ($pkg in $matches) {
                    try {
                        Write-vSEALLog -Message "  Removing: $($pkg.PackageName)" -Level VERBOSE `
                            -Type "package_remove_start" -Data @{ package = $pkg.PackageName }

                        $null = Remove-AppxProvisionedPackage -Online -PackageName $pkg.PackageName -ErrorAction Stop
                        $groupRemoved++

                        Write-vSEALLog -Message "  Removed: $($pkg.PackageName)" -Level VERBOSE `
                            -Type "package_removed" -Data @{ package = $pkg.PackageName; description = $description }
                    }
                    catch {
                        $groupFailed++
                        [void]$groupErrors.Add(@{
                            Package     = $pkg.PackageName
                            Pattern     = $pattern
                            Description = $description
                            Error       = $_.Exception.Message
                        })

                        Write-vSEALLog -Message "  Failed: $($pkg.PackageName) - $_" -Level WARNING `
                            -Type "package_remove_error" -Data @{
                                package = $pkg.PackageName
                                error   = $_.Exception.Message
                            }
                    }
                }
            }

            $groupDuration = ([DateTime]::UtcNow - $groupStart).TotalMilliseconds

            # Log group completion
            $groupTotal = $groupRemoved + $groupFailed
            $statusMsg = if ($groupFailed -eq 0) {
                "Removed $groupRemoved of $groupTotal packages"
            } else {
                "Removed $groupRemoved of $groupTotal packages ($groupFailed failed)"
            }

            Write-vSEALLog -Message "[$groupName] $statusMsg ($('{0:F1}' -f ($groupDuration/1000))s)" -Level INFO `
                -Type "group_complete" -Data @{
                    group    = $groupName
                    removed  = $groupRemoved
                    failed   = $groupFailed
                    skipped  = $groupSkipped
                } -DurationMs $groupDuration

            # Accumulate totals
            $totalRemoved += $groupRemoved
            $totalFailed += $groupFailed
            $totalSkipped += $groupSkipped
            foreach ($err in $groupErrors) { [void]$allErrors.Add($err) }
        }

        # Process declarative actions (Registry/Service groups)
        if ($hasActions) {
            $result = Invoke-vSEALActionGroup `
                -Group $group `
                -Phase 'Seal' `
                -PluginId 'bloatware' `
                -Globals $Globals

            # Log the group result summary
            $statusMsg = switch ($result.Status) {
                'Completed'           { "$($result.Succeeded)/$($result.Total) succeeded" }
                'CompletedWithErrors' { "$($result.Succeeded)/$($result.Total) succeeded, $($result.Failed) failed" }
                'Skipped'             { "skipped (conditions not met)" }
                'Stopped'             { "stopped at '$($result.StoppedBy)'" }
                'RolledBack'          { "rolled back ($($result.Failed) failures)" }
                default               { $result.Status }
            }

            Write-vSEALLog -Message "[$($result.Group)] $statusMsg" -Level INFO `
                -Type "group_complete" -Data @{
                    group     = $result.Group
                    status    = $result.Status
                    succeeded = $result.Succeeded
                    failed    = $result.Failed
                }
        }
    }

    # Final summary
    Write-vSEALLog -Message "Bloatware removal complete: $totalRemoved packages removed, $totalFailed failed, $totalSkipped patterns skipped" -Level INFO `
        -Type "plugin_complete" -Data @{
            plugin  = 'bloatware'
            removed = $totalRemoved
            failed  = $totalFailed
            skipped = $totalSkipped
        }

    # Report aggregated errors if any
    if ($allErrors.Count -gt 0) {
        Write-vSEALLog -Message "Package removal errors: $($allErrors.Count) failures" -Level WARNING
        foreach ($err in $allErrors) {
            Write-vSEALLog -Message "  $($err.Package): $($err.Error)" -Level WARNING
        }
    }
}

# NOTE: No Invoke-Engage function - AppX removal is Seal-only and not reversible

#endregion

# =============================================================================
# END OF BLOATWARE PLUGIN
# =============================================================================
# This hybrid plugin combines:
#   - TOML template: Package patterns and declarative actions (data)
#   - PowerShell logic: Single-query pattern matching (efficiency)
#
# Groups 1-4 (AppX): Processed via custom Invoke-Seal with O(1) query
# Groups 5-6 (Registry/Service): Delegated to Invoke-vSEALActionGroup
#
# To modify package lists: Edit bloatware.toml
# To modify execution logic: Edit this file
# =============================================================================
