Invoke-AICli.ps1

function Invoke-AICli {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('ClaudeCode', 'GitHubCopilot', 'OpenAI')]
        [string]$Provider,

        [Parameter(Mandatory = $true)]
        [string]$Prompt,

        [Parameter()]
        [string[]]$SkillFiles,

        [Parameter()]
        [string[]]$AllowedTools,

        [Parameter()]
        [string]$Model,

        [Parameter()]
        [string]$ApiKey,

        [Parameter()]
        [string]$WorkingDirectory,

        [Parameter()]
        [hashtable]$EnvironmentVars,

        [Parameter()]
        [int]$TimeoutMinutes = 30,

        [Parameter()]
        [switch]$EnableStream,

        [Parameter()]
        [switch]$ShowRawOutput,

        [Parameter()]
        [string]$RawOutputPath
    )

    if ($WorkingDirectory -and (Test-Path $WorkingDirectory)) {
        Push-Location $WorkingDirectory
    }

    try {
        $cliArgs = Build-AICLiArguments -Provider $Provider -Prompt $Prompt -SkillFiles $SkillFiles `
            -AllowedTools $AllowedTools -Model $Model

        $envBackup = @{}
        if ($ApiKey) {
            switch ($Provider) {
                'ClaudeCode' {
                    $envBackup['ANTHROPIC_API_KEY'] = $env:ANTHROPIC_API_KEY
                    $env:ANTHROPIC_API_KEY = $ApiKey
                }
                'GitHubCopilot' {
                    $envBackup['COPILOT_GITHUB_TOKEN'] = $env:COPILOT_GITHUB_TOKEN
                    $env:COPILOT_GITHUB_TOKEN = $ApiKey
                }
                'OpenAI' {
                    $envBackup['OPENAI_API_KEY'] = $env:OPENAI_API_KEY
                    $env:OPENAI_API_KEY = $ApiKey
                }
            }
        }

        if ($EnvironmentVars) {
            foreach ($key in $EnvironmentVars.Keys) {
                $envBackup[$key] = [System.Environment]::GetEnvironmentVariable($key)
                [System.Environment]::SetEnvironmentVariable($key, $EnvironmentVars[$key])
            }
        }

        $executable = Get-AICliExecutable -Provider $Provider
        Write-Host "Invoking $Provider CLI: $executable $($cliArgs -join ' ')"

        if ($EnableStream) {
            $result = Invoke-AICliStreaming -Executable $executable -Arguments $cliArgs -TimeoutMinutes $TimeoutMinutes
        }
        else {
            $result = Invoke-AICliBuffered -Executable $executable -Arguments $cliArgs -TimeoutMinutes $TimeoutMinutes
        }

        if ($ShowRawOutput -and $result.Output) {
            Write-Host "##[group]AI CLI Raw Output"
            Write-Host $result.Output
            Write-Host "##[endgroup]"
        }

        if ($RawOutputPath -and $result.Output) {
            $result.Output | Set-Content -Path $RawOutputPath -Encoding UTF8
            Write-Host "Raw output saved to: $RawOutputPath"
        }

        return $result
    }
    finally {
        foreach ($key in $envBackup.Keys) {
            if ($null -eq $envBackup[$key]) {
                [System.Environment]::SetEnvironmentVariable($key, $null)
            }
            else {
                [System.Environment]::SetEnvironmentVariable($key, $envBackup[$key])
            }
        }

        if ($WorkingDirectory -and (Test-Path $WorkingDirectory)) {
            Pop-Location
        }
    }
}

function Build-AICLiArguments {
    [CmdletBinding()]
    param(
        [string]$Provider,
        [string]$Prompt,
        [string[]]$SkillFiles,
        [string[]]$AllowedTools,
        [string]$Model
    )

    $cliArgs = @()

    switch ($Provider) {
        'ClaudeCode' {
            $cliArgs += '--print'
            if ($AllowedTools) {
                $cliArgs += '--allowedTools'
                $cliArgs += ($AllowedTools -join ',')
            }
            else {
                $cliArgs += '--allowedTools'
                $cliArgs += '""'
            }
            if ($SkillFiles) {
                foreach ($skillFile in $SkillFiles) {
                    if (Test-Path $skillFile) {
                        $cliArgs += '--skill-file'
                        $cliArgs += $skillFile
                    }
                    else {
                        Write-Warning "Skill file not found: $skillFile"
                    }
                }
            }
            if ($Model) {
                $cliArgs += '--model'
                $cliArgs += $Model
            }
            $cliArgs += '--'
            $cliArgs += $Prompt
        }
        'GitHubCopilot' {
            $cliArgs += '-p'
            $cliArgs += $Prompt
            $cliArgs += '-s'
            $cliArgs += '--no-ask-user'
            if ($AllowedTools) {
                $cliArgs += '--allow-tool'
                $cliArgs += ($AllowedTools -join ',')
            }
            else {
                $cliArgs += '--allow-all-tools'
            }
            if ($SkillFiles) {
                foreach ($skillFile in $SkillFiles) {
                    if (Test-Path $skillFile) {
                        $cliArgs += '--attachment'
                        $cliArgs += $skillFile
                    }
                    else {
                        Write-Warning "Skill file not found: $skillFile"
                    }
                }
            }
            if ($Model) {
                $cliArgs += '--model'
                $cliArgs += $Model
            }
            $cliArgs += '--secret-env-vars'
            $cliArgs += 'COPILOT_GITHUB_TOKEN,GH_TOKEN,GITHUB_TOKEN,SYSTEM_ACCESSTOKEN'
        }
        'OpenAI' {
            $cliArgs += 'chat'
            if ($Model) {
                $cliArgs += '--model'
                $cliArgs += $Model
            }
            $cliArgs += '--message'
            $cliArgs += $Prompt
        }
    }

    return $cliArgs
}

function Get-AICliExecutable {
    [CmdletBinding()]
    param(
        [string]$Provider
    )

    switch ($Provider) {
        'ClaudeCode' { return Get-ExecutablePath -ExecutableName 'claude' -NativeInstallSubPath '.local\bin\claude.exe' }
        'GitHubCopilot' { return 'copilot' }
        'OpenAI' { return 'openai' }
    }
}

function Get-ExecutablePath {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$ExecutableName,

        [Parameter(Mandatory = $true)]
        [string]$NativeInstallSubPath
    )

    $command = Get-Command $ExecutableName -ErrorAction SilentlyContinue
    if ($command) {
        return $command.Source
    }

    $nativeInstallPath = Join-Path $HOME $NativeInstallSubPath
    if (Test-Path $nativeInstallPath) {
        return $nativeInstallPath
    }

    return $ExecutableName
}

function Invoke-AICliBuffered {
    [CmdletBinding()]
    param(
        [string]$Executable,
        [string[]]$Arguments,
        [int]$TimeoutMinutes
    )

    $psi = [System.Diagnostics.ProcessStartInfo]::new()
    $psi.FileName = $Executable
    $psi.Arguments = ($Arguments | ForEach-Object { if ($_ -match '\s') { "`"$_`"" } else { $_ } }) -join ' '
    $psi.UseShellExecute = $false
    $psi.RedirectStandardOutput = $true
    $psi.RedirectStandardError = $true
    $psi.CreateNoWindow = $true

    $process = [System.Diagnostics.Process]::new()
    $process.StartInfo = $psi

    try {
        $process.Start() | Out-Null

        $outputTask = $process.StandardOutput.ReadToEndAsync()
        $errorTask = $process.StandardError.ReadToEndAsync()

        $timeoutMs = $TimeoutMinutes * 60 * 1000
        if (-not $process.WaitForExit($timeoutMs)) {
            $process.Kill()
            Write-Warning "AI CLI process timed out after $TimeoutMinutes minutes"
            return @{
                ExitCode = -1
                Output   = "Process timed out after $TimeoutMinutes minutes"
                Success  = $false
            }
        }

        $output = $outputTask.GetAwaiter().GetResult()
        $errorOutput = $errorTask.GetAwaiter().GetResult()

        if ($errorOutput) {
            Write-Warning "AI CLI stderr: $errorOutput"
        }

        return @{
            ExitCode = $process.ExitCode
            Output   = $output
            Success  = ($process.ExitCode -eq 0)
        }
    }
    finally {
        if ($process) { $process.Dispose() }
    }
}

