<#
.SYNOPSIS
    {{ app_display }} installer (Windows).

.DESCRIPTION
    Generated by ariza from {{ app_name }}'s ariza.toml. Do not edit this
    file: edit ariza's resources/templates/install-windows.ps1.j2 and
    re-run `ariza installers --app=<this repository>`.

    Downloads the prebuilt {{ app_display }} bundle for this machine from the
    {{ repo }} releases, verifies its sha256, and unpacks it into
    %LOCALAPPDATA%\{{ app_display }}\versions\<version>\. A junction named
    `current` points at the version in use, and
    %LOCALAPPDATA%\{{ app_display }}\current\bin goes on your user PATH once.

    Nothing is compiled, nothing needs administrator rights, and nothing
    outside %LOCALAPPDATA%\{{ app_display }} and your user PATH is written.

.PARAMETER Version
    Install a specific release tag (e.g. -Version v1.2.3) rather than the
    latest one.

.PARAMETER Url
    Install this archive instead, bypassing GitHub entirely. A local file
    path works as well as a URL. Also settable as $env:{{ env_url }}.

.PARAMETER InsecureNoVerify
    With -Url only: proceed even when the source has no sibling .sha256.

.EXAMPLE
    irm {{ raw_base }}/install.ps1 | iex

.EXAMPLE
    # With arguments, a piped script needs to become a script block first:
    & ([scriptblock]::Create((irm {{ raw_base }}/install.ps1))) -Version v1.2.3
#>
[CmdletBinding()]
param(
    [string] $Version,
    [string] $Url,
    [switch] $InsecureNoVerify
)

$AppDisplay = '{{ app_display }}'
$AppExec    = '{{ app_exec }}'
$AppRepo    = '{{ repo }}'

# The platforms {{ app_name }} publishes bundles for. Detection produces one
# of these strings or nothing at all; there is deliberately no "closest
# match", because a bundle for the wrong architecture does not run.
$ArizaSlugs = @({{ slugs_ps }})

$ArizaRoot     = Join-Path $env:LOCALAPPDATA $AppDisplay
$ArizaVersions = Join-Path $ArizaRoot 'versions'
$ArizaBinDir   = Join-Path (Join-Path $ArizaRoot 'current') 'bin'

############################# common runtime ##################################
{{ lib_windows | safe }}
###############################################################################

function Ariza-DetectSlug {
    $arch = $env:PROCESSOR_ARCHITECTURE
    if (-not $arch) { $arch = '' }
    switch ($arch.ToUpper()) {
        'AMD64' { return 'windows-x86_64' }
        'ARM64' { return 'windows-arm64' }
        'X86'   {
            # A 32-bit PowerShell on a 64-bit machine reports x86; ask
            # what the hardware actually is before giving up on it.
            if ($env:PROCESSOR_ARCHITEW6432 -eq 'AMD64') { return 'windows-x86_64' }
            if ($env:PROCESSOR_ARCHITEW6432 -eq 'ARM64') { return 'windows-arm64' }
            return ''
        }
    }
    return ''
}

function Ariza-VersionFromName {
    param([string]$Name)
    # ariza names a bundle <exec>-<version>-<slug>. Stripping a known slug
    # off the end rather than splitting on dashes is what makes a version
    # containing one (1.0-rc1) parse correctly.
    $n = $Name
    foreach ($ext in @('.tar.gz', '.tgz', '.zip')) {
        if ($n.ToLower().EndsWith($ext)) { $n = $n.Substring(0, $n.Length - $ext.Length) }
    }
    if (-not $n.StartsWith("$AppExec-")) { return '' }
    $n = $n.Substring($AppExec.Length + 1)
    foreach ($slug in $ArizaSlugs) {
        if ($n.EndsWith("-$slug")) { return $n.Substring(0, $n.Length - $slug.Length - 1) }
    }
    return ''
}

