# capability-tests.ps1
#
# Static capability checks (PROJECT_PLAN.md "Static capability checks"):
# captures -version/-buildconf/-devices/-formats/-filters/-encoders/-decoders
# /-hwaccels/-bsfs/-protocols from a built ffmpeg.exe and cross-checks them against
# config/required-capabilities.json -- every 'required' entry must be present,
# every 'forbidden' entry must be absent.
#
# Not intended to be run standalone; called by test.ps1 with a shared
# $Paths/$FfmpegPath context. Returns a result object; does not exit the
# process, so test.ps1 can aggregate multiple test files into one report.

[CmdletBinding()]
param(
    [Parameter(Mandatory)][string]$FfmpegPath,
    [Parameter(Mandatory)][string]$OutDir
)

$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
Import-Module (Join-Path $PSScriptRoot '..\scripts\BuildCommon.psm1') -Force

function Invoke-Dump {
    # Param name deliberately avoids $Args -- that collides with PowerShell's
    # automatic $args variable and silently broke argument passing here
    # (every dump call ended up running ffmpeg.exe with no actual -X flag,
    # producing its "no output specified" usage error and exit code 1 for
    # all nine dumps uniformly -- confirmed against the target machine).
    param([string]$DumpArgs)
    $psi = New-Object System.Diagnostics.ProcessStartInfo
    $psi.FileName = $FfmpegPath; $psi.Arguments = "-hide_banner $DumpArgs"
    $psi.UseShellExecute = $false; $psi.RedirectStandardOutput = $true; $psi.RedirectStandardError = $true
    $psi.CreateNoWindow = $true
    $p = [System.Diagnostics.Process]::Start($psi)
    $out = $p.StandardOutput.ReadToEnd()
    $p.WaitForExit(15000) | Out-Null
    [ordered]@{ exitCode = $p.ExitCode; text = $out }
}

$reqCaps = Get-Content (Join-Path (Get-RepoRoot) 'config\required-capabilities.json') -Raw | ConvertFrom-Json

$dumps = [ordered]@{
    license    = '-L'
    version    = '-version'
    buildconf  = '-buildconf'
    devices    = '-devices'
    formats    = '-formats'
    filters    = '-filters'
    encoders   = '-encoders'
    decoders   = '-decoders'
    hwaccels   = '-hwaccels'
    bsfs       = '-bsfs'
    protocols  = '-protocols'
}

$results = [ordered]@{}
$dumpText = [ordered]@{}
foreach ($name in $dumps.Keys) {
    $r = Invoke-Dump $dumps[$name]
    $outFile = Join-Path $OutDir "ffmpeg-$name.txt"
    Set-Content -Path $outFile -Value $r.text -Encoding UTF8
    $results[$name] = [ordered]@{ exitCode = $r.exitCode; file = $outFile }
    $dumpText[$name] = $r.text
    Write-Host ("{0,-12} exit={1}" -f $name, $r.exitCode)
}
$staticOk = -not ($results.Values | Where-Object { $_.exitCode -ne 0 })

$licenseMarker = [string]$reqCaps.requiredLicenseMarker
$normalisedLicenseText = [regex]::Replace([string]$dumpText.license, '\s+', ' ').Trim()
$licenseOk = -not [string]::IsNullOrWhiteSpace($licenseMarker) -and
    $normalisedLicenseText.IndexOf($licenseMarker, [System.StringComparison]::OrdinalIgnoreCase) -ge 0

# Columnar dumps (devices/formats/decoders/encoders/filters): a flags token,
# then the exact name, then whitespace -- verified against the Gyan baseline
# capture's real output (baseline/gyan-capture/*.txt) during M0/M2 authoring.
function Test-ColumnarPresence { param([string]$Text, [string]$Name)
    $Text -match ('(?m)^\s*\S+\s+' + [regex]::Escape($Name) + '\s')
}
# Flat list dumps (bsfs; protocols' Input:/Output: sections): the name alone
# on its own line, possibly indented.
function Test-FlatPresence { param([string]$Text, [string]$Name)
    $Text -match ('(?m)^\s*' + [regex]::Escape($Name) + '\s*$')
}

