<#
.SYNOPSIS
    Automatyczne budowanie paczki aktualizacji shopPRO na podstawie git diff miedzy tagami.

.DESCRIPTION
    Skrypt porownuje dwa tagi git, filtruje pliki przez .updateignore,
    tworzy ZIP + manifest JSON i aktualizuje changelog.php oraz versions.php.

.PARAMETER FromTag
    Tag poczatkowy (np. v0.299). Domyslnie: ostatni tag.

.PARAMETER ToTag
    Tag docelowy (np. v0.300). Wymagany.

.PARAMETER ChangelogEntry
    Wpis do changelogu. Wymagany (chyba ze -DryRun).

.PARAMETER DryRun
    Tylko pokaz co zostaloby zrobione, bez tworzenia plikow.

.EXAMPLE
    ./build-update.ps1 -ToTag v0.300 -ChangelogEntry "NEW - Manifest-based update system"
    ./build-update.ps1 -FromTag v0.299 -ToTag v0.300 -ChangelogEntry "NEW - opis" -DryRun
#>

param(
    [string]$FromTag = "",
    [Parameter(Mandatory=$true)]
    [string]$ToTag,
    [string]$ChangelogEntry = "",
    [switch]$DryRun
)

$ErrorActionPreference = "Stop"
$Utf8NoBom = New-Object System.Text.UTF8Encoding $false

# --- Helpers ---

function Write-Step($msg) {
    Write-Host "  [*] $msg" -ForegroundColor Cyan
}

function Write-Ok($msg) {
    Write-Host "  [OK] $msg" -ForegroundColor Green
}

function Write-Warn($msg) {
    Write-Host "  [!] $msg" -ForegroundColor Yellow
}

function Write-Err($msg) {
    Write-Host "  [ERROR] $msg" -ForegroundColor Red
}

# --- 1. Walidacja tagow ---

Write-Host "`n=== shopPRO Build Update ===" -ForegroundColor White

if (-not $FromTag) {
    $FromTag = (git describe --tags --abbrev=0 2>$null)
    if (-not $FromTag) {
        Write-Err "Nie znaleziono zadnego taga. Uzyj parametru -FromTag."
        exit 1
    }
    Write-Step "Auto-detect FromTag: $FromTag"
}

# Sprawdz czy tagi istnieja
$tagExists = git tag -l $FromTag
if (-not $tagExists) {
    Write-Err "Tag '$FromTag' nie istnieje."
    exit 1
}

$toTagExists = git tag -l $ToTag
if (-not $toTagExists) {
    Write-Warn "Tag '$ToTag' nie istnieje. Uzywam HEAD jako punktu docelowego."
    $diffTarget = "HEAD"
} else {
    $diffTarget = $ToTag
}

# --- 2. Wersja i katalog ---

$versionNumber = $ToTag -replace '^v', ''
$versionInt = [int]($versionNumber -replace '^0\.', '')

# Oblicz katalog: 0.001-0.009 -> 0.00, 0.010-0.099 -> 0.00, 0.100-0.199 -> 0.10, 0.200-0.299 -> 0.20, 0.300-0.399 -> 0.30
$dirTens = [math]::Floor($versionInt / 100)
$dirStr = "0.{0}0" -f ($dirTens * 10).ToString().PadLeft(1, '0')

# Format: jesli dirTens < 10 to "0.X0", np 0.00, 0.10, 0.20, 0.30
if ($dirTens -lt 10) {
    $dirStr = "0.{0}0" -f $dirTens.ToString().PadLeft(1, '0')
} else {
    $dirStr = "0.{0}0" -f $dirTens
}

Write-Step "Wersja: $versionNumber (int: $versionInt)"
Write-Step "Katalog: updates/$dirStr/"

# --- 3. Git diff ---

Write-Step "Porownywanie: $FromTag..$diffTarget"

