1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5package init
6
7import (
8 "errors"
9 "fmt"
10
11 "github.com/charmbracelet/huh"
12
13 "git.secluded.site/lune/internal/config"
14)
15
16func maybeConfigureNotebooks(cfg *config.Config) error {
17 var configure bool
18
19 err := huh.NewConfirm().
20 Title("Configure notebooks?").
21 Description("Notebooks organize your notes in Lunatask.").
22 Affirmative("Yes").
23 Negative("Not now").
24 Value(&configure).
25 Run()
26 if err != nil {
27 if errors.Is(err, huh.ErrUserAborted) {
28 return nil
29 }
30
31 return err
32 }
33
34 if configure {
35 return manageNotebooks(cfg)
36 }
37
38 return nil
39}
40
41func manageNotebooks(cfg *config.Config) error {
42 for {
43 options := buildNotebookOptions(cfg.Notebooks)
44
45 choice, err := runListSelect("Notebooks", "Manage your notebooks.", options)
46 if err != nil {
47 return err
48 }
49
50 if choice == choiceBack {
51 return nil
52 }
53
54 if choice == choiceAdd {
55 if err := addNotebook(cfg); err != nil {
56 return err
57 }
58
59 continue
60 }
61
62 idx, ok := parseEditIndex(choice)
63 if !ok {
64 continue
65 }
66
67 if err := manageNotebookActions(cfg, idx); err != nil {
68 return err
69 }
70 }
71}
72
73func buildNotebookOptions(notebooks []config.Notebook) []huh.Option[string] {
74 options := []huh.Option[string]{
75 huh.NewOption("Add new notebook", choiceAdd),
76 }
77
78 for idx, notebook := range notebooks {
79 label := fmt.Sprintf("%s (%s)", notebook.Name, notebook.Key)
80 options = append(options, huh.NewOption(label, fmt.Sprintf("edit:%d", idx)))
81 }
82
83 options = append(options, huh.NewOption("Back", choiceBack))
84
85 return options
86}
87
88func addNotebook(cfg *config.Config) error {
89 notebook, err := editNotebook(nil, cfg)
90 if err != nil {
91 if errors.Is(err, errUserAborted) {
92 return nil
93 }
94
95 return err
96 }
97
98 cfg.Notebooks = append(cfg.Notebooks, *notebook)
99
100 return nil
101}
102
103func manageNotebookActions(cfg *config.Config, idx int) error {
104 if idx < 0 || idx >= len(cfg.Notebooks) {
105 return fmt.Errorf("%w: notebook %d", errIndexOutRange, idx)
106 }
107
108 notebook := &cfg.Notebooks[idx]
109
110 action, err := runActionSelect(fmt.Sprintf("Notebook: %s (%s)", notebook.Name, notebook.Key), false)
111 if err != nil {
112 return err
113 }
114
115 switch action {
116 case itemActionEdit:
117 updated, err := editNotebook(notebook, cfg)
118 if err != nil && !errors.Is(err, errUserAborted) {
119 return err
120 }
121
122 if updated != nil {
123 cfg.Notebooks[idx] = *updated
124 }
125 case itemActionDelete:
126 return deleteNotebook(cfg, idx)
127 case itemActionNone, itemActionGoals:
128 // User cancelled or went back; goals not applicable here
129 }
130
131 return nil
132}
133
134func editNotebook(existing *config.Notebook, cfg *config.Config) (*config.Notebook, error) {
135 notebook := config.Notebook{}
136 if existing != nil {
137 notebook = *existing
138 }
139
140 err := runItemForm(¬ebook.Name, ¬ebook.Key, ¬ebook.ID, itemFormConfig{
141 itemType: "notebook",
142 namePlaceholder: "Gaelic Notes",
143 keyPlaceholder: "gaelic",
144 keyValidator: validateNotebookKey(cfg, existing),
145 })
146 if err != nil {
147 return nil, err
148 }
149
150 return ¬ebook, nil
151}
152
153func validateNotebookKey(cfg *config.Config, existing *config.Notebook) func(string) error {
154 return func(input string) error {
155 if err := validateKeyFormat(input); err != nil {
156 return err
157 }
158
159 for idx := range cfg.Notebooks {
160 if existing != nil && &cfg.Notebooks[idx] == existing {
161 continue
162 }
163
164 if cfg.Notebooks[idx].Key == input {
165 return errKeyDuplicate
166 }
167 }
168
169 return nil
170 }
171}
172
173func deleteNotebook(cfg *config.Config, idx int) error {
174 if idx < 0 || idx >= len(cfg.Notebooks) {
175 return fmt.Errorf("%w: notebook %d", errIndexOutRange, idx)
176 }
177
178 notebook := cfg.Notebooks[idx]
179
180 var confirm bool
181
182 err := huh.NewConfirm().
183 Title(fmt.Sprintf("Delete notebook '%s'?", notebook.Name)).
184 Description("This cannot be undone.").
185 Affirmative("Delete").
186 Negative("Cancel").
187 Value(&confirm).
188 Run()
189 if err != nil {
190 if errors.Is(err, huh.ErrUserAborted) {
191 return nil
192 }
193
194 return err
195 }
196
197 if confirm {
198 cfg.Notebooks = append(cfg.Notebooks[:idx], cfg.Notebooks[idx+1:]...)
199 if cfg.Defaults.Notebook == notebook.Key {
200 cfg.Defaults.Notebook = ""
201 }
202 }
203
204 return nil
205}