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// Target is a Screen adapter that wraps a huh Input for entering
14// the restore target directory. It validates that the input is
15// non-empty and returns the entered path via [Value].
16type Target struct {
17 styles *theme.Styles
18 form *huh.Form
19 entered string
20 selection string
21}
22
23// NewTarget creates a target directory screen. The styles pointer
24// should come from the session so theme updates propagate.
25func NewTarget(styles *theme.Styles) *Target {
26 return &Target{
27 styles: styles,
28 }
29}
30
31// buildForm constructs the huh form. Called from Init when the form
32// needs (re)building.
33func (t *Target) buildForm() {
34 // Preserve any previous selection so back-navigation shows the
35 // user's earlier input. Reset the breadcrumb label since the
36 // screen is being re-activated.
37 t.selection = ""
38
39 input := huh.NewInput().
40 Title("Restore destination").
41 Placeholder("e.g. /tmp/restore").
42 Value(&t.entered).
43 Validate(huh.ValidateNotEmpty())
44
45 t.form = huh.NewForm(
46 huh.NewGroup(input),
47 ).WithTheme(t.styles.Huh).WithShowHelp(false)
48}
49
50// Init initialises the embedded form. On first call or after
51// completion, the form is (re)built so it picks up the current
52// theme and can accept input again.
53func (t *Target) Init() tea.Cmd {
54 if t.form == nil || t.form.State != huh.StateNormal {
55 t.buildForm()
56 }
57 return t.form.Init()
58}
59
60// Update handles messages. Esc is intercepted for back navigation.
61// All other messages are forwarded to the huh form.
62func (t *Target) Update(msg tea.Msg) (ui.Screen, tea.Cmd) {
63 if t.form == nil {
64 return t, nil
65 }
66
67 switch msg.(type) {
68 case tea.BackgroundColorMsg:
69 t.buildForm()
70 return t, t.form.Init()
71 }
72
73 if kp, ok := msg.(tea.KeyPressMsg); ok {
74 if kp.Code == tea.KeyEscape {
75 return t, ui.BackCmd
76 }
77 }
78
79 model, cmd := t.form.Update(msg)
80 if f, ok := model.(*huh.Form); ok {
81 t.form = f
82 }
83
84 if t.form.State == huh.StateCompleted {
85 t.selection = t.entered
86 return t, ui.DoneCmd
87 }
88
89 return t, cmd
90}
91
92// View renders the form.
93func (t *Target) View() string {
94 if t.form == nil {
95 return ""
96 }
97 return t.form.View()
98}
99
100// Title returns the screen's display title.
101func (t *Target) Title() string { return "Target directory" }
102
103// KeyBindings returns bindings for the help bar.
104func (t *Target) KeyBindings() []key.Binding {
105 return []key.Binding{
106 key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "confirm")),
107 }
108}
109
110// Selection returns the entered path for breadcrumb display, or ""
111// if nothing has been entered yet.
112func (t *Target) Selection() string { return t.selection }
113
114// Value returns the entered target directory path.
115func (t *Target) Value() string { return t.entered }