src/UI/Screens/QuickChangeScreen.ps1

# QuickChangeScreen — Dashboard contextual del flujo Quick Changes (replica
# QuickChangeFlowController del v2).
#
# A diferencia de IntegrateScreen (configurable con inputs), Quick Changes
# muestra una lista DINÁMICA de acciones según el estado actual del repo:
#
# - Si hay uncommitted changes → Commit All
# - Si needsPush (ahead/no upstream) → Push, Push --no-verify
# - Si needsPull (behind) → Pull
# - Siempre → Switch Branch, Cancel
#
# Cada Enter ejecuta una acción atómica (Push/Pull) o entra en sub-modo
# (input-message para commit, select-branch para switch).

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

    [object]   $Repo
    [int]      $SelectedIndex = 0
    [string]   $Mode = 'list'   # 'list' | 'input-message' | 'select-branch' | 'executing'
    [bool]     $QuitRequested = $false
    [bool]     $UseAltBuffer = $true

    # Estado calculado en cada Refresh.
    [bool]     $HasChanges = $false
    [string]   $CurrentBranch = ''
    [bool]     $HasRemote = $false
    [bool]     $NeedsPush = $false
    [bool]     $NeedsPull = $false
    [int]      $Ahead = 0
    [int]      $Behind = 0

    # Mensaje del commit (cuando estamos en input-message).
    [string]   $InputBuffer = ''

    # Selector branches — usa FilteredListPicker componente reusable.
    [object]   $BranchPicker = $null

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

    QuickChangeScreen($theme, $renderer, $primitives, $frame, $appHeader, $statusBar, $git) {
        $this.Theme       = $theme
        $this.Renderer    = $renderer
        $this.Primitives  = $primitives
        $this.Frame       = $frame
        $this.AppHeader   = $appHeader
        $this.StatusBar   = $statusBar
        $this.Git         = $git
    }

    [void] Open([object]$repo) {
        if ($null -eq $repo) { throw "QuickChangeScreen.Open: repo no puede ser null." }

        $this.Repo = $repo
        $this.SelectedIndex = 0
        $this.Mode = 'list'
        $this.StatusMessage = ''
        $this.StatusKind = ''
        $this.RefreshState()

        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 { }
    }

    # Recalcula estado: dirty, ahead/behind, current branch, hasRemote.
    # Invalida el cache primero — Refresh implica "quiero datos frescos".
    hidden [void] RefreshState() {
        $repoPath = $this.Repo.Path
        $this.Git.InvalidatePath($repoPath)
        $state = $this.Git.GetRepoState($repoPath)
        $this.CurrentBranch = $state.Branch
        $this.Ahead  = $state.Ahead
        $this.Behind = $state.Behind
        $this.HasChanges = ($state.Status -in @('dirty', 'conflict'))

        # NeedsPull: behind > 0.
        $this.NeedsPull = ($state.Behind -gt 0)

        # HasRemote: hay url configurada.
        $url = $this.Git.RunGit($repoPath, @('config', '--get', 'remote.origin.url'))
        $this.HasRemote = ($url -and $url[0])

        # NeedsPush: tiene remote y (ahead > 0 o branch local sin tracking).
        if ($this.HasRemote) {
            if ($state.Ahead -gt 0) {
                $this.NeedsPush = $true
            } else {
                # Sin upstream → tampoco está sincronizada todavía.
                $hasUp = $this.Git.RunGit($repoPath, @('rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'))
                $this.NeedsPush = -not ($hasUp -and $hasUp[0])
            }
        } else {
            $this.NeedsPush = $false
        }
    }

    # Lista de acciones disponibles según estado actual. Cada item es un hashtable
    # @{ Label; Action }. La SelectedIndex apunta al índice de este array.
    [object[]] AvailableActions() {
        $items = @()
        if ($this.HasChanges) {
            $items += @{ Label = 'Commit todos los cambios'; Action = 'commit' }
        }
        if ($this.NeedsPush) {
            $items += @{ Label = 'Push'; Action = 'push' }
            $items += @{ Label = 'Push --no-verify'; Action = 'push-no-verify' }
        }
        if ($this.NeedsPull) {
            $items += @{ Label = 'Pull'; Action = 'pull' }
        }
        $items += @{ Label = 'Switch Branch'; Action = 'switch' }
        $items += @{ Label = 'Cancel'; Action = 'cancel' }
        return $items
    }

    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()
    }

    hidden [string] HandleKey([System.ConsoleKeyInfo]$key) {
        switch ($this.Mode) {
            'list'           { return $this.HandleKeyList($key) }
            'input-message'  { return $this.HandleKeyInputMessage($key) }
            'select-branch'  { return $this.HandleKeyBranchSelector($key) }
            'executing'      { return $this.HandleKeyExecuting($key) }
        }
        return ''
    }

    hidden [string] HandleKeyList([System.ConsoleKeyInfo]$key) {
        $k = $key.Key
        $c = $key.KeyChar
        $items = $this.AvailableActions()

        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 ($items.Count -gt 0) {
                $this.SelectedIndex = if ($this.SelectedIndex -le 0) { $items.Count - 1 } else { $this.SelectedIndex - 1 }
            }
            return ''
        }
        if ($k -eq 'DownArrow' -or $c -eq 'j') {
            if ($items.Count -gt 0) {
                $this.SelectedIndex = if ($this.SelectedIndex -ge $items.Count - 1) { 0 } else { $this.SelectedIndex + 1 }
            }
            return ''
        }

        if ($k -eq 'Enter') {
            if ($items.Count -eq 0) { return '' }
            $action = $items[$this.SelectedIndex].Action
            switch ($action) {
                'commit'         { $this.OpenCommitInput(); return '' }
                'push'           { $this.DoPush($false); return '' }
                'push-no-verify' { $this.DoPush($true); return '' }
                'pull'           { $this.DoPull(); return '' }
                'switch'         { $this.OpenBranchSelector(); return '' }
                'cancel'         { return 'back' }
            }
        }
        return ''
    }

    hidden [void] OpenCommitInput() {
        $this.InputBuffer = ''
        $this.Mode = 'input-message'
    }

    hidden [void] OpenBranchSelector() {
        $branches = @($this.Git.GetBranches($this.Repo.Path))
        $names = @($branches | ForEach-Object Name)
        $this.BranchPicker = [FilteredListPicker]::new()
        $this.BranchPicker.Title = 'Switch a'
        $this.BranchPicker.SetItems($names)
        $this.Mode = 'select-branch'
    }

    hidden [string] HandleKeyInputMessage([System.ConsoleKeyInfo]$key) {
        $k = $key.Key
        $c = $key.KeyChar

        if ($k -eq 'Escape') {
            $this.InputBuffer = ''
            $this.Mode = 'list'
            return ''
        }
        if ($k -eq 'Enter') {
            $msg = $this.InputBuffer.Trim()
            $this.InputBuffer = ''
            $this.Mode = 'list'
            if (-not $msg) {
                $this.SetStatus('mensaje vacío — cancelado', 'error')
                return ''
            }
            $this.DoCommit($msg)
            return ''
        }
        if ($k -eq 'Backspace') {
            if ($this.InputBuffer.Length -gt 0) {
                $this.InputBuffer = $this.InputBuffer.Substring(0, $this.InputBuffer.Length - 1)
            }
            return ''
        }
        if (-not [char]::IsControl($c)) {
            $this.InputBuffer += $c
        }
        return ''
    }

    hidden [string] HandleKeyBranchSelector([System.ConsoleKeyInfo]$key) {
        $c = $key.KeyChar
        $k = $key.Key
        if ($c -eq 'q' -or $c -eq 'Q') { return 'quit' }
        if ($k -eq 'LeftArrow') { $this.Mode = 'list'; return '' }

        $action = $this.BranchPicker.HandleKey($key)
        if ($action -eq 'enter') {
            $picked = $this.BranchPicker.Selected()
            $this.Mode = 'list'
            if ($picked) { $this.DoSwitch([string]$picked) }
        } elseif ($action -eq 'escape') {
            $this.Mode = 'list'
        }
        return ''
    }

    hidden [string] HandleKeyExecuting([System.ConsoleKeyInfo]$key) {
        $c = $key.KeyChar
        if ($c -eq 'q' -or $c -eq 'Q') { return 'quit' }
        return 'back'
    }

    hidden [void] DoCommit([string]$message) {
        $this.SetStatus('staging y commiteando…', 'ok')
        $rAdd = $this.Git.Add($this.Repo.Path, '.')
        if (-not $rAdd.Ok) {
            $this.SetStatus("add falló: $($rAdd.Message)", 'error')
            return
        }
        $rCommit = $this.Git.Commit($this.Repo.Path, $message)
        if ($rCommit.Ok) {
            $this.SetStatus("commit OK", 'ok')
            $this.RefreshState()
        } else {
            $this.SetStatus("commit falló: $($rCommit.Message)", 'error')
        }
    }

    hidden [void] DoPush([bool]$noVerify) {
        $r = $this.Git.Push($this.Repo.Path, $noVerify)
        $this.SetStatus($r.Message, ($(if ($r.Ok) { 'ok' } else { 'error' })))
        if ($r.Ok) { $this.RefreshState() }
    }

    hidden [void] DoPull() {
        $r = $this.Git.Pull($this.Repo.Path)
        $this.SetStatus($r.Message, ($(if ($r.Ok) { 'ok' } else { 'error' })))
        if ($r.Ok) { $this.RefreshState() }
    }

    hidden [void] DoSwitch([string]$branchName) {
        $r = $this.Git.CheckoutBranch($this.Repo.Path, $branchName)
        $this.SetStatus($r.Message, ($(if ($r.Ok) { 'ok' } else { 'error' })))
        if ($r.Ok) { $this.RefreshState() }
    }

    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 bar
        $repoName = if ($this.Repo) { $this.Repo.Name } else { '—' }
        $lines.Add($this.Frame.TitleBar('repo-nav', "Quick Changes · $repoName", '1.0.0'))

        # AppHeader
        $bc = [BreadcrumbBuilder]::Build($this.Repo.Path)
        $segs = @($bc.Segs) + @($bc.Current) + @('Branches')
        $lines.Add($this.AppHeader.Render($segs, 'Quick Changes', @()))
        $lines.Add($r.HRule())

        # Cuerpo según modo
        if ($this.Mode -eq 'select-branch') {
            $this.AppendSelectorBody($lines, $r, $reset)
        } else {
            $this.AppendDashboardBody($lines, $r, $reset)
        }

        # StatusBar
        $hints = $this.StatusBarHints()
        $lines.Add($r.HRule())
        return @($lines.ToArray()) + $this.StatusBar.RenderAnchored($hints, '', $lines.Count)
    }

    hidden [void] AppendDashboardBody([object]$lines, [object]$r, [string]$reset) {
        # Status / prompt line
        if ($this.Mode -eq 'input-message') {
            $cursor = $this.Theme.Fg('acc') + '_' + $reset
            $label  = $this.Theme.Fg('fg2') + 'Mensaje del commit: ' + $reset
            $buf    = $this.Theme.Fg('fg1') + $this.InputBuffer + $reset
            $lines.Add(' ' + $label + $buf + $cursor)
        } elseif ($this.StatusMessage) {
            $color = if ($this.StatusKind -eq 'error') { 'gitConflict' } else { 'gitClean' }
            $lines.Add(' ' + $this.Theme.Fg($color) + $this.StatusMessage + $reset)
        } else {
            $lines.Add('')
        }

        # State summary
        $summaryParts = @()
        $summaryParts += $this.Theme.Fg('fg2') + 'branch:' + $reset + ' ' + $this.Theme.Fg('acc') + $this.CurrentBranch + $reset
        if ($this.HasChanges) { $summaryParts += $this.Theme.Fg('gitDirty') + 'dirty' + $reset }
        else { $summaryParts += $this.Theme.Fg('gitClean') + 'clean' + $reset }
        if ($this.Ahead -gt 0)  { $summaryParts += $this.Theme.Fg('gitAhead')  + ('▲' + $this.Ahead)  + $reset }
        if ($this.Behind -gt 0) { $summaryParts += $this.Theme.Fg('gitBehind') + ('▼' + $this.Behind) + $reset }
        if (-not $this.HasRemote) { $summaryParts += $this.Theme.Fg('fg3') + '(no remote)' + $reset }
        $lines.Add(' ' + ($summaryParts -join ' · '))
        $lines.Add($r.HRule())

        # Action items
        $items = $this.AvailableActions()
        for ($i = 0; $i -lt $items.Count; $i++) {
            $sel = ($this.SelectedIndex -eq $i -and $this.Mode -eq 'list')
            $marker = if ($sel) { $this.Theme.Fg('acc') + '▎ ▶ ' + $reset } else { ' · ' }
            $color = switch ($items[$i].Action) {
                'push-no-verify' { 'gitDirty' }     # naranja para destacar el flag peligroso
                'cancel'         { 'fg2' }
                default          { if ($sel) { 'acc' } else { 'fg1' } }
            }
            $lines.Add($marker + $this.Theme.Fg($color) + $items[$i].Label + $reset)
        }
    }

    hidden [void] AppendSelectorBody([object]$lines, [object]$r, [string]$reset) {
        # Delega al FilteredListPicker componente reusable.
        $maxVisible = [Math]::Max(5, [Console]::WindowHeight - 12)
        $body = $this.BranchPicker.BuildBody($this.Theme, $r, $maxVisible)
        foreach ($l in $body) { $lines.Add($l) }
    }

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

    hidden [array] StatusBarHints() {
        switch ($this.Mode) {
            'select-branch' {
                return @(
                    @{ k = '↑↓';                 label = $this.T('hint.move') }
                    @{ k = $this.T('hint.type'); label = $this.T('hint.search') }
                    @{ k = '↵';                  label = 'Switch' }
                    @{ k = 'Esc';                label = $this.T('hint.cancel') }
                )
            }
            'input-message' {
                return @(
                    @{ k = $this.T('hint.type'); label = $this.T('hint.save') }
                    @{ k = '↵';                  label = 'Commit' }
                    @{ k = 'Esc';                label = $this.T('hint.cancel') }
                )
            }
            default {
                return @(
                    @{ 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') }
                )
            }
        }
        return @()
    }
}