src/UI/Screens/SetupScreen.ps1

# SetupScreen — Vista de status del entorno + acciones de install/uninstall.
#
# Read-only mostly: muestra deps, paths, aliases instalados. 2 acciones:
# Reinstall (Install.ps1 -Force) y Uninstall (Install.ps1 -Uninstall). Las
# acciones invocan el script externo en proceso separado para no contaminar
# el state actual.
#
# Accesible desde PreferencesScreen como un item 'Setup & Status'.

class SetupScreen {
    [object] $Theme
    [object] $Renderer
    [object] $Primitives
    [object] $Frame
    [object] $AppHeader
    [object] $StatusBar
    [object] $Setup           # SetupService
    [object] $I18n = $null    # Inyectable post-construct.

    [int]      $SelectedIndex = 0
    [bool]     $QuitRequested = $false
    [bool]     $UseAltBuffer = $true

    [string]   $StatusMessage = ''
    [string]   $StatusKind = ''

    SetupScreen($theme, $renderer, $primitives, $frame, $appHeader, $statusBar, $setupSvc) {
        $this.Theme       = $theme
        $this.Renderer    = $renderer
        $this.Primitives  = $primitives
        $this.Frame       = $frame
        $this.AppHeader   = $appHeader
        $this.StatusBar   = $statusBar
        $this.Setup       = $setupSvc
    }

    [void] Open() {
        $this.SelectedIndex = 0
        $this.StatusMessage = ''
        $this.StatusKind = ''

        if ([Console]::IsInputRedirected) {
            $lines = $this.BuildLines()
            foreach ($l in $lines) { Write-Host $l }
            return
        }

        $errOut = [Console]::Error
        $this.Render($errOut)

        try {
            while ($true) {
                $key = [Console]::ReadKey($true)
                $exit = $this.HandleKey($key)
                if ($exit -eq 'back') { return }
                if ($exit -eq 'quit') { $this.QuitRequested = $true; return }
                $this.Render($errOut)
            }
        }
        finally { }
    }

    hidden [void] Render([object]$errOut) {
        if ([Console]::IsInputRedirected) { return }
        $lines = $this.BuildLines()
        $framePayload = [AnsiService]::BeginSync() + [AnsiService]::MoveTo(1, 1) + ($lines -join "`e[K`n") + "`e[K`e[J" + [AnsiService]::EndSync()
        $errOut.Write($framePayload)
        $errOut.Flush()
    }

    # Helper i18n con fallback graceful (mismo patrón que MainScreen.T).
    hidden [string] T([string]$key) {
        if ($null -ne $this.I18n) { return $this.I18n.T($key) }
        return ([I18nService]::new('es')).T($key)
    }

    hidden [string] T([string]$key, [object[]]$values) {
        if ($null -ne $this.I18n) { return $this.I18n.T($key, $values) }
        return ([I18nService]::new('es')).T($key, $values)
    }

    # Items: solo las acciones son seleccionables; las rows de info son display.
    # Las acciones tienen Action='reinstall'|'uninstall'|'cancel'. El nombre del
    # alias se resuelve dinámicamente — usa el primer alias managed instalado,
    # o 'rnav' (default oficial) si no hay ninguno.
    [object[]] Actions() {
        $installed = $this.Setup.GetInstalledAliases()
        $aliasName = if ($installed.Count -gt 0) { $installed[0] } else { 'rnav' }
        return @(
            @{ Label = $this.T('setup.reinstallAlias', @($aliasName));  Action = 'reinstall' }
            @{ Label = $this.T('setup.uninstallAlias', @($aliasName));  Action = 'uninstall' }
            @{ Label = $this.T('setup.cancel');                          Action = 'cancel' }
        )
    }

