param(
    [Parameter(Mandatory=$true)][ValidateSet('run','merge','paired','compare')][string]$Action,
    [string]$Executable,
    [string]$CandidateExecutable,
    [string]$Metadata,
    [string]$CandidateMetadata,
    [Parameter(Mandatory=$true)][string]$Output,
    [ValidateSet('representative','matrix','reference','transitions','quick','all')][string]$Suite='representative',
    [ValidateSet('audio','timing','profile')][string]$Mode='audio',
    [string]$Case='',
    [string[]]$Cases=@(),
    [int]$Repeat=5,
    [int]$ShardIndex=0,
    [int]$ShardCount=1,
    [string]$Reference='',
    [string[]]$Inputs=@(),
    [string]$Baseline='',
    [string]$Candidate=''
)
# All performance work is serial. No process affinity/power-plan/priority changes,
# no forced closing of unrelated programs, and no simultaneous builds or tests.
$ErrorActionPreference='Stop'
. (Join-Path $PSScriptRoot 'CPUReportIO.ps1')
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
if(-not[IO.Path]::IsPathRooted($Output)){throw 'Output must be absolute'}
$Output=[IO.Path]::GetFullPath($Output)
$artifactRoot=[IO.Path]::GetFullPath((Join-Path $repo 'Artifacts'))+[IO.Path]::DirectorySeparatorChar
if(-not$Output.StartsWith($artifactRoot,[StringComparison]::OrdinalIgnoreCase)){throw 'Evidence must be in a new project-local Artifacts subdirectory'}
if(Test-Path -LiteralPath $Output){throw 'Output exists; evidence is append-only'}
function Read-Json([string]$Path){
    if(-not(Test-Path -LiteralPath $Path -PathType Leaf)){throw "Missing evidence: $Path"}
    Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json
}
function Write-Json($Value,[string]$Path){
    if(Test-Path -LiteralPath $Path){throw "Refusing to overwrite $Path"}
    $Value | ConvertTo-Json -Depth 40 | Set-Content -LiteralPath $Path -Encoding utf8
}
function Invoke-Run([string]$Exe,[string]$Meta,[string]$Destination,[int]$Count,[string]$SelectedCase,[string]$Ref){
    $args=@('--suite',$Suite,'--mode',$Mode,'--repeat',"$Count",'--output',$Destination,'--metadata',$Meta,
        '--shard-index',"$ShardIndex",'--shard-count',"$ShardCount")
    if($SelectedCase){$args+=@('--case',$SelectedCase)}
    if($Ref){$args+=@('--reference',$Ref)}
    $started=[DateTime]::UtcNow.ToString('o')
    & $Exe @args
    $nativeExitCode=$LASTEXITCODE
    # Save the actual native exit while the invoking shell still owns it, not a
    # later inference from results.json or an already-reaped background PID.
    if(-not(Test-Path -LiteralPath $Destination)){New-Item -ItemType Directory -Path $Destination | Out-Null}
    Write-Json ([ordered]@{startedUtc=$started;completedUtc=[DateTime]::UtcNow.ToString('o');
        executable=[IO.Path]::GetFullPath($Exe);arguments=$args;nativeExitCode=$nativeExitCode;
        fullMilestonePass=$false}) (Join-Path $Destination 'invocation.json')
    if($nativeExitCode -ne 0){throw "Benchmark failed: $Destination"}
}
function Assert-Identity($A,$B){
    foreach($run in @($A,$B)){
        if($run.manifest_version -ne 'DeepPluck-CPU-0.11.5-Rev1' -or $run.configuration -notin @('Debug','Release') -or
            $run.provenance.environment_sha256 -notmatch '^[0-9A-Fa-f]{64}$' -or
            $run.provenance.source_sha256 -notmatch '^[0-9A-Fa-f]{64}$' -or
            $run.executable_sha256 -notmatch '^[0-9A-Fa-f]{64}$' -or
            $run.case_count -le 0 -or $run.case_count -ne $run.cases.Count){throw 'Invalid run metadata'}
    }
    if($A.manifest_version -ne $B.manifest_version -or $A.configuration -ne $B.configuration -or
       (Get-CPUComparisonEnvironmentHash $A.provenance) -cne (Get-CPUComparisonEnvironmentHash $B.provenance)){throw 'Manifest/configuration/environment identity mismatch'}
}
function Check-Hard-Gates($Run){
    $failures=@()
    if($Run.mode -ne 'timing' -or $Run.configuration -ne 'Release'){throw 'Performance acceptance requires normal Release timing'}
    foreach($result in $Run.results){
        if(-not$result.total.valid -or $result.total.blocks -le 0){throw 'Missing valid whole-case timing'}
        $stats=@($result.total)+@($result.phases)
        foreach($phase in $stats){
            if(-not$phase.valid){continue}
            foreach($name in @('rtf','callback_seconds','audio_seconds','max_block_seconds','max_block_ratio')){
                $value=[double]$phase.$name
                if([double]::IsNaN($value) -or [double]::IsInfinity($value) -or $value -le 0){throw "Invalid timing metric: $name"}
            }
            if($phase.blocks -le 0 -or $phase.deadline_misses -lt 0 -or [int]$phase.deadline_misses -ne $phase.deadline_misses){throw 'Invalid block counters'}
            if($phase.rtf -ge 1 -or $phase.max_block_ratio -ge 1 -or $phase.deadline_misses -gt 0 -or
               ($result.id -like 'D-*' -and $phase.rtf -gt 0.9)){
                $failures+=[ordered]@{id=$result.id;repeat=$result.repeat;phase=$phase.name;rtf=$phase.rtf;maxBlockRatio=$phase.max_block_ratio;deadlineMisses=$phase.deadline_misses}
            }
        }
    }
    return $failures
}
if($Action -eq 'run'){
    Invoke-Run $Executable $Metadata $Output $Repeat $Case $Reference
    exit 0
}
New-Item -ItemType Directory -Path $Output | Out-Null
if($Action -eq 'merge'){
    if($Inputs.Count -eq 0){throw 'No shards supplied'}
    $runs=@($Inputs | ForEach-Object {Read-Json (Join-Path $_ 'results.json')})
    $first=$runs[0]; $expectedShards=[int]$first.shard_count
    if($runs.Count -ne $expectedShards){throw 'Missing shard directory'}
    $shards=@{};$ids=@{};$allResults=@();$allCases=@();$sourceById=@{}
    $inputIndex=0
    foreach($run in $runs){
        Assert-Identity $first $run
        if(-not$run.completed_requested_run -or $run.executable_sha256 -ne $first.executable_sha256 -or
           $run.provenance.source_sha256 -ne $first.provenance.source_sha256 -or
           $run.repeat_count -ne $first.repeat_count -or $run.mode -ne $first.mode -or
           $run.suite -ne $first.suite -or $run.shard_count -ne $expectedShards){throw 'Inconsistent/incomplete shard'}
        if($shards.ContainsKey([int]$run.shard_index)){throw 'Duplicate shard'}
        $shards[[int]$run.shard_index]=$true
        $byCase=@{}
        foreach($row in $run.results){
            if(-not$byCase.ContainsKey($row.id)){$byCase[$row.id]=[Collections.Generic.List[object]]::new()}
            $byCase[$row.id].Add($row)
        }
        foreach($entry in $run.cases){
            if($ids.ContainsKey($entry.id)){throw 'Duplicate case across shards'}
            $ids[$entry.id]=$true; $allCases+=$entry
            $sourceById[$entry.id]=$Inputs[$inputIndex]
            $repetitions=@($byCase[$entry.id])
            if($repetitions.Count -ne $first.repeat_count){throw "Missing repeats: $($entry.id)"}
            if(@($repetitions.repeat | Sort-Object -Unique).Count -ne $first.repeat_count){throw 'Duplicate repetition'}
            for($repeatIndex=0;$repeatIndex -lt $first.repeat_count;$repeatIndex++){
                if($repeatIndex -notin $repetitions.repeat){throw 'Missing/noncanonical repetition index'}
            }
        }
        if($run.results.Count -ne $run.cases.Count*$run.repeat_count){throw 'Unexpected extra results in shard'}
        $allResults+=@($run.results)
        $inputIndex++
    }
    for($i=0;$i -lt $expectedShards;$i++){if(-not$shards.ContainsKey($i)){throw 'Invalid shard indices'}}
    $inventoryPath=Join-Path $Output 'validated-inventory'
    & $Executable --suite $first.suite --list --output $inventoryPath
    if($LASTEXITCODE -ne 0){throw 'Cannot obtain frozen full inventory'}
    $inventory=Read-Json (Join-Path $inventoryPath 'manifest.json')
    if($inventory.executable_sha256 -ne $first.executable_sha256){throw 'Merger executable does not match the measured build'}
    $expectedIds=@($inventory.cases.id)
    if($ids.Count -ne $expectedIds.Count){throw 'Merged coverage is incomplete'}
    foreach($id in $expectedIds){if(-not$ids.ContainsKey($id)){throw "Missing case: $id"}}
    $caseById=@{};foreach($entry in $allCases){$caseById[$entry.id]=$entry}
    $first.cases=@($expectedIds | ForEach-Object {$caseById[$_]})
    $first.results=$allResults;$first.case_count=$allCases.Count
    $first.started_utc=($runs | Sort-Object {[DateTimeOffset]$_.started_utc} | Select-Object -First 1).started_utc
    $first.completed_utc=($runs | Sort-Object {[DateTimeOffset]$_.completed_utc} | Select-Object -Last 1).completed_utc
    $first.shard_count=1;$first.shard_index=0
    $first.selected_manifest_sha256=$inventory.selected_manifest_sha256
    $first | Add-Member -NotePropertyName mergedInputs -NotePropertyValue $Inputs -Force
    # Preserve the complete file-backed corpus for external null tests and phase
    # analysis. Same-volume hard links avoid duplicating gigabytes of immutable
    # raw evidence. Never modify a linked file after creating a merged run.
    foreach($row in $allResults){
        $prefix="$($row.id)-rep$($row.repeat)"
        foreach($extension in @('.json','.f32','.wav','.timings.f64')){
            $source=Join-Path $sourceById[$row.id] ($prefix+$extension)
            if(Test-Path -LiteralPath $source -PathType Leaf){
                $destination=Join-Path $Output ($prefix+$extension)
                New-Item -ItemType HardLink -Path $destination -Target ([IO.Path]::GetFullPath($source)) | Out-Null
            }elseif($extension -eq '.json' -or ($extension -eq '.timings.f64' -and $first.mode -eq 'timing')){
                throw "Missing shard evidence: $source"
            }
        }
    }
    $mergedManifest=[ordered]@{}
    foreach($property in $first.PSObject.Properties){
        if($property.Name -notin @('results','completed_requested_run','completed_utc')){$mergedManifest[$property.Name]=$property.Value}
    }
    Write-Json $mergedManifest (Join-Path $Output 'manifest.json')
    Write-Json $first (Join-Path $Output 'results.json')
    $csvRows=@($allResults | ForEach-Object {[pscustomobject]@{id=$_.id;repeat=$_.repeat;rtf=$_.total.rtf;maxBlockRatio=$_.total.max_block_ratio;deadlineMisses=$_.total.deadline_misses;audioSha256=$_.audio_sha256}})
    Export-CPUCsv $csvRows (Join-Path $Output 'results.csv')
    Write-Host "Merged $($ids.Count) cases with $($first.repeat_count) repetitions; not milestone approval"
    exit 0
}
if($Action -eq 'paired'){
    if($Mode -ne 'timing'){throw 'Paired orchestration is timing-only'}
    if($Repeat -ne 5 -and $Repeat -ne 9){throw 'Paired repeats must be five (final) or nine (experiment/confirmation)'}
    if($ShardCount -ne 1 -or $ShardIndex -ne 0){throw 'Select paired case inventory explicitly, not internal shards'}
    $a=Read-Json $Metadata;$b=Read-Json $CandidateMetadata
    if((Get-CPUComparisonEnvironmentHash $a) -cne (Get-CPUComparisonEnvironmentHash $b) -or $a.configuration -ne 'Release' -or $b.configuration -ne 'Release'){throw 'Paired environment mismatch'}
    if($Case -and $Cases.Count){throw 'Use Case or Cases, not both'}
    $ids=if($Cases.Count){@($Cases)}elseif($Case){@($Case)}else{@(& $Executable --suite $Suite --list | Where-Object {$_ -notlike '#*'})}
    if($ids.Count -eq 0){throw 'Empty paired inventory'}
    if(@($ids | Sort-Object -Unique).Count -ne $ids.Count){throw 'Duplicate paired case'}
    $schedule=@()
    foreach($id in $ids){
        for($r=0;$r -lt $Repeat;$r++){
            $order=if($r%2 -eq 0){@('S','C')}else{@('C','S')}
            foreach($side in $order){
                $destination=Join-Path $Output "$id-pair$r-$side"
                if($side -eq 'S'){Invoke-Run $Executable $Metadata $destination 1 $id ''}
                else{Invoke-Run $CandidateExecutable $CandidateMetadata $destination 1 $id ''}
            }
            $schedule+=[ordered]@{id=$id;pair=$r;order=$order}
            Write-Json $schedule[-1] (Join-Path $Output "$id-pair$r-schedule.json")
        }
    }
    Write-Json ([ordered]@{suite=$Suite;pairs=$Repeat;schedule=$schedule;complete=$true;fullMilestonePass=$false}) (Join-Path $Output 'paired.json')
    exit 0
}
if($Action -eq 'compare'){
    $s=Read-Json (Join-Path $Baseline 'results.json');$c=Read-Json (Join-Path $Candidate 'results.json')
    Assert-Identity $s $c
    if($s.repeat_count -lt 5 -or $c.repeat_count -lt 5 -or $s.suite -ne 'representative' -or $c.suite -ne 'representative' -or
       $s.case_count -ne 39 -or $c.case_count -ne 39){throw 'Current acceptance requires all 39 representative cases and at least five repeats'}
    $failures=@(Check-Hard-Gates $c)
    $baselineFailures=@(Check-Hard-Gates $s)
    $comparisons=@()
    $sRows=@{};$cRows=@{}
    foreach($row in $s.results){
        if(-not$sRows.ContainsKey($row.id)){$sRows[$row.id]=[Collections.Generic.List[object]]::new()}
        $sRows[$row.id].Add($row)
    }
    foreach($row in $c.results){
        if(-not$cRows.ContainsKey($row.id)){$cRows[$row.id]=[Collections.Generic.List[object]]::new()}
        $cRows[$row.id].Add($row)
    }
    foreach($entry in $s.cases){
        $sr=@($sRows[$entry.id] | Sort-Object repeat)
        $cr=@($cRows[$entry.id] | Sort-Object repeat)
        if($sr.Count -ne $s.repeat_count -or $cr.Count -ne $c.repeat_count -or $sr[0].manifest_sha256 -ne $cr[0].manifest_sha256){throw 'Missing/mismatched comparison case'}
        $st=@($sr.total.rtf | Sort-Object);$ct=@($cr.total.rtf | Sort-Object)
        $sm=$st[[int][math]::Floor($st.Count/2)];$cm=$ct[[int][math]::Floor($ct.Count/2)]
        if($sm -le 0){throw 'Invalid zero baseline'}
        $change=100*($sm-$cm)/$sm
        $comparisons+=[ordered]@{id=$entry.id;baselineMedianRtf=$sm;candidateMedianRtf=$cm;improvementPercent=$change;requiresNinePairRegressionProbe=($change -lt -3)}
    }
    Write-Json ([ordered]@{baselineHardViolations=$baselineFailures;candidateHardViolations=$failures;comparisons=$comparisons;
        pairedOrderStillRequiresVerification=$true;optimizationTwoRunEvidenceStillRequired=$true;fullMilestonePass=$false}) (Join-Path $Output 'comparison.json')
    Export-CPUCsv @($comparisons | ForEach-Object {[pscustomobject]$_}) (Join-Path $Output 'comparison.csv')
    if($failures.Count -gt 0){throw 'Candidate violates hard performance gates'}
    if(@($comparisons | Where-Object {$_.requiresNinePairRegressionProbe}).Count -gt 0){throw 'Suspected regressions require nine-pair confirmation'}
    Write-Host 'No hard violations in supplied runs; paired provenance, audio and optimization gates remain separate'
}
