# ============================================================================
#  stamp-version.ps1 - write RP_L4\rpl4build.h from the repository's own state
# ============================================================================
#
#  The patch number IS the commit count, so a build names the commit it came
#  from and there is never a question about which changes are in a binary
#  somebody is holding. Run as RP_L4's pre-build step; also readable by
#  pack-dist.ps1, so the package and the exe cannot disagree.
#
#  A hardcoded version could not do this: the commit that records "4.12.96"
#  is itself commit 96, so the file is stale the moment it is committed.
#
#  Usage:  powershell -ExecutionPolicy Bypass -File stamp-version.ps1
#
$ErrorActionPreference = 'Stop'

# The product line. Bump this by hand when the line moves; the patch
# number after it looks after itself.
$line = '4.12'

# How many days a build stays good for. Test builds go stale so nobody is
# still racing a fortnight-old binary and reporting things that were fixed
# a week ago - the expired build says so and stops, rather than quietly
# wasting everyone's afternoon.
#
# SET THIS TO 0 FOR A REAL RELEASE. A shipped build that expires is a
# catastrophe, and this is the one line that decides it.
$expireDays = 14

$root   = Split-Path -Parent $MyInvocation.MyCommand.Path
$header = Join-Path $root 'RP_L4\rpl4build.h'

$count  = 0
$commit = 'nogit'
$dirty  = 0

#
# Every git call goes through cmd so that stderr never reaches PowerShell's
# error stream. Windows PowerShell turns a native command's stderr into
# ErrorRecords, and with $ErrorActionPreference = 'Stop' git's routine
# "LF will be replaced by CRLF" warning is enough to throw - which silently
# skipped the dirty check and stamped every modified build as clean.
#
try {
    Push-Location $root
    $c = cmd /c "git rev-list --count HEAD 2>NUL"
    if ($LASTEXITCODE -eq 0 -and $c) {
        $count  = [int]$c
        $commit = (cmd /c "git rev-parse --short HEAD 2>NUL").Trim()
        #
        # Tracked modifications only. An untracked scratch file in the tree
        # is not in the binary, and marking every build dirty for one would
        # make the marker mean nothing.
        #
        cmd /c "git diff --quiet HEAD -- 2>NUL"
        if ($LASTEXITCODE -ne 0) { $dirty = 1 }
    }
} catch {
    # no git, or not a repository: fall through to the placeholder below
} finally {
    Pop-Location
}

if ($count -eq 0) {
    # Built outside the repository (a source drop, say). Say so plainly
    # rather than inventing a number that would sort against real ones.
    $version = "$line.x"
    $long    = "$line.x (no repository)"
} else {
    $version = "$line.$count"
    $long    = "$version ($commit$(if ($dirty) { '+' } else { '' }))"
}

#
# Expiry, from the day it was BUILT rather than the day the code was
# written: rebuilding an old commit to chase something should give a
# usable binary, not one that was born stale.
#
# The date is what makes this header change from one day to the next, so
# the first build of each day recompiles RPL4.CPP and the rest of that
# day's builds do not. One file, a couple of seconds.
#
# Only 0 means "never". A NEGATIVE count backdates the expiry, which is how
# the refusal gets tested without touching the machine's clock.
if ($expireDays -ne 0) {
    $expiry = (Get-Date).Date.AddDays($expireDays)
    $expires   = 1
    $expiryY   = $expiry.Year
    $expiryM   = $expiry.Month
    $expiryD   = $expiry.Day
    $expiryTxt = $expiry.ToString('d MMMM yyyy', [Globalization.CultureInfo]::InvariantCulture)
} else {
    $expires   = 0
    $expiryY   = 0
    $expiryM   = 0
    $expiryD   = 0
    $expiryTxt = 'never'
}

$content = @"
//===========================================================================//
// File:    rpl4build.h            GENERATED - do not edit, do not commit    //
//---------------------------------------------------------------------------//
// Written by stamp-version.ps1 as RP_L4's pre-build step. The patch number   //
// is the repository's commit count and the hash beside it names the exact    //
// commit, so a running build always says where it came from. A trailing '+'  //
// means the tree had uncommitted changes to tracked files when it was built. //
//                                                                           //
// EXPIRES is the test-build shelf life - see \$expireDays in the script. A    //
// release must be built with it at 0.                                       //
//===========================================================================//

#pragma once

#define RP412_BUILD_COUNT   $count
#define RP412_BUILD_COMMIT  "$commit"
#define RP412_BUILD_DIRTY   $dirty
#define RP412_VERSION       "$version"
#define RP412_VERSION_LONG  "$long"

#define RP412_EXPIRES       $expires
#define RP412_EXPIRY_YEAR   $expiryY
#define RP412_EXPIRY_MONTH  $expiryM
#define RP412_EXPIRY_DAY    $expiryD
#define RP412_EXPIRY_TEXT   "$expiryTxt"
"@

#
# Only rewrite when something actually changed: an unconditional write
# would touch the header on every build and drag RPL4.CPP through a
# recompile each time.
#
$existing = if (Test-Path $header) { [IO.File]::ReadAllText($header) } else { '' }
$note = if ($expires) { "expires $expiryTxt" } else { "no expiry" }
if ($existing -ne $content) {
    [IO.File]::WriteAllText($header, $content, (New-Object System.Text.ASCIIEncoding))
    Write-Host "stamp-version: $long, $note"
} else {
    Write-Host "stamp-version: $long, $note (unchanged)"
}

#
# Explicitly: this runs as a pre-build step, and the last thing above it is
# "git diff --quiet", which exits 1 to mean "there are changes". Letting that
# escape would fail the build on every modified tree - the exact case a
# developer builds in.
#
exit 0
