# hallucinate PowerShell hook
# Dot-source from your profile or from `hallucinate activate --shell powershell`.

if (-not $env:HALLUCINATE_STREAM_FILE -or [string]::IsNullOrWhiteSpace($env:HALLUCINATE_STREAM_FILE)) {
    $env:HALLUCINATE_STREAM_FILE = Join-Path $env:TEMP "hallucinate_stream.json"
}

$global:HallucinateSessionId = $PID
$global:HallucinateLastHistorySignature = ""
$global:HallucinateLastErrorCount = $Error.Count
$global:HallucinatePendingCommand = ""
$global:HallucinatePrevAddToHistoryHandler = $null
$global:HallucinateEnterHandlerInstalled = $false

if (-not $global:HallucinateOriginalPromptScript) {
    $promptCmd = Get-Command prompt -ErrorAction SilentlyContinue
    if ($null -ne $promptCmd -and $promptCmd.ScriptBlock) {
        $global:HallucinateOriginalPromptScript = $promptCmd.ScriptBlock
    }
}

function Invoke-HallucinateHistoryHandler {
    param(
        [object]$Handler,
        [string]$Line
    )

    if ($null -eq $Handler) {
        return $true
    }

    if ($Handler -is [scriptblock]) {
        return [bool](& $Handler $Line)
    }

    $invoke = $Handler.PSObject.Methods["Invoke"]
    if ($null -ne $invoke) {
        return [bool]($Handler.Invoke($Line))
    }

    return $true
}

function Add-HallucinateUtf8Line {
    param(
        [string]$Path,
        [string]$Line
    )

    $directory = Split-Path -Parent $Path
    if ($directory) {
        New-Item -ItemType Directory -Path $directory -Force -ErrorAction SilentlyContinue | Out-Null
    }

    $encoding = [System.Text.UTF8Encoding]::new($false)
    $writer = [System.IO.StreamWriter]::new($Path, $true, $encoding)
    try {
        $writer.WriteLine($Line)
    } finally {
        $writer.Dispose()
    }
}