    hidden [string] HandleKey([System.ConsoleKeyInfo]$key) {
        $k = $key.Key
        $c = $key.KeyChar
        $actions = $this.Actions()

        if ($c -eq 'q' -or $c -eq 'Q') { return 'quit' }
        if ($k -eq 'Escape' -or $k -eq 'LeftArrow' -or $c -eq 'h') { return 'back' }

        if ($k -eq 'UpArrow' -or $c -eq 'k') {
            if ($actions.Count -gt 0) {
                $this.SelectedIndex = if ($this.SelectedIndex -le 0) { $actions.Count - 1 } else { $this.SelectedIndex - 1 }
            }
            return ''
        }
        if ($k -eq 'DownArrow' -or $c -eq 'j') {
            if ($actions.Count -gt 0) {
                $this.SelectedIndex = if ($this.SelectedIndex -ge $actions.Count - 1) { 0 } else { $this.SelectedIndex + 1 }
            }
            return ''
        }

        if ($k -eq 'Enter') {
            if ($actions.Count -eq 0) { return '' }
            $action = $actions[$this.SelectedIndex].Action
            switch ($action) {
                'reinstall' { $this.RunInstall($true);  return '' }
                'uninstall' { $this.RunInstall($false); return '' }
                'cancel'    { return 'back' }
            }
        }
        return ''
    }

    hidden [void] RunInstall([bool]$install) {
        $repoRoot = $this.Setup.GetRepoRoot()
        $installScript = Join-Path $repoRoot 'Install.ps1'

        if (-not [System.IO.File]::Exists($installScript)) {
            $this.SetStatus($this.T('setup.installNotFound', @($repoRoot)), 'error')
            return
        }

        # Invocamos en proceso separado para evitar contaminar el state actual
        # de la sesión PS interactiva.
        $args = if ($install) { @('-Force') } else { @('-Uninstall') }
        $pwshExe = (Get-Command pwsh -ErrorAction SilentlyContinue).Source
        if (-not $pwshExe) { $pwshExe = 'pwsh' }

        try {
            $output = & $pwshExe -NoProfile -File $installScript @args 2>&1
            $exitCode = $LASTEXITCODE
            if ($exitCode -eq 0) {
                $msgKey = if ($install) { 'setup.aliasReinstalled' } else { 'setup.aliasUninstalled' }
                $this.SetStatus($this.T($msgKey), 'ok')
            } else {
                $msg = ($output | Select-Object -Last 1).ToString()
                $this.SetStatus($this.T('setup.installFailed', @($exitCode, $msg)), 'error')
            }
        } catch {
            $this.SetStatus($this.T('setup.error', @($_.Exception.Message)), 'error')
        }
    }

    hidden [void] SetStatus([string]$msg, [string]$kind) {
        $this.StatusMessage = $msg
        $this.StatusKind = $kind
    }

