VBAF.Center.ClaudeBrain.ps1

#Requires -Version 5.1
<#
.SYNOPSIS
    VBAF-Center Phase 19 — AI Brain (Multi-Provider)
.DESCRIPTION
    Real AI decision engine supporting multiple providers.
    Replaces the if-statement with genuine intelligence.

    Supported providers:
      Claude — Anthropic (paid, best quality)
      Gemini — Google (FREE — 1500 req/day — excellent)
      Groq — Groq (FREE — extremely fast)
      OpenRouter — OpenRouter (FREE — 200 req/day)
      Mistral — Mistral AI (FREE)

    Returns full intelligence:
      Action number (0-3)
      Reason in plain Danish
      Specific dispatcher instruction
      Pattern recognition from history
      Confidence level

    Functions:
      Set-VBAFCenterAIKey — save API key for a provider
      Get-VBAFCenterAIProviders — show all providers and status
      Test-VBAFCenterAIProvider — test a provider connection
      Invoke-VBAFCenterClaudeBrain — run full AI analysis
      Get-VBAFCenterClaudeBrainHistory — show AI decision history
#>


$script:AIConfigPath = Join-Path $env:USERPROFILE "VBAFCenter\ai"

# ============================================================
# PROVIDER DEFINITIONS
# ============================================================
$script:AIProviders = @{

    "Claude" = @{
        Name        = "Claude Sonnet (Anthropic)"
        URL         = "https://api.anthropic.com/v1/messages"
        Model       = "claude-sonnet-4-20250514"
        Format      = "Anthropic"
        Free        = $false
        Description = "Best quality — paid API"
        GetKey      = "https://console.anthropic.com"
    }

    "Gemini" = @{
        Name        = "Gemini 2.0 Flash (Google)"
        URL         = "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions"
        Model       = "gemini-2.0-flash"
        Format      = "OpenAI"
        Free        = $true
        Description = "FREE — 1500 req/day — excellent quality"
        GetKey      = "https://aistudio.google.com/app/apikey"
    }

    "Groq" = @{
        Name        = "Llama 3.3 70B (Groq)"
        URL         = "https://api.groq.com/openai/v1/chat/completions"
        Model       = "llama-3.3-70b-versatile"
        Format      = "OpenAI"
        Free        = $true
        Description = "FREE — extremely fast inference"
        GetKey      = "https://console.groq.com/keys"
    }

    "OpenRouter" = @{
        Name        = "DeepSeek R1 (OpenRouter)"
        URL         = "https://openrouter.ai/api/v1/chat/completions"
        Model       = "deepseek/deepseek-r1:free"
        Format      = "OpenAI"
        Free        = $true
        Description = "FREE — 200 req/day — many models"
        GetKey      = "https://openrouter.ai/keys"
    }

    "Mistral" = @{
        Name        = "Mistral Small (Mistral AI)"
        URL         = "https://api.mistral.ai/v1/chat/completions"
        Model       = "mistral-small-latest"
        Format      = "OpenAI"
        Free        = $true
        Description = "FREE tier — good quality"
        GetKey      = "https://console.mistral.ai/api-keys"
    }
}

# ============================================================
# INITIALIZE
# ============================================================
function Initialize-VBAFCenterAIStore {
    if (-not (Test-Path $script:AIConfigPath)) {
        New-Item -ItemType Directory -Path $script:AIConfigPath -Force | Out-Null
    }
}

# ============================================================
# SET-VBAFCENTERAIKEY
# ============================================================
function Set-VBAFCenterAIKey {
    <#
    .SYNOPSIS
        Save an API key for a provider.
    .EXAMPLE
        Set-VBAFCenterAIKey -Provider "Gemini" -APIKey "AIzaXXXXXX"
        Set-VBAFCenterAIKey -Provider "Groq" -APIKey "gsk_XXXXXXXX"
        Set-VBAFCenterAIKey -Provider "OpenRouter" -APIKey "sk-or-XXXXXXX"
        Set-VBAFCenterAIKey -Provider "Mistral" -APIKey "XXXXXXXXXX"
        Set-VBAFCenterAIKey -Provider "Claude" -APIKey "sk-ant-XXXXXX"
    #>

    param(
        [Parameter(Mandatory)]
        [ValidateSet("Claude","Gemini","Groq","OpenRouter","Mistral")]
        [string] $Provider,
        [Parameter(Mandatory)] [string] $APIKey
    )

    Initialize-VBAFCenterAIStore

    $configFile = Join-Path $script:AIConfigPath "$Provider-key.json"
    @{
        Provider = $Provider
        APIKey   = $APIKey
        SavedAt  = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
    } | ConvertTo-Json | Set-Content $configFile -Encoding UTF8

    $p = $script:AIProviders[$Provider]
    Write-Host ""
    Write-Host (" API key saved: {0}" -f $p.Name) -ForegroundColor Green
    Write-Host (" Free : {0}" -f (if ($p.Free) { "Yes — " + $p.Description } else { "No (paid)" })) -ForegroundColor White
    Write-Host (" Model : {0}" -f $p.Model) -ForegroundColor White
    Write-Host (" Test : Test-VBAFCenterAIProvider -Provider ""{0}""" -f $Provider) -ForegroundColor DarkGray
    Write-Host ""
}

