param(
    [string]$Root,
    [string[]]$Case=@(),
    [Parameter(Mandatory=$true)][string]$Output,
    [switch]$SelfTest
)
$ErrorActionPreference='Stop'
. (Join-Path $PSScriptRoot 'CPUReportIO.ps1')
if(-not[IO.Path]::IsPathRooted($Output) -or (Test-Path -LiteralPath $Output)){throw 'Use a new absolute listening-export directory'}
New-Item -ItemType Directory -Path $Output | Out-Null
if(-not('DeepPluckFloatWaveExport' -as [type])){Add-Type -TypeDefinition @'
using System;
using System.IO;
using System.Text;
public static class DeepPluckFloatWaveExport {
    public static void Write(string input, string output, int sampleRate, long frames) {
        byte[] raw=File.ReadAllBytes(input);
        if(sampleRate<=0 || sampleRate>384000 || frames<=0 || frames>uint.MaxValue ||
           raw.LongLength!=frames*8 || raw.LongLength>uint.MaxValue-50L)
            throw new InvalidOperationException("Invalid fixed-rate stereo float32 length/rate");
        for(int i=0;i<raw.Length;i+=4)
            if((BitConverter.ToUInt32(raw,i)&0x7f800000u)==0x7f800000u)
                throw new InvalidOperationException("Nonfinite source sample");
        using(var stream=new FileStream(output,FileMode.CreateNew,FileAccess.Write))
        using(var writer=new BinaryWriter(stream)) {
            writer.Write(Encoding.ASCII.GetBytes("RIFF")); writer.Write((uint)(50+raw.LongLength));
            writer.Write(Encoding.ASCII.GetBytes("WAVEfmt ")); writer.Write((uint)18);
            writer.Write((ushort)3); writer.Write((ushort)2); // IEEE float, stereo
            writer.Write((uint)sampleRate); writer.Write((uint)(sampleRate*8));
            writer.Write((ushort)8); writer.Write((ushort)32); writer.Write((ushort)0);
            writer.Write(Encoding.ASCII.GetBytes("fact")); writer.Write((uint)4); writer.Write((uint)frames);
            writer.Write(Encoding.ASCII.GetBytes("data")); writer.Write((uint)raw.Length); writer.Write(raw);
        }
        // Independent read-back verifies the container and each original byte.
        using(var stream=File.OpenRead(output))
        using(var reader=new BinaryReader(stream)) {
            if(stream.Length!=58+raw.LongLength || reader.ReadUInt32()!=0x46464952u ||
               reader.ReadUInt32()!=50+raw.LongLength || reader.ReadUInt32()!=0x45564157u ||
               reader.ReadUInt32()!=0x20746d66u || reader.ReadUInt32()!=18 ||
               reader.ReadUInt16()!=3 || reader.ReadUInt16()!=2 || reader.ReadUInt32()!=sampleRate ||
               reader.ReadUInt32()!=sampleRate*8 || reader.ReadUInt16()!=8 || reader.ReadUInt16()!=32 ||
               reader.ReadUInt16()!=0 || reader.ReadUInt32()!=0x74636166u || reader.ReadUInt32()!=4 ||
               reader.ReadUInt32()!=frames || reader.ReadUInt32()!=0x61746164u || reader.ReadUInt32()!=raw.Length)
                throw new InvalidOperationException("Wave container verification failed");
            for(int offset=0;offset<raw.Length;) {
                byte[] chunk=reader.ReadBytes(Math.Min(65536,raw.Length-offset));
                if(chunk.Length==0) throw new InvalidOperationException("Truncated wave data");
                for(int i=0;i<chunk.Length;i++) if(chunk[i]!=raw[offset+i])
                    throw new InvalidOperationException("Wave data differs from original raw bytes");
                offset+=chunk.Length;
            }
        }
    }
}
'@}
function Save($Value,[string]$Name){$Value | ConvertTo-Json -Depth 35 | Set-Content -LiteralPath (Join-Path $Output $Name) -Encoding utf8}
if($SelfTest){
    $raw=Join-Path $Output 'synthetic.f32';$wave=Join-Path $Output 'synthetic.wav'
    $values=[single[]]@(1.25,-1.125,.00001,0);$bytes=New-Object byte[] 16
    [Buffer]::BlockCopy($values,0,$bytes,0,16);[IO.File]::WriteAllBytes($raw,$bytes)
    [DeepPluckFloatWaveExport]::Write($raw,$wave,48000,2)
    $read=[IO.File]::ReadAllBytes($wave)
    if([BitConverter]::ToSingle($read,58) -ne 1.25 -or [BitConverter]::ToSingle($read,62) -ne -1.125){throw 'Over-full-scale values were changed'}
    foreach($kind in @('NaN','Infinity','truncated','wrong-length','wrong-rate','existing-output')){
        $bad=Join-Path $Output "$kind.f32";$destination=Join-Path $Output "$kind.wav"
        $copy=[byte[]]$bytes.Clone();$rate=48000;$frames=2
        switch($kind){
            'NaN' {[Buffer]::BlockCopy([BitConverter]::GetBytes([single]::NaN),0,$copy,0,4)}
            'Infinity' {[Buffer]::BlockCopy([BitConverter]::GetBytes([single]::PositiveInfinity),0,$copy,0,4)}
            'truncated' {$copy=[byte[]]$copy[0..14]}
            'wrong-length' {$frames=3}
            'wrong-rate' {$rate=0}
            'existing-output' {Copy-Item -LiteralPath $wave -Destination $destination}
        }
        [IO.File]::WriteAllBytes($bad,$copy);$failed=$false
        try{[DeepPluckFloatWaveExport]::Write($bad,$destination,$rate,$frames)}catch{$failed=$true}
        if(-not$failed){throw "Invalid wave input was accepted: $kind"}
        if($kind -eq 'existing-output'){
            if((Get-FileHash -LiteralPath $destination).Hash -ne (Get-FileHash -LiteralPath $wave).Hash){throw 'Existing wave was overwritten'}
        }elseif(Test-Path -LiteralPath $destination){throw 'Invalid input left a misleading wave file'}
    }
    Save @{syntheticUnitTests=$true;passed=$true;rawBytesPreserved=$true;fullMilestonePass=$false} 'unit-test-result.json'
    Write-Host 'PASS: synthetic float32 wave export/read-back and six negative checks (not listening or performance evidence)'
    exit 0
}
if(-not$Root -or $Case.Count -eq 0 -or @($Case | Sort-Object -Unique).Count -ne $Case.Count){throw 'Specify a real audio root and distinct case IDs'}
$manifest=Get-Content -LiteralPath (Join-Path $Root 'manifest.json') -Raw | ConvertFrom-Json
if($manifest.mode -ne 'audio' -or $manifest.configuration -notin @('Debug','Release') -or $manifest.syntheticUnitFixture -or
    $manifest.target_version -cnotmatch '^\d+\.\d+\.\d+$' -or
    $manifest.executable_sha256 -cnotmatch '^[a-fA-F0-9]{64}$' -or
    $manifest.provenance.source_sha256 -cnotmatch '^[a-fA-F0-9]{64}$' -or
    $manifest.metadata_sha256 -cnotmatch '^[a-fA-F0-9]{64}$'){throw 'Actual audio-render provenance required'}
$byId=@{};foreach($entry in $manifest.cases){$byId[$entry.id]=$entry}
$exports=@()
foreach($id in $Case){
    if($id -cnotmatch '^[A-Za-z0-9.\-]+$' -or -not$byId.ContainsKey($id)){throw "Unknown/invalid listening case: $id"}
    $entry=$byId[$id]
    if($entry.lifecycle_plan){throw 'Multi-rate lifecycle clips cannot be represented by one fixed-rate WAV without transformation; retain their raw evidence'}
    $raw=Join-Path $Root "$id-rep0.f32"
    if(-not(Test-Path -LiteralPath $raw)){throw 'Own raw render missing: rerender this binary without --reference; never substitute reference samples as candidate audio'}
    $diagnostic=Get-Content -LiteralPath (Join-Path $Root "$id-rep0.json") -Raw | ConvertFrom-Json
    $rawHash=(Get-FileHash -LiteralPath $raw).Hash
    if($rawHash -ine $diagnostic.audio_sha256 -or $diagnostic.frames -ne $entry.frames){throw 'Raw/diagnostic identity mismatch'}
    $wave=Join-Path $Output "$id.wav"
    [DeepPluckFloatWaveExport]::Write($raw,$wave,$entry.sample_rate,$entry.frames)
    $exports+=@{id=$id;rawPath=[IO.Path]::GetFullPath($raw);rawSha256=$rawHash;wavePath=$wave;
        waveSha256=(Get-FileHash -LiteralPath $wave).Hash;frames=$entry.frames;sampleRate=$entry.sample_rate;
        originalBytesPreserved=$true;gainAppliedDb=0;normalizationClippingResamplingOrDither=$false;
        inputSha256=$diagnostic.manifest_sha256;scenario=$entry}
}
Save @{schema='DeepPluck-CPU-Listening-Export-Rev1';sourceRoot=[IO.Path]::GetFullPath($Root);
    configuration=$manifest.configuration;targetVersion=$manifest.target_version;executableSha256=$manifest.executable_sha256;
    sourceSha256=$manifest.provenance.source_sha256;metadataSha256=$manifest.metadata_sha256;
    exports=$exports;manualListening='not performed';exportOnlyNotEquivalenceGate=$true;fullMilestonePass=$false} 'listening-export.json'
Write-Host "Exported $($exports.Count) byte-preserving float32 WAVs; no listening or equivalence approval"
