<#
  Install Claude Proxy Control (product) into %USERPROFILE%\.claude-proxy-control
  - copies the bundled Python, the app, runtime scripts + builders
  - copies/links sing-box
  - installs CPC watcher task
  - creates Desktop shortcut

  -FilesOnly copies everything and touches nothing else on the machine: no
  scheduled tasks, no shortcut. It exists so a release bundle can be verified
  against a throwaway CLAUDE_PROXY_HOME without disturbing a real installation.

  -UserDesktop / -UserName приходят от Install-Launcher.ps1, который считает их
  ДО повышения прав. Внутри elevated-процесса рабочий стол и имя пользователя
  принадлежат той учётной записи, которую подтвердили в окне UAC: если это
  другой администратор, ярлык уедет на его рабочий стол, а задания планировщика
  зарегистрируются на него же.
#>
param(
  [switch]$FilesOnly,
  [switch]$NoTasks,
  [string]$UserDesktop = "",
  [string]$UserName = "",
  [string]$LogTo = ""
)
$ErrorActionPreference = "Stop"
try { chcp 65001 | Out-Null } catch {}

# Окно установки закрывается вместе с процессом, и при ошибке человек не успевал
# ничего прочитать. Теперь всё пишется в файл, а лог показывает лаунчер.
if ($LogTo) {
  try { Start-Transcript -Path $LogTo -Force | Out-Null } catch {}
}

# Любая ошибка = ненулевой код возврата. Без этого powershell -File завершался
# нулём даже после throw, и лаунчер честно докладывал об успехе установки,
# которой не было.
trap {
  Write-Host ""
  Write-Host ("Установка прервалась: " + $_.Exception.Message) -ForegroundColor Red
  if ($_.ScriptStackTrace) { Write-Host $_.ScriptStackTrace -ForegroundColor DarkGray }
  if ($LogTo) { try { Stop-Transcript | Out-Null } catch {} }
  exit 1
}

# Admin is needed for the scheduled tasks, not for copying files. Checked here
# rather than with #Requires so -FilesOnly can run unelevated and so the message
# is one a buyer can act on.
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()
           ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin -and -not $FilesOnly -and -not $NoTasks) {
  Write-Host ""
  Write-Host "Нужны права администратора: программа создаёт сетевой туннель." -ForegroundColor Red
  Write-Host "Запустите Install.bat — он сам попросит подтверждение." -ForegroundColor Yellow
  Write-Host ""
  exit 1
}

$RuntimeSrc = $PSScriptRoot
$BundleRoot = Split-Path $RuntimeSrc -Parent
$DesktopSrc = Join-Path $BundleRoot "desktop"
# Same override the rest of the runtime honours (_env.ps1). Without it this script
# was the one place that could only ever install into the real profile.
$ProductHome = $env:CLAUDE_PROXY_HOME
if (-not $ProductHome) { $ProductHome = Join-Path $env:USERPROFILE ".claude-proxy-control" }
$PersonalSingBox = Join-Path $env:USERPROFILE ".claude-proxy\sing-box"

# Two ways this script gets run, and they must not behave the same:
#   * from a release bundle - a python\ folder sits next to runtime\. Everything is
#     copied into the product home, so the user can delete the unpacked archive.
#   * from the source tree  - no python\ folder. The app keeps running where it is,
#     which is what development needs.
$BundledPython = Join-Path $BundleRoot "python"
$FromBundle = Test-Path (Join-Path $BundledPython "python.exe")

Write-Host ""
Write-Host "============================================================" -ForegroundColor White
Write-Host "  Install Claude Proxy Control" -ForegroundColor Cyan
Write-Host "============================================================" -ForegroundColor White
Write-Host ("  Target:  " + $ProductHome)
Write-Host ("  Runtime: " + $RuntimeSrc)
Write-Host ("  App:     " + $DesktopSrc)

New-Item -ItemType Directory -Force -Path $ProductHome, (Join-Path $ProductHome "merged"), (Join-Path $ProductHome "sing-box") | Out-Null

# scripts
#
# Копируем ВСЕ .ps1 из runtime, а не перечисленные поимённо. Список поимённо уже
# однажды подвёл: в нём не оказалось Run-ElevatedReload.ps1, и применение
# маршрутов падало на чужой машине с невнятным «bool object has no attribute
# get» — потому что скрипта, который поднимает elevated-перезапуск, там просто
# не было. Забыть файл в списке слишком легко, а последствия видит покупатель.
$skip = @("Cleanup-Leave-Product-Only.ps1")   # только для разработки
foreach ($src in @(Get-ChildItem $RuntimeSrc -Filter "*.ps1" -File)) {
  if ($skip -contains $src.Name) { continue }
  Copy-Item $src.FullName (Join-Path $ProductHome $src.Name) -Force
}
# Без этих двух продукт не работает вовсе — проверяем, а не надеемся.
foreach ($must in @("_env.ps1", "Start-VpnClaude.ps1", "Reload-VpnRoutes.ps1", "Run-ElevatedReload.ps1")) {
  if (-not (Test-Path (Join-Path $ProductHome $must))) {
    throw ("В комплекте нет обязательного скрипта: " + $must)
  }
}
Copy-Item (Join-Path $RuntimeSrc "merged\*.py") (Join-Path $ProductHome "merged") -Force

