modules/core/Utils.ps1

function Write-Info($msg)  { Write-Host "[INFO] $msg" -ForegroundColor Cyan }
function Write-Ok($msg)    { Write-Host "[OK] $msg" -ForegroundColor Green }
function Write-Warn($msg)  { Write-Host "[WARN] $msg" -ForegroundColor Yellow }
function Write-Err($msg, $code = "")   { 
    $prefix = if ($code) { "[$code] " } else { "" }
    Write-Host "[ERROR] $prefix$msg" -ForegroundColor Red 
}

$utf8 = New-Object System.Text.UTF8Encoding($false)

$Global:GeneratorManifest = $null
function Get-GeneratorManifest {
    if ($Global:GeneratorManifest) { return $Global:GeneratorManifest }
    $dir = $PSScriptRoot
    $path = Join-Path (Split-Path $dir) "generator.manifest.json"
    if (Test-Path $path) { $Global:GeneratorManifest = Get-Content $path | ConvertFrom-Json }
    return $Global:GeneratorManifest
}

function Get-MinVer($key, $default) {
    $m = Get-GeneratorManifest
    if ($m -and $m.$key) { return $m.$key }
    return $default
}

function Write-CoreFile($fullPath, $content) {
    if ($Global:DryRun) {
        # Dry run modunda sadece ne yazilacagini logla
        Write-Host " [DRY] $fullPath" -ForegroundColor DarkGray
        return
    }
    $dir = Split-Path $fullPath
    if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
    [System.IO.File]::WriteAllText($fullPath, $content)
}

function T($template, $projectName) { 
    $template.Replace("{P}", $projectName) 
}

function Get-Plural($name) {
    if ($name.EndsWith("y") -and -not $name.EndsWith("ay") -and -not $name.EndsWith("ey") -and -not $name.EndsWith("oy") -and -not $name.EndsWith("uy")) {
        return $name.Substring(0, $name.Length - 1) + "ies"
    }
    if ($name.EndsWith("s") -or $name.EndsWith("x") -or $name.EndsWith("ch") -or $name.EndsWith("sh")) {
        return $name + "es"
    }
    return $name + "s"
}

function Invoke-Step($Name, $Script) {
    if ($Global:DryRun) {
        Write-Host "[PLAN] $Name" -ForegroundColor Cyan
        & $Script
        return
    }
    Write-Host "[WAIT] $Name..." -NoNewline
    $sw = [System.Diagnostics.Stopwatch]::StartNew()
    try {
        & $Script
        $sw.Stop()
        $duration = "{0:N1}s" -f ($sw.Elapsed.TotalSeconds)
        Write-Host ("`r[OK] $Name".PadRight(50) + "[$duration]") -ForegroundColor Green
    } catch {
        $sw.Stop()
        Write-Host "`r[FAIL] $Name".PadRight(50) -ForegroundColor Red
        throw $_
    }
}

function Invoke-Cmd($cmd, $arguments) {
    if ($Global:DryRun) {
        Write-Host " [EXEC] $cmd $arguments" -ForegroundColor DarkGray
        return
    }
    # Direct execution via Invoke-Expression to handle complex strings and show output
    Invoke-Expression "$cmd $arguments"
    if ($LASTEXITCODE -ne 0) { 
        throw "Komut basarisiz: $cmd $arguments (ExitCode: $LASTEXITCODE)" 
    }
}