PwshCopilot.psm1

# Load internals
. "$PSScriptRoot\Private\Config.ps1"
. "$PSScriptRoot\Private\LLMClient.ps1"
. "$PSScriptRoot\Private\Completer.ps1"


# Ensure configuration on import (prompts on first run if missing or on version change)
$PwshCopilotConfigPath = "$env:USERPROFILE\.pwshcopilot_config.json"
$PwshCopilotVersionPath = "$env:USERPROFILE\.pwshcopilot_version.txt"
$CurrentModuleVersion = '1.1.0'
if (Test-Path $PwshCopilotVersionPath) {
    $lastVersion = Get-Content $PwshCopilotVersionPath -ErrorAction SilentlyContinue
    if ($lastVersion -ne $CurrentModuleVersion) {
        Remove-Item $PwshCopilotConfigPath -Force -ErrorAction SilentlyContinue
        Write-Host "[PwshCopilot] Module upgraded or reinstalled. Please re-run Initialize-PwshCopilot to set up your LLM credentials." -ForegroundColor Yellow
    }
} else {
    # First install
    Remove-Item $PwshCopilotConfigPath -Force -ErrorAction SilentlyContinue
    Write-Host "[PwshCopilot] Please run 'Initialize-PwshCopilot' to set up your LLM credentials before using LLM features." -ForegroundColor Yellow
}
Set-Content -Path $PwshCopilotVersionPath -Value $CurrentModuleVersion -Force

function Get-PSCommandSuggestion {
    param([string]$Description)
    $prompt = "Convert to PowerShell:\n$Description"
    Invoke-PSCopilotLLM -Prompt $prompt
}

if ($MyInvocation.InvocationName -eq 'PwshCopilot') {
    Write-Host "[PwshCopilot] Please run 'Initialize-PwshCopilot' to set up your LLM credentials before using LLM features." -ForegroundColor Yellow
}
# Helper: extract PowerShell code from LLM responses
function Convert-LLMResponseToPSCommand {
    param([string]$Text)

    if ([string]::IsNullOrWhiteSpace($Text)) { return $null }

    $codeBlockMatch = [regex]::Match($Text, '(?s)```(?:[a-zA-Z]+)?\s*(.*?)```')
    if ($codeBlockMatch.Success) {
        return ($codeBlockMatch.Groups[1].Value.Trim())
    }

    $lines = $Text -split "(`r`n|`n|`r)"
    $candidateLines = $lines | Where-Object {
        $_ -and ($_ -match '(\||\b(Get|Set|New|Remove|Start|Stop|Restart|Invoke|Enable|Disable|Select|Sort|Where|ForEach|Import|Export|Test|Measure|Write|Add|Clear|Copy|Move|Rename|Join|Split|Out|Format)-\w+)')
    }
    if ($candidateLines.Count -gt 0) {
        return (($candidateLines -join "`n").Trim())
    }

    return $Text.Trim()
}

# Helper: detect exit intent from natural language
function Test-PSCopilotExitIntent {
    param([string]$Text)
    if ([string]::IsNullOrWhiteSpace($Text)) { return $false }
    $patterns = @(
        '^\s*(exit|quit|quite|q)\b',
        '^\s*(bye|goodbye)\b',
        '^\s*(end|stop|close|terminate)\b',
        'close\s+(the\s+)?(session|chat|pscopilotsession)\b',
        'end\s+(the\s+)?(session|chat)\b',
        '(session|chat)\s+(close|end)\b',
        '\bthank\s*(you)?\b'
    )
    foreach ($p in $patterns) {
        if ($Text -match $p) { return $true }
    }
    return $false
}

# Helper: detect assistant invitation to continue
function Test-PSCopilotInviteToContinue {
    param([string]$Text)
    if ([string]::IsNullOrWhiteSpace($Text)) { return $false }
    $patterns = @(
        'more\s+questions',
        'anything\s+else',
        'feel\s+free\s+to\s+ask',
        'let\s+me\s+know\s+if',
        'any\s+other\s+question',
        'what\s+else\s+can\s+i\s+help',
        'need\s+anything\s+else',
        'do\s+you\s+have\s+any\s+other'
    )
    foreach ($p in $patterns) { if ($Text -match $p) { return $true } }
    return $false
}

# Helper: detect a simple negative response like "no"
function Test-PSCopilotNegativeResponse {
    param([string]$Text)
    if ([string]::IsNullOrWhiteSpace($Text)) { return $false }
    $patterns = @(
        '^\s*(no|nope|nah)\b',
        '^\s*(not\s+now|nothing|all\s+good)\b',
        '^(that''s|that\s+is)\s+all\b',
        '^\s*(i\s*(am|''m)\s+good)\b',
        '^\s*(no\s+thanks?|no\s+thank\s+you)\b'
    )
    foreach ($p in $patterns) { if ($Text -match $p) { return $true } }
    return $false
}

function Get-PSCommandExplanation {
    param([string]$Command)
    $prompt = "Explain in plain English what this does:\n$Command"
    Invoke-PSCopilotLLM -Prompt $prompt
}
Set-Alias -Name Explain-PSCommand -Value Get-PSCommandExplanation

function New-PSHelperScript {
    param([string]$Description, [string]$OutputPath = "$env:USERPROFILE\Documents\PwshCopilot")
    if (-not (Test-Path $OutputPath)) { New-Item -ItemType Directory -Path $OutputPath | Out-Null }

    $prompt = "Write a PowerShell script to do:\n$Description"
    $script = Invoke-PSCopilotLLM -Prompt $prompt

    $fileName = "Script_{0}.ps1" -f (Get-Date -Format "yyyyMMdd_HHmmss")
    $filePath = Join-Path $OutputPath $fileName
    $script | Set-Content $filePath
    Write-Host "Script saved: $filePath"
}

