param(
    [string]$InstallDir = "",
    [switch]$NoPath,
    [switch]$Uninstall,
    [switch]$Quiet
)

$ErrorActionPreference = "Stop"
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
$script:InstallLogPath = ""

function Initialize-InstallLog {
    $base = if (-not [string]::IsNullOrWhiteSpace($env:LOCALAPPDATA)) {
        Join-Path $env:LOCALAPPDATA "Sura\Logs"
    } else {
        Join-Path ([System.IO.Path]::GetTempPath()) "Sura\Logs"
    }
    New-Item -ItemType Directory -Force -Path $base | Out-Null
    $script:InstallLogPath = Join-Path $base ("install-{0}-{1}.log" -f [DateTime]::Now.ToString("yyyyMMdd-HHmmss"), $PID)
    [System.IO.File]::WriteAllText(
        $script:InstallLogPath,
        ("Sura Language installer {0}`r`nStarted: {1}`r`n" -f "1.11.1", [DateTime]::Now.ToString("o")),
        $utf8NoBom
    )
}

function Write-InstallLog {
    param([string]$Message)
    if ([string]::IsNullOrWhiteSpace($script:InstallLogPath)) { return }
    Add-Content -LiteralPath $script:InstallLogPath -Encoding UTF8 -Value ("[{0}] {1}" -f [DateTime]::Now.ToString("o"), $Message)
}

trap {
    $message = $_.Exception.Message
    Write-InstallLog ("FAILED: {0}`r`n{1}" -f $message, ($_ | Out-String).Trim())
    [Console]::Error.WriteLine("Sura Language installation failed: $message")
    if (-not [string]::IsNullOrWhiteSpace($script:InstallLogPath)) {
        [Console]::Error.WriteLine("Installation log: $script:InstallLogPath")
    }
    exit 1
}

function Write-Step {
    param([string]$Message)
    Write-InstallLog $Message
    if (-not $Quiet) { Write-Host $Message }
}

function Assert-FileReplaceable {
    param([string]$Path, [string]$DisplayName)
    if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { return }

    $lastError = ""
    for ($attempt = 0; $attempt -lt 12; $attempt++) {
        $stream = $null
        try {
            $stream = [System.IO.File]::Open(
                $Path,
                [System.IO.FileMode]::Open,
                [System.IO.FileAccess]::ReadWrite,
                [System.IO.FileShare]::None
            )
            return
        } catch {
            $lastError = $_.Exception.Message
            if ($attempt -lt 11) { Start-Sleep -Milliseconds 250 }
        } finally {
            if ($null -ne $stream) { $stream.Dispose() }
        }
    }

    $processText = ""
    try {
        $fullPath = [System.IO.Path]::GetFullPath($Path)
        $matches = @(Get-Process -ErrorAction SilentlyContinue | Where-Object {
            try {
                -not [string]::IsNullOrWhiteSpace($_.Path) -and
                    [string]::Equals([System.IO.Path]::GetFullPath($_.Path), $fullPath, [System.StringComparison]::OrdinalIgnoreCase)
            } catch {
                $false
            }
        } | ForEach-Object { "{0} (PID {1})" -f $_.ProcessName, $_.Id })
        if ($matches.Count -gt 0) {
            $processText = " Running: " + ($matches -join ", ") + "."
        }
    } catch {
        $processText = ""
    }

    Write-InstallLog ("Locked file: {0}; detail: {1}" -f $Path, $lastError)
    throw "$DisplayName is in use and cannot be updated.$processText Close Sura terminals, the Sura REPL, and VS Code windows using Sura, then retry the installer."
}

