1package core
  2
  3import (
  4	"image/color"
  5	"strings"
  6
  7	"github.com/alecthomas/chroma/v2"
  8	"github.com/charmbracelet/bubbles/v2/help"
  9	"github.com/charmbracelet/bubbles/v2/key"
 10	"github.com/charmbracelet/crush/internal/tui/exp/diffview"
 11	"github.com/charmbracelet/crush/internal/tui/styles"
 12	"github.com/charmbracelet/lipgloss/v2"
 13	"github.com/charmbracelet/x/ansi"
 14)
 15
 16type KeyMapHelp interface {
 17	Help() help.KeyMap
 18}
 19
 20type simpleHelp struct {
 21	shortList []key.Binding
 22	fullList  [][]key.Binding
 23}
 24
 25func NewSimpleHelp(shortList []key.Binding, fullList [][]key.Binding) help.KeyMap {
 26	return &simpleHelp{
 27		shortList: shortList,
 28		fullList:  fullList,
 29	}
 30}
 31
 32// FullHelp implements help.KeyMap.
 33func (s *simpleHelp) FullHelp() [][]key.Binding {
 34	return s.fullList
 35}
 36
 37// ShortHelp implements help.KeyMap.
 38func (s *simpleHelp) ShortHelp() []key.Binding {
 39	return s.shortList
 40}
 41
 42func Section(text string, width int) string {
 43	t := styles.CurrentTheme()
 44	char := "─"
 45	length := lipgloss.Width(text) + 1
 46	remainingWidth := width - length
 47	lineStyle := t.S().Base.Foreground(t.Border)
 48	if remainingWidth > 0 {
 49		text = text + " " + lineStyle.Render(strings.Repeat(char, remainingWidth))
 50	}
 51	return text
 52}
 53
 54func SectionWithInfo(text string, width int, info string) string {
 55	t := styles.CurrentTheme()
 56	char := "─"
 57	length := lipgloss.Width(text) + 1
 58	remainingWidth := width - length
 59
 60	if info != "" {
 61		remainingWidth -= lipgloss.Width(info) + 1 // 1 for the space before info
 62	}
 63	lineStyle := t.S().Base.Foreground(t.Border)
 64	if remainingWidth > 0 {
 65		text = text + " " + lineStyle.Render(strings.Repeat(char, remainingWidth)) + " " + info
 66	}
 67	return text
 68}
 69
 70func Title(title string, width int) string {
 71	t := styles.CurrentTheme()
 72	char := "╱"
 73	length := lipgloss.Width(title) + 1
 74	remainingWidth := width - length
 75	titleStyle := t.S().Base.Foreground(t.Primary)
 76	if remainingWidth > 0 {
 77		lines := strings.Repeat(char, remainingWidth)
 78		lines = styles.ApplyForegroundGrad(lines, t.Primary, t.Secondary)
 79		title = titleStyle.Render(title) + " " + lines
 80	}
 81	return title
 82}
 83
 84type StatusOpts struct {
 85	Icon             string // if empty no icon will be shown
 86	Title            string
 87	TitleColor       color.Color
 88	Description      string
 89	DescriptionColor color.Color
 90	ExtraContent     string // additional content to append after the description
 91}
 92
 93func Status(opts StatusOpts, width int) string {
 94	t := styles.CurrentTheme()
 95	icon := opts.Icon
 96	title := opts.Title
 97	titleColor := t.FgMuted
 98	if opts.TitleColor != nil {
 99		titleColor = opts.TitleColor
100	}
101	description := opts.Description
102	descriptionColor := t.FgSubtle
103	if opts.DescriptionColor != nil {
104		descriptionColor = opts.DescriptionColor
105	}
106	title = t.S().Base.Foreground(titleColor).Render(title)
107	if description != "" {
108		extraContentWidth := lipgloss.Width(opts.ExtraContent)
109		if extraContentWidth > 0 {
110			extraContentWidth += 1
111		}
112		description = ansi.Truncate(description, width-lipgloss.Width(icon)-lipgloss.Width(title)-2-extraContentWidth, "…")
113		description = t.S().Base.Foreground(descriptionColor).Render(description)
114	}
115
116	content := []string{}
117	if icon != "" {
118		content = append(content, icon)
119	}
120	content = append(content, title)
121	if description != "" {
122		content = append(content, description)
123	}
124	if opts.ExtraContent != "" {
125		content = append(content, opts.ExtraContent)
126	}
127
128	return strings.Join(content, " ")
129}
130
131type ButtonOpts struct {
132	Text           string
133	UnderlineIndex int  // Index of character to underline (0-based)
134	Selected       bool // Whether this button is selected
135}
136
137// SelectableButton creates a button with an underlined character and selection state
138func SelectableButton(opts ButtonOpts) string {
139	t := styles.CurrentTheme()
140
141	// Base style for the button
142	buttonStyle := t.S().Text
143
144	// Apply selection styling
145	if opts.Selected {
146		buttonStyle = buttonStyle.Foreground(t.White).Background(t.Secondary)
147	} else {
148		buttonStyle = buttonStyle.Background(t.BgSubtle)
149	}
150
151	// Create the button text with underlined character
152	text := opts.Text
153	if opts.UnderlineIndex >= 0 && opts.UnderlineIndex < len(text) {
154		before := text[:opts.UnderlineIndex]
155		underlined := text[opts.UnderlineIndex : opts.UnderlineIndex+1]
156		after := text[opts.UnderlineIndex+1:]
157
158		message := buttonStyle.Render(before) +
159			buttonStyle.Underline(true).Render(underlined) +
160			buttonStyle.Render(after)
161
162		return buttonStyle.Padding(0, 2).Render(message)
163	}
164
165	// Fallback if no underline index specified
166	return buttonStyle.Padding(0, 2).Render(text)
167}
168
169// SelectableButtons creates a horizontal row of selectable buttons
170func SelectableButtons(buttons []ButtonOpts, spacing string) string {
171	if spacing == "" {
172		spacing = "  "
173	}
174
175	var parts []string
176	for i, button := range buttons {
177		parts = append(parts, SelectableButton(button))
178		if i < len(buttons)-1 {
179			parts = append(parts, spacing)
180		}
181	}
182
183	return lipgloss.JoinHorizontal(lipgloss.Left, parts...)
184}
185
186// SelectableButtonsVertical creates a vertical row of selectable buttons
187func SelectableButtonsVertical(buttons []ButtonOpts, spacing int) string {
188	var parts []string
189	for i, button := range buttons {
190		parts = append(parts, SelectableButton(button))
191		if i < len(buttons)-1 {
192			for range spacing {
193				parts = append(parts, "")
194			}
195		}
196	}
197
198	return lipgloss.JoinVertical(lipgloss.Center, parts...)
199}
200
201func DiffFormatter() *diffview.DiffView {
202	t := styles.CurrentTheme()
203	formatDiff := diffview.New()
204	style := chroma.MustNewStyle("crush", styles.GetChromaTheme())
205	diff := formatDiff.ChromaStyle(style).Style(t.S().Diff).TabWidth(4)
206	return diff
207}