# ============================================================
# GET-VBAFCENTERAIPROVIDERS
# ============================================================
function Get-VBAFCenterAIProviders {
    <#
    .SYNOPSIS
        Show all providers and which ones have API keys configured.
    #>

    Initialize-VBAFCenterAIStore

    Write-Host ""
    Write-Host " VBAF-Center AI Providers" -ForegroundColor Cyan
    Write-Host ""
    Write-Host (" {0,-12} {1,-32} {2,-6} {3,-10} {4}" -f "Provider","Name","Free","Status","Description") -ForegroundColor Yellow
    Write-Host (" {0}" -f ("-" * 85)) -ForegroundColor DarkGray

    foreach ($key in ($script:AIProviders.Keys | Sort-Object)) {
        $p       = $script:AIProviders[$key]
        $keyFile = Join-Path $script:AIConfigPath "$key-key.json"
        $status  = if (Test-Path $keyFile) { "Key OK" } else { "No key" }
        $color   = if (Test-Path $keyFile) { "Green" } else { "DarkGray" }
        $free    = if ($p.Free) { "FREE" } else { "Paid" }
        Write-Host (" {0,-12} {1,-32} {2,-6} {3,-10} {4}" -f $key, $p.Name, $free, $status, $p.Description) -ForegroundColor $color
    }

    Write-Host ""
    Write-Host " Get free API keys:" -ForegroundColor Yellow
    foreach ($key in ($script:AIProviders.Keys | Where-Object { $script:AIProviders[$_].Free } | Sort-Object)) {
        Write-Host (" {0,-12} {1}" -f $key, $script:AIProviders[$key].GetKey) -ForegroundColor DarkGray
    }
    Write-Host ""
}

# ============================================================
# GET API KEY (internal)
# ============================================================
function Get-VBAFCenterAIKey {
    param([string] $Provider)
    Initialize-VBAFCenterAIStore
    $configFile = Join-Path $script:AIConfigPath "$Provider-key.json"
    if (-not (Test-Path $configFile)) { return $null }
    $config = Get-Content $configFile -Raw | ConvertFrom-Json
    return $config.APIKey
}

# ============================================================
# REPAIR-VBAFCENTERDANISH — fix encoding of Danish characters
# ============================================================
function Repair-VBAFCenterDanish {
    param([string] $Text)
    $Text = $Text -replace 'æ', 'ae' -replace 'Ã…', 'AA' -replace 'Ã¥', 'aa'
    $Text = $Text -replace 'ø', 'oe' -replace 'Ø', 'OE'
    $Text = $Text -replace 'æ', 'ae' -replace 'Æ', 'AE'
    $Text = $Text -replace 'é', 'e'  -replace 'è', 'e'
    $Text = $Text -replace 'à ', 'a'  -replace 'â', 'a'
    $Text = $Text -replace 'ë', 'e'  -replace 'ï', 'i'
    $Text = $Text -replace 'î', 'i'  -replace 'ô', 'o'
    $Text = $Text -replace 'û', 'u'  -replace 'ù', 'u'
    $Text = $Text -replace 'ç', 'c'  -replace 'ñ', 'n'
    # Common Danish words - direct fixes
    $Text = $Text -replace 'rA¸de',    'roede'
    $Text = $Text -replace 'hA¸j',     'hoej'
    $Text = $Text -replace 'kA¦r',     'kaer'
    $Text = $Text -replace 'brA¦nd',   'braend'
    $Text = $Text -replace 'stofA',    'stofa'
    $Text = $Text -replace 'A¸je',     'oje'
    $Text = $Text -replace 'A¸kono',   'okono'
    $Text = $Text -replace 'lA¦nge',   'laenge'
    $Text = $Text -replace 'tilgA¦ng', 'tilgaeng'
    $Text = $Text -replace 'forA¦ld',  'foraeld'
    $Text = $Text -replace 'omrA¥d',   'omraad'
    return $Text
}

# ============================================================
# INVOKE AI CALL (internal) — with auto-retry on 429
# ============================================================
function Invoke-VBAFCenterAICall {
    param([string]$Provider, [string]$Prompt, [string]$APIKey)

    $p          = $script:AIProviders[$Provider]
    $maxRetries = 3
    $retryWait  = 65  # seconds — just over Mistral 1-per-minute limit

    for ($attempt = 1; $attempt -le $maxRetries; $attempt++) {

        try {
            if ($p.Format -eq "Anthropic") {
                $body = @{
                    model      = $p.Model
                    max_tokens = 1000
                    messages   = @(@{ role="user"; content=[string]$Prompt })
                } | ConvertTo-Json -Depth 5

                $headers = @{
                    "x-api-key"         = $APIKey
                    "anthropic-version" = "2023-06-01"
                    "content-type"      = "application/json"
                }

                $response = Invoke-RestMethod -Uri $p.URL -Method POST -Headers $headers -Body $body -ErrorAction Stop
                return $response.content[0].text

            } else {
                $promptString = [string]$Prompt
                $bodyObj = [ordered]@{
                    model    = [string]$p.Model
                    messages = @([ordered]@{ role="user"; content=$promptString })
                }
                $body      = $bodyObj | ConvertTo-Json -Depth 5
                $bodyBytes = [System.Text.Encoding]::UTF8.GetBytes($body)

                $headers = @{
                    "Authorization" = "Bearer $APIKey"
                    "Content-Type"  = "application/json; charset=utf-8"
                }

                if ($Provider -eq "OpenRouter") {
                    $headers["HTTP-Referer"] = "https://github.com/JupyterPS/VBAF-Center"
                    $headers["X-Title"]      = "VBAF-Center"
                }

                $response = Invoke-RestMethod -Uri $p.URL -Method POST -Headers $headers -Body $bodyBytes -ErrorAction Stop
                return $response.choices[0].message.content
            }

        } catch {
            $errMsg = $_.Exception.Message
            if ($errMsg -like "*429*" -or $errMsg -like "*Too Many*") {
                if ($attempt -lt $maxRetries) {
                    Write-Host (" Rate limit hit — waiting {0} seconds before retry {1}/{2}..." -f $retryWait, $attempt, $maxRetries) -ForegroundColor Yellow
                    Start-Sleep -Seconds $retryWait
                } else {
                    throw "Rate limit exceeded after $maxRetries attempts. Try again in a few minutes."
                }
            } else {
                throw $_
            }
        }
    }
}

