@echo off
setlocal EnableExtensions
chcp 65001 >nul
title Mirage API - Client Setup
color 0B
echo.
echo   ==============================================
echo          MIRAGE API  /  CLIENT AUTO-SETUP
echo   ==============================================
echo.
echo   Этот мастер проверит ваш ключ и настроит клиенты.
echo   Существующие файлы будут сохранены в .bak.
echo.

set "MIRAGE_SETUP_FILE=%~f0"
powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "$raw = [System.IO.File]::ReadAllText($env:MIRAGE_SETUP_FILE); $marker = '# --- MIRAGE POWERSHELL PAYLOAD ---'; $index = $raw.LastIndexOf($marker); if ($index -lt 0) { Write-Error 'PowerShell payload missing'; exit 1 }; $payload = $raw.Substring($index + $marker.Length); & ([scriptblock]::Create($payload))"
set "MIRAGE_EXIT_CODE=%ERRORLEVEL%"
if not "%MIRAGE_EXIT_CODE%"=="0" (
  echo.
  echo Установка прервана с кодом ошибки %MIRAGE_EXIT_CODE%.
)
echo.
pause
exit /b %MIRAGE_EXIT_CODE%
# --- MIRAGE POWERSHELL PAYLOAD ---

$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
$BaseUrl = 'https://mirage-api.xyz/v1'
$TestUrl = 'https://mirage-api.xyz/v1/chat/completions'
$failedSteps = New-Object 'System.Collections.Generic.List[string]'

function Write-Section([string]$Message) {
    Write-Host "`n$Message" -ForegroundColor Cyan
}

function Backup-File([string]$Path) {
    if (Test-Path -LiteralPath $Path -PathType Leaf) {
        $backupPath = "$Path.bak"
        Copy-Item -LiteralPath $Path -Destination $backupPath -Force -ErrorAction Stop
        Write-Host "    Backup: $backupPath" -ForegroundColor DarkGray
    }
}

function Ensure-ParentDirectory([string]$Path) {
    $parent = Split-Path -Parent $Path
    if (-not (Test-Path -LiteralPath $parent -PathType Container)) {
        New-Item -ItemType Directory -Path $parent -Force -ErrorAction Stop | Out-Null
    }
}

function Write-AtomicText([string]$Path, [string]$Text) {
    $temporaryPath = "$Path.mirage-tmp"
    $utf8NoBom = New-Object System.Text.UTF8Encoding($false)
    try {
        [System.IO.File]::WriteAllText($temporaryPath, $Text, $utf8NoBom)
        Move-Item -LiteralPath $temporaryPath -Destination $Path -Force -ErrorAction Stop | Out-Null
    }
    finally {
        if (Test-Path -LiteralPath $temporaryPath -PathType Leaf) {
            Remove-Item -LiteralPath $temporaryPath -Force -ErrorAction SilentlyContinue
        }
    }
}

