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 manageHabits(cfg *config.Config) error {
17 nav := manageHabitsAsStep(cfg)
18 if nav == navQuit {
19 return errQuit
20 }
21
22 return nil
23}
24
25// manageHabitsAsStep runs habits management as a wizard step with Back/Next navigation.
26func manageHabitsAsStep(cfg *config.Config) wizardNav {
27 for {
28 options := buildHabitStepOptions(cfg.Habits)
29
30 choice, err := runListSelect(
31 "Habits",
32 "Track habits from the command line.",
33 options,
34 )
35 if err != nil {
36 return navQuit
37 }
38
39 switch choice {
40 case choiceBack:
41 return navBack
42 case choiceNext:
43 return navNext
44 case choiceAdd:
45 if err := addHabit(cfg); errors.Is(err, errQuit) {
46 return navQuit
47 }
48 default:
49 idx, ok := parseEditIndex(choice)
50 if !ok {
51 continue
52 }
53
54 if err := manageHabitActions(cfg, idx); errors.Is(err, errQuit) {
55 return navQuit
56 }
57 }
58 }
59}
60
61func buildHabitStepOptions(habits []config.Habit) []huh.Option[string] {
62 options := []huh.Option[string]{
63 huh.NewOption("Add new habit", choiceAdd),
64 }
65
66 for idx, habit := range habits {
67 label := fmt.Sprintf("%s (%s)", habit.Name, habit.Key)
68 options = append(options, huh.NewOption(label, fmt.Sprintf("edit:%d", idx)))
69 }
70
71 options = append(options,
72 huh.NewOption("← Back", choiceBack),
73 huh.NewOption("Next →", choiceNext),
74 )
75
76 return options
77}
78
79func addHabit(cfg *config.Config) error {
80 habit, err := editHabit(nil, cfg)
81 if err != nil {
82 if errors.Is(err, errBack) {
83 return nil
84 }
85
86 return err
87 }
88
89 cfg.Habits = append(cfg.Habits, *habit)
90
91 return nil
92}
93
94func manageHabitActions(cfg *config.Config, idx int) error {
95 if idx < 0 || idx >= len(cfg.Habits) {
96 return fmt.Errorf("%w: habit %d", errIndexOutRange, idx)
97 }
98
99 habit := &cfg.Habits[idx]
100
101 action, err := runActionSelect(fmt.Sprintf("Habit: %s (%s)", habit.Name, habit.Key), false)
102 if err != nil {
103 return err
104 }
105
106 switch action {
107 case itemActionEdit:
108 updated, err := editHabit(habit, cfg)
109 if err != nil {
110 if errors.Is(err, errBack) {
111 return nil
112 }
113
114 return err
115 }
116
117 if updated != nil {
118 cfg.Habits[idx] = *updated
119 }
120 case itemActionDelete:
121 return deleteHabit(cfg, idx)
122 case itemActionNone, itemActionGoals:
123 // User cancelled or went back; goals not applicable here
124 }
125
126 return nil
127}
128
129func editHabit(existing *config.Habit, cfg *config.Config) (*config.Habit, error) {
130 habit := config.Habit{}
131 if existing != nil {
132 habit = *existing
133 }
134
135 err := runItemForm(&habit.Name, &habit.Key, &habit.ID, itemFormConfig{
136 itemType: "habit",
137 namePlaceholder: "Study Gaelic",
138 keyPlaceholder: "gaelic",
139 keyValidator: validateHabitKey(cfg, existing),
140 supportsDeepLink: false,
141 })
142 if err != nil {
143 return nil, err
144 }
145
146 return &habit, nil
147}
148
149func validateHabitKey(cfg *config.Config, existing *config.Habit) func(string) error {
150 return func(input string) error {
151 if err := validateKeyFormat(input); err != nil {
152 return err
153 }
154
155 for idx := range cfg.Habits {
156 if existing != nil && &cfg.Habits[idx] == existing {
157 continue
158 }
159
160 if cfg.Habits[idx].Key == input {
161 return errKeyDuplicate
162 }
163 }
164
165 return nil
166 }
167}
168
169func deleteHabit(cfg *config.Config, idx int) error {
170 if idx < 0 || idx >= len(cfg.Habits) {
171 return fmt.Errorf("%w: habit %d", errIndexOutRange, idx)
172 }
173
174 habit := cfg.Habits[idx]
175
176 var confirm bool
177
178 err := huh.NewConfirm().
179 Title(fmt.Sprintf("Delete habit '%s'?", habit.Name)).
180 Description("This cannot be undone.").
181 Affirmative("Delete").
182 Negative("Cancel").
183 Value(&confirm).
184 Run()
185 if err != nil {
186 if errors.Is(err, huh.ErrUserAborted) {
187 return errQuit
188 }
189
190 return err
191 }
192
193 if confirm {
194 cfg.Habits = append(cfg.Habits[:idx], cfg.Habits[idx+1:]...)
195 }
196
197 return nil
198}