# ============================================================
# TEST-VBAFCENTERAIPROVIDER
# ============================================================
function Test-VBAFCenterAIProvider {
    <#
    .SYNOPSIS
        Test a provider with a simple ping.
    .EXAMPLE
        Test-VBAFCenterAIProvider -Provider "Gemini"
        Test-VBAFCenterAIProvider -Provider "Groq"
    #>

    param(
        [Parameter(Mandatory)]
        [ValidateSet("Claude","Gemini","Groq","OpenRouter","Mistral")]
        [string] $Provider
    )

    $apiKey = Get-VBAFCenterAIKey -Provider $Provider
    if (-not $apiKey) {
        Write-Host ("No API key for {0}." -f $Provider) -ForegroundColor Red
        Write-Host (" Run: Set-VBAFCenterAIKey -Provider ""{0}"" -APIKey ""your-key""" -f $Provider) -ForegroundColor Yellow
        return
    }

    $p = $script:AIProviders[$Provider]
    Write-Host ""
    Write-Host ("Testing: {0}" -f $p.Name) -ForegroundColor Yellow
    Write-Host (" URL : {0}" -f $p.URL)   -ForegroundColor White
    Write-Host (" Model : {0}" -f $p.Model) -ForegroundColor White

    try {
        $result = Invoke-VBAFCenterAICall -Provider $Provider -APIKey $apiKey `
            -Prompt 'Reply with exactly: {"status":"ok","message":"VBAF connection test successful"}'
        Write-Host (" Result: Connection OK") -ForegroundColor Green
        Write-Host (" Response: {0}" -f ($result -replace "`n"," ")) -ForegroundColor DarkGray
    } catch {
        Write-Host (" FAILED: {0}" -f $_.Exception.Message) -ForegroundColor Red
    }
    Write-Host ""
}

