1package quit
  2
  3import (
  4	"github.com/charmbracelet/bubbles/v2/key"
  5	tea "github.com/charmbracelet/bubbletea/v2"
  6	"github.com/charmbracelet/crush/internal/tui/components/dialogs"
  7	"github.com/charmbracelet/crush/internal/tui/styles"
  8	"github.com/charmbracelet/crush/internal/tui/util"
  9	"github.com/charmbracelet/lipgloss/v2"
 10)
 11
 12const (
 13	question                      = "Are you sure you want to quit?"
 14	QuitDialogID dialogs.DialogID = "quit"
 15)
 16
 17// QuitDialog represents a confirmation dialog for quitting the application.
 18type QuitDialog interface {
 19	dialogs.DialogModel
 20}
 21
 22type quitDialogCmp struct {
 23	wWidth  int
 24	wHeight int
 25
 26	selectedNo bool // true if "No" button is selected
 27	keymap     KeyMap
 28}
 29
 30// NewQuitDialog creates a new quit confirmation dialog.
 31func NewQuitDialog() QuitDialog {
 32	return &quitDialogCmp{
 33		selectedNo: true, // Default to "No" for safety
 34		keymap:     DefaultKeymap(),
 35	}
 36}
 37
 38func (q *quitDialogCmp) Init() tea.Cmd {
 39	return nil
 40}
 41
 42// Update handles keyboard input for the quit dialog.
 43func (q *quitDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 44	switch msg := msg.(type) {
 45	case tea.WindowSizeMsg:
 46		q.wWidth = msg.Width
 47		q.wHeight = msg.Height
 48	case tea.KeyPressMsg:
 49		switch {
 50		case key.Matches(msg, q.keymap.LeftRight, q.keymap.Tab):
 51			q.selectedNo = !q.selectedNo
 52			return q, nil
 53		case key.Matches(msg, q.keymap.EnterSpace):
 54			if !q.selectedNo {
 55				return q, tea.Quit
 56			}
 57			return q, util.CmdHandler(dialogs.CloseDialogMsg{})
 58		case key.Matches(msg, q.keymap.Yes):
 59			return q, tea.Quit
 60		case key.Matches(msg, q.keymap.No, q.keymap.Close):
 61			return q, util.CmdHandler(dialogs.CloseDialogMsg{})
 62		}
 63	}
 64	return q, nil
 65}
 66
 67// View renders the quit dialog with Yes/No buttons.
 68func (q *quitDialogCmp) View() string {
 69	t := styles.CurrentTheme()
 70	baseStyle := t.S().Base
 71	yesStyle := t.S().Text
 72	noStyle := yesStyle
 73
 74	if q.selectedNo {
 75		noStyle = noStyle.Foreground(t.White).Background(t.Secondary)
 76		yesStyle = yesStyle.Background(t.BgSubtle)
 77	} else {
 78		yesStyle = yesStyle.Foreground(t.White).Background(t.Secondary)
 79		noStyle = noStyle.Background(t.BgSubtle)
 80	}
 81
 82	const horizontalPadding = 3
 83	yesButton := yesStyle.PaddingLeft(horizontalPadding).Underline(true).Render("Y") +
 84		yesStyle.PaddingRight(horizontalPadding).Render("ep!")
 85	noButton := noStyle.PaddingLeft(horizontalPadding).Underline(true).Render("N") +
 86		noStyle.PaddingRight(horizontalPadding).Render("ope")
 87
 88	buttons := baseStyle.Width(lipgloss.Width(question)).Align(lipgloss.Right).Render(
 89		lipgloss.JoinHorizontal(lipgloss.Center, yesButton, "  ", noButton),
 90	)
 91
 92	content := baseStyle.Render(
 93		lipgloss.JoinVertical(
 94			lipgloss.Center,
 95			question,
 96			"",
 97			buttons,
 98		),
 99	)
100
101	quitDialogStyle := baseStyle.
102		Padding(1, 2).
103		Border(lipgloss.RoundedBorder()).
104		BorderForeground(t.BorderFocus)
105
106	return quitDialogStyle.Render(content)
107}
108
109func (q *quitDialogCmp) Position() (int, int) {
110	row := q.wHeight / 2
111	row -= 7 / 2
112	col := q.wWidth / 2
113	col -= (lipgloss.Width(question) + 4) / 2
114
115	return row, col
116}
117
118func (q *quitDialogCmp) ID() dialogs.DialogID {
119	return QuitDialogID
120}