# build.ps1
#
# Milestone 2 orchestrator: produces a compatibility ffmpeg.exe from the
# pinned sources. This script does not contain build logic itself -- it
# extracts sources with tar.exe (a generic operation, done once here) and
# then calls, in order, the focused scripts that do the real work:
#   bootstrap-msys2.ps1  -> pinned MSYS2 toolchain (skipped if already done)
#   fetch-and-verify.ps1 -> pinned, hash/PGP-verified source downloads
#   build-dependencies.sh -> nv-codec-headers, libvpl (inside MSYS2)
#   build-ffmpeg.sh       -> FFmpeg itself (inside MSYS2)
#   verify-binary.ps1     -> confirms -buildconf matches the checked-in args
#   test.ps1              -> capability + CPU smoke tests
#
#   powershell -ExecutionPolicy Bypass -File scripts\build.ps1
#
# Options:
#   -WorkRoot <path>     work root (default C:\nnffb)
#   -BuildId <id>        optional release-manager build identifier
#   -DependencyLockPath  explicit candidate lock; default is config\dependencies.lock.json
#   -ArtifactDirectory   explicit build-record destination; default is
#                        artifacts\<version>-<build-id> below this repository
#   -SkipBootstrap       assume MSYS2 is already bootstrapped
#   -SkipFetch           assume sources are already downloaded and verified
#   -SkipDependencies    assume nv-codec-headers/libvpl are already built
#                        in <WorkRoot>\deps (skips build-dependencies.sh --
#                        use this to iterate on FFmpeg's own configure/build
#                        without rebuilding the dependencies every time)
#   -SkipTests           build only; do not run verify-binary.ps1/test.ps1
#                        (the plan requires these before any release use --
#                        only use this to iterate quickly on build failures)
#
# Produces:
#   <WorkRoot>\dist\bin\ffmpeg.exe          the built binary
#   <ArtifactDirectory>\                    build record (see README.md)

[CmdletBinding()]
param(
    [string]$WorkRoot = 'C:\nnffb',
    [string]$BuildId,
    [string]$DependencyLockPath,
    [string]$ArtifactDirectory,
    [switch]$SkipBootstrap,
    [switch]$SkipFetch,
    [switch]$SkipDependencies,
    [switch]$SkipTests
)

$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
Import-Module (Join-Path $PSScriptRoot 'BuildCommon.psm1') -Force

Write-Host ("BuildCommon revision: {0}" -f (Get-BuildCommonRevision))
Assert-WorkRoot $WorkRoot
$paths    = Get-BuildPaths $WorkRoot
$repoRoot = Get-RepoRoot
$dependencyLockPath = Get-DependencyLockPath $DependencyLockPath
New-Item -ItemType Directory -Force -Path $paths.sources, $paths.deps, $paths.dist | Out-Null

$buildId = if ([string]::IsNullOrWhiteSpace($BuildId)) {
    (Get-Date).ToUniversalTime().ToString('yyyyMMdd-HHmmss')
} else {
    if ($BuildId -notmatch '^[A-Za-z0-9][A-Za-z0-9._-]*$') {
        throw "-BuildId must contain only letters, digits, dots, underscores, and hyphens: $BuildId"
    }
    $BuildId
}
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()

# ---------------------------------------------------------------------------
Write-Host "`n=== Stage 1/6: MSYS2 bootstrap ===" -ForegroundColor Cyan
if ($SkipBootstrap) {
    Write-Host "Skipped (-SkipBootstrap)."
    if (-not (Test-Path (Join-Path $paths.msysRoot 'usr\bin\bash.exe'))) {
        throw "-SkipBootstrap given but no MSYS2 install found at $($paths.msysRoot)."
    }
} else {
    & (Join-Path $PSScriptRoot 'bootstrap-msys2.ps1') -WorkRoot $WorkRoot -DependencyLockPath $dependencyLockPath
}

# ---------------------------------------------------------------------------
Write-Host "`n=== Stage 2/6: Fetch and verify pinned sources ===" -ForegroundColor Cyan
if ($SkipFetch) {
    Write-Host "Skipped (-SkipFetch)."
} else {
    & (Join-Path $PSScriptRoot 'fetch-and-verify.ps1') -WorkRoot $WorkRoot -DependencyLockPath $dependencyLockPath
}

$verificationManifestPath = Join-Path $paths.manifests 'verification-manifest.json'
if (-not (Test-Path $verificationManifestPath)) {
    throw "Pinned-source verification manifest is missing: $verificationManifestPath. Do not use -SkipFetch until fetch-and-verify.ps1 has completed successfully."
}
$sourceVerification = Get-Content $verificationManifestPath -Raw | ConvertFrom-Json
if ($sourceVerification.overallResult -ne 'verified') {
    throw "Pinned-source verification is not complete ($($sourceVerification.overallResult)). Refusing to build from unverified inputs."
}
$currentLockHash = Get-FileSha256 $dependencyLockPath
if ($sourceVerification.lockSha256 -ne $currentLockHash) {
    throw "Pinned-source verification used dependency lock $($sourceVerification.lockSha256), but the current lock is $currentLockHash. Re-run fetch-and-verify.ps1."
}

