param(
  [string]$Output
)
$ErrorActionPreference = 'Stop'
$repo = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
. (Join-Path $repo 'scripts/hash-utils.ps1')
$projectDeclaration = Get-Content -LiteralPath (Join-Path $repo 'CMakeLists.txt') -Raw
if ($projectDeclaration -notmatch 'project\(SmartCut64 VERSION ([0-9]+\.[0-9]+\.[0-9]+) LANGUAGES CXX\)') {
  throw 'Could not determine the SmartCut64 project version from CMakeLists.txt'
}
$version = $Matches[1]
$sourceRoot = "SmartCut64-$version-source"
if ([string]::IsNullOrWhiteSpace($Output)) { $Output = Join-Path $repo "build/$sourceRoot.zip" }
$manifestPath = Join-Path $repo 'dependencies/manifests/m1-dependencies.json'
$manifest = Get-Content -Raw -LiteralPath $manifestPath | ConvertFrom-Json
$files = [System.Collections.Generic.List[object]]::new()

$gitExe = 'C:\Program Files\Git\cmd\git.exe'
if (-not (Test-Path -LiteralPath $gitExe)) {
  $gitCommand = Get-Command git.exe -ErrorAction Stop
  $gitExe = $gitCommand.Source
}
$projectFiles = @(& $gitExe -C $repo ls-files --cached --others --exclude-standard)
if ($LASTEXITCODE -ne 0 -or $projectFiles.Count -eq 0) { throw 'Could not enumerate SmartCut64 source files' }
$forbiddenProjectFiles = @($projectFiles | Where-Object {
  $_ -match '(?i)\.(pfx|p12|sc64|key)$' -or
  $_ -match '(?i)(private[-_ ]?key|licen[cs]e[-_ ]?signing).*\.pem$'
})
if ($forbiddenProjectFiles.Count -gt 0) {
  throw "Refusing to package private entitlement material: $($forbiddenProjectFiles -join ', ')"
}
$textExtensions = @('.c', '.cc', '.cpp', '.h', '.hpp', '.cmake', '.in', '.json',
  '.md', '.mjs', '.ps1', '.sh', '.toml', '.txt', '.xml', '.yml', '.yaml')
$forbiddenContentFiles = [System.Collections.Generic.List[string]]::new()
foreach ($relative in $projectFiles) {
  $sourcePath = Join-Path $repo $relative
  if ([IO.Path]::GetFileName($sourcePath) -eq 'CMakeLists.txt' -or
      [IO.Path]::GetExtension($sourcePath).ToLowerInvariant() -in $textExtensions) {
    $content = Get-Content -Raw -LiteralPath $sourcePath
    # Test fixtures may explicitly suppress a single generated or deliberately-invalid
    # value.  Remove only annotated lines so that an unmarked secret anywhere else in
    # the same file still fails the source-release gate.
    $scannableContent = (($content -split "`r?`n") |
      Where-Object { $_ -notmatch '(?i)gitleaks:allow' }) -join "`n"
    if ($scannableContent -match '-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----\r?\n[A-Za-z0-9+/]{32,}' -or
        $scannableContent -match '(?i)SMARTCUT_PRIVATE_KEY_PEM\s*=\s*["''][^-\s]' -or
        $scannableContent -match '(?i)LEMON_WEBHOOK_SECRET\s*=\s*["''][^R\s]') {
      $forbiddenContentFiles.Add($relative)
    }
  }
}
if ($forbiddenContentFiles.Count -gt 0) { throw "Refusing to package embedded secret material: $($forbiddenContentFiles -join ', ')" }
foreach ($relative in $projectFiles) {
  $sourcePath = Join-Path $repo $relative
  if (-not (Test-Path -LiteralPath $sourcePath -PathType Leaf)) { throw "Source file is missing: $relative" }
  $entryPath = $relative.Replace('\','/')
  $files.Add([pscustomobject]@{Path=$sourcePath;Entry="$sourceRoot/project/$entryPath"})
}

foreach ($dependency in $manifest.dependencies) {
  $archiveName = if ($dependency.archiveFile) { $dependency.archiveFile } else { "$($dependency.name).archive" }
  $archivePath = Join-Path $repo "dependencies/sources/$archiveName"
  if (-not (Test-Path -LiteralPath $archivePath)) { throw "Missing corresponding source archive: $archivePath" }
  $actualHash = Get-SmartCutSha256 -LiteralPath $archivePath
  if ($actualHash -ne $dependency.archiveSha256) { throw "Source archive hash mismatch: $archiveName" }
  $files.Add([pscustomobject]@{Path=$archivePath;Entry="$sourceRoot/sources/$archiveName"})
}

$outputDirectory = Split-Path -Parent $Output
New-Item -ItemType Directory -Path $outputDirectory -Force | Out-Null
if (Test-Path -LiteralPath $Output) { [IO.File]::Delete($Output) }
Add-Type -AssemblyName System.IO.Compression.FileSystem
Add-Type -AssemblyName System.IO.Compression
$archive = [IO.Compression.ZipFile]::Open($Output, [IO.Compression.ZipArchiveMode]::Create)
try {
  foreach ($file in $files) { [IO.Compression.ZipFileExtensions]::CreateEntryFromFile($archive, $file.Path, $file.Entry, [IO.Compression.CompressionLevel]::Optimal) | Out-Null }
} finally { $archive.Dispose() }
Write-Host "Corresponding source bundle created: $Output"