$diffOutput = git diff --name-status "$FromTag..$diffTarget" 2>&1
if ($LASTEXITCODE -ne 0) {
    Write-Err "git diff nie powiodl sie: $diffOutput"
    exit 1
}

$addedFiles = @()
$modifiedFiles = @()
$deletedFiles = @()
$renamedFiles = @()

foreach ($line in ($diffOutput -split "`n")) {
    $line = $line.Trim()
    if (-not $line) { continue }

    $parts = $line -split "`t"
    $status = $parts[0]
    $filePath = if ($parts.Count -gt 1) { $parts[1] } else { "" }

    # Zamien backslash na slash
    $filePath = $filePath -replace '\\', '/'

    switch -Wildcard ($status) {
        "A"  { $addedFiles += $filePath }
        "M"  { $modifiedFiles += $filePath }
        "D"  { $deletedFiles += $filePath }
        "R*" {
            # Rename: stary plik = deleted, nowy = added
            $newPath = if ($parts.Count -gt 2) { $parts[2] -replace '\\', '/' } else { "" }
            $deletedFiles += $filePath
            if ($newPath) { $addedFiles += $newPath }
        }
    }
}

Write-Step "Zmiany: A=$($addedFiles.Count), M=$($modifiedFiles.Count), D=$($deletedFiles.Count)"

# --- 4. Filtrowanie przez .updateignore ---

$ignorePatterns = @()
$ignoreFile = ".updateignore"

if (Test-Path $ignoreFile) {
    $ignorePatterns = Get-Content $ignoreFile | ForEach-Object { $_.Trim() } | Where-Object { $_ -and ($_ -notmatch '^\s*#') }
    Write-Step "Zaladowano $($ignorePatterns.Count) wzorcow z .updateignore"
}

function Test-Ignored {
    param([string]$FilePath)

    foreach ($pattern in $ignorePatterns) {
        # Wzorzec katalogu (konczy sie na /)
        if ($pattern.EndsWith('/')) {
            $dirPattern = $pattern.TrimEnd('/')
            if ($FilePath -like "$dirPattern/*" -or $FilePath -eq $dirPattern) {
                return $true
            }
        }
        # Wzorzec z wildcard
        elseif ($pattern.Contains('*')) {
            # *.md -> dopasuj nazwe pliku
            if ($pattern.StartsWith('*')) {
                $fileName = Split-Path $FilePath -Leaf
                if ($fileName -like $pattern) { return $true }
            }
            # Zwykly glob
            elseif ($FilePath -like $pattern) { return $true }
        }
        # Dokladne dopasowanie
        else {
            if ($FilePath -eq $pattern) { return $true }
        }
    }
    return $false
}

$filteredAdded = @()
$filteredModified = @()
$filteredDeleted = @()
$ignoredCount = 0

foreach ($f in $addedFiles) {
    if (Test-Ignored $f) { $ignoredCount++; continue }
    $filteredAdded += $f
}
foreach ($f in $modifiedFiles) {
    if (Test-Ignored $f) { $ignoredCount++; continue }
    $filteredModified += $f
}
foreach ($f in $deletedFiles) {
    if (Test-Ignored $f) { $ignoredCount++; continue }
    $filteredDeleted += $f
}

Write-Step "Po filtrowaniu: A=$($filteredAdded.Count), M=$($filteredModified.Count), D=$($filteredDeleted.Count) (pominieto: $ignoredCount)"

# Rozdziel usuniete pliki i katalogi
$deletedDirs = @()
$deletedFilesOnly = @()

foreach ($f in $filteredDeleted) {
    # Sprawdz czy to byl katalog (w git nie ma katalogow, ale mozemy sprawdzic po wzorcu)
    # Git nie trackuje pustych katalogow, wiec deleted entries to zawsze pliki
    $deletedFilesOnly += $f
}

# --- 5. Odczyt migracji SQL ---

$sqlQueries = @()
$migrationFile = "migrations/$versionNumber.sql"

