param(
  [Parameter(Mandatory = $true)][string]$Root,
  [Parameter(Mandatory = $true)][string]$Cli,
  [string]$Output = "build/test-results/media-corpus-report.json"
)

$ErrorActionPreference = 'Stop'
$extensions = @('.mkv', '.mp4', '.m4v', '.mov', '.avi', '.ts', '.m2ts', '.mpg', '.mpeg', '.vob', '.webm')
$rootPath = (Resolve-Path -LiteralPath $Root).Path
$cliPath = (Resolve-Path -LiteralPath $Cli).Path
$outputPath = [IO.Path]::GetFullPath((Join-Path (Get-Location) $Output))
$outputDirectory = Split-Path -Parent $outputPath
[IO.Directory]::CreateDirectory($outputDirectory) | Out-Null

function Invoke-JsonCommand {
  param([string[]]$Arguments)
  $quoted = @($Arguments | ForEach-Object { '"' + $_.Replace('"', '\"') + '"' }) -join ' '
  $startInfo = [Diagnostics.ProcessStartInfo]::new()
  $startInfo.FileName = $cliPath
  $startInfo.Arguments = $quoted
  $startInfo.UseShellExecute = $false
  $startInfo.CreateNoWindow = $true
  $startInfo.RedirectStandardOutput = $true
  $startInfo.RedirectStandardError = $true
  $process = [Diagnostics.Process]::new()
  $process.StartInfo = $startInfo
  [void]$process.Start()
  $stdoutTask = $process.StandardOutput.ReadToEndAsync()
  $stderrTask = $process.StandardError.ReadToEndAsync()
  $timeout = if ($Arguments[0] -eq 'export-range-mkv') { 90000 } elseif ($Arguments[0] -eq 'probe') { 10000 } else { 30000 }
  if (-not $process.WaitForExit($timeout)) {
    try { $process.Kill() } catch { }
    $process.WaitForExit()
    return [pscustomobject]@{ exit_code = 124; document = $null; output = "Timed out after $timeout ms" }
  }
  $lines = @(($stdoutTask.Result -split "`r?`n") | Where-Object { $_ })
  $errorText = $stderrTask.Result
  $exitCode = $process.ExitCode
  $jsonLine = $lines | Where-Object { $_.TrimStart().StartsWith('{') -and $_.TrimEnd().EndsWith('}') } | Select-Object -Last 1
  $document = $null
  if ($jsonLine) {
    try { $document = $jsonLine | ConvertFrom-Json } catch { }
  }
  [pscustomobject]@{ exit_code = $exitCode; document = $document; output = (($lines + $errorText) -join "`n") }
}

$files = @(Get-ChildItem -LiteralPath $rootPath -Recurse -File -ErrorAction SilentlyContinue |
  Where-Object { $extensions -contains $_.Extension.ToLowerInvariant() } | Sort-Object FullName)
$inventory = [Collections.Generic.List[object]]::new()
$probeFailures = [Collections.Generic.List[object]]::new()
$position = 0
foreach ($file in $files) {
  ++$position
  if (($position % 20) -eq 0 -or $position -eq $files.Count) { Write-Host "Probed $position/$($files.Count) media files" }
  $probe = Invoke-JsonCommand @('probe', '--input', $file.FullName)
  if ($probe.exit_code -ne 0 -or -not $probe.document -or $probe.document.schema -ne 'probe-v1') {
    $probeFailures.Add([pscustomobject]@{ path = $file.FullName; detail = $probe.output })
    continue
  }
  $video = @($probe.document.streams | Where-Object type -eq 0 | Select-Object -First 1)
  $audio = @($probe.document.streams | Where-Object type -eq 1)
  $subtitles = @($probe.document.streams | Where-Object type -eq 3)
  if (-not $video.Count) {
    $probeFailures.Add([pscustomobject]@{ path = $file.FullName; detail = 'No video stream found' })
    continue
  }
  $videoSignature = "$($video[0].codec)/p$($video[0].profile)/$($video[0].pixel_format)"
  $audioSignature = (@($audio | ForEach-Object { "$($_.codec)/$($_.sample_rate)/$($_.channel_layout)" } | Sort-Object -Unique) -join '+')
  $signature = "$($probe.document.format)|$videoSignature|$audioSignature"
  $inventory.Add([pscustomobject]@{
    path = $file.FullName; bytes = $file.Length; extension = $file.Extension.ToLowerInvariant(); signature = $signature
    format = $probe.document.format; video_codec = if ($video.Count) { $video[0].codec } else { '' }
    video_profile = if ($video.Count) { $video[0].profile } else { -1 }; pixel_format = if ($video.Count) { $video[0].pixel_format } else { '' }
    video_signature = "$($probe.document.format)|$videoSignature"
    audio_signatures = @($audio | ForEach-Object { "$($_.codec)/$($_.sample_rate)/$($_.channel_layout)" } | Sort-Object -Unique)
    audio_codecs = @($audio | ForEach-Object codec | Sort-Object -Unique); audio_layouts = @($audio | ForEach-Object channel_layout | Sort-Object -Unique)
    streams = $probe.document.stream_count; subtitles = @($subtitles).Count
  })
}