# ============================================================
# GET-VBAFCENTERHISTORYSUMMARY — 30-day aggregated analysis
# ============================================================
function Get-VBAFCenterHistorySummary {
    param(
        [string] $CustomerID,
        [int]    $Days = 30
    )

    $historyPath = Join-Path $env:USERPROFILE "VBAFCenter\history"
    if (-not (Test-Path $historyPath)) { return " Ingen historik tilgaengelig.`n" }

    $cutoff = (Get-Date).AddDays(-$Days)
    $files  = Get-ChildItem $historyPath -Filter "$CustomerID-*.json" |
              Where-Object { $_.LastWriteTime -ge $cutoff } |
              Sort-Object LastWriteTime

    if ($files.Count -eq 0) { return " Ingen historik i de seneste $Days dage.`n" }

    $runs = @()
    foreach ($f in $files) {
        try { $runs += Get-Content $f.FullName -Raw | ConvertFrom-Json } catch {}
    }

    if ($runs.Count -eq 0) { return " Ingen gyldige historik-poster fundet.`n" }

    # Action distribution
    $actionCounts = @{0=0;1=0;2=0;3=0}
    foreach ($r in $runs) { $actionCounts[[int]$r.Action]++ }

    # Average per day of week
    $dayAvgs = @{}
    $dayNames = @("Sondag","Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lordag")
    foreach ($r in $runs) {
        try {
            $dt  = [DateTime]::Parse($r.Timestamp)
            $day = [int]$dt.DayOfWeek
            if (-not $dayAvgs.ContainsKey($day)) { $dayAvgs[$day] = @() }
            $dayAvgs[$day] += [double]$r.AvgSignal
        } catch {}
    }

    # Trend — last 5 vs previous 5
    $trend = "stabil"
    if ($runs.Count -ge 10) {
        $recent5 = ($runs | Select-Object -Last 5  | ForEach-Object { [double]$_.AvgSignal } | Measure-Object -Average).Average
        $prev5   = ($runs | Select-Object -First 5 | ForEach-Object { [double]$_.AvgSignal } | Measure-Object -Average).Average
        $diff    = [Math]::Round($recent5 - $prev5, 3)
        if ($diff -gt 0.05)       { $trend = "stigende (+$diff)" }
        elseif ($diff -lt -0.05)  { $trend = "faldende ($diff)" }
    }

    # Override rate
    $overrideCount = @($runs | Where-Object { $_.OverrideApplied -eq $true }).Count
    $overridePct   = if ($runs.Count -gt 0) { [Math]::Round($overrideCount / $runs.Count * 100, 0) } else { 0 }

    # Worst signal combination
    $escalateRuns = @($runs | Where-Object { [int]$_.Action -eq 3 })
    $worstAvg     = if ($escalateRuns.Count -gt 0) {
        [Math]::Round(($escalateRuns | ForEach-Object { [double]$_.AvgSignal } | Measure-Object -Average).Average, 3)
    } else { "N/A" }

    # Overall average
    $overallAvg = [Math]::Round(($runs | ForEach-Object { [double]$_.AvgSignal } | Measure-Object -Average).Average, 3)

    # Build summary text
    $summary = "HISTORIK SAMMENDRAG ($Days dage — $($runs.Count) koersler):`n"
    $summary += " Samlet gennemsnit : $overallAvg`n"
    $summary += " Trend (nu vs start) : $trend`n"
    $summary += " Override rate : $overridePct% (dispatcher korrigerede $overrideCount gange)`n"
    $summary += " Action fordeling : Monitor=$($actionCounts[0]) Reassign=$($actionCounts[1]) Reroute=$($actionCounts[2]) Escalate=$($actionCounts[3])`n"

    if ($escalateRuns.Count -gt 0) {
        $summary += " Kritiske situationer: $($escalateRuns.Count) Escalate-haendelser (gns signal ved krise: $worstAvg)`n"
    }

    # Day of week pattern
    $dayPattern = ""
    foreach ($day in ($dayAvgs.Keys | Sort-Object)) {
        $avg = [Math]::Round(($dayAvgs[$day] | Measure-Object -Average).Average, 3)
        $dayPattern += "$($dayNames[$day])=$avg "
    }
    if ($dayPattern -ne "") {
        $summary += " Ugedags-moenster : $dayPattern`n"
        # Find worst day
        $worstDay = $dayAvgs.Keys | Sort-Object { ($dayAvgs[$_] | Measure-Object -Average).Average } -Descending | Select-Object -First 1
        $summary += " Typisk vaerste dag : $($dayNames[$worstDay])`n"
    }

    # Time of day pattern
    $morningRuns   = @($runs | Where-Object { try { [DateTime]::Parse($_.Timestamp).Hour -lt 12   } catch { $false } })
    $afternoonRuns = @($runs | Where-Object { try { $h=[DateTime]::Parse($_.Timestamp).Hour; $h -ge 12 -and $h -lt 17 } catch { $false } })
    $eveningRuns   = @($runs | Where-Object { try { [DateTime]::Parse($_.Timestamp).Hour -ge 17  } catch { $false } })

    if ($morningRuns.Count -gt 0 -and $afternoonRuns.Count -gt 0) {
        $morningAvg   = [Math]::Round(($morningRuns   | ForEach-Object { [double]$_.AvgSignal } | Measure-Object -Average).Average, 3)
        $afternoonAvg = [Math]::Round(($afternoonRuns | ForEach-Object { [double]$_.AvgSignal } | Measure-Object -Average).Average, 3)
        $summary += " Tidspunkt moenster : Formiddag=$morningAvg Eftermiddag=$afternoonAvg`n"
        if ($afternoonAvg -gt $morningAvg + 0.10) {
            $summary += " OBS: Situationen forvaerres typisk om eftermiddagen`n"
        }
    }

    return $summary
}

# ============================================================
# GET-VBAFCENTERAIOVERRIDECONTEXT — feed overrides back to Mistral
# ============================================================
function Get-VBAFCenterAIOverrideContext {
    <#
    .SYNOPSIS
        Reads override history and previous AI decisions to build
        a feedback context for the next Mistral prompt.
        This closes the loop — Mistral learns from its own mistakes.
    #>

    param(
        [string] $CustomerID,
        [int]    $Last = 10
    )

    $overridePath = Join-Path $env:USERPROFILE "VBAFCenter\overrides\$CustomerID-overrides.json"
    $historyPath  = Join-Path $env:USERPROFILE "VBAFCenter\history"
    $actionNames  = @("Monitor","Reassign","Reroute","Escalate")

    $lines = @()

    # Load dispatcher overrides
    if (Test-Path $overridePath) {
        try {
            $overrides = @(Get-Content $overridePath -Raw | ConvertFrom-Json)
            $recent    = $overrides | Select-Object -Last $Last

            foreach ($o in $recent) {
                $vbafAction = $actionNames[[int]($o.VBAFAction | Select-Object -First 1)]
                $dispAction = $actionNames[[int]($o.DispatcherAction | Select-Object -First 1)]
                $reason     = if ($o.Reason) { [string]$o.Reason } else { "ingen begrundelse" }
                $ts         = [string]$o.Timestamp

                if ($vbafAction -ne $dispAction) {
                    $lines += " $ts : AI anbefalede $vbafAction — dispatcher valgte $dispAction — Begrundelse: $reason"
                }
            }
        } catch {}
    }

    # Load previous AI decisions from daily log
    $logPath = Join-Path $env:USERPROFILE "VBAFCenter\dailylog"
    if (Test-Path $logPath) {
        $logFiles = Get-ChildItem $logPath -Filter "$CustomerID-*.log" |
                    Sort-Object LastWriteTime -Descending |
                    Select-Object -First 3

        foreach ($lf in $logFiles) {
            $logLines = Get-Content $lf.FullName -ErrorAction SilentlyContinue
            if ($logLines) {
                foreach ($ll in ($logLines | Select-Object -Last 3)) {
                    $lines += " Tidligere AI log: $ll"
                }
            }
        }
    }

    # Load AI brain history to find patterns
    if (Test-Path $historyPath) {
        $aiFiles = Get-ChildItem $historyPath -Filter "$CustomerID-*.json" |
                   Sort-Object LastWriteTime -Descending |
                   Select-Object -First 20

        $aiDecisions = @()
        foreach ($f in $aiFiles) {
            try {
                $h = Get-Content $f.FullName -Raw | ConvertFrom-Json
                if ($h.Source -like "AI-*") { $aiDecisions += $h }
            } catch {}
        }

        if ($aiDecisions.Count -gt 0) {
            # Find cases where AI said Escalate repeatedly
            $escalateCount = @($aiDecisions | Where-Object { [int]$_.Action -eq 3 }).Count
            if ($escalateCount -gt ($aiDecisions.Count * 0.6)) {
                $lines += " OBS: AI har anbefalet Escalate i $escalateCount af $($aiDecisions.Count) seneste koersler — overvej om terskler skal justeres"
            }

            # Check agreement with overrides
            $overrideCount = @($aiDecisions | Where-Object { $_.OverrideApplied -eq $true }).Count
            if ($overrideCount -gt 0) {
                $lines += " Override rate for AI-beslutninger: $overrideCount af $($aiDecisions.Count) havde roed-signal override"
            }
        }
    }

    if ($lines.Count -eq 0) {
        return " Ingen tidligere AI-anbefalinger eller dispatcher-overrides registreret endnu.`n Systemet laerer efterhaanden som dispatcher logger overrides via Start-VBAFCenterOverride."
    }

    return $lines -join "`n"
}