function Convert-JsoncToJson([string]$Text) {
    $commentFree = New-Object System.Text.StringBuilder
    $quote = [char]34; $slash = [char]47; $star = [char]42; $backslash = [char]92
    $lineFeed = [char]10; $carriageReturn = [char]13; $space = [char]32
    $insideString = $false; $escaped = $false; $lineComment = $false; $blockComment = $false

    for ($i = 0; $i -lt $Text.Length; $i++) {
        $ch = $Text[$i]
        if ($lineComment) {
            if ($ch -eq $lineFeed -or $ch -eq $carriageReturn) { $lineComment = $false; [void]$commentFree.Append($ch) } else { [void]$commentFree.Append($space) }
            continue
        }
        if ($blockComment) {
            if ($ch -eq $star -and $i + 1 -lt $Text.Length -and $Text[$i + 1] -eq $slash) { [void]$commentFree.Append('  '); $i++; $blockComment = $false }
            elseif ($ch -eq $lineFeed -or $ch -eq $carriageReturn) { [void]$commentFree.Append($ch) } else { [void]$commentFree.Append($space) }
            continue
        }
        if ($insideString) {
            [void]$commentFree.Append($ch)
            if ($escaped) { $escaped = $false } elseif ($ch -eq $backslash) { $escaped = $true } elseif ($ch -eq $quote) { $insideString = $false }
            continue
        }
        if ($ch -eq $quote) { $insideString = $true; [void]$commentFree.Append($ch); continue }
        if ($ch -eq $slash -and $i + 1 -lt $Text.Length) {
            $next = $Text[$i + 1]
            if ($next -eq $slash) { [void]$commentFree.Append('  '); $i++; $lineComment = $true; continue }
            if ($next -eq $star) { [void]$commentFree.Append('  '); $i++; $blockComment = $true; continue }
        }
        [void]$commentFree.Append($ch)
    }

    $source = $commentFree.ToString()
    $clean = New-Object System.Text.StringBuilder
    $insideString = $false; $escaped = $false
    for ($i = 0; $i -lt $source.Length; $i++) {
        $ch = $source[$i]
        if ($insideString) {
            [void]$clean.Append($ch)
            if ($escaped) { $escaped = $false } elseif ($ch -eq $backslash) { $escaped = $true } elseif ($ch -eq $quote) { $insideString = $false }
            continue
        }
        if ($ch -eq $quote) { $insideString = $true; [void]$clean.Append($ch); continue }
        if ($ch -eq ',') {
            $lookAhead = $i + 1
            while ($lookAhead -lt $source.Length -and [char]::IsWhiteSpace($source[$lookAhead])) { $lookAhead++ }
            if ($lookAhead -lt $source.Length -and ($source[$lookAhead] -eq '}' -or $source[$lookAhead] -eq ']')) { continue }
        }
        [void]$clean.Append($ch)
    }
    return $clean.ToString()
}

function Get-JsonValue($Object, [string]$Name) {
    if ($null -eq $Object) { return $null }
    if ($Object -is [System.Collections.IDictionary]) { if ($Object.Contains($Name)) { return $Object[$Name] }; return $null }
    $property = $Object.PSObject.Properties[$Name]
    if ($null -ne $property) { return $property.Value }
    return $null
}

function Set-JsonValue($Object, [string]$Name, $Value) {
    if ($Object -is [System.Collections.IDictionary]) { $Object[$Name] = $Value; return }
    $property = $Object.PSObject.Properties[$Name]
    if ($null -ne $property) { $property.Value = $Value }
    else { $Object | Add-Member -MemberType NoteProperty -Name $Name -Value $Value -Force }
}

function Assert-JsonObject($Object, [string]$Label) {
    if ($null -eq $Object -or $Object -is [Array] -or $Object -is [string] -or $Object -is [ValueType]) {
        throw "$Label должен содержать JSON-объект. Он не был перезаписан."
    }
}

function Update-JsonSettings([string]$Path, [scriptblock]$Mutator) {
    Ensure-ParentDirectory $Path
    $settings = [pscustomobject]@{}
    if (Test-Path -LiteralPath $Path -PathType Leaf) {
        $rawJson = [System.IO.File]::ReadAllText($Path)
        if (-not [string]::IsNullOrWhiteSpace($rawJson)) {
            try {
                $settings = ConvertFrom-Json -InputObject (Convert-JsoncToJson $rawJson) -ErrorAction Stop
                Assert-JsonObject $settings $Path
            } catch {
                throw "Ошибка JSON/JSONC в '$Path': $($_.Exception.Message)"
            }
        }
        Backup-File $Path
    }
    & $Mutator $settings
    $jsonText = ConvertTo-Json -InputObject $settings -Depth 100
    Write-AtomicText $Path $jsonText
}

function Get-OrCreate-JsonObject($Parent, [string]$Name) {
    $value = Get-JsonValue $Parent $Name
    if ($null -eq $value) { $value = [pscustomobject]@{}; Set-JsonValue $Parent $Name $value }
    Assert-JsonObject $value $Name
    return $value
}

