# Roslyn Bridge PowerShell helper
#
# Purpose: Fast, low‑token wrapper around the Roslyn Bridge Web API (port 5001)
#          and the legacy VS plugin (port 59123). Auto-detects which is running
#          and provides concise commands for common operations.
#
# Usage examples:
#   .\rb.ps1 health
#   .\rb.ps1 projects
#   .\rb.ps1 overview
#   .\rb.ps1 diagnostics -FilePath C:/path/to/File.cs
#   .\rb.ps1 symbol -FilePath C:/path/to/File.cs -Line 12 -Column 4
#   .\rb.ps1 references -FilePath C:/path/to/File.cs -Line 12 -Column 4
#   .\rb.ps1 search -SymbolName MyService -Kind class
#   .\rb.ps1 build -ProjectName MyProject
#   .\rb.ps1 package-add -ProjectName MyProject -PackageName Newtonsoft.Json -Version 13.0.3
#   .\rb.ps1 history-recent -Count 10
#   .\rb.ps1 query -BodyJson '{"queryType":"searchcode","symbolName":".*Controller"}'
#
# Output:
#   - Compressed JSON by default (minimal tokens)
#   - Add -Pretty for indented JSON
#   - Add -Raw to return the raw response string

[CmdletBinding(PositionalBinding=$true)]
param(
    [Parameter(Position=0, Mandatory=$true)]
    [ValidateSet(
        'health','projects','overview','diagnostics','symbol','references','search',
        'build','package-add','history','history-recent','history-stats',
        'plugin-health','solution-overview'
    )]
    [string]$Command,

    # Common params
    [string]$BaseUrl,                  # Overrides auto-detected base URL
    [switch]$NoDetect,                 # Skip auto-detection
    [int]$TimeoutSec = 6,              # Short default to stay snappy
    [switch]$Raw,                      # Emit raw response string
    [switch]$Pretty,                   # Indented JSON output

    # Query params
    [string]$FilePath,
    [int]$Line,
    [int]$Column,
    [string]$SymbolName,
    [ValidateSet('class','method','property','field','interface','enum','struct','namespace','event','any')]
    [string]$Kind = 'any',

    # Project ops
    [string]$ProjectName,
    [string]$Configuration,
    [string]$PackageName,
    [string]$Version,

    # History
    [int]$Count = 25,

    # Solution overview summary options
    [ValidateSet('json','text','yaml')]
    [string]$Format = 'json',
    [int]$Top = 5,
    [bool]$IncludeNamespaces = $true
)

set-strictmode -version latest
$ErrorActionPreference = 'Stop'

function Write-Err {
    param([string]$Msg)
    Write-Error -Message $Msg
}

function Get-JsonOutput {
    param(
        [Parameter(Mandatory=$true)] $Data
    )
    if ($Raw) {
        # If we were given an object, compress to a compact string; if it's already a string, emit it.
        if ($Data -is [string]) { return $Data }
        return ($Data | ConvertTo-Json -Depth 50 -Compress)
    }
    $json = $Data | ConvertTo-Json -Depth 50 -Compress
    if ($Pretty) { $json = ($Data | ConvertTo-Json -Depth 50) }
    return $json
}

function Test-Url {
    param(
        [string]$Url,
        [ValidateSet('GET','POST')][string]$Method = 'GET'
    )
    try {
        if ($Method -eq 'GET') {
            $resp = Invoke-WebRequest -Method GET -Uri $Url -TimeoutSec $TimeoutSec
        } else {
            $resp = Invoke-WebRequest -Method POST -Uri $Url -TimeoutSec $TimeoutSec -ContentType 'application/json' -Body '{}'
        }
        return $true
    } catch {
        return $false
    }
}