if ($FromBundle) {
  # Interpreter first: it is the one thing without which nothing else matters.
  $dstPython = Join-Path $ProductHome "python"
  Write-Host "  Copy bundled Python (tkinter + customtkinter included)…" -ForegroundColor Yellow
  Remove-Item $dstPython -Recurse -Force -ErrorAction SilentlyContinue
  Copy-Item $BundledPython $dstPython -Recurse -Force

  # The app itself, so deleting the unpacked archive does not break the shortcut.
  $dstDesktop = Join-Path $ProductHome "desktop"
  Write-Host "  Copy application…" -ForegroundColor Yellow
  New-Item -ItemType Directory -Force -Path $dstDesktop | Out-Null
  Get-ChildItem $DesktopSrc -File | Where-Object { $_.Extension -in ".py", ".bat", ".vbs", ".ico", ".png", ".txt" } |
    ForEach-Object { Copy-Item $_.FullName $dstDesktop -Force }
  $DesktopSrc = $dstDesktop

  # Tell the elevated side which interpreter to use before the UI has ever run.
  # Without this the very first Apply would look for Python on the machine.
  $consolePy = Join-Path $dstPython "python.exe"
  Set-Content -Path (Join-Path $ProductHome "python-path.txt") -Value $consolePy -Encoding UTF8
  Write-Host ("  Python: " + $consolePy) -ForegroundColor Green
} else {
  # Source tree: use whatever interpreter the developer has, and make sure the UI
  # dependencies are there. Offline wheels are the fallback.
  $req = Join-Path $DesktopSrc "requirements.txt"
  if (Test-Path $req) {
    Write-Host "  Installing desktop Python deps (customtkinter)…" -ForegroundColor Yellow
    try {
      & python -m pip install -r $req --quiet 2>$null
      if ($LASTEXITCODE -ne 0) {
        $vendor = Join-Path $DesktopSrc "vendor"
        if (Test-Path $vendor) {
          & python -m pip install --no-index --find-links $vendor -r $req --quiet
        }
      }
    } catch {
      Write-Host "  WARN: pip install customtkinter failed — install manually: pip install -r desktop\requirements.txt" -ForegroundColor Yellow
    }
  }
}

Set-Content -Path (Join-Path $ProductHome "desktop-path.txt") -Value $DesktopSrc -Encoding UTF8

# Версия и паспорт сборки едут вместе с программой: по ним видно, что именно
# сейчас установлено, и в окне, и в журнале, и в отчёте для поддержки.
foreach ($mark in @("VERSION", "BUILD.txt")) {
  $src = Join-Path $BundleRoot $mark
  if (Test-Path $src) { Copy-Item $src (Join-Path $ProductHome $mark) -Force }
}
$verFile = Join-Path $ProductHome "VERSION"
if (Test-Path $verFile) {
  Write-Host ("  Версия: " + (Get-Content $verFile -Raw).Trim()) -ForegroundColor Green
}

# sing-box: prefer already installed → release bundle → personal copy
$dstSb = Join-Path $ProductHome "sing-box"
$have = Get-ChildItem $dstSb -Directory -Filter "sing-box-*-windows-amd64" -ErrorAction SilentlyContinue
if (-not $have) {
  $bundle = Get-ChildItem (Join-Path $RuntimeSrc "sing-box-bundle") -Directory -Filter "sing-box-*-windows-amd64" -ErrorAction SilentlyContinue |
    Sort-Object Name -Descending | Select-Object -First 1
  if ($bundle) {
    Write-Host ("  Copy bundled sing-box: " + $bundle.Name) -ForegroundColor Yellow
    Copy-Item $bundle.FullName (Join-Path $dstSb $bundle.Name) -Recurse -Force
  } else {
    $srcSb = Get-ChildItem $PersonalSingBox -Directory -Filter "sing-box-*-windows-amd64" -ErrorAction SilentlyContinue |
      Sort-Object Name -Descending | Select-Object -First 1
    if ($srcSb) {
      Write-Host ("  Copy sing-box from " + $srcSb.FullName) -ForegroundColor Yellow
      Copy-Item $srcSb.FullName (Join-Path $dstSb $srcSb.Name) -Recurse -Force
    } else {
      Write-Host "  WARN: sing-box not found. Put sing-box-*-windows-amd64 under:" -ForegroundColor Yellow
      Write-Host ("         " + $dstSb)
    }
  }
} else {
  Write-Host ("  sing-box already present: " + $have[0].Name) -ForegroundColor Green
}