function Install-HallucinatePSReadLineCapture {
    if ($global:HallucinatePSRLInstalled) {
        return
    }
    Import-Module PSReadLine -ErrorAction SilentlyContinue | Out-Null
    $setOpt = Get-Command Set-PSReadLineOption -ErrorAction SilentlyContinue
    if ($null -eq $setOpt) {
        return
    }

    try {
        $opts = Get-PSReadLineOption -ErrorAction SilentlyContinue
        if ($opts -and $opts.AddToHistoryHandler) {
            $global:HallucinatePrevAddToHistoryHandler = $opts.AddToHistoryHandler
        }
    } catch {
        $global:HallucinatePrevAddToHistoryHandler = $null
    }

    try {
        Set-PSReadLineKeyHandler -Chord Enter -BriefDescription "HallucinateAcceptLine" -ScriptBlock {
            param($key, $arg)
            $line = $null
            $cursor = $null
            try {
                [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
            } catch {
                $line = $null
            }
            if (-not [string]::IsNullOrWhiteSpace($line)) {
                $global:HallucinatePendingCommand = [string]$line
            }
            [Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()
        }
        $global:HallucinateEnterHandlerInstalled = $true
    } catch {
    }

    try {
        Set-PSReadLineOption -AddToHistoryHandler {
            param($line)
            $allow = $true
            if ($global:HallucinatePrevAddToHistoryHandler) {
                try {
                    $allow = Invoke-HallucinateHistoryHandler -Handler $global:HallucinatePrevAddToHistoryHandler -Line $line
                } catch {
                    $allow = $true
                }
            }
            if ($allow) {
                $global:HallucinatePendingCommand = [string]$line
            }
            return $allow
        }
        $global:HallucinatePSRLInstalled = $true
    } catch {
        if ($global:HallucinateEnterHandlerInstalled) {
            $global:HallucinatePSRLInstalled = $true
        }
    }
}

function Write-HallucinateDebug {
    param([string]$Message)
    if ($env:HALLUCINATE_HOOK_DEBUG -ne "1") {
        return
    }
    $log = Join-Path $env:TEMP "hallucinate_hook_debug.log"
    Add-HallucinateUtf8Line -Path $log -Line ("{0} {1}" -f (Get-Date).ToString("o"), $Message)
}

function Test-HallucinatePathUnderRoot {
    param(
        [string]$Path,
        [string]$Root
    )

    if ([string]::IsNullOrWhiteSpace($Root)) {
        return $true
    }

    try {
        $fullPath = [System.IO.Path]::GetFullPath($Path)
        $fullRoot = [System.IO.Path]::GetFullPath($Root)
    } catch {
        return $false
    }

    if ($fullPath -eq $fullRoot) {
        return $true
    }

    $normalizedRoot = $fullRoot.TrimEnd('\', '/') + [System.IO.Path]::DirectorySeparatorChar
    return $fullPath.StartsWith($normalizedRoot, [System.StringComparison]::OrdinalIgnoreCase)
}

function Write-HallucinateEvent {
    param(
        [int]$ExitCode,
        [string]$Command,
        [string]$StderrText
    )

    $cwd = (Get-Location).Path
    if (-not (Test-HallucinatePathUnderRoot -Path $cwd -Root $env:HALLUCINATE_WORKSPACE_ROOT)) {
        return
    }

    $payload = [ordered]@{
        ts             = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
        shell          = "powershell"
        actor          = $(if ($env:HALLUCINATE_ACTOR) { $env:HALLUCINATE_ACTOR } else { "human" })
        session        = $global:HallucinateSessionId
        pid            = $PID
        cwd            = $cwd
        workspace_root = $env:HALLUCINATE_WORKSPACE_ROOT
        exit_code      = $ExitCode
        command        = $Command
        stderr         = $StderrText
    }

    $line = $payload | ConvertTo-Json -Compress
    Add-HallucinateUtf8Line -Path $env:HALLUCINATE_STREAM_FILE -Line $line
}

function Get-HallucinateExitCode {
    param(
        [bool]$LastSucceeded,
        [int]$LastNativeExit,
        [string]$CommandLine
    )

    $trimmed = if ($CommandLine) { $CommandLine.Trim() } else { "" }
    $firstToken = if ($trimmed) { ($trimmed -split "\s+")[0].Trim("'`"") } else { "" }
    $commandInfo = if ($firstToken) { Get-Command $firstToken -ErrorAction SilentlyContinue } else { $null }
    $isNative = $false
    if ($null -ne $commandInfo) {
        $isNative = $commandInfo.CommandType -in @("Application", "ExternalScript")
    }

    if ($isNative) {
        if ($null -ne $LastNativeExit) {
            return [int]$LastNativeExit
        }
        return $(if ($LastSucceeded) { 0 } else { 1 })
    }

    if ($LastSucceeded) {
        return 0
    }
    return 1
}

function Get-HallucinateRecentErrors {
    if ($Error.Count -le 0) {
        $global:HallucinateLastErrorCount = 0
        return ""
    }

    $delta = $Error.Count - $global:HallucinateLastErrorCount
    if ($delta -le 0) {
        $global:HallucinateLastErrorCount = $Error.Count
        return ""
    }

    $upper = [Math]::Min($delta, $Error.Count)
    $recent = for ($i = 0; $i -lt $upper; $i++) { $Error[$i].ToString() }
    $global:HallucinateLastErrorCount = $Error.Count
    return ($recent -join [Environment]::NewLine)
}

function global:prompt {
    $lastSucceeded = $?
    $lastNativeExit = if ($null -eq $global:LASTEXITCODE) { 0 } else { [int]$global:LASTEXITCODE }

    $cmd = ""
    if (-not [string]::IsNullOrWhiteSpace($global:HallucinatePendingCommand)) {
        $cmd = [string]$global:HallucinatePendingCommand
        $global:HallucinatePendingCommand = ""
    } else {
        $history = Get-History -Count 1 -ErrorAction SilentlyContinue
        if ($null -ne $history) {
            $start = if ($history.StartExecutionTime) { $history.StartExecutionTime.ToUniversalTime().ToString("o") } else { "" }
            $sig = "{0}|{1}|{2}" -f $history.Id, $start, [string]$history.CommandLine
            if ($sig -ne $global:HallucinateLastHistorySignature) {
                $cmd = [string]$history.CommandLine
                $global:HallucinateLastHistorySignature = $sig
            }
        }
    }

    if (-not [string]::IsNullOrWhiteSpace($cmd)) {
        $exitCode = Get-HallucinateExitCode -LastSucceeded $lastSucceeded -LastNativeExit $lastNativeExit -CommandLine $cmd
        $stderrText = Get-HallucinateRecentErrors
        Write-HallucinateDebug ("log cmd=`"{0}`" exit={1} native={2} success={3}" -f $cmd, $exitCode, $lastNativeExit, $lastSucceeded)
        Write-HallucinateEvent -ExitCode $exitCode -Command $cmd -StderrText $stderrText
    } else {
        Write-HallucinateDebug ("skip cmd; pending empty, history unchanged; native={0} success={1}" -f $lastNativeExit, $lastSucceeded)
    }

    if ($global:HallucinateOriginalPromptScript) {
        & $global:HallucinateOriginalPromptScript
    } else {
        "PS $($executionContext.SessionState.Path.CurrentLocation)> "
    }
}

$global:HallucinateHookLoaded = $true
Install-HallucinatePSReadLineCapture