# Every key in config/required-capabilities.json's "required" object must
# appear here, even if its style is 'skip' -- an absent key would silently
# skip verification of that whole category instead of documenting why.
$categoryDump = @{
    programs         = @{ dump = $null;       style = 'skip' }   # inherently true: this script IS running ffmpeg.exe
    inputDevices     = @{ dump = 'devices';   style = 'columnar' }
    demuxers         = @{ dump = 'formats';   style = 'columnar' }
    muxers           = @{ dump = 'formats';   style = 'columnar' }
    decoders         = @{ dump = 'decoders';  style = 'columnar' }
    hardwareAccelerators = @{ dump = 'hwaccels'; style = 'flat' }
    encoders         = @{ dump = 'encoders';  style = 'columnar' }
    parsers          = @{ dump = $null;       style = 'skip' }   # not exposed by any -X dump; covered by decode smoke test
    bitstreamFilters = @{ dump = 'bsfs';      style = 'flat' }
    protocols        = @{ dump = 'protocols'; style = 'flat' }
    filters          = @{ dump = 'filters';   style = 'columnar' }
    pixelFormats     = @{ dump = $null;       style = 'skip' }   # not gated by any configure flag; proven end-to-end by encode/decode smoke tests
    configureFlags   = @{ dump = $null;       style = 'skip' }   # checked separately below via the buildconf token set, not a columnar/flat dump
}

$missing = @()
$checked = 0
foreach ($cat in $reqCaps.required.PSObject.Properties.Name) {
    $mapping = $categoryDump[$cat]
    if (-not $mapping -or $mapping.style -eq 'skip') { continue }
    $text = $dumpText[$mapping.dump]
    foreach ($item in $reqCaps.required.$cat) {
        $checked++
        $present = if ($mapping.style -eq 'columnar') { Test-ColumnarPresence $text $item }
                   else { Test-FlatPresence $text $item }
        if (-not $present) { $missing += "$cat/$item" }
    }
}

# configureFlags: verified against the buildconf 'configuration:' section
# (Get-BuildconfTokens -- its header line has nothing after the colon,
# flags follow one per line, not -version's single-line format). Plain
# array + -contains rather than [HashSet[string]]::new(...): that generic
# constructor's overload resolution is unreliable in Windows PowerShell 5.1
# ("Multiple ambiguous overloads found for 'new'"), proven against the
# target machine.
$buildconfTokens = @(Get-BuildconfTokens -Text $dumpText.buildconf)
foreach ($flag in $reqCaps.required.configureFlags) {
    $checked++
    if ($buildconfTokens -notcontains $flag) { $missing += "configureFlags/$flag" }
}

# --- Forbidden ---
$forbiddenPresent = @()
foreach ($flag in $reqCaps.forbidden.configureFlags) {
    if ($buildconfTokens -contains $flag) { $forbiddenPresent += "configureFlags/$flag" }
}
foreach ($encoder in $reqCaps.forbidden.encoders) {
    if (Test-ColumnarPresence $dumpText.encoders $encoder) {
        $forbiddenPresent += "encoders/$encoder"
    }
}
$distDir = Split-Path -Parent $FfmpegPath
foreach ($prog in $reqCaps.forbidden.programs) {
    $progPath = Join-Path $distDir "$prog.exe"
    if (Test-Path $progPath) { $forbiddenPresent += "programs/$prog (found at $progPath)" }
}

$passed = ($staticOk -and $licenseOk -and $missing.Count -eq 0 -and $forbiddenPresent.Count -eq 0)

[ordered]@{
    name              = 'capability-tests'
    passed            = $passed
    staticDumpsOk     = $staticOk
    licenseMarker     = $licenseMarker
    licenseMarkerOk   = $licenseOk
    dumps             = $results
    requiredChecked   = $checked
    missingRequired   = $missing
    forbiddenPresent  = $forbiddenPresent
}
