[CmdletBinding()]
param(
  [string]$BuildDir,
  [string]$OutputDir,
  [string]$Version
)

$ErrorActionPreference = 'Stop'
$repo = Split-Path -Parent $PSScriptRoot
. (Join-Path $repo 'scripts/hash-utils.ps1')
if ([string]::IsNullOrWhiteSpace($Version)) {
  $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]
}
if ([string]::IsNullOrWhiteSpace($BuildDir)) {
  $BuildDir = Join-Path $repo 'build/windows-ucrt-gui-release'
}
$BuildDir = [IO.Path]::GetFullPath($BuildDir)

$gitExe = 'C:\Program Files\Git\cmd\git.exe'
if (-not (Test-Path -LiteralPath $gitExe)) {
  $gitExe = (Get-Command git.exe -ErrorAction Stop).Source
}
$commit = (& $gitExe -C $repo rev-parse --short=12 HEAD).Trim()
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($commit)) {
  throw 'Could not resolve the SmartCut64 source commit.'
}
$worktreeChanges = @(& $gitExe -C $repo status --porcelain --untracked-files=all)
if ($LASTEXITCODE -ne 0) { throw 'Could not inspect the SmartCut64 worktree.' }
if ($worktreeChanges.Count -gt 0) {
  throw "Store staging requires a clean committed source tree:`n$($worktreeChanges -join "`n")"
}

if ([string]::IsNullOrWhiteSpace($OutputDir)) {
  $OutputDir = Join-Path $repo "build/store-staging/SmartCut64-$Version-$commit"
}
$OutputDir = [IO.Path]::GetFullPath($OutputDir)
if (Test-Path -LiteralPath $OutputDir) {
  throw "Refusing to overwrite an existing store-staging directory: $OutputDir"
}

$artifactNames = @(
  "SmartCut64-$Version-win64.exe",
  "SmartCut64-$Version-win64.zip",
  "SmartCut64-$Version-source.zip",
  "SmartCut64-$Version-SHA256SUMS.txt"
)
$artifactPaths = @{}
foreach ($name in $artifactNames) {
  $path = Join-Path $BuildDir $name
  if (-not (Test-Path -LiteralPath $path -PathType Leaf)) {
    throw "Missing release artifact: $path"
  }
  $artifactPaths[$name] = $path
}

$checksumPath = $artifactPaths["SmartCut64-$Version-SHA256SUMS.txt"]
$recordedHashes = @{}
foreach ($line in Get-Content -LiteralPath $checksumPath) {
  if ($line -notmatch '^([0-9a-fA-F]{64})\s{2}(.+)$') {
    throw "Malformed checksum line: $line"
  }
  $recordedHashes[$Matches[2]] = $Matches[1].ToLowerInvariant()
}
foreach ($name in $artifactNames | Where-Object { $_ -ne "SmartCut64-$Version-SHA256SUMS.txt" }) {
  if (-not $recordedHashes.ContainsKey($name)) {
    throw "Checksum manifest does not cover $name"
  }
  $actual = (Get-SmartCutSha256 -LiteralPath $artifactPaths[$name]).ToLowerInvariant()
  if ($actual -ne $recordedHashes[$name]) {
    throw "Checksum mismatch for $name"
  }
}

# A checksum file can be internally consistent while referring to a source ZIP
# from an older commit. Compare every tracked project file with its corresponding
# archive entry so the staged binaries and advertised source tree cannot drift.
Add-Type -AssemblyName System.IO.Compression.FileSystem
$sourceArchive = [IO.Compression.ZipFile]::OpenRead($artifactPaths["SmartCut64-$Version-source.zip"])
try {
  $sourceEntries = @{}
  foreach ($entry in $sourceArchive.Entries) { $sourceEntries[$entry.FullName] = $entry }
  $trackedFiles = @(& $gitExe -C $repo ls-files --cached)
  if ($LASTEXITCODE -ne 0 -or $trackedFiles.Count -eq 0) {
    throw 'Could not enumerate tracked SmartCut64 source files.'
  }
  foreach ($relative in $trackedFiles) {
    $entryName = "SmartCut64-$Version-source/project/$($relative.Replace('\','/'))"
    if (-not $sourceEntries.ContainsKey($entryName)) {
      throw "Corresponding-source ZIP is not from the current commit; missing $relative"
    }
    $fileHash = (Get-SmartCutSha256 -LiteralPath (Join-Path $repo $relative)).ToLowerInvariant()
    $entryStream = $sourceEntries[$entryName].Open()
    try {
      $sha256 = [Security.Cryptography.SHA256]::Create()
      try {
        $entryHash = [BitConverter]::ToString($sha256.ComputeHash($entryStream)).Replace('-', '').ToLowerInvariant()
      } finally {
        $sha256.Dispose()
      }
    } finally {
      $entryStream.Dispose()
    }
    if ($fileHash -ne $entryHash) {
      throw "Corresponding-source ZIP is not byte-identical to the current commit: $relative"
    }
  }
} finally {
  $sourceArchive.Dispose()
}

function Assert-LinekerpandiSignature([string]$Path) {
  $signature = Get-AuthenticodeSignature -LiteralPath $Path
  if ($signature.Status -ne [System.Management.Automation.SignatureStatus]::Valid) {
    throw "Authenticode signature is not valid: $Path ($($signature.Status))"
  }
  if ($null -eq $signature.SignerCertificate -or
      $signature.SignerCertificate.Subject -notmatch 'O=LINEKERPANDI LIMITED') {
    throw "Unexpected Authenticode signer: $Path"
  }
  [pscustomobject]@{
    file = [IO.Path]::GetFileName($Path)
    signer = $signature.SignerCertificate.Subject
    thumbprint = $signature.SignerCertificate.Thumbprint
  }
}

$signatureEvidence = [System.Collections.Generic.List[object]]::new()
$signatureEvidence.Add((Assert-LinekerpandiSignature $artifactPaths["SmartCut64-$Version-win64.exe"]))

$portablePath = $artifactPaths["SmartCut64-$Version-win64.zip"]
$temporaryRoot = Join-Path ([IO.Path]::GetTempPath()) ('SmartCut64-store-stage-' + [guid]::NewGuid().ToString('N'))
New-Item -ItemType Directory -Path $temporaryRoot | Out-Null
try {
  $archive = [IO.Compression.ZipFile]::OpenRead($portablePath)
  try {
    foreach ($executableName in 'smartcut-gui.exe', 'smartcut-cli.exe', 'smartcut-worker.exe') {
      $entry = @($archive.Entries | Where-Object { $_.FullName -match "/bin/$([regex]::Escape($executableName))$" })
      if ($entry.Count -ne 1) {
        throw "Portable archive must contain exactly one $executableName"
      }
      $destination = Join-Path $temporaryRoot $executableName
      [IO.Compression.ZipFileExtensions]::ExtractToFile($entry[0], $destination)
      $signatureEvidence.Add((Assert-LinekerpandiSignature $destination))
    }
  } finally {
    $archive.Dispose()
  }
} finally {
  if ((Test-Path -LiteralPath $temporaryRoot) -and
      $temporaryRoot.StartsWith([IO.Path]::GetTempPath(), [StringComparison]::OrdinalIgnoreCase) -and
      [IO.Path]::GetFileName($temporaryRoot).StartsWith('SmartCut64-store-stage-', [StringComparison]::Ordinal)) {
    Remove-Item -LiteralPath $temporaryRoot -Recurse -Force
  }
}

$storeImage = Join-Path $repo 'assets/smartcut64-store-card-1600x1200.png'
if (-not (Test-Path -LiteralPath $storeImage -PathType Leaf)) {
  throw "Missing store image: $storeImage"
}
Add-Type -AssemblyName System.Drawing
$image = [Drawing.Image]::FromFile($storeImage)
try {
  if ($image.Width -ne 1600 -or $image.Height -ne 1200) {
    throw "Store image must be exactly 1600x1200; found $($image.Width)x$($image.Height)"
  }
} finally {
  $image.Dispose()
}

New-Item -ItemType Directory -Path $OutputDir | Out-Null
foreach ($name in $artifactNames) {
  Copy-Item -LiteralPath $artifactPaths[$name] -Destination (Join-Path $OutputDir $name)
}
Copy-Item -LiteralPath $storeImage -Destination (Join-Path $OutputDir 'smartcut64-store-card-1600x1200.png')
Copy-Item -LiteralPath (Join-Path $repo 'docs/PRODUCT_LISTING.md') -Destination (Join-Path $OutputDir 'SELLER-PRODUCT-LISTING.md')
Copy-Item -LiteralPath (Join-Path $repo 'docs/LEMON_UPLOAD_CHECKLIST.md') -Destination (Join-Path $OutputDir 'SELLER-UPLOAD-CHECKLIST.md')
Copy-Item -LiteralPath (Join-Path $repo 'docs/COMMERCIAL_RELEASE_SIGNOFF.md') -Destination (Join-Path $OutputDir 'SELLER-COMMERCIAL-SIGNOFF.md')

$stagedFiles = Get-ChildItem -LiteralPath $OutputDir -File | Sort-Object Name | ForEach-Object {
  [pscustomobject]@{
    name = $_.Name
    bytes = $_.Length
    sha256 = (Get-SmartCutSha256 -LiteralPath $_.FullName).ToLowerInvariant()
  }
}
$manifest = [ordered]@{
  schema = 'smartcut64-store-staging-v1'
  product = 'SmartCut64 Personal'
  version = $Version
  source_commit = $commit
  prepared_utc = [DateTime]::UtcNow.ToString('o')
  live_upload_allowed = $false
  status = 'PRELAUNCH ONLY - production licensing and external release gates remain open'
  first_party_signatures = @($signatureEvidence)
  files = @($stagedFiles)
  remaining_live_gates = @(
    'Two independently stored and restored offline production-key backups verified',
    'Production activation bridge populated with approved live Lemon IDs and secrets and live lifecycle passed',
    'Lemon Squeezy live-store approval and exact staged-file delivery links verified',
    'One controlled live purchase, activation, refund, and webhook-revocation cycle passed',
    'Independent open-source/customer-terms/trademark and codec-patent review approved',
    'Broader multi-player/long-file/device-diverse GPU matrix and optional elevated Application Verifier completed'
  )
}
$manifestPath = Join-Path $OutputDir "SmartCut64-$Version-STORE-STAGING.json"
[IO.File]::WriteAllText($manifestPath, ($manifest | ConvertTo-Json -Depth 6), [Text.UTF8Encoding]::new($false))

$readme = @"
SMARTCUT64 STORE-STAGING BUNDLE

This folder is cryptographically checked and internally consistent, but it is
NOT approved for a live paid product. See SELLER-UPLOAD-CHECKLIST.md and the
live_upload_allowed field in SmartCut64-$Version-STORE-STAGING.json.

Safe now: use smartcut64-store-card-1600x1200.png as draft product media and
use SELLER-PRODUCT-LISTING.md as the approval draft.

Do not enable live checkout or deliver these binaries as a paid build until
every remaining live gate is complete and SELLER-COMMERCIAL-SIGNOFF.md has
been completed for these exact hashes. This script intentionally never makes
the legal or business decision to set live_upload_allowed=true.
"@
[IO.File]::WriteAllText((Join-Path $OutputDir 'README-FIRST.txt'), $readme, [Text.UTF8Encoding]::new($false))

Write-Host "Verified prelaunch store-staging bundle: $OutputDir"
Write-Host 'Live upload remains blocked; read README-FIRST.txt and SELLER-UPLOAD-CHECKLIST.md.'
