<#
.SYNOPSIS
  Packt die gebaute Companion-App als OTA-Bündel in die Integration.

.BESCHREIBUNG
  Erzeugt aus companion-app/dist zwei Dateien:

      custom_components/audi_dashboard/frontend/app/bundle.zip
      custom_components/audi_dashboard/frontend/app/bundle.json

  Die iOS-App lädt die Zip zur Laufzeit nach und tauscht ihre Oberfläche aus,
  ohne dass Xcode gebraucht wird (siehe companion-app/src/daten/ota.ts).

  WARUM DAS BÜNDEL IN DER INTEGRATION LIEGT
  -----------------------------------------
  Weil es dann von selbst dorthin kommt, wo die App es sucht. install.ps1
  (der einzige Installationsweg - HACS scheidet aus, siehe VERSIONIERUNG.md)
  kopiert den Ordner custom_components/audi_dashboard/ vollständig - das
  Bündel reist bei jedem Lauf mit, ohne zweiten Auslieferungsweg. Ein
  Ablageort unter /config/www/ hätte einen eigenen Weg dorthin gebraucht, den
  man beim Ausliefern hätte vergessen können.

  Der Nebeneffekt ist der eigentliche Gewinn: Bündel und Integration können
  gar nicht auseinanderlaufen, weil sie dieselbe Datei sind.

  DREI DINGE, DIE HIER BEWUSST SO SIND
  ------------------------------------
  1. **Sourcemaps fliegen raus.** Sie machen rund zwei Drittel von dist/ aus
     (1,4 von 2,0 MB) und werden auf dem Gerät nie gebraucht. Sie mitzuladen
     hieße, bei jedem Update das Dreifache über die Leitung zu schicken.

  2. **Die Einträge werden von Hand geschrieben, nicht von einer Bequem-API.**
     Die Zip-Spezifikation verlangt "/" als Trennzeichen. Windows PowerShell
     5.1 läuft auf dem .NET Framework, und dort schreiben SOWOHL
     Compress-Archive ALS AUCH [IO.Compression.ZipFile]::CreateFromDirectory
     Backslashes in die Einträge von Unterordnern (in .NET Core behoben, hier
     aber nicht verfügbar). Nachgemessen: die erste Fassung dieses Skripts
     erzeugte "assets\index-*.js" statt "assets/index-*.js" - iOS hätte daraus
     Dateien mit Backslash im Namen gemacht statt einen Ordner, und die App
     wäre mit fehlenden Assets gestartet.

     Deshalb ZipFile::Open plus CreateEntryFromFile mit selbst gebautem
     Eintragsnamen: dann steht genau das im Archiv, was hier steht. Der Inhalt
     liegt ohne umschließenden Ordner in der Wurzel, index.html also direkt
     dort - genau das erwartet das Plugin; fehlt es, wirft set() "no index.html
     file inside the bundle folder".

  3. **SHA-256 über die Zip, klein geschrieben.** Das Plugin rechnet auf dem
     Gerät denselben Hash über die heruntergeladene Datei (calcChecksum in
     CapgoUpdater.swift) und verwirft das Bündel bei Abweichung. Ein
     abgeschnittener Download oder eine halb gespiegelte Datei fällt damit
     auf, bevor die App sie startet.

.PARAMETER Bauen
  Vorher "npm run build" ausführen. Ohne das wird der Inhalt von dist/
  genommen, wie er gerade daliegt.

.BEISPIEL
  .\ota-paket.ps1 -Bauen
  npm run ota
#>
param(
    [switch]$Bauen
)

$ErrorActionPreference = "Stop"
Add-Type -AssemblyName System.IO.Compression
Add-Type -AssemblyName System.IO.Compression.FileSystem

$hier     = $PSScriptRoot
$app      = Split-Path $hier -Parent
$projekt  = Split-Path $app -Parent
$dist     = Join-Path $app "dist"
$manifest = Join-Path $projekt "custom_components\audi_dashboard\manifest.json"
$ziel     = Join-Path $projekt "custom_components\audi_dashboard\frontend\app"

