param(
    [Parameter(Mandatory=$true)][string]$Reference,
    [Parameter(Mandatory=$true)][string]$Candidate,
    [Parameter(Mandatory=$true)][string]$OldReader,
    [Parameter(Mandatory=$true)][string]$Output
)
$ErrorActionPreference='Stop'
. (Join-Path $PSScriptRoot 'CPUReportIO.ps1')
if(-not[IO.Path]::IsPathRooted($Output) -or (Test-Path -LiteralPath $Output)){throw 'Use a new absolute output directory'}
New-Item -ItemType Directory -Path $Output | Out-Null
function Read([string]$Path){Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json}
function Assert([bool]$Condition,[string]$Message){if(-not$Condition){throw $Message}}
function Hash([string]$Path){(Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash}
function Same([string]$Relative){
    Assert ((Hash (Join-Path $Reference $Relative)) -ceq (Hash (Join-Path $Candidate $Relative))) "Tone contract/component/fixture changed: $Relative"
}
$s=Read (Join-Path $Reference 'run.json');$c=Read (Join-Path $Candidate 'run.json');$old=Read (Join-Path $OldReader 'run.json')
foreach($run in @($s,$c,$old)){
    Assert ('failures' -in $run.PSObject.Properties.Name -and $run.failures -eq 0) 'Incomplete or failed ToneAudit run'
    Assert ($run.configuration -in @('Debug','Release') -and $run.configuration -eq $s.configuration) 'Cross-configuration tone comparison is not acceptance'
}
foreach($relative in @('contract.json','envelopes.json','oscillators.json','filters.json','gain-chain.json','cases.json')){Same $relative}
$contract=Read (Join-Path $Reference 'contract.json')
Assert ($contract.manifest.Count -eq 62 -and $contract.factories.Count -eq 4 -and $contract.seeded10000Actions.Count -eq 10000) 'Incomplete tone contract coverage'
for($i=0;$i -lt 62;$i++){
    Assert ($contract.manifest[$i].index -eq $i -and $contract.manifest[$i].grid.Count -eq 101) 'Incomplete frozen host manifest/grid'
}
$prefixes=@(2,6,16,24,29,32,40,44,62)
foreach($prefix in $prefixes){
    foreach($extension in @('state','json')){Same "states/prefix-$prefix.$extension"}
}
foreach($direction in @(@{root=$Candidate;input=$Reference},@{root=$OldReader;input=$Candidate})){
    $cross=Read (Join-Path $direction.root 'cross-state.json')
    Assert ($cross.fixtures -eq 9 -and $cross.failures -eq 0 -and
        [IO.Path]::GetFullPath($cross.input) -eq [IO.Path]::GetFullPath($direction.input)) 'Missing actual bidirectional old/new state-reader evidence'
}
if(-not('DeepPluckStrictRawNull' -as [type])){Add-Type -TypeDefinition @'
using System;
using System.IO;
public static class DeepPluckStrictRawNull {
    public static long Compare(string reference, string candidate) {
        byte[] a=File.ReadAllBytes(reference), b=File.ReadAllBytes(candidate);
        if(a.Length==0 || a.Length%8!=0 || a.Length!=b.Length)
            throw new InvalidOperationException("Raw stereo length/channel mismatch");
        for(int i=0;i<a.Length;i+=4) {
            if((BitConverter.ToUInt32(a,i)&0x7f800000u)==0x7f800000u ||
               (BitConverter.ToUInt32(b,i)&0x7f800000u)==0x7f800000u)
                throw new InvalidOperationException("Nonfinite raw sample");
            for(int j=0;j<4;j++) if(a[i+j]!=b[i+j])
                throw new InvalidOperationException("Non-bitidentical tone corpus; no FP-reordering exception is enabled");
        }
        return a.LongLength/8;
    }
}
'@}
$cases=Read (Join-Path $Reference 'cases.json')
Assert ($cases.Count -eq 711 -and @($cases | Where-Object {$_.metrics}).Count -eq 669) 'Incomplete 669+42 tone corpus'
foreach($directory in @($Reference,$Candidate)){
    Assert (@(Get-ChildItem -LiteralPath (Join-Path $directory 'raw') -Filter '*.f32' -File).Count -eq 711) 'Missing or unexpected raw tone file'
}
$ids=@{};$rows=[Collections.Generic.List[object]]::new()
foreach($case in $cases){
    Assert ($case.id -cmatch '^[A-Za-z0-9.\-]+$' -and -not$ids.ContainsKey($case.id)) 'Invalid/duplicate tone case ID'
    $ids[$case.id]=$true
    $a=Join-Path $Reference "raw/$($case.id).f32";$b=Join-Path $Candidate "raw/$($case.id).f32"
    $frames=[DeepPluckStrictRawNull]::Compare($a,$b)
    # Every raw sample/byte was compared above, not merely a checksum or peak.
    # Exact finite identity proves zero L/R/M/S error in every full/partial window.
    $rows.Add([pscustomobject]@{id=$case.id;frames=$frames;windows1024=[math]::Ceiling($frames/1024.0);
        referenceSha256=(Hash $a);candidateSha256=(Hash $b);bitIdentical=$true;
        maximumSampleError=0;fullAndWindowDifferenceRms=0;relativeWindowError=0;peakDifference=0})
}
Export-CPUCsv $rows (Join-Path $Output 'tone-null.csv')
[ordered]@{configuration=$s.configuration;referenceVersion=$s.version;candidateVersion=$c.version;oldReaderVersion=$old.version;
    syntheticUnitFixture=[bool]($s.syntheticUnitFixture -or $c.syntheticUnitFixture -or $old.syntheticUnitFixture);
    reference=[IO.Path]::GetFullPath($Reference);candidate=[IO.Path]::GetFullPath($Candidate);oldReader=[IO.Path]::GetFullPath($OldReader);
    corpusCases=711;fullProcessorCases=669;partitionCases=42;frozenParameters=62;mappingTextGridPoints=101;
    factorySnapshots=4;seededActions=10000;bidirectionalPrefixes=$prefixes;allSamplesFiniteAndBitIdentical=$true;
    allComponentContractAndFixtureFilesIdentical=$true;comparedFrames=($rows.frames | Measure-Object -Sum).Sum;
    fpReorderingException=$false;completedUtc=[DateTime]::UtcNow.ToString('o');fullMilestonePass=$false} |
    ConvertTo-Json -Depth 10 | Set-Content -LiteralPath (Join-Path $Output 'tone-null.json') -Encoding utf8
Write-Host 'PASS: strict 669+42 tone null, components, frozen contract, actual bidirectional state readers'