    [string[]] BuildLines() {
        $reset = [AnsiService]::Reset
        $r = $this.Renderer
        $lines = [System.Collections.Generic.List[string]]::new()

        $title = $this.T('setup.title')
        # Title bar
        $lines.Add($this.Frame.TitleBar('repo-nav', $title, '1.0.0'))

        # AppHeader
        $lines.Add($this.AppHeader.Render(@($this.T('prefs.title')), $title, @()))
        $lines.Add($r.HRule())

        # Status / prompt line
        if ($this.StatusMessage) {
            $color = if ($this.StatusKind -eq 'error') { 'gitConflict' } else { 'gitClean' }
            $lines.Add(' ' + $this.Theme.Fg($color) + $this.StatusMessage + $reset)
        } else {
            $lines.Add('')
        }

        # ─── Información del entorno ────────────────────────────────────────
        $lines.Add($this.Theme.Fg('fg2') + ' ' + $this.T('setup.envSection') + $reset)

        $psVer = $this.Setup.GetPsVersion()
        $psOk = $false
        if ($psVer -and $psVer -ne 'unknown' -and $null -ne $global:PSVersionTable) {
            $psOk = $global:PSVersionTable.PSVersion.Major -ge 7
        }
        $lines.Add($this.RenderInfoRow($this.T('setup.psLabel'), $psVer, $psOk))

        $gitVer = $this.Setup.GetGitVersion()
        $gitDisplay = if ($gitVer) { $gitVer } else { $this.T('setup.gitNotInstalled') }
        $lines.Add($this.RenderInfoRow($this.T('setup.gitLabel'), $gitDisplay, [bool]$gitVer))

        $repoRoot = $this.Setup.GetRepoRoot()
        $lines.Add($this.RenderInfoRow($this.T('setup.repoPath'), $repoRoot, $true))

        # ─── Profile + aliases instalados ───────────────────────────────────
        $lines.Add('')
        $lines.Add($this.Theme.Fg('fg2') + ' ' + $this.T('setup.profileSection') + $reset)

        $profInfo = $this.Setup.GetProfileInfo()
        $lines.Add($this.RenderInfoRow($this.T('setup.path'), $profInfo.Path, $profInfo.Exists))

        # Mostramos los aliases registrados en el bloque managed. Soporta
        # cualquier nombre custom (rnav, listc, etc.) y combinaciones múltiples.
        $installedAliases = $this.Setup.GetInstalledAliases()
        $aliasOk = $installedAliases.Count -gt 0
        $aliasMsg = if ($aliasOk) {
            $this.T('setup.aliasManaged', @(($installedAliases -join ', ')))
        } else {
            $this.T('setup.notInstalled')
        }
        $lines.Add($this.RenderInfoRow($this.T('setup.alias'), $aliasMsg, $aliasOk))

        $functions = $this.Setup.GetInstalledFunctions()
        $userFunctions = @($functions | Where-Object { $_.Source -eq 'user' })
        if ($userFunctions.Count -gt 0) {
            $names = ($userFunctions | ForEach-Object Name) -join ', '
            $lines.Add($this.RenderInfoRow($this.T('setup.otherFunctions'), $names, $true))
        }

        # ─── Settings ───────────────────────────────────────────────────────
        $lines.Add('')
        $lines.Add($this.Theme.Fg('fg2') + ' ' + $this.T('setup.settingsSection') + $reset)

        $settingsInfo = $this.Setup.GetSettingsInfo()
        $lines.Add($this.RenderInfoRow($this.T('setup.path'), $settingsInfo.Path, $settingsInfo.Exists))

        if ($settingsInfo.Exists) {
            $statusText = if ($settingsInfo.Valid) { $this.T('setup.jsonValid') } else { $this.T('setup.jsonInvalid') }
            $lines.Add($this.RenderInfoRow($this.T('setup.statusLabel'), $statusText, $settingsInfo.Valid))
        }

        # ─── Acciones ───────────────────────────────────────────────────────
        $lines.Add('')
        $lines.Add($r.HRule())
        $lines.Add($this.Theme.Fg('fg2') + ' ' + $this.T('setup.actionsSection') + $reset)

        $actions = $this.Actions()
        for ($i = 0; $i -lt $actions.Count; $i++) {
            $sel = ($i -eq $this.SelectedIndex)
            $marker = if ($sel) { $this.Theme.Fg('acc') + '▎ ▶ ' + $reset } else { ' · ' }
            $color = switch ($actions[$i].Action) {
                'uninstall' { 'gitDirty' }     # naranja para acción "delete"-ish
                'cancel'    { 'fg2' }
                default     { if ($sel) { 'acc' } else { 'fg1' } }
            }
            $lines.Add($marker + $this.Theme.Fg($color) + $actions[$i].Label + $reset)
        }

        # StatusBar
        $hints = @(
            @{ k = '↑↓'; label = $this.T('hint.move') }
            @{ k = '↵';  label = $this.T('hint.run') }
            @{ k = '←';  label = $this.T('hint.back') }
            @{ k = 'Q';  label = $this.T('hint.quit') }
        )
        $lines.Add($r.HRule())
        return @($lines.ToArray()) + $this.StatusBar.RenderAnchored($hints, '', $lines.Count)
    }

    hidden [string] RenderInfoRow([string]$label, [string]$value, [bool]$ok) {
        $reset = [AnsiService]::Reset
        $tickFg = if ($ok) { 'gitClean' } else { 'gitConflict' }
        $tick = $this.Theme.Fg($tickFg) + $(if ($ok) { '✓' } else { '✗' }) + $reset
        $labelFmt = [Renderer]::PadRight(' ' + $this.Theme.Fg('fg2') + $label + $reset, 22)
        $valFmt = $this.Theme.Fg('fg1') + $value + $reset
        return $tick + ' ' + $labelFmt + ' ' + $valFmt
    }
}