function Ariza-LatestTag {
    # The releases/latest redirect names the tag, so this needs no JSON
    # parsing and no API token.
    $url = "https://github.com/$AppRepo/releases/latest"
    try {
        $resp = Invoke-WebRequest -Uri $url -UseBasicParsing -MaximumRedirection 5
    }
    catch {
        return ''
    }
    $final = ''
    if ($resp.BaseResponse.PSObject.Properties.Name -contains 'ResponseUri') {
        $final = $resp.BaseResponse.ResponseUri.AbsoluteUri      # PowerShell 5.1
    }
    elseif ($resp.BaseResponse.PSObject.Properties.Name -contains 'RequestMessage') {
        $final = $resp.BaseResponse.RequestMessage.RequestUri.AbsoluteUri   # PowerShell 7
    }
    if (-not $final) { return '' }
    return ($final -split '/')[-1]
}

function Ariza-LinkBin {
    Ariza-PersistPath $ArizaBinDir
}

function Ariza-Prune {
    param([string]$Keep)
    # Exactly one older version is kept, so a bad release can be rolled
    # back to the one before it without hoarding every bundle ever
    # downloaded.
    $old = 0
    $dirs = Get-ChildItem -LiteralPath $ArizaVersions -Directory -ErrorAction SilentlyContinue |
            Sort-Object LastWriteTime -Descending
    foreach ($dir in $dirs) {
        if ($dir.Name -eq $Keep) { continue }
        $old++
        if ($old -gt 1) {
            Remove-Item -LiteralPath $dir.FullName -Recurse -Force
            Ariza-Log "removed superseded version $($dir.Name)"
        }
    }
}

function Ariza-CheckExisting {
    param([string]$InstalledVersion)
    # Returns $true when that version is already installed and usable, in
    # which case the junction and PATH are repaired on the way past: a
    # re-run is the obvious thing to try when something has gone missing,
    # and it should fix it.
    $dir = Join-Path $ArizaVersions $InstalledVersion
    if (-not (Test-Path -LiteralPath $dir)) { return $false }
    if (-not (Test-Path -LiteralPath (Join-Path $dir "bin\$AppExec.cmd"))) {
        Ariza-Warn "$dir is incomplete -- installing it again"
        Remove-Item -LiteralPath $dir -Recurse -Force
        return $false
    }
    Ariza-PointCurrent $dir
    Ariza-LinkBin
    Ariza-Ok "$AppDisplay $InstalledVersion is already installed"
    return $true
}

function Ariza-Report {
    param([string]$InstalledVersion)
    Write-Host ''
    Write-Host "    run it:        $AppExec"
    Write-Host "    installed in:  $(Join-Path $ArizaVersions $InstalledVersion)"
    Write-Host "    uninstall:     irm {{ raw_base }}/uninstall.ps1 | iex"
    Write-Host ''
    Write-Host "    Open a new terminal to pick up the PATH change."
}