function Get-BridgeEndpoints {
    # Decide which base URL to use.
    # Priority: explicit -BaseUrl -> Web API (5001) if healthy -> Plugin (59123) if healthy
    $web = 'http://localhost:5001'
    $plugin = 'http://localhost:59123'

    if ($BaseUrl) {
        return @{ BaseUrl = $BaseUrl; Mode = 'explicit' }
    }

    if ($NoDetect) {
        return @{ BaseUrl = $web; Mode = 'web' }
    }

    if (Test-Url -Url "$web/api/health/ping" -Method GET) {
        return @{ BaseUrl = $web; Mode = 'web' }
    }

    if (Test-Url -Url "$plugin/health" -Method POST) {
        return @{ BaseUrl = $plugin; Mode = 'plugin' }
    }

    # Fallback to web
    return @{ BaseUrl = $web; Mode = 'web' }
}

function Invoke-WebApi {
    param(
        [string]$BaseUrl,
        [ValidateSet('GET','POST')][string]$Method,
        [string]$Path,
        [hashtable]$Query,
        $Body
    )
    $uri = [System.UriBuilder]::new($BaseUrl)
    if ($Path.StartsWith('/')) { $uri.Path = $Path } else { $uri.Path = "$($uri.Path.TrimEnd('/'))/$Path" }
    if ($Query) {
        $qs = [System.Web.HttpUtility]::ParseQueryString([string]::Empty)
        foreach ($k in $Query.Keys) {
            if ($null -ne $Query[$k] -and $Query[$k] -ne '') { $qs[$k] = [string]$Query[$k] }
        }
        $uri.Query = $qs.ToString()
    }
    if ($Raw) {
        if ($Method -eq 'GET') {
            return (Invoke-WebRequest -Method GET -Uri $uri.Uri -TimeoutSec $TimeoutSec).Content
        } else {
            $bodyStr = if ($Body -is [string]) { $Body } else { ($Body | ConvertTo-Json -Depth 50 -Compress) }
            return (Invoke-WebRequest -Method POST -Uri $uri.Uri -TimeoutSec $TimeoutSec -ContentType 'application/json' -Body $bodyStr).Content
        }
    }
    if ($Method -eq 'GET') {
        return Invoke-RestMethod -Method GET -Uri $uri.Uri -TimeoutSec $TimeoutSec
    } else {
        if ($Body -is [string]) {
            return Invoke-RestMethod -Method POST -Uri $uri.Uri -TimeoutSec $TimeoutSec -ContentType 'application/json' -Body $Body
        } else {
            return Invoke-RestMethod -Method POST -Uri $uri.Uri -TimeoutSec $TimeoutSec -ContentType 'application/json' -Body ($Body | ConvertTo-Json -Depth 50 -Compress)
        }
    }
}

function Invoke-PluginApi {
    param(
        [string]$BaseUrl,
        [string]$Path,  # '/query' or '/health'
        $Body           # hashtable or string
    )
    $uri = "$BaseUrl$Path"
    if ($Raw) {
        $bodyStr = if ($Body -is [string]) { $Body } else { ($Body | ConvertTo-Json -Depth 50 -Compress) }
        return (Invoke-WebRequest -Method POST -Uri $uri -TimeoutSec $TimeoutSec -ContentType 'application/json' -Body $bodyStr).Content
    }
    if ($Body -is [string]) {
        return Invoke-RestMethod -Method POST -Uri $uri -TimeoutSec $TimeoutSec -ContentType 'application/json' -Body $Body
    } else {
        return Invoke-RestMethod -Method POST -Uri $uri -TimeoutSec $TimeoutSec -ContentType 'application/json' -Body ($Body | ConvertTo-Json -Depth 50 -Compress)
    }
}

#
# Main dispatch
#

$endpoint = Get-BridgeEndpoints
$mode = $endpoint.Mode
$base = $endpoint.BaseUrl

