#Requires -Version 7.4

function Test-vSEALPluginHealth {
    <#
    .SYNOPSIS
        Performs structural validation checks on plugin folders.

    .DESCRIPTION
        Scans plugin root directories for plugin folders and runs 9 validation
        checks per plugin. This function operates independently of
        Get-vSEALPluginRegistry -- it uses direct directory scanning so it can
        find and report broken plugins that the registry would skip.

        Checks performed:
        1. plugin.toml exists (ERROR)
        2. TOML parses without syntax errors (ERROR)
        3. Required fields present: name, version, category, phases, priority (ERROR)
        4. SemVer version valid (ERROR)
        5. Phases array non-empty (ERROR)
        6. plugin.ps1 syntax valid if present (ERROR)
        7. No orphaned Get-PluginMetadata function (WARN)
        8. Template .toml files parse if present (WARN)
        9. Phase values from allowed set (WARN)

        Each check has a dependency chain: TOML parse failure skips field checks
        but PS1 checks still run independently.

    .PARAMETER Path
        One or more plugin root directories to scan. Each root is expected to
        contain plugin subdirectories. Defaults to the three standard roots:
        built-in ($ModuleBase/Plugins), local ($ModuleBase/plugins-custom),
        and shared ($env:ProgramData/vSEAL/plugins). Non-existent roots are
        silently skipped.

    .PARAMETER PluginId
        Filter to a specific plugin by folder name. If specified and not found
        in any scanned root, a warning is written and nothing is returned.

    .OUTPUTS
        [PSCustomObject] Objects with PSTypeName 'vSEAL.PluginHealth' containing:
        - PluginId: Plugin folder name
        - Path: Full path to plugin folder
        - Status: Healthy, Warning, or Error
        - Issues: Array of issue objects (Level, Check, Message)

    .EXAMPLE
        Test-vSEALPluginHealth

        Checks all plugins across all discovered roots.

    .EXAMPLE
        Test-vSEALPluginHealth -Path ./src/vSEAL/Plugins

        Checks only the built-in plugins directory.

    .EXAMPLE
        Test-vSEALPluginHealth -PluginId 'example-defender'

        Checks a single plugin by folder name.

    .EXAMPLE
        Test-vSEALPluginHealth | Where-Object Status -ne 'Healthy'

        Returns only plugins with issues.

    .EXAMPLE
        Test-vSEALPluginHealth | ConvertTo-Json -Depth 5

        Serializes health results to JSON for CLI consumption.
    #>
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter()]
        [string[]]$Path,

        [Parameter()]
        [string]$PluginId
    )

    # Resolve plugin roots if not specified
    if (-not $Path) {
        $builtInRoot = Join-Path -Path $PSScriptRoot -ChildPath '..\Plugins'
        $localRoot = Join-Path -Path $PSScriptRoot -ChildPath '..\plugins-custom'
        $sharedRoot = Join-Path -Path $env:ProgramData -ChildPath 'vSEAL\plugins'
        $Path = @($builtInRoot, $localRoot, $sharedRoot)
    }

    # Collect all plugin folders from all roots
    $pluginFolders = @()
    foreach ($root in $Path) {
        if (Test-Path -Path $root -PathType Container) {
            $folders = Get-ChildItem -Path $root -Directory
            foreach ($folder in $folders) {
                $pluginFolders += $folder
            }
        }
    }

    # Filter by PluginId if specified
    if ($PluginId) {
        $pluginFolders = @($pluginFolders | Where-Object { $_.Name -eq $PluginId })
        if ($pluginFolders.Count -eq 0) {
            Write-Warning "Plugin '$PluginId' not found in any scanned root."
            return
        }
    }

    $validPhases = @('seal', 'engage')

    foreach ($folder in $pluginFolders) {
        # Wrap entire per-plugin processing in try/catch
        try {
            $issues = [System.Collections.Generic.List[PSCustomObject]]::new()
            $tomlPath = Join-Path -Path $folder.FullName -ChildPath 'plugin.toml'
            $ps1Path = Join-Path -Path $folder.FullName -ChildPath 'plugin.ps1'

            # Track dependency chain gates
            $tomlExists = $false
            $tomlParsed = $false
            $toml = $null
            $requiredFieldsPresent = $false
            $ps1ParsedOk = $false

            # -- Check 1: toml-exists (ERROR, independent) --
            if (Test-Path -Path $tomlPath -PathType Leaf) {
                $tomlExists = $true
            }
            else {
                $issues.Add([PSCustomObject]@{
                    Level   = 'ERROR'
                    Check   = 'toml-exists'
                    Message = 'Missing plugin.toml'
                })
            }

            # -- Check 2: toml-parse (ERROR, depends on 1) --
            if ($tomlExists) {
                try {
                    $tomlContent = Get-Content -Path $tomlPath -Raw
                    $toml = $tomlContent | ConvertFrom-Toml -ErrorAction Stop
                    $tomlParsed = $true
                }
                catch {
                    $issues.Add([PSCustomObject]@{
                        Level   = 'ERROR'
                        Check   = 'toml-parse'
                        Message = "TOML parse error: $($_.Exception.Message)"
                    })
                }
            }

            # -- Check 3: required-fields (ERROR, depends on 2) --
            if ($tomlParsed) {
                $requiredKeys = @('name', 'version', 'category', 'phases', 'priority')
                $missingFields = @()

                if (-not $toml.Contains('plugin')) {
                    $missingFields = $requiredKeys
                }
                else {
                    foreach ($key in $requiredKeys) {
                        if (-not $toml.plugin.Contains($key)) {
                            $missingFields += $key
                        }
                    }
                }

                if ($missingFields.Count -gt 0) {
                    $issues.Add([PSCustomObject]@{
                        Level   = 'ERROR'
                        Check   = 'required-fields'
                        Message = "Missing required fields: $($missingFields -join ', ')"
                    })
                }
                else {
                    $requiredFieldsPresent = $true
                }
            }

            # -- Check 4: semver-valid (ERROR, depends on 3) --
            if ($requiredFieldsPresent) {
                try {
                    $null = [System.Management.Automation.SemanticVersion]::new($toml.plugin.version)
                }
                catch {
                    $issues.Add([PSCustomObject]@{
                        Level   = 'ERROR'
                        Check   = 'semver-valid'
                        Message = "Invalid SemVer version: '$($toml.plugin.version)'"
                    })
                }
            }

            # -- Check 5: phases-nonempty (ERROR, depends on 3) --
            if ($requiredFieldsPresent) {
                $phases = $toml.plugin.phases
                if ($null -eq $phases -or -not ($phases -is [System.Collections.IEnumerable] -and $phases -isnot [string]) -or @($phases).Count -eq 0) {
                    $issues.Add([PSCustomObject]@{
                        Level   = 'ERROR'
                        Check   = 'phases-nonempty'
                        Message = 'Phases must be a non-empty array'
                    })
                }
            }

            # -- Check 6: ps1-syntax (ERROR, independent) --
            if (Test-Path -Path $ps1Path -PathType Leaf) {
                $tokens = $null
                $parseErrors = $null
                $ast = [System.Management.Automation.Language.Parser]::ParseFile(
                    $ps1Path,
                    [ref]$tokens,
                    [ref]$parseErrors
                )

                if ($parseErrors.Count -gt 0) {
                    foreach ($parseError in $parseErrors) {
                        $issues.Add([PSCustomObject]@{
                            Level   = 'ERROR'
                            Check   = 'ps1-syntax'
                            Message = "Syntax error at line $($parseError.Extent.StartLineNumber): $($parseError.Message)"
                        })
                    }
                }
                else {
                    $ps1ParsedOk = $true
                }

                # -- Check 7: orphaned-metadata (WARN, depends on 6) --
                if ($ps1ParsedOk) {
                    $metadataFunctions = $ast.FindAll(
                        { param($node) $node -is [System.Management.Automation.Language.FunctionDefinitionAst] -and $node.Name -eq 'Get-PluginMetadata' },
                        $true
                    )
                    if ($metadataFunctions.Count -gt 0) {
                        $issues.Add([PSCustomObject]@{
                            Level   = 'WARN'
                            Check   = 'orphaned-metadata'
                            Message = 'Contains unused Get-PluginMetadata function - remove it'
                        })
                    }
                }
            }

            # -- Check 8: template-parse (WARN, independent) --
            $templateTomls = Get-ChildItem -Path $folder.FullName -Filter '*.toml' -File |
                Where-Object { $_.Name -ne 'plugin.toml' }

            foreach ($templateToml in $templateTomls) {
                try {
                    $templateContent = Get-Content -Path $templateToml.FullName -Raw
                    $null = $templateContent | ConvertFrom-Toml -ErrorAction Stop
                }
                catch {
                    $issues.Add([PSCustomObject]@{
                        Level   = 'WARN'
                        Check   = 'template-parse'
                        Message = "Template '$($templateToml.Name)' has TOML parse error: $($_.Exception.Message)"
                    })
                }
            }

            # -- Check 9: phase-values (WARN, depends on 3) --
            if ($requiredFieldsPresent) {
                $phases = @($toml.plugin.phases)
                $invalidPhases = @($phases | Where-Object { $_ -notin $validPhases })
                if ($invalidPhases.Count -gt 0) {
                    $issues.Add([PSCustomObject]@{
                        Level   = 'WARN'
                        Check   = 'phase-values'
                        Message = "Unknown phase values: $($invalidPhases -join ', ')"
                    })
                }
            }

            # -- Derive status --
            $status = 'Healthy'
            if ($issues | Where-Object { $_.Level -eq 'ERROR' }) {
                $status = 'Error'
            }
            elseif ($issues | Where-Object { $_.Level -eq 'WARN' }) {
                $status = 'Warning'
            }

            # -- Emit result --
            [PSCustomObject]@{
                PSTypeName = 'vSEAL.PluginHealth'
                PluginId   = $folder.Name
                Path       = $folder.FullName
                Status     = $status
                Issues     = @($issues)
            }
        }
        catch {
            # Catch-all: prevent one plugin from crashing the entire scan
            [PSCustomObject]@{
                PSTypeName = 'vSEAL.PluginHealth'
                PluginId   = $folder.Name
                Path       = $folder.FullName
                Status     = 'Error'
                Issues     = @(
                    [PSCustomObject]@{
                        Level   = 'ERROR'
                        Check   = 'internal-error'
                        Message = "Unexpected error during validation: $($_.Exception.Message)"
                    }
                )
            }
        }
    }
}
