param(
    [Parameter(Mandatory=$true)][string]$CandidateRoot,
    [Parameter(Mandatory=$true)][string]$OutputRoot,
    [Parameter(Mandatory=$true)][string]$Executable,
    [string]$ReferenceExecutable='',
    [ValidateSet('candidates','reference')][string]$Mode='candidates'
)
$ErrorActionPreference='Stop'
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
$root=[IO.Path]::GetFullPath($OutputRoot)
if(-not $root.StartsWith((Join-Path $repo 'Artifacts\V0.12.0')+'\',[StringComparison]::OrdinalIgnoreCase)){throw 'Evidence root must remain project-local'}
if(Test-Path -LiteralPath $root){throw 'Preserve previous evidence; use a new output root'}
$manifestPath=Join-Path $CandidateRoot 'candidate-manifest.json'
$manifest=Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json
if($manifest.format -ne 'DeepPluckCandidateRun' -or -not $manifest.complete -or $manifest.entries.Count -ne 160){throw 'Expected the initial complete 160-candidate run'}
$exe=(Resolve-Path -LiteralPath $Executable).Path
$binaryHash=(Get-FileHash -LiteralPath $exe).Hash
$reference=if($ReferenceExecutable){(Resolve-Path -LiteralPath $ReferenceExecutable).Path}else{''}
if($Mode -eq 'reference' -and -not $reference){throw 'Preserved reference executable required'}
New-Item -ItemType Directory -Path $root | Out-Null
function Write-Json($Value,[string]$Path){
    if(Test-Path -LiteralPath $Path){throw "Preserve existing evidence: $Path"}
    [IO.File]::WriteAllText($Path,($Value|ConvertTo-Json -Depth 25),[Text.UTF8Encoding]::new($false))
}
$selected=if($Mode -eq 'candidates'){@($manifest.entries)}else{@($manifest.entries|Where-Object{$_.index -eq 1})}
$plan=[ordered]@{mode=$Mode;candidateManifestSha256=(Get-FileHash -LiteralPath $manifestPath).Hash;executable=$exe;executableSha256=$binaryHash;
    reference=$reference;referenceSha256=$(if($reference){(Get-FileHash -LiteralPath $reference).Hash}else{''});
    sampleRate=48000;buffer=128;scene='short';selected=@($selected|Select-Object id,sha256,path);
    rawOutput='Stereo interleaved float32, no normalization, complete bounded tails; CPU metrics diagnostic only, not performance acceptance.'}
Write-Json $plan (Join-Path $root 'plan.json')
$rows=[Collections.Generic.List[object]]::new()
function Invoke-Audit([string]$Binary,[string]$Preset,[string]$Target,[string]$Fx){
    $arguments=@('--preset',$Preset,'--output',$Target,'--rate','48000','--buffer','128','--mode','audio','--scene','short','--fx',$Fx)
    $started=[DateTime]::UtcNow.ToString('o')
    $nativeText=@(& $Binary @arguments)
    $native=$LASTEXITCODE
    $nativeText | ForEach-Object {Write-Host $_}
    if(-not(Test-Path -LiteralPath $Target)){New-Item -ItemType Directory -Path $Target|Out-Null}
    Write-Json ([ordered]@{executable=$Binary;executableSha256=(Get-FileHash -LiteralPath $Binary).Hash;
        presetSha256=(Get-FileHash -LiteralPath $Preset).Hash;arguments=$arguments;nativeExit=$native;startedUtc=$started;completedUtc=[DateTime]::UtcNow.ToString('o')}) (Join-Path $Target 'invocation.json')
    if($native -notin @(0,2)){throw "Audio audit crashed or failed: $Target (exit $native)"}
    $data=Get-Content -LiteralPath (Join-Path $Target 'result.json') -Raw|ConvertFrom-Json
    if(($native -eq 0) -ne [bool]$data.contentPass){throw 'Native exit/content receipt mismatch'}
    return $data
}
foreach($entry in $selected){
    $preset=[IO.Path]::GetFullPath((Join-Path $CandidateRoot $entry.path))
    if(-not $preset.StartsWith([IO.Path]::GetFullPath($CandidateRoot)+'\',[StringComparison]::OrdinalIgnoreCase)){throw 'Candidate path escaped'}
    if((Get-FileHash -LiteralPath $preset).Hash -ine $entry.sha256){throw 'Candidate changed'}
    $fxModes=if($Mode -eq 'candidates'){@('wet')}else{@('dry','wet')}
    foreach($fx in $fxModes){
        $current=Join-Path $root ($entry.name+'-'+$fx+'-current')
        $data=Invoke-Audit $exe $preset $current $fx
        $equal=$null
        if($reference){
            $before=Join-Path $root ($entry.name+'-'+$fx+'-reference')
            $old=Invoke-Audit $reference $preset $before $fx
            $equal=((Get-FileHash -LiteralPath (Join-Path $before 'audio.f32')).Hash -ceq (Get-FileHash -LiteralPath (Join-Path $current 'audio.f32')).Hash)
            if(-not $equal -or ($data.appliedValues|ConvertTo-Json -Compress) -cne ($old.appliedValues|ConvertTo-Json -Compress)){throw "Audio or applied-value regression: $($entry.name)/$fx"}
        }
        $rows.Add([ordered]@{id=$entry.id;name=$entry.name;category=$entry.category;sha256=$entry.sha256;
            fx=$fx;technicalPass=[bool]$data.contentPass;peak=$data.peak;rms=$data.rms;activeRms=$data.activeRms;
            monoRms=$data.monoRms;frames=$data.frames;tailQuiet=$data.tailQuiet;rawAudioEqualsReference=$equal;result=$current})
    }
    Write-Host ("Audited {0}/{1}: {2}" -f ($rows.Count/$fxModes.Count),$selected.Count,$entry.name)
}
Write-Json ([ordered]@{mode=$Mode;count=$rows.Count;technicalFailures=@($rows|Where-Object{-not $_.technicalPass}).Count;results=@($rows.ToArray());manualApproval='OPEN'}) (Join-Path $root 'summary.json')
$lines=@('# Candidate listening list — technical screening only','','Keep is a shortlist choice, NOT Factory approval.','',
    '| Candidate | Category | Raw peak | Technical status |','|---|---|---:|---|')
foreach($row in $rows){$lines+='| '+$row.name+' | '+$row.category+' | '+([double]$row.peak).ToString('F6',[Globalization.CultureInfo]::InvariantCulture)+' | '+$(if($row.technicalPass){'Eligible for audition; unreviewed'}else{'EXCLUDED: technical review required'})+' |'}
[IO.File]::WriteAllText((Join-Path $root 'LISTENING_LIST.md'),($lines -join "`n"),[Text.UTF8Encoding]::new($false))
Write-Host "Completed $($rows.Count) audio checks. Manual approval remains OPEN."