function Quote-TomlString([string]$Value) {
    $escaped = $Value.Replace('\', '\\').Replace('"', '\"')
    return '"' + $escaped + '"'
}

function Set-TomlFields([string]$Text, [string]$Section, [System.Collections.IDictionary]$Fields) {
    $lines = New-Object 'System.Collections.Generic.List[string]'
    foreach ($line in [regex]::Split($Text, '\r?\n')) { [void]$lines.Add($line) }
    if ($lines.Count -eq 1 -and $lines[0] -eq '') { $lines.Clear() }

    if ([string]::IsNullOrEmpty($Section)) {
        $start = 0
        $end = $lines.Count
        for ($i = 0; $i -lt $lines.Count; $i++) { if ($lines[$i] -match '^\s*\[') { $end = $i; break } }
    } else {
        $targetHeader = '^\s*\[' + [regex]::Escape($Section) + '\]\s*(?:#.*)?$'
        $start = -1
        for ($i = 0; $i -lt $lines.Count; $i++) { if ($lines[$i] -match $targetHeader) { $start = $i; break } }
        if ($start -lt 0) {
            if ($lines.Count -gt 0 -and $lines[$lines.Count - 1].Trim() -ne '') { [void]$lines.Add('') }
            [void]$lines.Add("[$Section]")
            $start = $lines.Count - 1
            $end = $lines.Count
        } else {
            $end = $lines.Count
            for ($i = $start + 1; $i -lt $lines.Count; $i++) { if ($lines[$i] -match '^\s*\[') { $end = $i; break } }
        }
    }

    foreach ($key in $Fields.Keys) {
        $keyPattern = '^\s*' + [regex]::Escape([string]$key) + '\s*='
        $foundIndex = -1
        for ($i = $start; $i -lt $end; $i++) {
            if ($lines[$i] -match $keyPattern) { if ($foundIndex -lt 0) { $foundIndex = $i } else { $lines.RemoveAt($i); $i--; $end-- } }
        }
        $newLine = [string]$key + ' = ' + [string]$Fields[$key]
        if ($foundIndex -ge 0) { $lines[$foundIndex] = $newLine } else { $lines.Insert($end, $newLine); $end++ }
    }

    $result = [string]::Join("`r`n", $lines)
    if (-not $result.EndsWith("`r`n")) { $result += "`r`n" }
    return $result
}

function Invoke-ConfigStep([string]$Name, [scriptblock]$Action) {
    try {
        & $Action
        Write-Host "  [OK] $Name" -ForegroundColor Green
    } catch {
        [void]$failedSteps.Add($Name)
        Write-Host "  [ОШИБКА] $Name - $($_.Exception.Message)" -ForegroundColor Red
    }
}

function Set-EditorSettings([string]$Path, [string]$ModelId) {
    Update-JsonSettings $Path {
        param($settings)
        foreach ($namespace in @('cline', 'roo-cline', 'kilocode')) {
            Set-JsonValue $settings ($namespace + '.apiProvider') 'openai'
            Set-JsonValue $settings ($namespace + '.openAiBaseUrl') $BaseUrl
            Set-JsonValue $settings ($namespace + '.openAiApiKey') $apiKey
            Set-JsonValue $settings ($namespace + '.openAiModelId') $ModelId
        }
    }
}

try {
    Write-Section 'Добро пожаловать в автонастройку Mirage API'
    Write-Host 'API-ключ вводится скрыто и не выводится в консоль.' -ForegroundColor DarkGray
    $secureKey = Read-Host -Prompt 'Введите API-ключ (sk-...)' -AsSecureString
    if ($null -eq $secureKey -or $secureKey.Length -eq 0) {
        Write-Host 'Ключ не введён. Изменения не внесены.' -ForegroundColor Yellow
        exit 2
    }

    $keyPointer = [IntPtr]::Zero
    try {
        $keyPointer = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureKey)
        $apiKey = ([Runtime.InteropServices.Marshal]::PtrToStringBSTR($keyPointer)).Trim()
    } finally {
        if ($keyPointer -ne [IntPtr]::Zero) { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($keyPointer) }
    }
    if ($apiKey -notmatch '^sk-.+') {
        Write-Host 'Формат ключа должен начинаться с sk-. Изменения не внесены.' -ForegroundColor Yellow
        exit 2
    }

    Write-Section 'Выберите модель по умолчанию для IDE:'
    Write-Host '1. claude-opus-5.5 (Claude Opus 5.5)'
    Write-Host '2. gpt-6-astra (GPT-6 Astra)'
    Write-Host '3. qwen3.8-max (Qwen 3.8 Max)'
    $choice = Read-Host 'Введите номер (1-3) [по умолчанию 2]'
    switch ($choice) {
        '1' { $selectedModel = 'claude-opus-5.5'; $selectedName = 'Claude Opus 5.5' }
        '3' { $selectedModel = 'qwen3.8-max'; $selectedName = 'Qwen 3.8 Max' }
        Default { $selectedModel = 'gpt-6-astra'; $selectedName = 'GPT-6 Astra' }
    }
    Write-Host "Выбрана модель: $selectedName ($selectedModel)" -ForegroundColor Cyan

    Write-Section 'Проверяю ключ тестовым запросом...'
    $testBody = @{
        model = $selectedModel
        messages = @(@{ role = 'user'; content = 'Reply with OK.' })
        max_tokens = 1
    } | ConvertTo-Json -Depth 8 -Compress
    $testHeaders = @{ Authorization = "Bearer $apiKey" }
    $httpStatus = 0
    $requestError = $null
    try {
        $testResponse = Invoke-WebRequest -UseBasicParsing -Uri $TestUrl -Method Post -Headers $testHeaders -ContentType 'application/json' -Body $testBody -TimeoutSec 30 -ErrorAction Stop
        $httpStatus = [int]$testResponse.StatusCode
    } catch {
        $requestError = $_.Exception.Message
        if ($null -ne $_.Exception.Response) {
            try { $httpStatus = [int]$_.Exception.Response.StatusCode } catch { $httpStatus = 0 }
        }
    }
    if ($httpStatus -eq 401 -or $httpStatus -eq 403) {
        Write-Host "Ключ отклонён сервером (HTTP $httpStatus). Изменения не внесены." -ForegroundColor Red
        exit 3
    }
    if ($httpStatus -lt 200 -or $httpStatus -ge 300) {
        if ($httpStatus -gt 0) { Write-Host "Тестовый запрос завершился с HTTP $httpStatus. Конфигурации не менялись." -ForegroundColor Red }
        else { Write-Host "Не удалось связаться с Mirage API. $requestError" -ForegroundColor Red }
        exit 4
    }
    Write-Host "Ключ принят (HTTP $httpStatus)." -ForegroundColor Green

    Write-Section 'Настраиваю клиентов и сохраняю резервные копии'

    Invoke-ConfigStep 'Claude Code' {
        $path = Join-Path $env:USERPROFILE '.claude\settings.json'
        Update-JsonSettings $path {
            param($settings)
            $envSettings = Get-OrCreate-JsonObject $settings 'env'
            Set-JsonValue $envSettings 'ANTHROPIC_BASE_URL' 'https://mirage-api.xyz'
            Set-JsonValue $envSettings 'ANTHROPIC_AUTH_TOKEN' $apiKey
        }
    }

    Invoke-ConfigStep 'Codex (MIRAGE_API_KEY + config.toml)' {
        $path = Join-Path $env:USERPROFILE '.codex\config.toml'
        Ensure-ParentDirectory $path
        $text = ''
        if (Test-Path -LiteralPath $path -PathType Leaf) {
            $text = [System.IO.File]::ReadAllText($path)
            Backup-File $path
        }
        $rootFields = [ordered]@{
            model = (Quote-TomlString $selectedModel)
            model_provider = (Quote-TomlString 'mirage')
        }
        $providerFields = [ordered]@{
            name = (Quote-TomlString 'Mirage API')
            base_url = (Quote-TomlString 'https://mirage-api.xyz/v1')
            env_key = (Quote-TomlString 'MIRAGE_API_KEY')
            wire_api = (Quote-TomlString 'responses')
        }
        $text = Set-TomlFields $text '' $rootFields
        $text = Set-TomlFields $text 'model_providers.mirage' $providerFields
        Write-AtomicText $path $text
        [Environment]::SetEnvironmentVariable('MIRAGE_API_KEY', $apiKey, 'User')
        $env:MIRAGE_API_KEY = $apiKey
    }

    Invoke-ConfigStep 'VS Code / Cline, Roo Code, Kilo Code' {
        $path = Join-Path $env:APPDATA 'Code\User\settings.json'
        Set-EditorSettings $path $selectedModel
    }

    Invoke-ConfigStep 'Cursor / Cline, Roo Code, Kilo Code' {
        $path = Join-Path $env:APPDATA 'Cursor\User\settings.json'
        Set-EditorSettings $path $selectedModel
    }

    foreach ($optionalApp in @(@{ Folder = 'Code - Insiders'; Name = 'VS Code Insiders' }, @{ Folder = 'VSCodium'; Name = 'VSCodium' })) {
        $optionalRoot = Join-Path $env:APPDATA $optionalApp.Folder
        if (Test-Path -LiteralPath $optionalRoot -PathType Container) {
            Invoke-ConfigStep $optionalApp.Name {
                $optionalSettings = Join-Path (Join-Path $env:APPDATA $optionalApp.Folder) 'User\settings.json'
                Set-EditorSettings $optionalSettings $selectedModel
            }
        }
    }

    Invoke-ConfigStep 'OpenCode' {
        $path = Join-Path $env:USERPROFILE '.config\opencode\opencode.json'
        Update-JsonSettings $path {
            param($settings)
            $providers = Get-OrCreate-JsonObject $settings 'provider'
            $mirage = Get-OrCreate-JsonObject $providers 'mirage'
            Set-JsonValue $mirage 'npm' '@ai-sdk/openai-compatible'
            Set-JsonValue $mirage 'name' 'Mirage API'
            $options = Get-OrCreate-JsonObject $mirage 'options'
            Set-JsonValue $options 'baseURL' 'https://mirage-api.xyz/v1'
            Set-JsonValue $options 'apiKey' $apiKey
            $models = Get-OrCreate-JsonObject $mirage 'models'
            $model = Get-OrCreate-JsonObject $models $selectedModel
            Set-JsonValue $model 'name' $selectedName
            Set-JsonValue $settings 'model' "mirage/$selectedModel"
        }
    }

    if ($failedSteps.Count -gt 0) {
        Write-Host "`nЗавершено частично. Не удалось настроить: $($failedSteps -join ', ')." -ForegroundColor Yellow
        Write-Host 'Проверьте сообщения выше; резервные копии сохранены в .bak.' -ForegroundColor Yellow
        exit 5
    }

    Write-Host "`nГотово! Все клиенты настроены. Base URL: https://mirage-api.xyz/v1" -ForegroundColor Green
    Write-Host "Модель по умолчанию: $selectedName ($selectedModel)" -ForegroundColor Green
    Write-Host 'Для Codex закройте и заново откройте терминал, чтобы подхватилась переменная MIRAGE_API_KEY.' -ForegroundColor DarkGray
    Write-Host 'Codex настроен на Responses API: для работы Mirage должен поддерживать /v1/responses.' -ForegroundColor Yellow
    Write-Host 'Ключ хранится в настройках клиентов и переменной окружения текущего пользователя.' -ForegroundColor DarkGray
    exit 0
} catch {
    Write-Host "`nОшибка установки: $($_.Exception.Message)" -ForegroundColor Red
    exit 1
}