try {
    switch ($Command) {
        'health' {
            if ($mode -eq 'web' -or $mode -eq 'explicit') {
                $resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/health'
                $out = Get-JsonOutput -Data $resp
                Write-Output $out
            } else {
                $resp = Invoke-PluginApi -BaseUrl $base -Path '/health' -Body '{}'
                $out = Get-JsonOutput -Data $resp
                Write-Output $out
            }
        }
        'projects' {
            if ($mode -eq 'web' -or $mode -eq 'explicit') {
                $resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/roslyn/projects'
            } else {
                $resp = Invoke-PluginApi -BaseUrl $base -Path '/query' -Body @{ queryType = 'getprojects' }
            }
            Write-Output (Get-JsonOutput -Data $resp)
        }
        'overview' {
            if ($mode -eq 'web' -or $mode -eq 'explicit') {
                $resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/roslyn/solution/overview'
            } else {
                $resp = Invoke-PluginApi -BaseUrl $base -Path '/query' -Body @{ queryType = 'getsolutionoverview' }
            }
            Write-Output (Get-JsonOutput -Data $resp)
        }
        'diagnostics' {
            if ($mode -eq 'web' -or $mode -eq 'explicit') {
                $q = @{}
                if ($FilePath) { $q.filePath = $FilePath }
                $resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/roslyn/diagnostics' -Query $q
            } else {
                $body = @{ queryType = 'getdiagnostics' }
                if ($FilePath) { $body.filePath = $FilePath }
                $resp = Invoke-PluginApi -BaseUrl $base -Path '/query' -Body $body
            }
            Write-Output (Get-JsonOutput -Data $resp)
        }
        'symbol' {
            if (-not $FilePath -or -not $PSBoundParameters.ContainsKey('Line') -or -not $PSBoundParameters.ContainsKey('Column')) {
                Write-Err 'symbol requires -FilePath, -Line, -Column'; exit 2
            }
            if ($mode -eq 'web' -or $mode -eq 'explicit') {
                $q = @{ filePath=$FilePath; line=$Line; column=$Column }
                $resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/roslyn/symbol' -Query $q
            } else {
                $body = @{ queryType='getsymbol'; filePath=$FilePath; line=$Line; column=$Column }
                $resp = Invoke-PluginApi -BaseUrl $base -Path '/query' -Body $body
            }
            Write-Output (Get-JsonOutput -Data $resp)
        }
        'references' {
            if (-not $FilePath -or -not $PSBoundParameters.ContainsKey('Line') -or -not $PSBoundParameters.ContainsKey('Column')) {
                Write-Err 'references requires -FilePath, -Line, -Column'; exit 2
            }
            if ($mode -eq 'web' -or $mode -eq 'explicit') {
                $q = @{ filePath=$FilePath; line=$Line; column=$Column }
                $resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/roslyn/references' -Query $q
            } else {
                $body = @{ queryType='findreferences'; filePath=$FilePath; line=$Line; column=$Column }
                $resp = Invoke-PluginApi -BaseUrl $base -Path '/query' -Body $body
            }
            Write-Output (Get-JsonOutput -Data $resp)
        }
        'search' {
            if (-not $SymbolName) { Write-Err 'search requires -SymbolName'; exit 2 }
            if ($mode -eq 'web' -or $mode -eq 'explicit') {
                $q = @{ symbolName=$SymbolName }
                if ($Kind -and $Kind -ne 'any') { $q.kind = $Kind }
                $resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/roslyn/symbol/search' -Query $q
            } else {
                $parameters = @{}
                if ($Kind -and $Kind -ne 'any') { $parameters.kind = $Kind }
                $body = @{ queryType='findsymbol'; symbolName=$SymbolName }
                if ($parameters.Count -gt 0) { $body.parameters = $parameters }
                $resp = Invoke-PluginApi -BaseUrl $base -Path '/query' -Body $body
            }
            Write-Output (Get-JsonOutput -Data $resp)
        }
        'build' {
            if (-not $ProjectName) { Write-Err 'build requires -ProjectName'; exit 2 }
            if ($mode -eq 'web' -or $mode -eq 'explicit') {
                $q = @{ projectName=$ProjectName }
                if ($Configuration) { $q.configuration = $Configuration }
                $resp = Invoke-WebApi -BaseUrl $base -Method POST -Path '/api/roslyn/project/build' -Query $q
            } else {
                $body = @{ queryType='buildproject'; projectName=$ProjectName }
                if ($Configuration) { $body.configuration = $Configuration }
                $resp = Invoke-PluginApi -BaseUrl $base -Path '/query' -Body $body
            }
            Write-Output (Get-JsonOutput -Data $resp)
        }
        'package-add' {
            if (-not $ProjectName -or -not $PackageName) { Write-Err 'package-add requires -ProjectName and -PackageName'; exit 2 }
            if ($mode -eq 'web' -or $mode -eq 'explicit') {
                $q = @{ projectName=$ProjectName; packageName=$PackageName }
                if ($Version) { $q.version = $Version }
                $resp = Invoke-WebApi -BaseUrl $base -Method POST -Path '/api/roslyn/project/package/add' -Query $q
            } else {
                $body = @{ queryType='addnugetpackage'; projectName=$ProjectName; packageName=$PackageName }
                if ($Version) { $body.version = $Version }
                $resp = Invoke-PluginApi -BaseUrl $base -Path '/query' -Body $body
            }
            Write-Output (Get-JsonOutput -Data $resp)
        }
        'history' {
            if ($mode -eq 'web' -or $mode -eq 'explicit') {
                $resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/history'
            } else {
                Write-Err 'history is only available via Web API (port 5001).'; exit 2
            }
            Write-Output (Get-JsonOutput -Data $resp)
        }
        'history-recent' {
            if ($mode -eq 'web' -or $mode -eq 'explicit') {
                $q = @{ count = $Count }
                $resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/history/recent' -Query $q
            } else {
                Write-Err 'history-recent is only available via Web API (port 5001).'; exit 2
            }
            Write-Output (Get-JsonOutput -Data $resp)
        }
        'history-stats' {
            if ($mode -eq 'web' -or $mode -eq 'explicit') {
                $resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/history/stats'
            } else {
                Write-Err 'history-stats is only available via Web API (port 5001).'; exit 2
            }
            Write-Output (Get-JsonOutput -Data $resp)
        }
        'plugin-health' {
            $pluginBase = if ($BaseUrl) { $BaseUrl } else { 'http://localhost:59123' }
            $resp = Invoke-PluginApi -BaseUrl $pluginBase -Path '/health' -Body '{}'
            Write-Output (Get-JsonOutput -Data $resp)
        }
        'solution-overview' {
            if ($mode -ne 'web' -and $mode -ne 'explicit') {
                Write-Err 'solution-overview requires the Web API (port 5001).'; exit 2
            }
            $q = @{ topNProjects=$Top; includeNamespaces=$IncludeNamespaces; format=$Format }
            # For text/yaml formats, we want the raw response as-is
            if ($Format -eq 'json') {
                $resp = Invoke-WebApi -BaseUrl $base -Method GET -Path '/api/roslyn/solution/overview/summary' -Query $q
                Write-Output (Get-JsonOutput -Data $resp)
            } else {
                # Build query string properly
                $qs = [System.Web.HttpUtility]::ParseQueryString([string]::Empty)
                $qs['topNProjects'] = [string]$Top
                $qs['includeNamespaces'] = [string]$IncludeNamespaces
                $qs['format'] = $Format
                $uri = [System.UriBuilder]::new("$base/api/roslyn/solution/overview/summary")
                $uri.Query = $qs.ToString()
                $content = (Invoke-WebRequest -Method GET -Uri $uri.Uri -TimeoutSec $TimeoutSec).Content
                Write-Output $content
            }
        }
        default {
            Write-Err "Unknown command: $Command"; exit 2
        }
    }
} catch {
    $msg = $_.Exception.Message
    $respProp = $_.Exception.PSObject.Properties['Response']
    if ($respProp -and $respProp.Value) {
        try {
            $resp = $respProp.Value
            $stream = $resp.GetResponseStream()
            if ($stream) {
                $reader = New-Object System.IO.StreamReader($stream)
                $content = $reader.ReadToEnd()
                if ($content) { $msg = "$msg`n$content" }
            }
        } catch {}
    }
    Write-Err $msg
    exit 1
}
