param(
    [switch]$Interactive,
    [string]$Uri = "",
    [string]$ManifestUrlOverride = "",
    [string]$UpdateRootOverride = "",
    [string]$UxpDestOverride = "",
    [string]$CepDestOverride = "",
    [switch]$IgnorePhotoshop
)

$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"

$manifestUrl = if ($ManifestUrlOverride) { $ManifestUrlOverride } else { "https://pschajian.qiyefu.top/files/update.json" }
$updateRoot = if ($UpdateRootOverride) { $UpdateRootOverride } else { Join-Path $env:LOCALAPPDATA "HuiCraft" }
$logPath = Join-Path $updateRoot "update.log"
$statusPath = Join-Path $updateRoot "status.json"
$pendingRoot = Join-Path $updateRoot "pending"
$uxpDest = if ($UxpDestOverride) { $UxpDestOverride } else { Join-Path $env:APPDATA "Adobe\UXP\Plugins\External\com.qiyefu.huijiang" }
$cepDest = if ($CepDestOverride) { $CepDestOverride } else { Join-Path $env:APPDATA "Adobe\CEP\extensions\com.qiyefu.huijiang.cep" }
$mutex = New-Object System.Threading.Mutex($false, "Local\HuiCraftUpdater")
$hasMutex = $false

function Write-UpdateLog([string]$message) {
    New-Item -ItemType Directory -Force -Path $updateRoot | Out-Null
    $line = (Get-Date -Format "yyyy-MM-dd HH:mm:ss") + " " + $message
    Add-Content -LiteralPath $logPath -Value $line -Encoding UTF8
    if ((Get-Item -LiteralPath $logPath).Length -gt 1048576) {
        Get-Content -LiteralPath $logPath -Tail 300 | Set-Content -LiteralPath $logPath -Encoding UTF8
    }
}

function Write-UpdateStatus([string]$state, [string]$version, [string]$message) {
    New-Item -ItemType Directory -Force -Path $updateRoot | Out-Null
    $obj = @{ state = $state; version = $version; message = $message;
              updated_at = [DateTimeOffset]::Now.ToUnixTimeSeconds() }
    $obj | ConvertTo-Json -Compress | Set-Content -LiteralPath $statusPath -Encoding UTF8
}

function Test-PhotoshopRunning {
    if ($IgnorePhotoshop) { return $false }
    return $null -ne (Get-Process -Name "Photoshop" -ErrorAction SilentlyContinue)
}

function Get-CurrentVersion {
    $manifestPath = Join-Path $uxpDest "manifest.json"
    if (Test-Path -LiteralPath $manifestPath) {
        try { return [string](Get-Content -LiteralPath $manifestPath -Raw -Encoding UTF8 | ConvertFrom-Json).version } catch {}
    }
    return "0.0.0"
}

function Test-Payload([string]$payload) {
    return ((Test-Path -LiteralPath (Join-Path $payload "uxp\manifest.json")) -and
            (Test-Path -LiteralPath (Join-Path $payload "cep\CSXS\manifest.xml")))
}

function Install-PluginTree([string]$source, [string]$destination) {
    $parent = Split-Path -Parent $destination
    $stage = $destination + ".update"
    $backup = $destination + ".backup"
    New-Item -ItemType Directory -Force -Path $parent | Out-Null
    if (Test-Path -LiteralPath $stage) { Remove-Item -LiteralPath $stage -Recurse -Force }
    if (Test-Path -LiteralPath $backup) { Remove-Item -LiteralPath $backup -Recurse -Force }
    Copy-Item -LiteralPath $source -Destination $stage -Recurse -Force
    if (Test-Path -LiteralPath $destination) { Move-Item -LiteralPath $destination -Destination $backup }
    try {
        Move-Item -LiteralPath $stage -Destination $destination
        if (Test-Path -LiteralPath $backup) { Remove-Item -LiteralPath $backup -Recurse -Force }
    } catch {
        if (Test-Path -LiteralPath $destination) { Remove-Item -LiteralPath $destination -Recurse -Force }
        if (Test-Path -LiteralPath $backup) { Move-Item -LiteralPath $backup -Destination $destination }
        throw
    }
}

