param(
  [string]$ProjectRoot = ""
)

$ErrorActionPreference = "SilentlyContinue"

if ([string]::IsNullOrWhiteSpace($ProjectRoot)) {
  $ProjectRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path
} else {
  $ProjectRoot = (Resolve-Path $ProjectRoot).Path
}

$toolDir = Join-Path $ProjectRoot "tools\oem-batch-builder"
$activeRunPath = Join-Path $toolDir "active-run.json"
$runsDir = Join-Path $toolDir "runs"
$currentPid = $PID
$killed = New-Object System.Collections.Generic.List[int]

function Write-CleanupLog([string]$Message) {
  $stamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
  Write-Output "[$stamp] $Message"
}

function Write-JsonNoBom($Path, $Value) {
  $json = $Value | ConvertTo-Json -Depth 10
  $utf8NoBom = New-Object System.Text.UTF8Encoding($false)
  [System.IO.File]::WriteAllText($Path, $json, $utf8NoBom)
}

function Get-ProcByPid([int]$ProcId) {
  Get-CimInstance Win32_Process -Filter "ProcessId=$ProcId"
}

function Test-IsProjectBuildProcess($Proc) {
  if (-not $Proc) { return $false }
  if ([int]$Proc.ProcessId -eq [int]$currentPid) { return $false }

  $name = [string]$Proc.Name
  $cmd = [string]$Proc.CommandLine
  $exe = [string]$Proc.ExecutablePath
  $text = "$cmd`n$exe"
  $lower = $text.ToLowerInvariant()
  $rootLower = $ProjectRoot.ToLowerInvariant()

  if ($name -ieq "OEMBatchBuilderTool.exe") { return $false }

  if ($lower.Contains("tools\oem-batch-builder\worker.cjs")) { return $true }
  if ($lower.Contains("tools\oem-batch-builder\main.cjs")) { return $true }
  if ($lower.Contains("electron-builder") -and $lower.Contains("--win") -and $lower.Contains("--x64")) { return $true }
  if ($lower.Contains("npm.cmd run build:win") -or $lower.Contains("npm run build:win")) { return $true }

  if (-not $lower.Contains($rootLower)) {
    return $false
  }

  if ($lower.Contains("\dist-release-final\")) { return $true }
  if ($lower.Contains("\build\oem-batch-temp\")) { return $true }
  if ($lower.Contains("dist-release-final\win-unpacked")) { return $true }
  if ($lower.Contains("node_modules\.bin\electron-builder")) { return $true }
  if ($lower.Contains("electron-builder.cmd")) { return $true }
  if ($lower.Contains("electron-builder --win")) { return $true }
  if ($lower.Contains("npm.cmd run build:win")) { return $true }
  if ($lower.Contains("npm run build:win")) { return $true }
  if ($lower.Contains("npm.cmd run prepare-package")) { return $true }
  if ($lower.Contains("npm run prepare-package")) { return $true }
  if ($lower.Contains("npm.cmd run build")) { return $true }
  if ($lower.Contains("npm run build")) { return $true }
  if ($lower.Contains("vite build")) { return $true }
  if ($lower.Contains("apply-oem.cjs")) { return $true }
  if ($lower.Contains("scripts\prepare-package.cjs")) { return $true }
  if ($lower.Contains("scripts\build-resource-artifacts.cjs")) { return $true }
  if ($lower.Contains("scripts\fix-latest-yml.cjs")) { return $true }
  if ($lower.Contains("scripts\assemble-release-artifacts.cjs")) { return $true }
  if ($lower.Contains("scripts\clean-build-artifacts.cjs")) { return $true }

  return $false
}

function Test-IsWorkerPidProcess($Proc) {
  if (-not $Proc) { return $false }
  $cmd = ([string]$Proc.CommandLine).ToLowerInvariant()
  return $cmd.Contains("tools\oem-batch-builder\worker.cjs")
}

function Test-ToolInstanceIsAlive() {
  $instances = @(Get-CimInstance Win32_Process | Where-Object {
    if (-not $_) { return $false }
    if ([int]$_.ProcessId -eq [int]$currentPid) { return $false }
    if ([string]$_.Name -ine "OEMBatchBuilderTool.exe") { return $false }
    $cmd = ([string]$_.CommandLine).ToLowerInvariant()
    $cmd.Contains("tools\oem-batch-builder")
  })
  if ($instances.Count -gt 0) {
    $ids = ($instances | ForEach-Object { $_.ProcessId }) -join ","
    Write-CleanupLog "OEM batch builder is already running. Skip cleanup and let single-instance focus it. PIDs=$ids"
    return $true
  }
  return $false
}

function Test-ActiveWorkerIsAlive() {
  if (-not (Test-Path $activeRunPath)) { return $false }
  try {
    $active = Get-Content -Path $activeRunPath -Raw -Encoding UTF8 | ConvertFrom-Json
    if (-not $active.running) { return $false }

    $candidatePids = New-Object System.Collections.Generic.List[int]
    if ($active.pid) { $candidatePids.Add([int]$active.pid) | Out-Null }
    if ($active.sessionDir) {
      $workerPidPath = Join-Path ([string]$active.sessionDir) "worker.pid"
      if (Test-Path $workerPidPath) {
        $pidText = (Get-Content -Path $workerPidPath -Raw).Trim()
        if ($pidText -match "^\d+$") { $candidatePids.Add([int]$pidText) | Out-Null }
      }
    }

    foreach ($candidatePid in $candidatePids) {
      $proc = Get-ProcByPid $candidatePid
      if ($proc -and (Test-IsWorkerPidProcess $proc)) {
        Write-CleanupLog "Active OEM build is still running. Skip cleanup. PID=$candidatePid Session=$($active.sessionDir)"
        return $true
      }
    }
  } catch {
    Write-CleanupLog "Failed to inspect active-run.json: $($_.Exception.Message)"
  }
  return $false
}

function Stop-Tree([int]$ProcId, [string]$Reason, [bool]$WorkerPidOnly = $false) {
  if ($ProcId -le 0 -or $ProcId -eq $currentPid) { return }
  if ($killed.Contains($ProcId)) { return }

  $proc = Get-ProcByPid $ProcId
  if (-not $proc) { return }
  if ($WorkerPidOnly -and -not (Test-IsWorkerPidProcess $proc)) {
    Write-CleanupLog "Skip PID $ProcId ($($proc.Name)): $Reason points to a non-worker process"
    return
  }
  if (-not (Test-IsProjectBuildProcess $proc)) { return }

  Write-CleanupLog "Killing PID $ProcId ($($proc.Name)): $Reason"
  & taskkill.exe /F /T /PID $ProcId | Out-Null
  $killed.Add($ProcId) | Out-Null
}

function Stop-ActiveRunState([string]$Message) {
  if (-not (Test-Path $activeRunPath)) { return }
  try {
    $state = Get-Content -Path $activeRunPath -Raw -Encoding UTF8 | ConvertFrom-Json
    if ($state.running -or $killed.Count -gt 0) {
      $state | Add-Member -NotePropertyName "running" -NotePropertyValue $false -Force
      $state | Add-Member -NotePropertyName "stoppedAt" -NotePropertyValue ((Get-Date).ToUniversalTime().ToString("o")) -Force
      $state | Add-Member -NotePropertyName "message" -NotePropertyValue $Message -Force
      Write-JsonNoBom $activeRunPath $state
      Write-CleanupLog "Marked active-run stopped: $Message"
    }
  } catch {
    Write-CleanupLog "Failed to update active-run.json: $($_.Exception.Message)"
  }
}

Write-CleanupLog "Cleanup started. ProjectRoot=$ProjectRoot"

if (Test-ToolInstanceIsAlive) {
  Write-CleanupLog "Cleanup skipped because the tool process is alive."
  exit 0
}

if (Test-ActiveWorkerIsAlive) {
  Write-CleanupLog "Cleanup skipped because an active worker is alive."
  exit 0
}

if (Test-Path $activeRunPath) {
  try {
    $active = Get-Content -Path $activeRunPath -Raw -Encoding UTF8 | ConvertFrom-Json
    if ($active.pid) { Stop-Tree ([int]$active.pid) "active-run pid" $true }
    if ($active.launcherPid) { Stop-Tree ([int]$active.launcherPid) "active-run launcherPid" }
    if ($active.sessionDir) {
      $workerPidPath = Join-Path ([string]$active.sessionDir) "worker.pid"
      if (Test-Path $workerPidPath) {
        $pidText = (Get-Content -Path $workerPidPath -Raw).Trim()
        if ($pidText -match "^\d+$") { Stop-Tree ([int]$pidText) "active session worker.pid" $true }
      }
    }
  } catch {
    Write-CleanupLog "Failed to read active-run.json: $($_.Exception.Message)"
  }
}

Get-CimInstance Win32_Process | Where-Object { Test-IsProjectBuildProcess $_ } | ForEach-Object {
  Stop-Tree ([int]$_.ProcessId) "matched OEM/build command"
}

Start-Sleep -Milliseconds 800

$remaining = @(Get-CimInstance Win32_Process | Where-Object { Test-IsProjectBuildProcess $_ })
if ($remaining.Count -gt 0) {
  foreach ($proc in $remaining) {
    Write-CleanupLog "Still running PID $($proc.ProcessId) ($($proc.Name))"
  }
} else {
  Write-CleanupLog "Cleanup finished. Killed=$($killed.Count)"
}

if ($killed.Count -gt 0) {
  Stop-ActiveRunState "Stale OEM build processes were cleaned before launching the tool. Killed=$($killed.Count)"
}