$formatGroups = @($inventory | Group-Object signature)
$videoRepresentatives = @($inventory | Group-Object video_signature | ForEach-Object { $_.Group | Sort-Object bytes | Select-Object -First 1 } | Sort-Object video_signature)
$audioCandidates = @($inventory | ForEach-Object { $item = $_; $_.audio_signatures | ForEach-Object { [pscustomobject]@{ signature = $_; item = $item } } })
$audioRepresentatives = @($audioCandidates | Group-Object signature | ForEach-Object { ($_.Group | Sort-Object { $_.item.bytes } | Select-Object -First 1) } | Sort-Object signature)
$tests = [Collections.Generic.List[object]]::new()
$testNumber = 0
foreach ($item in $videoRepresentatives) {
  ++$testNumber
  Write-Host "Testing video $testNumber/$($videoRepresentatives.Count): $($item.video_signature)"
  $preview = Invoke-JsonCommand @('preview', '--input', $item.path, '--frame', '0', '--bgra')
  $sampleOutput = Join-Path $outputDirectory ("corpus-sample-{0:D3}.mkv" -f $testNumber)
  Remove-Item -LiteralPath $sampleOutput -ErrorAction SilentlyContinue
  $export = Invoke-JsonCommand @('export-range-mkv', '--input', $item.path, '--output', $sampleOutput, '--first-frame', '1', '--end-frame', '2')
  $outputProbe = if ($export.exit_code -eq 0 -and (Test-Path -LiteralPath $sampleOutput)) { Invoke-JsonCommand @('probe', '--input', $sampleOutput) } else { $null }
  $formatPreserved = $false
  if ($outputProbe -and $outputProbe.document) {
    $sourceVideo = @((Invoke-JsonCommand @('probe', '--input', $item.path)).document.streams | Where-Object type -eq 0 | Select-Object -First 1)
    $outputVideo = @($outputProbe.document.streams | Where-Object type -eq 0 | Select-Object -First 1)
    $formatPreserved = $sourceVideo.Count -and $outputVideo.Count -and $sourceVideo[0].codec -eq $outputVideo[0].codec -and
      $sourceVideo[0].pixel_format -eq $outputVideo[0].pixel_format -and $item.streams -eq $outputProbe.document.stream_count
  }
  Remove-Item -LiteralPath $sampleOutput -ErrorAction SilentlyContinue
  $passed = $preview.exit_code -eq 0 -and $export.exit_code -eq 0 -and $formatPreserved
  $tests.Add([pscustomobject]@{
    kind = 'video'; signature = $item.video_signature; representative = $item.path; status = if ($passed) { 'PASSED' } else { 'FAILED' }
    preview = if ($preview.exit_code -eq 0) { 'PASSED' } else { 'FAILED' }
    smart_export = if ($export.exit_code -eq 0) { 'PASSED' } else { 'FAILED' }
    format_preserved = $formatPreserved
    preview_detail = if ($preview.exit_code -eq 0) { '' } else { $preview.output }
    export_detail = $export.output
  })
}
foreach ($candidate in $audioRepresentatives) {
  $item = $candidate.item
  Write-Host "Testing audio $($candidate.signature)"
  $audio = Invoke-JsonCommand @('audio-pcm', '--input', $item.path, '--max-frames', '48000')
  $tests.Add([pscustomobject]@{
    kind = 'audio'; signature = $candidate.signature; representative = $item.path
    status = if ($audio.exit_code -eq 0) { 'PASSED' } else { 'FAILED' }
    audio_decode = if ($audio.exit_code -eq 0) { 'PASSED' } else { 'FAILED' }
    audio_detail = if ($audio.exit_code -eq 0) { $audio.output } else { $audio.output }
  })
}

$failedTests = @($tests | Where-Object status -eq 'FAILED')
$report = [ordered]@{
  schema = 'media-corpus-report-v1'; root = $rootPath; generated_utc = [DateTime]::UtcNow.ToString('o')
  files_discovered = $files.Count; files_probed = $inventory.Count; probe_failures = $probeFailures.Count
  format_signatures = $formatGroups.Count; video_formats_tested = $videoRepresentatives.Count; audio_formats_tested = $audioRepresentatives.Count
  representative_tests = $tests.Count; failed_representative_tests = $failedTests.Count
  inventory_summary = @($formatGroups | Sort-Object Count -Descending | ForEach-Object {
    [pscustomobject]@{ signature = $_.Name; files = $_.Count; representative = ($_.Group | Sort-Object bytes | Select-Object -First 1).path }
  })
  probe_failure_details = @($probeFailures); tests = @($tests)
}
[IO.File]::WriteAllText($outputPath, ($report | ConvertTo-Json -Depth 20), [Text.UTF8Encoding]::new($false))
Write-Host "Corpus report: $outputPath"
Write-Host "Files $($files.Count), video formats $($videoRepresentatives.Count), audio formats $($audioRepresentatives.Count), failed probes $($probeFailures.Count), failed representative tests $($failedTests.Count)"
if ($probeFailures.Count -or $failedTests.Count) { exit 5 }