# ============================================================
# BUILD PROMPT (internal)
# ============================================================
function Build-VBAFCenterAIPrompt {
    param(
        [string]   $CustomerID,
        [object]   $Profile,
        [object[]] $Signals,
        [object[]] $History,
        [object]   $ActionMap,
        [double]   $WeightedAvg,
        [object[]] $RedSignals,
        [object[]] $YellowSignals,
        [string]   $HistorySummary = "",
        [string]   $OverrideHistoryText = " Ingen tidligere AI-anbefalinger registreret endnu."
    )

    $signalText = ""
    foreach ($s in $Signals) {
        $status = if ($s.SignalColour) { $s.SignalColour } else {
            if ($s.Normalised -gt 0.75) { "RED" } elseif ($s.Normalised -gt 0.40) { "YELLOW" } else { "GREEN" }
        }
        $thr = ""
        if ($s.GoodBelow -ge 0 -or $s.BadAbove -ge 0) { $thr = " (god under $($s.GoodBelow), kritisk over $($s.BadAbove))" }
        $signalText += " - $($s.SignalName): $($s.RawValue)$thr -- $status`n"
    }

    $historyText = ""
    if ($History -and $History.Count -gt 0) {
        foreach ($h in ($History | Select-Object -Last 5)) {
            $historyText += " - $($h.Timestamp): $($h.ActionName) (avg $($h.AvgSignal))"
            if ($h.OverrideApplied) { $historyText += " [OVERRIDE]" }
            $historyText += "`n"
        }
    } else { $historyText = " Ingen historik endnu.`n" }

    $actionText = ""
    if ($ActionMap) {
        $actionText = " Action 0: $($ActionMap.Action0Command)`n Action 1: $($ActionMap.Action1Command)`n Action 2: $($ActionMap.Action2Command)`n Action 3: $($ActionMap.Action3Command)`n"
    } else {
        $actionText = " Standard: Monitor / Reassign / Reroute / Escalate`n"
    }

    $redCount    = if ($RedSignals)    { @($RedSignals).Count    } else { 0 }
    $yellowCount = if ($YellowSignals) { @($YellowSignals).Count } else { 0 }

    return @"
Du er driftsassistent for $($Profile.CompanyName) - en $($Profile.BusinessType) virksomhed i Danmark.
Svar ALTID paa dansk. Vaer konkret. Ingen lange forklaringer.

KUNDEPROFIL:
  Virksomhed: $($Profile.CompanyName) | Branche: $($Profile.BusinessType) | Agent: $($Profile.Agent)
  Problem: $($Profile.Problem)

AKTUELLE SIGNALER:
$signalText
OVERSIGT: Vaegtet gns=$([Math]::Round($WeightedAvg,4)) | Roede=$redCount | Gule=$yellowCount

$HistorySummary
SENESTE 5 KOERSLER:
$historyText
HANDLINGER:
$actionText
TIDLIGERE AI ANBEFALINGER OG UDFALD:
$overrideHistoryText
OPGAVE - returner KUN dette JSON uden markdown eller forklaring:
{"Action":<0-3>,"ActionName":"<Monitor/Reassign/Reroute/Escalate>","Reason":"<2-3 saetninger>","Instruction":"<1-2 konkrete saetninger til dispatcher>","Pattern":"<1 saetning eller tom>","Confidence":"<Hoj/Medium/Lav>"}

REGLER: 1 roed=min Action 2 | 2+ roede=min Action 3 | avg>0.75=Action 3 | avg>0.50=Action 2 | avg>0.25=Action 1
Brug overrides til at laere hvad der virker for DENNE kunde. Hvis dispatcher gentagne gange vaelger lavere action end AI — vaer mere forsigtig.
"@

}

