src/UI/StatusBar.ps1

# StatusBar — Barra inferior. SIEMPRE devuelve 2+ líneas:
# - Línea(s) 1+: hints/keybindings, con auto-wrap si no entran en el ancho.
# - Línea final: counts/right (siempre su propia línea).
#
# Esto deja crecer el set de bindings sin que se "coman" el lado derecho
# (Tino reportó: 'son mucho y se comen la parte de la derecha').

class StatusBar {
    [object] $Theme
    [object] $Renderer
    [object] $Primitives

    StatusBar($theme, $renderer, $primitives) {
        $this.Theme = $theme
        $this.Renderer = $renderer
        $this.Primitives = $primitives
    }

    # $items = array de hashtables @{ k='↑↓'; label='Move' }
    # $right = string ya renderizado (counts, etc.). Vacío = sin línea de right.
    # Devuelve array de strings, una entrada por línea visible.
    #
    # Diseño: NO aplicamos bg propio — respetamos el fondo del terminal.
    # Razón: los ${reset} interiores en cada hint/cell quiebran cualquier ${bg}
    # que apliquemos al inicio de la línea, causando bandas inconsistentes (Tino
    # lo reportó como "el primer espacio queda con otro color"). Las HRules
    # encima del statusbar ya marcan visualmente el bloque.
    [string[]] Render([hashtable[]]$items, [string]$right) {
        $reset = [AnsiService]::Reset
        $sepFg = $this.Theme.Fg('fg3') + ' │ ' + $reset
        $width = $this.Renderer.Width()
        # Chars utiles dentro del padding lateral ' ... '
        $usable = [Math]::Max(10, $width - 2)

        # Renderiza cada hint como cell.
        $cells = @()
        foreach ($it in $items) {
            $k = $this.Primitives.K($it.k)
            $label = $this.Theme.Fg('fg1') + $it.label + $reset
            $cells += "$k $label"
        }

        $lines = @()

        # Agrupar cells en líneas de hints. Si una cell sola no entra (caso
        # extremo de width chico), igual la metemos en su línea — algo es
        # mejor que nada.
        $current = ''
        foreach ($cell in $cells) {
            $sep = if ($current) { $sepFg } else { '' }
            $candidate = $current + $sep + $cell
            if ([Renderer]::VisibleLength($candidate) -gt $usable) {
                # No entra — push current y arrancamos línea nueva con esta cell
                if ($current) {
                    $lines += " $current"
                }
                $current = $cell
            } else {
                $current = $candidate
            }
        }
        if ($current) {
            $lines += " $current"
        }

        # Línea final: right (counts). Si hay right, alineado a la derecha.
        if ($right) {
            $padCount = [Math]::Max(0, $width - [Renderer]::VisibleLength($right) - 2)
            $lines += "$(' ' * $padCount)${right}"
        }

        return $lines
    }

    # Conveniencia para llamadas legacy de 1 línea con right vacío.
    [string[]] Render([hashtable[]]$items) {
        return $this.Render($items, '')
    }

    # Devuelve [padding empty lines] + [statusbar lines] para que el statusbar
    # quede anclado al fondo de la consola. El caller pasa cuántas líneas de
    # contenido ya tiene; calculamos el gap contra WindowHeight y rellenamos.
    # Si el contenido excede el alto disponible, no se padea (degrade gracefully).
    [string[]] RenderAnchored([hashtable[]]$items, [string]$right, [int]$contentLineCount) {
        $statusLines = $this.Render($items, $right)
        $padding = [Console]::WindowHeight - $contentLineCount - $statusLines.Count
        if ($padding -le 0) { return $statusLines }
        $padArray = @()
        for ($p = 0; $p -lt $padding; $p++) { $padArray += '' }
        return $padArray + $statusLines
    }

    # Overload con right vacío para screens que no usan la línea de counts.
    [string[]] RenderAnchored([hashtable[]]$items, [int]$contentLineCount) {
        return $this.RenderAnchored($items, '', $contentLineCount)
    }

}