src/Osc.ps1

# Osc.ps1 - SPIKE: apply a scheme's colors via OSC escape sequences instead of
# writing Windows Terminal settings.json. See GitHub issue #12.
#
# This is an ALTERNATE applier for the *colors* of the scheme layer only. It
# lets the 16 ANSI colors + foreground/background/cursor land on terminals that
# aren't Windows Terminal (WezTerm, kitty, Alacritty, iTerm2, VTE, and - per
# real-hardware testing - macOS Terminal.app). Background image, opacity/acrylic
# and font have no OSC equivalent and stay Windows-Terminal-only.
#
# Everything here is additive: it does not touch the existing settings.json /
# $PROFILE appliers. Nothing calls into it yet - it's exposed as standalone
# commands so the approach can be verified on real terminals before it graduates
# into Set-PoshPaletteTheme.
#
# What OSC can/can't do (verified 2026-07-05 on Terminal.app):
# - Setting (OSC 4/10/11/12) works broadly, including Terminal.app.
# - Reset (OSC 104/110/111/112) and query (OSC ...;?) do NOT work on
# Terminal.app - so revert is "drop the emit and open a new session"
# (OSC state is per-session), never a reset code we can't rely on.

# #RRGGBB (or RRGGBB) -> rgb:RR/GG/BB. Lowercase 8-bit `rgb:` is the most
# portable XParseColor form (preferred over #RRGGBB and over X11 color names).
function ConvertTo-PoshPaletteOscColor {
    param([Parameter(Mandatory)][string] $Hex)
    $h = $Hex.TrimStart('#')
    if ($h -notmatch '^[0-9A-Fa-f]{6}$') { throw "Expected a #RRGGBB hex color, got '$Hex'." }
    'rgb:{0}/{1}/{2}' -f $h.Substring(0, 2).ToLower(), $h.Substring(2, 2).ToLower(), $h.Substring(4, 2).ToLower()
}

# Build the raw OSC byte string that recolors the current terminal from a scheme.
# $SchemeColors is a scheme's `colors` object (background/foreground/cursorColor/
# selectionBackground + the 16 ANSI names) - i.e. schemes/*.json `.colors`, which
# is also exactly $ResolvedTheme.terminal.scheme.
function New-PoshPaletteOscSequence {
    param([Parameter(Mandatory)] $SchemeColors)

    $e  = [char]27      # ESC
    $st = "$e\"         # String Terminator (ESC \) - spec-correct; BEL also works

    # The 16 ANSI palette indices, in Windows Terminal / standard ANSI order.
    $ansi = 'black', 'red', 'green', 'yellow', 'blue', 'purple', 'cyan', 'white',
            'brightBlack', 'brightRed', 'brightGreen', 'brightYellow',
            'brightBlue', 'brightPurple', 'brightCyan', 'brightWhite'

    $pairs = for ($i = 0; $i -lt $ansi.Count; $i++) {
        $hex = $SchemeColors.($ansi[$i])
        if ($hex) { '{0};{1}' -f $i, (ConvertTo-PoshPaletteOscColor $hex) }
    }

    $sb = [System.Text.StringBuilder]::new()
    # OSC 4 chains all palette entries in one sequence: ESC ] 4 ; i ; spec ; i ; spec ... ST
    if ($pairs)                          { [void]$sb.Append("$e]4;$($pairs -join ';')$st") }
    if ($SchemeColors.foreground)        { [void]$sb.Append("$e]10;$(ConvertTo-PoshPaletteOscColor $SchemeColors.foreground)$st") }
    if ($SchemeColors.background)        { [void]$sb.Append("$e]11;$(ConvertTo-PoshPaletteOscColor $SchemeColors.background)$st") }
    if ($SchemeColors.cursorColor)       { [void]$sb.Append("$e]12;$(ConvertTo-PoshPaletteOscColor $SchemeColors.cursorColor)$st") }
    if ($SchemeColors.selectionBackground) { [void]$sb.Append("$e]17;$(ConvertTo-PoshPaletteOscColor $SchemeColors.selectionBackground)$st") }
    $sb.ToString()
}

# Best-effort guess at whether the current terminal honors OSC color-setting.
# Heuristic and deliberately permissive: OSC set is a silent no-op on most
# terminals that ignore it, and callers can override with -Force. Refine the
# allow/deny lists as real-hardware results come in.
function Test-PoshPaletteOscTerminal {
    # Multiplexers need DCS passthrough we don't emit yet - skip for now.
    if ($env:TMUX -or $env:STY) { return $false }
    # Windows Terminal advertises itself and fully supports OSC.
    if ($env:WT_SESSION) { return $true }
    if ($env:KITTY_WINDOW_ID) { return $true }
    switch ($env:TERM_PROGRAM) {
        'WezTerm'        { return $true }
        'iTerm.app'      { return $true }
        'Apple_Terminal' { return $true }   # verified: OSC 4/10/11 set works
        'vscode'         { return $true }
    }
    # kitty/alacritty/foot/VTE advertise via TERM.
    if ($env:TERM -match '^(xterm|kitty|alacritty|foot|vte|screen-256color-bce)') { return $true }
    $false
}

# The runnable spike command: recolor the CURRENT session from a bundled scheme.
# Per-session only (nothing persisted) - open a new tab/window to revert.
# Set-PoshPaletteSchemeOsc eclipse
# Set-PoshPaletteSchemeOsc porcelain -Force # try a terminal not on the list
# Set-PoshPaletteSchemeOsc eclipse -ShowBytes # print the escapes (no apply)
function Set-PoshPaletteSchemeOsc {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position = 0)][string] $Scheme,
        [switch] $Force,       # apply even if the terminal isn't on the OSC-safe list
        [switch] $ShowBytes    # print the escape sequence (escaped) instead of applying
    )

    $item = Get-PoshPaletteCatalogItem -Kind 'schemes' -Id $Scheme
    $seq  = New-PoshPaletteOscSequence -SchemeColors $item.colors

    if ($ShowBytes) {
        # Render ESC as \e so the sequence is inspectable without moving the cursor.
        Write-Host ($seq -replace [char]27, '\e')
        return
    }

    if (-not $Force -and -not (Test-PoshPaletteOscTerminal)) {
        Write-Warning ("Terminal not on the OSC-safe list (TERM_PROGRAM='{0}', TERM='{1}'). Re-run with -Force to try anyway." -f $env:TERM_PROGRAM, $env:TERM)
        return
    }

    [Console]::Write($seq)
    Write-Host " Applied scheme '$($item.name)' to this session via OSC." -ForegroundColor Green
    Write-Host " (Colors are per-session. Open a new tab/window to revert.)" -ForegroundColor DarkGray
}

# Build the $PROFILE snippet that re-emits the OSC colors on every session start
# (the persistence story). Not wired into the profile writer yet - returned as a
# string so it can be reviewed/tested. Self-guards at runtime so the same profile
# opened in a non-OSC terminal is a harmless no-op.
function New-PoshPaletteOscBlock {
    param([Parameter(Mandatory)] $SchemeColors)
    $seq = New-PoshPaletteOscSequence -SchemeColors $SchemeColors
    # Emit as a here-string that reconstructs the ESC bytes at profile-load time.
    $literal = $seq -replace [char]27, '`e'
    @"
# PoshPalette OSC scheme (per-session color apply; see issue #12)
if (`$env:WT_SESSION -or `$env:TERM_PROGRAM -or (`$env:TERM -match '^(xterm|kitty|alacritty|foot|vte)')) {
    [Console]::Write("$literal")
}
"@

}