# ============================================================
# INVOKE-VBAFCENTERCLAUDEBRAIN
# ============================================================
function Invoke-VBAFCenterClaudeBrain {
    <#
    .SYNOPSIS
        Full AI analysis using your chosen provider.
    .EXAMPLE
        Invoke-VBAFCenterClaudeBrain -CustomerID "TruckCompanyDK"
        Invoke-VBAFCenterClaudeBrain -CustomerID "TruckCompanyDK" -Provider "Gemini"
        Invoke-VBAFCenterClaudeBrain -CustomerID "TruckCompanyDK" -Provider "Groq"
        Invoke-VBAFCenterClaudeBrain -CustomerID "TruckCompanyDK" -Provider "Claude"
    #>

    param(
        [Parameter(Mandatory)] [string] $CustomerID,
        [ValidateSet("Claude","Gemini","Groq","OpenRouter","Mistral")]
        [string] $Provider = "Gemini",
        [switch] $SuppressCrisis
    )

    $apiKey = Get-VBAFCenterAIKey -Provider $Provider
    if (-not $apiKey) {
        Write-Host ""
        Write-Host ("No API key for {0}." -f $Provider) -ForegroundColor Red
        $p = $script:AIProviders[$Provider]
        Write-Host (" Get free key : {0}" -f $p.GetKey) -ForegroundColor Yellow
        Write-Host (" Then run : Set-VBAFCenterAIKey -Provider ""{0}"" -APIKey ""your-key""" -f $Provider) -ForegroundColor Yellow
        Write-Host ""
        return $null
    }

    $p = $script:AIProviders[$Provider]

    Write-Host ""
    Write-Host ("AI Brain [{0}]: {1} — {2}" -f $Provider, $CustomerID, (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")) -ForegroundColor Cyan
    Write-Host (" Provider : {0}" -f $p.Name)  -ForegroundColor White
    Write-Host (" Model : {0}" -f $p.Model) -ForegroundColor White
    Write-Host " Gathering context..." -ForegroundColor DarkGray

    # Load profile
    $profilePath = Join-Path $env:USERPROFILE "VBAFCenter\customers\$CustomerID.json"
    if (-not (Test-Path $profilePath)) {
        Write-Host ("Customer not found: {0}" -f $CustomerID) -ForegroundColor Red
        return $null
    }
    $profile = Get-Content $profilePath -Raw | ConvertFrom-Json

    # Get signals
    if (-not (Get-Command Get-VBAFCenterAllSignals -ErrorAction SilentlyContinue)) {
        Write-Host "Phase 3 not loaded. Run: . .\VBAF-Center\VBAF.Center.LoadAll.ps1" -ForegroundColor Yellow
        return $null
    }

    $signalResult  = Get-VBAFCenterAllSignals -CustomerID $CustomerID
    $signals       = @($signalResult.Signals)
    $weightedAvg   = if ($signalResult.WeightedAvg) { $signalResult.WeightedAvg } else { $signalResult.SimpleAvg }
    $redSignals    = @($signalResult.RedSignals)
    $yellowSignals = @($signalResult.YellowSignals)

    if ($signals.Count -eq 0) {
        Write-Host ("No signals configured for: {0}" -f $CustomerID) -ForegroundColor Yellow
        return $null
    }

    # Load history
    $historyPath = Join-Path $env:USERPROFILE "VBAFCenter\history"
    $history     = @()
    if (Test-Path $historyPath) {
        $files = Get-ChildItem $historyPath -Filter "$CustomerID-*.json" |
                 Sort-Object LastWriteTime -Descending | Select-Object -First 10
        foreach ($f in $files) {
            try { $history += Get-Content $f.FullName -Raw | ConvertFrom-Json } catch {}
        }
        $history = @($history | Sort-Object Timestamp)
    }

    # Load action map
    $actionMap  = $null
    $actionFile = Join-Path $env:USERPROFILE "VBAFCenter\actions\$CustomerID-actions.txt"
    if (Test-Path $actionFile) {
        $lines = Get-Content $actionFile
        $actionMap = [PSCustomObject]@{
            Action0Command = ($lines | Where-Object { $_ -match "^0\|" } | ForEach-Object { ($_ -split "\|")[2] }) -join ""
            Action1Command = ($lines | Where-Object { $_ -match "^1\|" } | ForEach-Object { ($_ -split "\|")[2] }) -join ""
            Action2Command = ($lines | Where-Object { $_ -match "^2\|" } | ForEach-Object { ($_ -split "\|")[2] }) -join ""
            Action3Command = ($lines | Where-Object { $_ -match "^3\|" } | ForEach-Object { ($_ -split "\|")[2] }) -join ""
        }
    }

    # Build 30-day history summary
    Write-Host " Building 30-day history summary..." -ForegroundColor DarkGray
    $historySummary = Get-VBAFCenterHistorySummary -CustomerID $CustomerID -Days 30

    # Build override feedback context
    Write-Host " Loading override feedback context..." -ForegroundColor DarkGray
    $overrideContext = Get-VBAFCenterAIOverrideContext -CustomerID $CustomerID

    # Build and send prompt
    $prompt = Build-VBAFCenterAIPrompt `
        -CustomerID $CustomerID -Profile $profile -Signals $signals `
        -History $history -ActionMap $actionMap -WeightedAvg $weightedAvg `
        -RedSignals $redSignals -YellowSignals $yellowSignals `
        -HistorySummary $historySummary `
        -OverrideHistoryText $overrideContext

    Write-Host (" Calling {0}..." -f $p.Name) -ForegroundColor DarkGray

    $aiResponse = $null
    try {
        $rawText    = Invoke-VBAFCenterAICall -Provider $Provider -Prompt $prompt -APIKey $apiKey
        $rawText    = Repair-VBAFCenterDanish -Text $rawText
        $clean      = $rawText.Trim() -replace '```json', '' -replace '```', '' -replace "`n", " "
        # Extract JSON if surrounded by other text
        if ($clean -match '\{.*\}') { $clean = $Matches[0] }
        $aiResponse = $clean | ConvertFrom-Json
        # Fix confidence encoding
        if ($aiResponse.Confidence) {
            $conf = [string]$aiResponse.Confidence
            $conf = $conf -replace "Hoej","Høj" -replace "HOej","Høj" -replace "Hoj","Høj"
            $conf = $conf -replace "Haj","Høj"  -replace "High","Høj"
            $conf = $conf -replace "Lav","Lav"  -replace "Low","Lav"
            $aiResponse.Confidence = $conf
        }
    } catch {
        Write-Host (" AI call failed: {0}" -f $_.Exception.Message) -ForegroundColor Red
        Write-Host " Raw response was:" -ForegroundColor DarkGray
        if ($rawText) { Write-Host (" {0}" -f $rawText.Substring(0, [Math]::Min(200, $rawText.Length))) -ForegroundColor DarkGray }
        return $null
    }

    # Display
    $action      = [int]$aiResponse.Action
    $actionName  = [string]$aiResponse.ActionName
    $reason      = [string]$aiResponse.Reason
    $instruction = [string]$aiResponse.Instruction
    $pattern     = [string]$aiResponse.Pattern
    $confidence  = [string]$aiResponse.Confidence

    $actionColors = @("Green","Yellow","DarkYellow","Red")
    $color        = $actionColors[$action]

    Write-Host ""
    Write-Host (" Action : {0} — {1}" -f $action, $actionName) -ForegroundColor $color
    Write-Host (" Confidence : {0}" -f $confidence)                 -ForegroundColor White
    Write-Host ""
    Write-Host " Reason:" -ForegroundColor Yellow
    Write-Host (" {0}" -f $reason) -ForegroundColor White
    Write-Host ""
    Write-Host " Instruction to dispatcher:" -ForegroundColor Yellow
    Write-Host (" {0}" -f $instruction) -ForegroundColor $color
    if ($pattern -and $pattern -ne "") {
        Write-Host ""
        Write-Host " Pattern:" -ForegroundColor Cyan
        Write-Host (" {0}" -f $pattern) -ForegroundColor Cyan
    }
    Write-Host ""

    # Save to history
    $result = [PSCustomObject]@{
        CustomerID        = $CustomerID
        Timestamp         = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss.fff")
        Provider          = $Provider
        Model             = $p.Model
        Signals           = @($signals | ForEach-Object { $_.Normalised })
        AvgSignal         = [Math]::Round($weightedAvg, 4)
        WeightedAvg       = [Math]::Round($weightedAvg, 4)
        Action            = $action
        ActionName        = $actionName
        ActionCommand     = $instruction
        ActionReason      = $reason
        Pattern           = $pattern
        Confidence        = $confidence
        OverrideApplied   = ($redSignals.Count -gt 0)
        RedSignalCount    = $redSignals.Count
        YellowSignalCount = $yellowSignals.Count
        Source            = "AI-$Provider"
    }

    if (-not (Test-Path $historyPath)) { New-Item -ItemType Directory -Path $historyPath -Force | Out-Null }
    $histFile = Join-Path $historyPath "$CustomerID-$(Get-Date -Format 'yyyyMMdd_HHmmss_fff').json"
    $result | ConvertTo-Json -Depth 5 | Set-Content $histFile -Encoding UTF8

    # Crisis if Action 3 — only if not running in loop mode
    if ($action -ge 3) {
        Write-Host " [CRISIS] AI recommends Escalate!" -ForegroundColor Red
        try { [Console]::Beep(800,400); Start-Sleep -Milliseconds 100; [Console]::Beep(1200,800) } catch {}
        if (-not $SuppressCrisis) {
            if (Get-Command Start-VBAFCenterCrisis -ErrorAction SilentlyContinue) {
                Start-VBAFCenterCrisis -CustomerID $CustomerID
            }
        } else {
            Write-Host " [CRISIS] Crisis wizard suppressed in loop mode. Check portal." -ForegroundColor DarkYellow
        }
    }

    # Write decision to daily log
    $logPath = Join-Path $env:USERPROFILE "VBAFCenter\dailylog"
    if (-not (Test-Path $logPath)) { New-Item -ItemType Directory -Path $logPath -Force | Out-Null }
    $logFile = Join-Path $logPath "$CustomerID-$(Get-Date -Format 'yyyyMMdd').log"
    $logLine = "{0} | {1} | Action {2} {3} | Conf {4} | {5}" -f `
        (Get-Date).ToString("HH:mm:ss"), $Provider, $action, $actionName, $confidence, $reason
    $logBytes = [System.Text.Encoding]::UTF8.GetBytes($logLine + [Environment]::NewLine); [System.IO.File]::AppendAllText($logFile, $logLine + [Environment]::NewLine, [System.Text.Encoding]::UTF8)

    return $result
}

# ============================================================
# GET-VBAFCENTERCLAUDEBRAINHISTORY
# ============================================================
function Get-VBAFCenterClaudeBrainHistory {
    <#
    .SYNOPSIS
        Show recent AI Brain decisions for a customer.
    .EXAMPLE
        Get-VBAFCenterClaudeBrainHistory -CustomerID "TruckCompanyDK"
        Get-VBAFCenterClaudeBrainHistory -CustomerID "TruckCompanyDK" -Provider "Gemini"
    #>

    param(
        [Parameter(Mandatory)] [string] $CustomerID,
        [string] $Provider = "",
        [int]    $Last     = 10
    )

    $historyPath = Join-Path $env:USERPROFILE "VBAFCenter\history"
    if (-not (Test-Path $historyPath)) {
        Write-Host "No history found." -ForegroundColor Yellow
        return
    }

    $files  = Get-ChildItem $historyPath -Filter "$CustomerID-*.json" |
              Sort-Object LastWriteTime -Descending | Select-Object -First ($Last * 3)
    $aiOnly = @()

    foreach ($f in $files) {
        try {
            $h = Get-Content $f.FullName -Raw | ConvertFrom-Json
            if ($h.Source -like "AI-*") {
                if ($Provider -eq "" -or $h.Source -eq "AI-$Provider") { $aiOnly += $h }
            }
        } catch {}
    }

    $aiOnly = $aiOnly | Select-Object -First $Last

    if ($aiOnly.Count -eq 0) {
        Write-Host ("No AI Brain decisions found for: {0}" -f $CustomerID) -ForegroundColor Yellow
        Write-Host (" Run: Invoke-VBAFCenterClaudeBrain -CustomerID '{0}'" -f $CustomerID) -ForegroundColor DarkGray
        return
    }

    Write-Host ""
    Write-Host ("AI Brain History: {0} (last {1})" -f $CustomerID, $aiOnly.Count) -ForegroundColor Cyan
    Write-Host (" {0,-23} {1,-12} {2,-4} {3,-10} {4,-8} {5}" -f "Timestamp","Provider","Act","Name","Conf","Reason") -ForegroundColor Yellow
    Write-Host (" {0}" -f ("-" * 90)) -ForegroundColor DarkGray

    foreach ($h in $aiOnly) {
        $color    = @("Green","Yellow","DarkYellow","Red")[[int]$h.Action]
        $prov     = [string]$h.Source -replace "^AI-", ""
        $reason   = [string]$h.ActionReason
        $short    = if ($reason.Length -gt 40) { $reason.Substring(0,40) + "..." } else { $reason }
        Write-Host (" {0,-23} {1,-12} {2,-4} {3,-10} {4,-8} {5}" -f `
            $h.Timestamp, $prov, $h.Action, $h.ActionName, $h.Confidence, $short) -ForegroundColor $color
    }
    Write-Host ""
}

