param(
    [Parameter(Mandatory=$true)][string]$Executable,
    [Parameter(Mandatory=$true)][string]$Metadata,
    [Parameter(Mandatory=$true)][string]$CaseId,
    [Parameter(Mandatory=$true)][string]$AudioReference,
    [Parameter(Mandatory=$true)][string]$GateG1,
    [Parameter(Mandatory=$true)][string]$Output,
    [ValidateRange(30,3600)][int]$Seconds=35
)
# Separate diagnostic profiling only. Never run beside normal timing. Uses the
# installed VS CPU agent, no installation, tool-file patch or system symbol cache.
$ErrorActionPreference='Stop'
. (Join-Path $PSScriptRoot 'CPUReportIO.ps1')
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
$allowed=[IO.Path]::GetFullPath((Join-Path $repo 'Artifacts'))+[IO.Path]::DirectorySeparatorChar
if(-not[IO.Path]::IsPathRooted($Output) -or -not([IO.Path]::GetFullPath($Output)).StartsWith($allowed,[StringComparison]::OrdinalIgnoreCase) -or
    (Test-Path -LiteralPath $Output)){throw 'Use a new absolute project-local Artifacts output directory'}
foreach($file in @($Executable,$Metadata,$GateG1)){
    if(-not[IO.Path]::IsPathRooted($file) -or -not(Test-Path -LiteralPath $file -PathType Leaf)){throw "Missing absolute input: $file"}
}
$gate=Get-Content -LiteralPath $GateG1 -Raw | ConvertFrom-Json
if($gate.schema -cne 'DeepPluck-CPU-G1-Rev1' -or $gate.syntheticUnitFixture -or -not$gate.g1Pass -or -not$gate.profilingAllowed){throw 'Audited G1 is required before production profiling'}
$meta=Get-Content -LiteralPath $Metadata -Raw | ConvertFrom-Json
if($meta.schema -cne 'DeepPluck-CPU-Provenance-Rev1' -or $meta.configuration -cne 'Release' -or
    -not$meta.environment.profiling){throw 'Use metadata of the separate symbolised Release build'}
$diagnostic=Join-Path $AudioReference "$CaseId-rep0.json"
if(-not(Test-Path -LiteralPath $diagnostic -PathType Leaf)){throw 'Missing independently measured audio diagnostic for selected case'}
$inventory=@(& $Executable --suite representative --case $CaseId --list | Where-Object {$_ -notlike '#*'})
if($LASTEXITCODE -ne 0 -or $inventory.Count -ne 1 -or $inventory[0] -cne $CaseId){throw 'Invalid exact profiling case'}
$collector='C:/Program Files/Microsoft Visual Studio/18/Community/Team Tools/DiagnosticsHub/Collector/VSDiagnostics.exe'
if(-not(Test-Path -LiteralPath $collector -PathType Leaf)){throw 'Confirmed VS profiler is unavailable; do not install automatically'}
$Output=[IO.Path]::GetFullPath($Output)
New-Item -ItemType Directory -Path $Output | Out-Null
$scratch=Join-Path $Output 'scratch';$symbols=Join-Path $Output 'symbols';$workload=Join-Path $Output 'workload'
New-Item -ItemType Directory -Path $scratch,$symbols | Out-Null
$session=[Guid]::NewGuid().ToString()
$package=Join-Path $Output 'capture.diagsession'
$arguments='--suite representative --mode profile --repeat 1 --profile-seconds '+$Seconds+' --case '+$CaseId+
    ' --metadata "'+[IO.Path]::GetFullPath($Metadata)+'" --output "'+$workload+'" --reference "'+[IO.Path]::GetFullPath($AudioReference)+'"'
$collectorArguments=@('start',$session,('/launch:'+([IO.Path]::GetFullPath($Executable))),('/launchArgs:'+$arguments),
    ('/scratchLocation:'+$scratch),('/symbolCachePath:'+$symbols),'/package:dir',
    '/loadAgent:4EA90761-2248-496C-B854-3C0399A591A4;DiagnosticsHub.CpuAgent.dll')