# ---------------------------------------------------------------------------
Write-Host "`n=== Stage 3/6: Extract sources ===" -ForegroundColor Cyan
$lock = Read-DependencyLock -Path $dependencyLockPath
foreach ($name in 'ffmpeg', 'amf-headers', 'nv-codec-headers', 'libvpl') {
    $comp = Get-LockComponent $lock $name
    $archivePath = Join-Path $paths.downloads $comp.filename
    if (-not (Test-Path $archivePath)) {
        throw "$($comp.filename) not found in downloads -- run fetch-and-verify.ps1 (omit -SkipFetch)."
    }
    $extractedDir = Join-Path $paths.sources $comp.extractedRoot
    if (Test-Path $extractedDir) {
        Write-Host ("{0,-20} already extracted -> {1}" -f $name, $extractedDir)
        continue
    }
    Write-Host ("{0,-20} extracting {1} ..." -f $name, $comp.filename)
    & tar.exe -xf $archivePath -C $paths.sources
    if ($LASTEXITCODE -ne 0) { throw "tar extraction failed for $($comp.filename) (exit $LASTEXITCODE)." }
    if (-not (Test-Path $extractedDir)) {
        throw "Extraction of $($comp.filename) did not produce expected directory $extractedDir -- extractedRoot in the lock may be wrong."
    }
}

$ffmpegComp    = Get-LockComponent $lock 'ffmpeg'
$amfComp       = Get-LockComponent $lock 'amf-headers'
$nvComp        = Get-LockComponent $lock 'nv-codec-headers'
$libvplComp    = Get-LockComponent $lock 'libvpl'

$ffmpegDir = Join-Path $paths.sources $ffmpegComp.extractedRoot
$amfDir    = Join-Path $paths.sources $amfComp.extractedRoot
$nvDir     = Join-Path $paths.sources $nvComp.extractedRoot
$libvplDir = Join-Path $paths.sources $libvplComp.extractedRoot

# ---------------------------------------------------------------------------
Write-Host "`n=== Stage 4/6: Build dependencies (nv-codec-headers, libvpl) ===" -ForegroundColor Cyan
$depsPosix = ConvertTo-MsysPath $paths.deps
if ($SkipDependencies) {
    Write-Host "Skipped (-SkipDependencies)."
    foreach ($pc in 'ffnvcodec', 'vpl') {
        $pcPath = Join-Path $paths.deps "lib\pkgconfig\$pc.pc"
        if (-not (Test-Path $pcPath)) { throw "-SkipDependencies given but $pcPath does not exist." }
    }
} else {
    $depsScriptPosix = ConvertTo-MsysPath (Join-Path $PSScriptRoot 'build-dependencies.sh')
    $sourcesPosix    = ConvertTo-MsysPath $paths.sources

    $depsCmd = "bash '$depsScriptPosix' '$sourcesPosix' '$depsPosix' " +
               "'$($nvComp.extractedRoot)' '$($libvplComp.extractedRoot)'"
    Invoke-MsysCommand -MsysRoot $paths.msysRoot -Command $depsCmd | ForEach-Object { Write-Host $_ }
}

# ---------------------------------------------------------------------------
Write-Host "`n=== Stage 5/6: Build FFmpeg ===" -ForegroundColor Cyan
$ffScriptPosix  = ConvertTo-MsysPath (Join-Path $PSScriptRoot 'build-ffmpeg.sh')
$ffmpegDirPosix = ConvertTo-MsysPath $ffmpegDir
$amfDirPosix    = ConvertTo-MsysPath $amfDir
$argsFilePosix  = ConvertTo-MsysPath (Join-Path $repoRoot 'config\ffmpeg.configure.args')
$distPosix      = ConvertTo-MsysPath $paths.dist

$ffCmd = "bash '$ffScriptPosix' '$ffmpegDirPosix' '$depsPosix' '$amfDirPosix' '$argsFilePosix' '$distPosix'"
Invoke-MsysCommand -MsysRoot $paths.msysRoot -Command $ffCmd | ForEach-Object { Write-Host $_ }

$builtExe = Join-Path $paths.dist 'bin\ffmpeg.exe'
if (-not (Test-Path $builtExe)) { throw "Build reported success but $builtExe does not exist." }
$exeHash = Get-FileSha256 $builtExe
Write-Host ("`nBuilt: {0}  sha256={1}" -f $builtExe, $exeHash) -ForegroundColor Green