# ============================================================
# LOAD MESSAGE
# ============================================================
Initialize-VBAFCenterAIStore

Write-Host ""
Write-Host " +--------------------------------------------------+" -ForegroundColor Cyan
Write-Host " | VBAF-Center Phase 19 — AI Brain |" -ForegroundColor Cyan
Write-Host " | Multi-provider — Claude, Gemini, Groq + more |" -ForegroundColor Cyan
Write-Host " +--------------------------------------------------+" -ForegroundColor Cyan
Write-Host ""
Write-Host " Set-VBAFCenterAIKey — save API key for a provider"   -ForegroundColor White
Write-Host " Get-VBAFCenterAIProviders — show all providers and status" -ForegroundColor White
Write-Host " Test-VBAFCenterAIProvider — test a provider connection"    -ForegroundColor White
Write-Host " Invoke-VBAFCenterClaudeBrain — run full AI analysis"          -ForegroundColor White
Write-Host " Get-VBAFCenterClaudeBrainHistory — show AI decision history"      -ForegroundColor White
Write-Host ""

# Show configured providers
$configured = @()
foreach ($key in $script:AIProviders.Keys) {
    $keyFile = Join-Path $script:AIConfigPath "$key-key.json"
    if (Test-Path $keyFile) { $configured += $key }
}