function Get-OrDownloadPayload($manifest) {
    $version = [string]$manifest.version
    $pending = Join-Path $pendingRoot $version
    if (Test-Payload $pending) { return $pending }

    $work = Join-Path $updateRoot ("download-" + [guid]::NewGuid().ToString("N"))
    New-Item -ItemType Directory -Force -Path $work | Out-Null
    try {
        Write-UpdateStatus "downloading" $version "Downloading update"
        $zipPath = Join-Path $work "update.zip"
        Invoke-WebRequest -UseBasicParsing -Uri $manifest.package_url -OutFile $zipPath -TimeoutSec 300
        $actualHash = (Get-FileHash -LiteralPath $zipPath -Algorithm SHA256).Hash.ToLowerInvariant()
        if ($actualHash -ne ([string]$manifest.sha256).ToLowerInvariant()) { throw "Update package checksum mismatch" }
        Expand-Archive -LiteralPath $zipPath -DestinationPath $work -Force
        $payload = Join-Path $work "update"
        if (-not (Test-Payload $payload)) { throw "Update payload is incomplete" }
        New-Item -ItemType Directory -Force -Path $pendingRoot | Out-Null
        if (Test-Path -LiteralPath $pending) { Remove-Item -LiteralPath $pending -Recurse -Force }
        Move-Item -LiteralPath $payload -Destination $pending
        Write-UpdateLog ("Downloaded and verified v" + $version + "; waiting for Photoshop to exit")
        Write-UpdateStatus "ready" $version "Downloaded; waiting for Photoshop to exit"
        return $pending
    } finally {
        if (Test-Path -LiteralPath $work) { Remove-Item -LiteralPath $work -Recurse -Force -ErrorAction SilentlyContinue }
    }
}

try {
    $hasMutex = $mutex.WaitOne(0)
    if (-not $hasMutex) { exit 0 }
    New-Item -ItemType Directory -Force -Path $updateRoot | Out-Null
    $manifest = Invoke-RestMethod -Uri $manifestUrl -Method Get -TimeoutSec 30
    if (-not $manifest.version -or -not $manifest.package_url -or -not $manifest.sha256) {
        throw "Update manifest is incomplete"
    }

    $current = Get-CurrentVersion
    $target = [string]$manifest.version
    if ([version]$target -le [version]$current) {
        Write-UpdateStatus "current" $current "Already current"
        exit 0
    }

    $payload = Get-OrDownloadPayload $manifest
    if (Test-PhotoshopRunning) {
        if (-not $Interactive) { exit 0 }
        Write-UpdateStatus "waiting" $target "Downloaded; waiting for Photoshop to exit"
        while (Test-PhotoshopRunning) { Start-Sleep -Seconds 2 }
    }

    Write-UpdateStatus "installing" $target "Installing update"
    Install-PluginTree (Join-Path $payload "uxp") $uxpDest
    Install-PluginTree (Join-Path $payload "cep") $cepDest
    # Destination overrides are test-only. Never let an E2E sandbox rewrite the
    # real Photoshop registration to a temporary directory.
    if (-not $UxpDestOverride) {
        for ($psVersion = 23; $psVersion -le 40; $psVersion++) {
            $regPath = "HKCU:\SOFTWARE\Adobe\UXP\PluginsStorage\PHSP\$psVersion\External\com.qiyefu.huijiang"
            if (Test-Path $regPath) {
                Set-ItemProperty -Path $regPath -Name "manifestPath" -Value $uxpDest
                Set-ItemProperty -Path $regPath -Name "version" -Value $target
            }
        }
    }

    # The v3.4+ payload also upgrades the updater used by the next run.
    $newUpdater = Join-Path $payload "updater\update_agent.ps1"
    if (Test-Path -LiteralPath $newUpdater) {
        Copy-Item -LiteralPath $newUpdater -Destination (Join-Path $updateRoot "update_agent.ps1") -Force
    }
    if (Test-Path -LiteralPath $pendingRoot) { Remove-Item -LiteralPath $pendingRoot -Recurse -Force }
    Write-UpdateLog ("Updated successfully to v" + $target)
    Write-UpdateStatus "installed" $target "Update complete; reopen Photoshop"
} catch {
    Write-UpdateLog ("Update failed: " + $_.Exception.Message)
    Write-UpdateStatus "error" "" ("Update failed: " + $_.Exception.Message)
    exit 1
} finally {
    if ($hasMutex) { try { $mutex.ReleaseMutex() } catch {} }
    $mutex.Dispose()
}