if (Test-Path $migrationFile) {
    # Read entire file, strip comment lines, split by semicolons to get complete SQL statements
    $rawLines = Get-Content $migrationFile | Where-Object { $_.Trim() -ne '' -and $_.Trim() -notmatch '^\s*--' }
    $rawSql = ($rawLines -join "`n").Trim()
    $sqlQueries = @($rawSql -split ';' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' } | ForEach-Object { $_.ToString() })
    Write-Step "Znaleziono migracje SQL: $migrationFile ($($sqlQueries.Count) zapytan)"
} else {
    Write-Step "Brak migracji SQL ($migrationFile nie istnieje)"
}

# --- 6. Podsumowanie ---

$filesToPack = $filteredAdded + $filteredModified

if ($filesToPack.Count -eq 0 -and $filteredDeleted.Count -eq 0 -and $sqlQueries.Count -eq 0) {
    Write-Warn "Brak zmian do pakowania (po filtrowaniu). Przerywam."
    exit 0
}

Write-Host "`n--- Pliki do paczki ---" -ForegroundColor White
foreach ($f in $filteredAdded) { Write-Host "  A  $f" -ForegroundColor Green }
foreach ($f in $filteredModified) { Write-Host "  M  $f" -ForegroundColor Yellow }
foreach ($f in $filteredDeleted) { Write-Host "  D  $f" -ForegroundColor Red }

if ($sqlQueries.Count -gt 0) {
    Write-Host "`n--- SQL ---" -ForegroundColor White
    foreach ($q in $sqlQueries) { Write-Host "  $q" -ForegroundColor Gray }
}

# --- DryRun? ---

if ($DryRun) {
    Write-Host "`n[DRY RUN] Zadne pliki nie zostaly utworzone.`n" -ForegroundColor Magenta
    exit 0
}

# --- 7. Walidacja ChangelogEntry ---

if (-not $ChangelogEntry) {
    Write-Err "Parametr -ChangelogEntry jest wymagany (chyba ze uzywasz -DryRun)."
    exit 1
}

# --- 8. Tworzenie temp i kopiowanie plikow ---

$tempDir = "$env:TEMP\shopPRO_build_$versionInt"

if (Test-Path $tempDir) {
    Remove-Item -Recurse -Force $tempDir
}

if (-not (Test-Path $tempDir)) {
    New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
}

foreach ($f in $filesToPack) {
    $destPath = Join-Path $tempDir $f
    $destDir = Split-Path $destPath -Parent

    if (-not (Test-Path $destDir)) {
        New-Item -ItemType Directory -Path $destDir -Force | Out-Null
    }

    if (Test-Path $f) {
        Copy-Item $f $destPath -Force
    } else {
        Write-Warn "Plik nie istnieje (moze zostal usuniety po TAGU): $f"
    }
}

Write-Ok "Skopiowano $($filesToPack.Count) plikow do $tempDir"

# --- 9. Tworzenie ZIP ---

$updatesDir = "updates/$dirStr"
if (-not (Test-Path $updatesDir)) {
    New-Item -ItemType Directory -Path $updatesDir -Force | Out-Null
}

$zipPath = "$updatesDir/ver_$versionNumber.zip"

if (Test-Path $zipPath) {
    Remove-Item $zipPath -Force
}

# Pakuj zawartosc temp dir (bez folderu temp/)
$originalLocation = Get-Location
$absoluteZipPath = Join-Path $originalLocation $zipPath
Set-Location $tempDir
$tempItems = Get-ChildItem -Force
if ($tempItems) {
    Compress-Archive -Path '*' -DestinationPath $absoluteZipPath -Force
} else {
    # SQL-only update: create minimal ZIP with empty placeholder
    $placeholderPath = "_sql_only_update.txt"
    Set-Content -Path $placeholderPath -Value "SQL-only update $versionNumber"
    Compress-Archive -Path $placeholderPath -DestinationPath $absoluteZipPath -Force
    Remove-Item $placeholderPath -Force
}
Set-Location $originalLocation

Write-Ok "Utworzono ZIP: $zipPath"

# --- 10. Checksum SHA256 ---

$hash = (Get-FileHash $zipPath -Algorithm SHA256).Hash.ToLower()
Write-Ok "SHA256: $hash"

# --- 11. Manifest JSON ---

$manifest = @{
    version = $versionNumber
    date = (Get-Date -Format "yyyy-MM-dd")
    checksum_zip = "sha256:$hash"
    files = @{
        modified = $filteredModified
        added = $filteredAdded
        deleted = $deletedFilesOnly
    }
    directories_deleted = $deletedDirs
    sql = $sqlQueries
    changelog = $ChangelogEntry
}

$manifestJson = $manifest | ConvertTo-Json -Depth 4
$manifestPath = "$updatesDir/ver_${versionNumber}_manifest.json"
[System.IO.File]::WriteAllText($manifestPath, $manifestJson, $Utf8NoBom)

Write-Ok "Utworzono manifest: $manifestPath"

# --- 12. Legacy _sql.txt i _files.txt (okres przejsciowy) ---

if ($sqlQueries.Count -gt 0) {
    $sqlPath = "$updatesDir/ver_${versionNumber}_sql.txt"
    [System.IO.File]::WriteAllText($sqlPath, ($sqlQueries -join "`n"), $Utf8NoBom)
    Write-Ok "Utworzono legacy SQL: $sqlPath"
}

if ($deletedFilesOnly.Count -gt 0 -or $deletedDirs.Count -gt 0) {
    $filesContent = @()
    foreach ($f in $deletedFilesOnly) { $filesContent += "F: ../$f" }
    foreach ($d in $deletedDirs) { $filesContent += "D: ../$d" }

    $filesPath = "$updatesDir/ver_${versionNumber}_files.txt"
    [System.IO.File]::WriteAllText($filesPath, ($filesContent -join "`n"), $Utf8NoBom)
    Write-Ok "Utworzono legacy files: $filesPath"
}

# --- 13. Aktualizacja versions.php ---

$versionsFile = "updates/versions.php"
if (Test-Path $versionsFile) {
    $content = Get-Content $versionsFile -Raw
    $content = $content -replace '\$current_ver\s*=\s*\d+;', "`$current_ver = $versionInt;"
    [System.IO.File]::WriteAllText($versionsFile, $content, $Utf8NoBom)
    Write-Ok "Zaktualizowano versions.php: `$current_ver = $versionInt"
}

# --- 14. Aktualizacja changelog-data.html ---

$changelogFile = "updates/changelog-data.html"
if (Test-Path $changelogFile) {
    $dateStr = Get-Date -Format "dd.MM.yyyy"
    $newEntry = "<b>ver. $versionNumber - $dateStr</b><br />`n$ChangelogEntry`n<hr>`n"

    $changelogContent = [System.IO.File]::ReadAllText($changelogFile, $Utf8NoBom)
    $changelogContent = $newEntry + $changelogContent
    [System.IO.File]::WriteAllText($changelogFile, $changelogContent, $Utf8NoBom)
    Write-Ok "Zaktualizowano changelog-data.html"
}

# --- 15. Cleanup ---

if (Test-Path $tempDir) {
    Remove-Item -Recurse -Force $tempDir
}
# Usun pusty folder temp jesli nie ma juz w nim nic
if ((Test-Path "temp") -and ((Get-ChildItem "temp" -Force).Count -eq 0)) {
    Remove-Item "temp" -Force
}

Write-Ok "Wyczyszczono pliki tymczasowe"

# --- Podsumowanie ---

Write-Host "`n=== Gotowe ===" -ForegroundColor Green
Write-Host "  ZIP:      $zipPath"
Write-Host "  Manifest: $manifestPath"
Write-Host "  Wersja:   $versionNumber (int: $versionInt)"
Write-Host ""