function Ariza-Main {
    $src        = if ($Url) { $Url } elseif ($env:{{ env_url }}) { $env:{{ env_url }} } else { '' }
    $override   = [bool]$src
    $installVer = ''

    if ($override) {
        # An explicit source bypasses GitHub completely. It is how a
        # release candidate, an air-gapped copy or an offline test gets
        # installed, so it accepts a plain file path as readily as a URL.
        $installVer = Ariza-VersionFromName (Split-Path -Leaf $src)
        Ariza-Log "$AppDisplay from $src"
    }
    else {
        $slug = Ariza-DetectSlug
        if (-not $slug -or $ArizaSlugs -notcontains $slug) {
            Ariza-Err "no prebuilt $AppDisplay bundle for Windows $env:PROCESSOR_ARCHITECTURE yet -- see https://github.com/$AppRepo/releases for what is published"
        }
        $tag = if ($Version) { $Version } else { Ariza-LatestTag }
        if (-not $tag) {
            Ariza-Err "could not read the latest release tag from https://github.com/$AppRepo/releases/latest -- pass -Version to name one"
        }
        $installVer = $tag -replace '^v', ''
        $src = "https://github.com/$AppRepo/releases/download/$tag/$AppExec-$installVer-$slug.tar.gz"
        Ariza-Log "$AppDisplay $installVer for $slug"
    }

    if ($installVer -and (Ariza-CheckExisting $installVer)) {
        Ariza-Report $installVer
        return
    }

    $tmp = Join-Path ([IO.Path]::GetTempPath()) "$AppExec-install-$PID"
    New-Item -ItemType Directory -Path $tmp -Force | Out-Null
    $staging = Join-Path $ArizaRoot ".staging-$PID"

    try {
        $archive = Join-Path $tmp (Split-Path -Leaf $src)
        if (Test-Path -LiteralPath $src) {
            Copy-Item -LiteralPath $src -Destination $archive -Force
        }
        else {
            Ariza-Log "downloading $src"
            Ariza-Download $src $archive
        }

        $expected = ''
        $digestSrc = "$src.sha256"
        if (Test-Path -LiteralPath $digestSrc) {
            $expected = Ariza-FirstWord $digestSrc
        }
        elseif (-not (Test-Path -LiteralPath $src)) {
            try {
                Ariza-Download $digestSrc "$archive.sha256"
                $expected = Ariza-FirstWord "$archive.sha256"
            }
            catch {
                $expected = ''
            }
        }

        if ($expected) {
            Ariza-VerifySha256 $archive $expected
            Ariza-Ok 'sha256 verified'
        }
        elseif ($override -and $InsecureNoVerify) {
            # Only reachable for a source the user named themselves, and
            # only when they asked for it in as many words. A published
            # release always has a checksum beside it, so its absence
            # there is a tampered or half-uploaded release and stays fatal.
            Ariza-Warn "NOT VERIFIED: no $(Split-Path -Leaf $digestSrc) beside the source you gave, and -InsecureNoVerify was passed"
        }
        else {
            Ariza-Err "no checksum published at $digestSrc -- refusing to install a bundle that cannot be verified"
        }

        # Unpacked inside the data directory rather than in TEMP, so the
        # move into place is a rename on the same volume: the new version
        # appears complete or not at all, and `current` is repointed only
        # after it is there.
        New-Item -ItemType Directory -Path $ArizaVersions -Force | Out-Null
        if (Test-Path -LiteralPath $staging) { Remove-Item -LiteralPath $staging -Recurse -Force }
        Ariza-Extract $archive $staging

        $tops = @(Get-ChildItem -LiteralPath $staging -Directory)
        if ($tops.Count -ne 1) {
            Ariza-Err "that archive does not look like a $AppDisplay bundle (expected one directory inside it)"
        }
        $top = $tops[0]

        # What the bundle says it is beats what the archive was called: a
        # versions\<x> directory that does not contain <x> would make
        # every later "already installed" check a lie.
        $actual = Ariza-VersionFromName $top.Name
        if (-not $actual) {
            if (-not $installVer) {
                Ariza-Err "cannot tell which version $($top.Name) is -- expected $AppExec-<version>-<platform>"
            }
        }
        else {
            if ($installVer -and $actual -ne $installVer) {
                Ariza-Warn "that archive is named $installVer but contains $actual -- installing it as $actual"
            }
            $installVer = $actual
        }

        if (Ariza-CheckExisting $installVer) {
            Ariza-Report $installVer
            return
        }

        $dest = Join-Path $ArizaVersions $installVer
        if (Test-Path -LiteralPath $dest) { Remove-Item -LiteralPath $dest -Recurse -Force }
        Move-Item -LiteralPath $top.FullName -Destination $dest
        # Stamped with the install time, not the build time: the archive
        # carries the directory's original timestamp, which is the same
        # for every bundle cut from one source tree, and pruning by age
        # has to mean "the one I installed before this one".
        (Get-Item -LiteralPath $dest).LastWriteTime = Get-Date

        if (-not (Test-Path -LiteralPath (Join-Path $dest "bin\$AppExec.cmd"))) {
            Ariza-Err "the unpacked bundle has no bin\$AppExec.cmd -- not touching your existing install"
        }

        Ariza-PointCurrent $dest
        Ariza-LinkBin
        Ariza-Prune $installVer
        Ariza-Ok "$AppDisplay $installVer installed"
        Ariza-Report $installVer
    }
    finally {
        if (Test-Path -LiteralPath $tmp)     { Remove-Item -LiteralPath $tmp -Recurse -Force }
        if (Test-Path -LiteralPath $staging) { Remove-Item -LiteralPath $staging -Recurse -Force }
    }
}

Ariza-Main