function Normalize-PathText {
    param([string]$Path)
    if ([string]::IsNullOrWhiteSpace($Path)) { return "" }
    return ([System.IO.Path]::GetFullPath($Path)).TrimEnd('\', '/')
}

function Get-DefaultInstallDir {
    if (-not [string]::IsNullOrWhiteSpace($env:LOCALAPPDATA)) {
        return (Join-Path $env:LOCALAPPDATA "Programs\Sura")
    }
    return (Join-Path $HOME ".sura")
}

function Get-DirectoryByteSize {
    param([string]$Path)
    if (-not (Test-Path -LiteralPath $Path)) { return 0L }
    $total = 0L
    foreach ($file in Get-ChildItem -LiteralPath $Path -Recurse -File -ErrorAction SilentlyContinue) {
        $total += [int64]$file.Length
    }
    return $total
}

function Format-ByteSize {
    param([int64]$Bytes)
    if ($Bytes -ge 1GB) { return ("{0:N1} GB" -f ($Bytes / 1GB)) }
    if ($Bytes -ge 1MB) { return ("{0:N1} MB" -f ($Bytes / 1MB)) }
    if ($Bytes -ge 1KB) { return ("{0:N1} KB" -f ($Bytes / 1KB)) }
    return ("{0} bytes" -f $Bytes)
}

function Get-UserPathParts {
    $raw = [Environment]::GetEnvironmentVariable("Path", "User")
    if ([string]::IsNullOrWhiteSpace($raw)) { return @() }
    return @($raw -split ";" | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
}

function Set-UserPathParts {
    param([string[]]$Parts)
    [Environment]::SetEnvironmentVariable("Path", ($Parts -join ";"), "User")
}

function Add-UserPathEntry {
    param([string]$Entry)
    $want = Normalize-PathText $Entry
    $parts = @(Get-UserPathParts | Where-Object {
        -not [string]::Equals((Normalize-PathText $_), $want, [System.StringComparison]::OrdinalIgnoreCase)
    })
    $parts = @($Entry) + $parts
    Set-UserPathParts $parts
}

function Remove-UserPathEntry {
    param([string]$Entry)
    $want = Normalize-PathText $Entry
    $parts = @(Get-UserPathParts | Where-Object {
        -not [string]::Equals((Normalize-PathText $_), $want, [System.StringComparison]::OrdinalIgnoreCase)
    })
    Set-UserPathParts $parts
}

function Write-CmdLauncher {
    param([string]$Path, [string]$Target)
    $content = "@echo off`r`n`"%~dp0$Target`" %*`r`n"
    [System.IO.File]::WriteAllText($Path, $content, [System.Text.Encoding]::ASCII)
}

function Resolve-PayloadDir {
    $sourceRoot = if (-not [string]::IsNullOrWhiteSpace($PSScriptRoot)) {
        $PSScriptRoot
    } else {
        Split-Path -Parent $PSCommandPath
    }
    foreach ($candidate in @(
        (Join-Path $sourceRoot "payload"),
        (Join-Path (Split-Path -Parent $sourceRoot) "payload")
    )) {
        if (Test-Path -LiteralPath $candidate) {
            return $candidate
        }
    }
    return (Join-Path $sourceRoot "payload")
}

if ([string]::IsNullOrWhiteSpace($InstallDir)) {
    $InstallDir = Get-DefaultInstallDir
}
Initialize-InstallLog
$payloadBytes = Get-DirectoryByteSize (Join-Path $PSScriptRoot "payload")
$estimatedInstallBytes = $payloadBytes + 1MB
$InstallDir = [System.IO.Path]::GetFullPath($InstallDir)
$binDir = Join-Path $InstallDir "bin"

if ($Uninstall) {
    if (-not $NoPath) {
        Remove-UserPathEntry $binDir
    }
    if (Test-Path -LiteralPath $InstallDir) {
        Remove-Item -LiteralPath $InstallDir -Recurse -Force
    }
    Write-Step "Sura Language uninstalled from $InstallDir"
    return
}

$payload = Resolve-PayloadDir
$engineSource = Join-Path $payload "SuraLanguage.exe"
$pkgSource = Join-Path $payload "surapkg.exe"
if (-not (Test-Path -LiteralPath $engineSource)) {
    throw "installer payload is missing the Sura Language engine"
}

New-Item -ItemType Directory -Force -Path $binDir | Out-Null
$engineTarget = Join-Path $binDir "SuraLanguage.exe"
$pkgTarget = Join-Path $binDir "surapkg.exe"
Assert-FileReplaceable $engineTarget "The installed Sura Language engine"
Assert-FileReplaceable $pkgTarget "The installed Sura package manager"
foreach ($legacyInstalledName in @("SuraFinal.exe", "SuraEngine.exe")) {
    $legacyInstalledEngine = Join-Path $binDir $legacyInstalledName
    if (Test-Path -LiteralPath $legacyInstalledEngine -PathType Leaf) {
        Assert-FileReplaceable $legacyInstalledEngine "The legacy Sura engine"
        Remove-Item -LiteralPath $legacyInstalledEngine -Force
    }
}
Copy-Item -LiteralPath $engineSource -Destination $engineTarget -Force
if (Test-Path -LiteralPath $pkgSource) {
    Copy-Item -LiteralPath $pkgSource -Destination $pkgTarget -Force
}

Write-CmdLauncher (Join-Path $binDir "sura.cmd") "SuraLanguage.exe"
if (Test-Path -LiteralPath (Join-Path $binDir "surapkg.exe")) {
    Write-CmdLauncher (Join-Path $binDir "surapkg.cmd") "surapkg.exe"
}

$installInfo = [ordered]@{
    schema = "sura.installation.v1"
    product_name = "Sura Language"
    version = "1.11.1"
    installed_utc = [DateTime]::UtcNow.ToString("o")
    install_dir = $InstallDir
    bin_dir = $binDir
    commands = @("sura", "surapkg")
    engine = "SuraLanguage.exe"
    path_added = (-not $NoPath)
}
[System.IO.File]::WriteAllText(
    (Join-Path $InstallDir "installation.json"),
    ($installInfo | ConvertTo-Json -Depth 4),
    $utf8NoBom
)

if (-not $NoPath) {
    Add-UserPathEntry $binDir
    Write-Step "Added $binDir to the front of the user PATH. Open a new terminal before running sura."
}

Write-Step "Sura Language installed to $InstallDir"
Write-Step "Run: sura --help"