function Invoke-AICliStreaming {
    [CmdletBinding()]
    param(
        [string]$Executable,
        [string[]]$Arguments,
        [int]$TimeoutMinutes
    )

    $outputBuilder = [System.Text.StringBuilder]::new()

    $psi = [System.Diagnostics.ProcessStartInfo]::new()
    $psi.FileName = $Executable
    $psi.Arguments = ($Arguments | ForEach-Object { if ($_ -match '\s') { "`"$_`"" } else { $_ } }) -join ' '
    $psi.UseShellExecute = $false
    $psi.RedirectStandardOutput = $true
    $psi.RedirectStandardError = $true
    $psi.CreateNoWindow = $true

    $process = [System.Diagnostics.Process]::new()
    $process.StartInfo = $psi

    Write-Host "##[group]AI CLI Streaming Output"

    try {
        $process.Start() | Out-Null

        $deadline = [DateTime]::Now.AddMinutes($TimeoutMinutes)

        while (-not $process.StandardOutput.EndOfStream) {
            if ([DateTime]::Now -gt $deadline) {
                $process.Kill()
                Write-Host "##[endgroup]"
                Write-Warning "AI CLI process timed out after $TimeoutMinutes minutes"
                return @{
                    ExitCode = -1
                    Output   = $outputBuilder.ToString()
                    Success  = $false
                }
            }

            $line = $process.StandardOutput.ReadLine()
            if ($null -ne $line) {
                Write-Host $line
                $outputBuilder.AppendLine($line) | Out-Null
            }
        }

        $process.WaitForExit()
        Write-Host "##[endgroup]"

        $errorOutput = $process.StandardError.ReadToEnd()
        if ($errorOutput) {
            Write-Warning "AI CLI stderr: $errorOutput"
        }

        return @{
            ExitCode = $process.ExitCode
            Output   = $outputBuilder.ToString()
            Success  = ($process.ExitCode -eq 0)
        }
    }
    catch {
        Write-Host "##[endgroup]"
        throw
    }
}