1package screens
2
3import (
4 "charm.land/bubbles/v2/key"
5 tea "charm.land/bubbletea/v2"
6
7 "charm.land/huh/v2"
8
9 "git.secluded.site/keld/internal/theme"
10 "git.secluded.site/keld/internal/ui"
11)
12
13// overwriteOptions defines the choices for restic's --overwrite flag
14// in the order they are presented to the user.
15var overwriteOptions = []huh.Option[string]{
16 huh.NewOption("if-changed (recommended — only restore what differs)", "if-changed"),
17 huh.NewOption("if-newer (only overwrite older files)", "if-newer"),
18 huh.NewOption("never (skip existing files entirely)", "never"),
19 huh.NewOption("always (restic default — overwrite everything)", "always"),
20}
21
22// Overwrite is a Screen adapter that wraps a huh Select for choosing
23// the restore --overwrite behaviour. It follows the same pattern as
24// [Preset]: builds the huh form on Init, intercepts Esc for back
25// navigation, and returns DoneCmd on completion.
26type Overwrite struct {
27 styles *theme.Styles
28 form *huh.Form
29 selected string
30 selection string
31}
32
33// NewOverwrite creates an overwrite selector screen. The styles
34// pointer should come from the session so theme updates propagate.
35func NewOverwrite(styles *theme.Styles) *Overwrite {
36 return &Overwrite{
37 styles: styles,
38 }
39}
40
41// buildForm constructs the huh form and select field.
42func (o *Overwrite) buildForm() {
43 o.selection = ""
44
45 sel := huh.NewSelect[string]().
46 Options(overwriteOptions...).
47 Value(&o.selected)
48
49 o.form = huh.NewForm(
50 huh.NewGroup(sel),
51 ).WithTheme(o.styles.Huh).WithShowHelp(false)
52}
53
54// Init initialises the embedded form. On first call or after
55// completion, the form is (re)built.
56func (o *Overwrite) Init() tea.Cmd {
57 if o.form == nil || o.form.State != huh.StateNormal {
58 o.buildForm()
59 }
60 return o.form.Init()
61}
62
63// Update handles messages. Esc is intercepted for back navigation.
64func (o *Overwrite) Update(msg tea.Msg) (ui.Screen, tea.Cmd) {
65 if o.form == nil {
66 return o, nil
67 }
68
69 switch msg.(type) {
70 case tea.BackgroundColorMsg:
71 o.buildForm()
72 return o, o.form.Init()
73 }
74
75 if kp, ok := msg.(tea.KeyPressMsg); ok {
76 if kp.Code == tea.KeyEscape {
77 return o, ui.BackCmd
78 }
79 }
80
81 model, cmd := o.form.Update(msg)
82 if f, ok := model.(*huh.Form); ok {
83 o.form = f
84 }
85
86 if o.form.State == huh.StateCompleted {
87 o.selection = o.selected
88 return o, ui.DoneCmd
89 }
90
91 return o, cmd
92}
93
94// View renders the form.
95func (o *Overwrite) View() string {
96 if o.form == nil {
97 return ""
98 }
99 return o.form.View()
100}
101
102// Title returns the screen's display title.
103func (o *Overwrite) Title() string { return "Overwrite existing files?" }
104
105// KeyBindings returns bindings for the help bar.
106func (o *Overwrite) KeyBindings() []key.Binding {
107 return []key.Binding{
108 key.NewBinding(key.WithKeys("↑/↓"), key.WithHelp("↑/↓", "navigate")),
109 key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "select")),
110 }
111}
112
113// Selection returns the chosen overwrite mode for breadcrumb display,
114// or "" if nothing has been selected yet.
115func (o *Overwrite) Selection() string { return o.selection }
116
117// Value returns the chosen overwrite mode value (one of: if-changed,
118// if-newer, never, always).
119func (o *Overwrite) Value() string { return o.selected }