# keep existing license/proxy.env
. (Join-Path $ProductHome "_env.ps1")

if ($FilesOnly) {
  Write-Host ""
  Write-Host "  -FilesOnly: задания планировщика и ярлык не трогаю" -ForegroundColor Yellow
} else {
  # Ярлык — ПЕРВЫМ, до заданий планировщика.
  #
  # Он был последним, и порядок стоил установки: сначала ставились ловец узлов и
  # монитор сети, оба с $ErrorActionPreference = "Stop", и любая осечка в них
  # (например, Start-ScheduledTask на учётку, которой на этой машине нет)
  # обрывала скрипт до создания ярлыка. Пользователь оставался без ярлыка,
  # а Install.bat всё равно писал, что ярлык создан. Ярлык — то, ради чего
  # человек запускал установку; задания планировщика — удобство.
  $bat = Join-Path $DesktopSrc "Run-Product.bat"
  if (-not (Test-Path $bat)) { throw ("В комплекте нет " + $bat) }

  $desk = $UserDesktop
  if (-not $desk -or -not (Test-Path $desk)) { $desk = [Environment]::GetFolderPath("Desktop") }
  $ico = Join-Path $DesktopSrc "claude-proxy-control.ico"
  $w = New-Object -ComObject WScript.Shell

  $lnkPath = Join-Path $desk "Claude Proxy Control.lnk"
  $lnk = $w.CreateShortcut($lnkPath)
  $lnk.TargetPath = $bat
  $lnk.WorkingDirectory = $DesktopSrc
  $lnk.WindowStyle = 7
  if (Test-Path $ico) { $lnk.IconLocation = $ico }
  $lnk.Description = "Claude Proxy Control"
  $lnk.Save()
  # Save() молчит, если файл не появился, — проверяем сами, иначе снова получим
  # «установка прошла успешно» без ярлыка.
  if (-not (Test-Path $lnkPath)) { throw ("Не удалось создать ярлык: " + $lnkPath) }
  Write-Host ("  Ярлык: " + $lnkPath) -ForegroundColor Green

  # Второй ярлык — в меню «Пуск». Рабочий стол легко перепутать, потерять или
  # держать закрытым, а через «Пуск» программа находится поиском по названию.
  try {
    # Папку меню считаем от профиля, которому принадлежит рабочий стол.
    # GetFolderPath("Programs") здесь брать нельзя: под UAC это меню той учётки,
    # которую подтвердили в окне, и ярлык уедет к ней. Не нашли — пропускаем:
    # чужое меню засорять хуже, чем обойтись одним ярлыком на рабочем столе.
    $menuDir = Join-Path (Split-Path $desk -Parent) "AppData\Roaming\Microsoft\Windows\Start Menu\Programs"
    if (-not (Test-Path $menuDir)) { $menuDir = "" }
    if (-not $menuDir) { throw "профиль меню «Пуск» не найден" }
    $menu = Join-Path $menuDir "Claude Proxy Control.lnk"
    $m = $w.CreateShortcut($menu)
    $m.TargetPath = $bat
    $m.WorkingDirectory = $DesktopSrc
    $m.WindowStyle = 7
    if (Test-Path $ico) { $m.IconLocation = $ico }
    $m.Description = "Claude Proxy Control"
    $m.Save()
    Write-Host ("  Меню «Пуск»: " + $menu) -ForegroundColor Green
  } catch {
    Write-Host ("  Ярлык в меню «Пуск» не создался: " + $_.Exception.Message) -ForegroundColor Yellow
  }

  # Задания планировщика — приятное дополнение, а не условие работы: программа
  # без них запускается и туннель поднимает. Падать из-за них нельзя.
  # -NoTasks: установка без них вовсе (и тогда права администратора не нужны).
  if ($NoTasks) {
    Write-Host "  -NoTasks: задания планировщика не ставлю" -ForegroundColor Yellow
  } else {
    $env:CLAUDE_PROXY_NOPAUSE = "1"
    if ($UserName) { $env:CPC_TARGET_USER = $UserName }
    foreach ($task in @("Install-SotaCaptureWatcher.ps1", "Install-NetMonitor.ps1")) {
      try {
        & (Join-Path $ProductHome $task)
      } catch {
        Write-Host ("  Не установилось (не критично): " + $task) -ForegroundColor Yellow
        Write-Host ("    " + $_.Exception.Message) -ForegroundColor Yellow
      }
    }
    $env:CLAUDE_PROXY_NOPAUSE = $null
  }
}

Write-Host ""
Write-Host "Установка завершена." -ForegroundColor Green
Write-Host "1) Ключ подписки: https://proxy.mindq.ru"
Write-Host "2) Запустите ярлык «Claude Proxy Control» и вставьте ключ"
Write-Host ""
if ($LogTo) { try { Stop-Transcript | Out-Null } catch {} }