$record=[ordered]@{schema='DeepPluck-CPU-Profile-Capture-Rev1';caseId=$CaseId;requestedSeconds=$Seconds;
    qpcFrequency=[Diagnostics.Stopwatch]::Frequency;
    startedUtc=[DateTime]::UtcNow.ToString('o');session=$session;collector=$collector;
    collectorSha256=(Get-FileHash -LiteralPath $collector).Hash;collectorArguments=$collectorArguments;
    executable=[IO.Path]::GetFullPath($Executable);executableSha256=(Get-FileHash -LiteralPath $Executable).Hash;
    metadata=[IO.Path]::GetFullPath($Metadata);metadataSha256=(Get-FileHash -LiteralPath $Metadata).Hash;
    gateG1=[IO.Path]::GetFullPath($GateG1);gateG1Sha256=(Get-FileHash -LiteralPath $GateG1).Hash;
    audioDiagnostic=$diagnostic;audioDiagnosticSha256=(Get-FileHash -LiteralPath $diagnostic).Hash;
    package=$package;workload=$workload;startExit=$null;stopExit=$null;workloadCompleted=$false;
    captureComplete=$false;symbolsResolved=$false;fullMilestonePass=$false;error=$null}
$attempted=$false
try {
    $attempted=$true
    & $collector @collectorArguments 2>&1 | Tee-Object -FilePath (Join-Path $Output 'start.log')
    $record.startExit=$LASTEXITCODE
    # This version of VSDiagnostics sometimes returns 0 for a failed operation.
    # Require real Running state, full workload output and a later parsed trace.
    $status=@(& $collector status $session 2>&1)
    $status | Out-File -LiteralPath (Join-Path $Output 'started-status.log') -Encoding utf8
    if($record.startExit -ne 0 -or -not($status -match '^\s*Running\s*$')){throw 'VS CPU collection did not enter Running state'}
    # Any unexpected parent exit destroys only this own session. No service stop
    # and no cancel/stop against unrelated ETW/WPR sessions is permitted.
    & $collector update $session "/lifetimeProcess:$PID" 2>&1 |
        Out-File -LiteralPath (Join-Path $Output 'lifetime.log') -Encoding utf8
    $wait=[Diagnostics.Stopwatch]::StartNew()
    $resultFile=Join-Path $workload 'results.json'
    while(-not(Test-Path -LiteralPath $resultFile -PathType Leaf)){
        if($wait.Elapsed.TotalSeconds -gt $Seconds+180){throw 'Own profile workload did not finish within bounded wait'}
        Start-Sleep -Milliseconds 500
    }
    # results.json is written immediately before native exit; the independent
    # ETL reader must additionally verify that exact process's real exit status.
    Start-Sleep -Milliseconds 500
    $result=Get-Content -LiteralPath $resultFile -Raw | ConvertFrom-Json
    if(-not$result.completed_requested_run -or $result.mode -cne 'profile' -or $result.configuration -cne 'Release' -or
        $result.case_count -ne 1 -or $result.cases[0].id -cne $CaseId -or $result.profile_wall_seconds -lt $Seconds -or
        $result.executable_sha256 -ine $record.executableSha256 -or $result.metadata_sha256 -ine $record.metadataSha256){throw 'Incomplete or mismatched profile workload'}
    $record.workloadCompleted=$true
    $record.workloadResultSha256=(Get-FileHash -LiteralPath $resultFile).Hash
    $record.profileWallSeconds=$result.profile_wall_seconds
    $record.profileRepetitions=$result.profile_repetitions
} catch {$record.error=$_.Exception.Message}
finally {
    if($attempted){
        & $collector stop $session ("/output:"+$package) 2>&1 | Tee-Object -FilePath (Join-Path $Output 'stop.log')
        $record.stopExit=$LASTEXITCODE
        & $collector update $session "/lifetimeProcess:$PID" 2>&1 |
            Out-File -LiteralPath (Join-Path $Output 'cleanup-lifetime.log') -Encoding utf8
    }
    $traces=@(Get-ChildItem -LiteralPath $package -Recurse -File -Filter '*.etl' -ErrorAction SilentlyContinue)
    $record.traces=@($traces | ForEach-Object {[ordered]@{path=$_.FullName;bytes=$_.Length;sha256=(Get-FileHash -LiteralPath $_.FullName).Hash}})
    $record.captureComplete=($record.workloadCompleted -and $record.stopExit -eq 0 -and $traces.Count -gt 0 -and -not$record.error)
    $record.completedUtc=[DateTime]::UtcNow.ToString('o')
    $record | ConvertTo-Json -Depth 15 | Set-Content -LiteralPath (Join-Path $Output 'capture.json') -Encoding utf8
}
if(-not$record.captureComplete){throw "Profile capture incomplete: $($record.error)"}
Write-Host 'Captured separate diagnostic workload. Native exit, duration, lost events, symbols and callback stacks still require trace analysis; not performance approval.'