# ---------------------------------------------------------------------------
Write-Host "`n=== Stage 6/6: Verify and test ===" -ForegroundColor Cyan
$verifyResult = $null
$testResult   = $null
if ($SkipTests) {
    Write-Host "Skipped (-SkipTests) -- this binary has NOT been verified or tested. Do not use it beyond local iteration."
} else {
    $verifyResult = Get-SingleResult -Context 'verify-binary' -Results @(
        & (Join-Path $PSScriptRoot 'verify-binary.ps1') -FfmpegPath $builtExe -WorkRoot $WorkRoot)
    $testResult = Get-SingleResult -Context 'test suite' -Results @(
        & (Join-Path $repoRoot 'test.ps1') -FfmpegPath $builtExe -WorkRoot $WorkRoot)
    if (-not $verifyResult.passed -or $testResult.overallResult -ne 'passed') {
        throw 'Verification or automated tests did not pass. No build artifact will be recorded as releasable.'
    }
}

# ---------------------------------------------------------------------------
Write-Host "`n=== Build record ===" -ForegroundColor Cyan
$artifactDir = if ([string]::IsNullOrWhiteSpace($ArtifactDirectory)) {
    Join-Path $repoRoot ("artifacts\{0}-{1}" -f $ffmpegComp.version, $buildId)
} else {
    [System.IO.Path]::GetFullPath($ArtifactDirectory)
}
if (Test-Path $artifactDir) {
    throw "Build record directory already exists: $artifactDir"
}
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
Copy-FileAtomically -Source $builtExe -Destination (Join-Path $artifactDir 'ffmpeg.exe')

$manifest = [ordered]@{
    schemaVersion  = 1
    buildId        = $buildId
    builtUtc       = (Get-Date).ToUniversalTime().ToString('o')
    machine        = $env:COMPUTERNAME
    elapsedSeconds = [math]::Round($stopwatch.Elapsed.TotalSeconds, 1)
    ffmpegVersion  = $ffmpegComp.version
    unsignedSha256 = $exeHash
    dependencyLockPath = $dependencyLockPath
    dependencyLockSha256 = Get-FileSha256 $dependencyLockPath
    configureArgsSha256  = Get-FileSha256 (Join-Path $repoRoot 'config\ffmpeg.configure.args')
    sourceVerification = [ordered]@{
        manifestSha256 = Get-FileSha256 $verificationManifestPath
        overallResult  = $sourceVerification.overallResult
    }
    configurationVerification = $verifyResult
    automatedTests = $testResult
    status         = if ($SkipTests) { 'unverified' } else { 'automated-tests-passed' }
    note           = if ($SkipTests) {
        'Unsigned iteration build. Verification and automated tests were skipped; this artifact cannot be packaged or deployed.'
    } else {
        'Unsigned build with automated verification passed. Hardware qualification, signing, release packaging, and source publication remain required before deployment.'
    }
}
Write-JsonFile -Object $manifest -Path (Join-Path $artifactDir 'build-manifest.json')
"$exeHash *ffmpeg.exe" | Set-Content -Path (Join-Path $artifactDir 'SHA256SUMS.txt') -Encoding ASCII

$recordFiles = @(
    'bootstrap-manifest.json',
    'msys2-package-inventory.txt',
    'verification-manifest.json',
    'verify-binary-result.json',
    'ffmpeg-buildconf.txt',
    'test-report.json'
)
foreach ($recordFile in $recordFiles) {
    $sourcePath = Join-Path $paths.manifests $recordFile
    if (Test-Path $sourcePath) {
        Copy-Item $sourcePath (Join-Path $artifactDir $recordFile) -Force
    }
}

$testOutput = Join-Path $paths.manifests 'test-output'
if (Test-Path $testOutput) {
    Copy-Item $testOutput (Join-Path $artifactDir 'test-output') -Recurse -Force
}

$buildLogs = @(
    (Join-Path $paths.deps 'build-dependencies-manifest.txt'),
    (Join-Path $ffmpegDir 'ffbuild-manifest.txt'),
    (Join-Path $ffmpegDir 'ffbuild-configure.log'),
    (Join-Path $ffmpegDir 'ffbuild-make.log'),
    (Join-Path $ffmpegDir 'ffbuild\config.log')
)
$artifactLogs = Join-Path $artifactDir 'build-logs'
foreach ($buildLog in $buildLogs) {
    if (Test-Path $buildLog) {
        New-Item -ItemType Directory -Force -Path $artifactLogs | Out-Null
        Copy-Item $buildLog (Join-Path $artifactLogs (Split-Path -Leaf $buildLog)) -Force
    }
}

Write-Host ("Build record: {0}" -f $artifactDir) -ForegroundColor Green
Write-Host ("Total time: {0:N1} minutes" -f ($stopwatch.Elapsed.TotalMinutes))