function Start-PSCopilotSession {
    Write-Host "PwshCopilot session started. Type 'exit' to quit."
    $messages = @()
    $systemPrompt = "You are a helpful PowerShell assistant. Answer concisely. When asked to generate commands, return only PowerShell unless explanation is explicitly requested. Prefer single-line commands for direct execution."
    $awaitingMore = $false
    while ($true) {
        $inputText = Read-Host "You"
        if ($inputText -eq "exit") { break }
        if ([string]::IsNullOrWhiteSpace($inputText)) { continue }

        if (Test-PSCopilotExitIntent -Text $inputText) {
            Write-Host "Ending session. Goodbye!" -ForegroundColor Cyan
            break
        }

        if ($awaitingMore -and (Test-PSCopilotNegativeResponse -Text $inputText)) {
            Write-Host "Okay, closing the session. Goodbye!" -ForegroundColor Cyan
            break
        }

        $messages += @{ role = 'user'; content = $inputText }
        $response = Invoke-PSCopilotLLMChat -Messages $messages -SystemPrompt $systemPrompt
        if (-not $response) { Write-Host "No response" -ForegroundColor Yellow; continue }

        $messages += @{ role = 'assistant'; content = $response }
        $awaitingMore = Test-PSCopilotInviteToContinue -Text $response

        $commandToRun = Convert-LLMResponseToPSCommand -Text $response

        Write-Host "Copilot:" -ForegroundColor Green
        Write-Host $response
        Write-Host "\nCommand candidate:" -ForegroundColor Green
        Write-Host $commandToRun -ForegroundColor Cyan

        $confirm = Read-Host "Run this command? (y/n)"
        if ($confirm -match '^(y|yes)$') {
            try {
                Write-Host "Executing..." -ForegroundColor Yellow
                Invoke-Expression -Command $commandToRun | Out-Host
            }
            catch {
                Write-Error $_
            }
        }
        else {
            Write-Host "Skipped."
        }
    }
}

function Invoke-PSCopilotDemo {
    param(
        [Parameter(Mandatory=$true)]
        [string[]]$Prompts,
        [string]$ExitInput = 'thanks',
        [switch]$SimulateNoAfterInvite
    )

    $systemPrompt = 'You are a helpful PowerShell assistant. Answer concisely. When asked to generate commands, return only PowerShell unless explanation is explicitly requested. Prefer single-line commands for direct execution.'
    $messages = @()
    $awaitingMore = $false

    foreach ($p in $Prompts) {
        $messages += @{ role = 'user'; content = $p }
        $response = Invoke-PSCopilotLLMChat -Messages $messages -SystemPrompt $systemPrompt
        Write-Host "Assistant:" -ForegroundColor Green
        Write-Host $response
        $cmd = Convert-LLMResponseToPSCommand -Text $response
        Write-Host "Command:" -ForegroundColor Green
        Write-Host $cmd -ForegroundColor Cyan
        if ($cmd) {
            Write-Host "Executing..." -ForegroundColor Yellow
            Invoke-Expression -Command $cmd | Out-Host
        }
        $messages += @{ role = 'assistant'; content = $response }
        $awaitingMore = Test-PSCopilotInviteToContinue -Text $response
        if ($awaitingMore -and $SimulateNoAfterInvite) {
            Write-Host "User: no" -ForegroundColor Magenta
            if (Test-PSCopilotNegativeResponse -Text 'no') {
                Write-Host 'Okay, closing the session. Goodbye!' -ForegroundColor Cyan
                return
            }
        }
    }

    if (Test-PSCopilotExitIntent -Text $ExitInput) {
        Write-Host 'Ending session. Goodbye!' -ForegroundColor Cyan
    }
}

function Enable-PSCopilotCompletion {
    # Helper: Collect session context (recent commands + last error)
    function Get-PSCopilotContext {
        $history = (Get-History | Select-Object -Last 10 | ForEach-Object { $_.CommandLine }) -join "`n"
        $lastError = if ($Error.Count -gt 0) { $Error[0].ToString() } else { "" }

        return @{
            History   = $history
            LastError = $lastError
        }
    }

    # Override the completer to send context to LLM
    function Register-PSCopilotCompleter {
        Register-ArgumentCompleter -CommandName * -ScriptBlock {
            param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParams)

            $context = Get-PSCopilotContext
            $prompt = @"
The user is typing a PowerShell command.
Recent history:
$($context.History)
 
Last error:
$($context.LastError)
 
Partial input: $wordToComplete
Suggest several possible next completions. Return them as a list.
"@


            # Call LLM using module helper
            $suggestions = Invoke-PSCopilotLLM -Prompt $prompt

            # Parse into multiple suggestions (split by newline or semicolon)
            $choices = $suggestions -split "[`n;]" | Where-Object { $_ -match '\\S' }

            foreach ($c in $choices) {
                [System.Management.Automation.CompletionResult]::new(
                    $c.Trim(),
                    $c.Trim(),
                    'ParameterValue',
                    $c.Trim()
                )
            }
        }
    }

    # Register the completer with AI backend
    Register-PSCopilotCompleter
    Write-Host "PwshCopilot live AI completion enabled! Use Tab to cycle through multiple AI suggestions."
}