if ($configured.Count -gt 0) {
    Write-Host (" Configured: {0}" -f ($configured -join ", ")) -ForegroundColor Green
    Write-Host (" Default : Gemini (free) — use -Provider to switch") -ForegroundColor DarkGray
} else {
    Write-Host " No providers configured yet — start with free Gemini:" -ForegroundColor Yellow
    Write-Host " 1. Go to : https://aistudio.google.com/app/apikey" -ForegroundColor DarkGray
    Write-Host " 2. Run : Set-VBAFCenterAIKey -Provider ""Gemini"" -APIKey ""AIzaXXXX""" -ForegroundColor DarkGray
    Write-Host " 3. Test : Test-VBAFCenterAIProvider -Provider ""Gemini""" -ForegroundColor DarkGray
    Write-Host " 4. Analyse: Invoke-VBAFCenterClaudeBrain -CustomerID ""TruckCompanyDK"" -Provider ""Gemini""" -ForegroundColor DarkGray
}
Write-Host ""
<#

Set-VBAFCenterAIKey -Provider "Mistral" -APIKey "PvPxsvxKVk1SoefDnGbsW9gnjWTiMHOJ"
Test-VBAFCenterAIProvider -Provider "Mistral"

Invoke-VBAFCenterClaudeBrain -CustomerID "TruckCompanyDK" -Provider "Mistral"



#>