function Gut($t)  { Write-Host "  [ok]   $t" -ForegroundColor Green }
function Info($t) { Write-Host "  [info] $t" -ForegroundColor DarkGray }

Write-Host ""
Write-Host "  OTA-Bündel bauen" -ForegroundColor White
Write-Host "  ================" -ForegroundColor White

if ($Bauen) {
    Info "npm run build ..."
    Push-Location $app
    try { npm run build; if ($LASTEXITCODE -ne 0) { throw "npm run build fehlgeschlagen" } }
    finally { Pop-Location }
}

if (-not (Test-Path (Join-Path $dist "index.html"))) {
    throw "$dist\index.html fehlt - erst 'npm run build' laufen lassen (oder -Bauen benutzen)."
}

$version = (Get-Content $manifest -Raw | ConvertFrom-Json).version
if (-not $version) { throw "Kein 'version'-Feld in $manifest" }
Gut "Version: $version"

# In einen Zwischenordner spiegeln, damit die Sourcemaps nicht mitwandern -
# dist/ selbst bleibt unangetastet (die Maps sind beim Entwickeln nützlich).
$tmp = Join-Path ([IO.Path]::GetTempPath()) ("dm360-ota-" + [Guid]::NewGuid().ToString("N"))
New-Item -ItemType Directory -Force -Path $tmp | Out-Null
try {
    Copy-Item "$dist\*" $tmp -Recurse -Force
    $maps = Get-ChildItem $tmp -Recurse -File -Filter "*.map"
    $maps | ForEach-Object { Remove-Item $_.FullName -Force }
    Info "$($maps.Count) Sourcemap(s) ausgelassen"

    if (-not (Test-Path (Join-Path $tmp "index.html"))) {
        throw "index.html liegt nicht in der Wurzel des Bündels - das Plugin würde es ablehnen."
    }

    New-Item -ItemType Directory -Force -Path $ziel | Out-Null
    $zip = Join-Path $ziel "bundle.zip"
    if (Test-Path $zip) { Remove-Item $zip -Force }

    # Eintragsnamen selbst bauen (siehe Punkt 2 im Kopfkommentar): relativer
    # Pfad ab dem Zwischenordner, Backslashes durch "/" ersetzt.
    $archiv = [IO.Compression.ZipFile]::Open($zip, [System.IO.Compression.ZipArchiveMode]::Create)
    try {
        foreach ($datei in Get-ChildItem $tmp -Recurse -File | Sort-Object FullName) {
            $name = $datei.FullName.Substring($tmp.Length + 1).Replace("\", "/")
            [void][IO.Compression.ZipFileExtensions]::CreateEntryFromFile(
                $archiv, $datei.FullName, $name, [IO.Compression.CompressionLevel]::Optimal)
        }
    }
    finally { $archiv.Dispose() }

    $hash   = (Get-FileHash $zip -Algorithm SHA256).Hash.ToLower()
    $groesse = (Get-Item $zip).Length
    Gut ("bundle.zip  {0:N0} Bytes" -f $groesse)
    Gut "sha256      $hash"

    # Kleingeschriebener Hex-Hash, weil das Plugin auf dem Geraet ebenfalls
    # kleingeschrieben vergleicht.
    $daten = [ordered]@{
        version = $version
        sha256  = $hash
        bytes   = $groesse
        gebaut  = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
    }
    $json = ($daten | ConvertTo-Json -Compress)
    [IO.File]::WriteAllText((Join-Path $ziel "bundle.json"), $json, (New-Object Text.UTF8Encoding($false)))
    Gut "bundle.json geschrieben"
}
finally {
    Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue
}

Write-Host ""
Write-Host "  Fertig. Das Bündel wird mit der Integration ausgeliefert -" -ForegroundColor Green
Write-Host "  homeassistant\installationspaket\Installieren.cmd nimmt es automatisch mit."
Write-Host ""
