$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
[Console]::InputEncoding = [System.Text.UTF8Encoding]::new()
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()
$OutputEncoding = [System.Text.UTF8Encoding]::new()

$toolArgs = @()
$decodedArgs = ConvertFrom-Json -InputObject ([System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($argsJsonB64)))
if ($null -ne $decodedArgs) { foreach ($item in @($decodedArgs)) { $toolArgs += [string]$item } }
$rgPolicy = ConvertFrom-Json -InputObject ([System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($rgPolicyJsonB64)))

function Fail([string]$Message, [int]$Code) { [Console]::Error.WriteLine($Message); exit $Code }
function Resolve-UnideskPath([string]$Raw) {
  if ([string]::IsNullOrWhiteSpace($Raw) -or $Raw -eq '.') {
    if (-not [string]::IsNullOrWhiteSpace($basePath)) { return [IO.Path]::GetFullPath($basePath) }
    return (Get-Location).ProviderPath
  }
  if ([IO.Path]::IsPathRooted($Raw)) { return [IO.Path]::GetFullPath($Raw) }
  if (-not [string]::IsNullOrWhiteSpace($basePath)) { return [IO.Path]::GetFullPath([IO.Path]::Combine($basePath, $Raw)) }
  return [IO.Path]::GetFullPath($Raw)
}
function Need-File([string]$Path) { if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { Fail ('file not found: ' + $Path) 1 } }
function Get-Sha256([string]$Path) { return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant() }
function Parse-NonNegativeInt([string]$Name, [string]$Value, [int]$Max) {
  $parsed = 0
  if (-not [int]::TryParse($Value, [ref]$parsed) -or $parsed -lt 0 -or $parsed -gt $Max) { Fail ($Name + ' must be an integer from 0 to ' + $Max) 2 }
  return $parsed
}
function Read-StrictText([string]$Path, [long]$MaxBytes) {
  Need-File $Path
  $info = [IO.FileInfo]$Path
  if ($MaxBytes -gt 0 -and $info.Length -gt $MaxBytes) { Fail ('file too large for windows fs read operation: ' + $Path + ' bytes=' + $info.Length + ' maxBytes=' + $MaxBytes + '; use head/tail/download or --max-bytes') 23 }
  $bytes = [IO.File]::ReadAllBytes($Path)
  if ([Array]::IndexOf($bytes, [byte]0) -ge 0) { Fail ('binary file refused by windows fs read operation: ' + $Path) 24 }
  try { return [Text.UTF8Encoding]::new($false, $true).GetString($bytes) } catch { Fail ('file is not valid UTF-8: ' + $Path + ': ' + $_.Exception.Message) 25 }
}
function Try-Read-StrictText([string]$Path, [long]$MaxBytes) {
  if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { return $null }
  if (([IO.FileInfo]$Path).Length -gt $MaxBytes) { return $null }
  $bytes = [IO.File]::ReadAllBytes($Path)
  if ([Array]::IndexOf($bytes, [byte]0) -ge 0) { return $null }
  try { return [Text.UTF8Encoding]::new($false, $true).GetString($bytes) } catch { return $null }
}
function Text-Lines([string]$Text) {
  $lines = [Collections.Generic.List[string]]::new()
  foreach ($line in ($Text -split "`r?`n", -1)) { $lines.Add($line) }
  if ($lines.Count -gt 0 -and $lines[$lines.Count - 1] -eq '') { $lines.RemoveAt($lines.Count - 1) }
  return [string[]]$lines
}
function Print-Lines([string[]]$Lines) { if ($Lines.Count -gt 0) { [Console]::Out.Write(([string]::Join("`n", $Lines)) + "`n") } }
