1package main
2
3import (
4 "fmt"
5 "log"
6 "os"
7 "time"
8
9 "github.com/andrinoff/email-cli/config"
10 "github.com/andrinoff/email-cli/fetcher"
11 "github.com/andrinoff/email-cli/sender"
12 "github.com/andrinoff/email-cli/tui"
13 tea "github.com/charmbracelet/bubbletea"
14)
15
16// mainModel now holds the state for the entire application.
17type mainModel struct {
18 current tea.Model
19 config *config.Config
20 emails []fetcher.Email
21 inbox *tui.Inbox
22 width int
23 height int
24 err error
25}
26
27// newInitialModel now returns a pointer, which is crucial for state management.
28func newInitialModel(cfg *config.Config) *mainModel {
29 // If config is nil, it means we're starting from the login screen.
30 if cfg == nil {
31 return &mainModel{
32 current: tui.NewLogin(),
33 }
34 }
35 return &mainModel{
36 current: tui.NewChoice(),
37 config: cfg,
38 }
39}
40
41func (m *mainModel) Init() tea.Cmd {
42 return m.current.Init()
43}
44
45func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
46 var cmd tea.Cmd
47 var cmds []tea.Cmd
48
49 switch msg := msg.(type) {
50 case tea.WindowSizeMsg:
51 m.width = msg.Width
52 m.height = msg.Height
53 // Pass the window size message to the current view
54 m.current, cmd = m.current.Update(msg)
55 cmds = append(cmds, cmd)
56 return m, tea.Batch(cmds...)
57
58 case tea.KeyMsg:
59 if msg.String() == "ctrl+c" {
60 return m, tea.Quit
61 }
62 // Allow ESC to go back
63 if msg.String() == "esc" {
64 // Check the type of the current view to decide where to go.
65 switch m.current.(type) {
66 case *tui.EmailView:
67 m.current = m.inbox // Go back to the cached inbox
68 return m, nil
69 case *tui.Inbox, *tui.Composer, *tui.Login:
70 m.current = tui.NewChoice() // Go back to the main menu
71 return m, m.current.Init()
72 }
73 }
74
75 // --- Custom Messages for switching views ---
76 case tui.Credentials: // This is a struct, not a pointer
77 cfg := &config.Config{
78 ServiceProvider: msg.Provider,
79 Name: msg.Name,
80 Email: msg.Email,
81 Password: msg.Password,
82 }
83 if err := config.SaveConfig(cfg); err != nil {
84 // TODO: Show an error message to the user
85 log.Printf("could not save config: %v", err)
86 return m, tea.Quit
87 }
88 m.config = cfg
89 m.current = tui.NewChoice()
90 cmds = append(cmds, m.current.Init())
91
92 case tui.GoToInboxMsg:
93 m.current = tui.NewStatus("Fetching emails...")
94 return m, tea.Batch(m.current.Init(), fetchEmails(m.config))
95
96 case tui.EmailsFetchedMsg:
97 m.emails = msg.Emails
98 m.inbox = tui.NewInbox(m.emails)
99 m.current = m.inbox
100 // Manually set the size of the new view
101 m.current, _ = m.current.Update(tea.WindowSizeMsg{Width: m.width, Height: m.height})
102 cmds = append(cmds, m.current.Init())
103
104 case tui.GoToSendMsg:
105 // NewComposer now returns *Composer, so we assign it directly.
106 m.current = tui.NewComposer(m.config.Email)
107 m.current, _ = m.current.Update(tea.WindowSizeMsg{Width: m.width, Height: m.height})
108 cmds = append(cmds, m.current.Init())
109
110 case tui.GoToSettingsMsg:
111 m.current = tui.NewLogin()
112 m.current, _ = m.current.Update(tea.WindowSizeMsg{Width: m.width, Height: m.height})
113 cmds = append(cmds, m.current.Init())
114
115 case tui.ViewEmailMsg:
116 emailView := tui.NewEmailView(m.emails[msg.Index], m.width, m.height)
117 m.current = emailView
118 cmds = append(cmds, m.current.Init())
119
120 case tui.SendEmailMsg:
121 m.current = tui.NewStatus("Sending email...")
122 cmds = append(cmds, m.current.Init(), sendEmail(m.config, msg))
123
124 case tui.EmailResultMsg:
125 m.current = tui.NewChoice()
126 cmds = append(cmds, m.current.Init())
127 }
128
129 // Pass all other messages to the current view
130 m.current, cmd = m.current.Update(msg)
131 cmds = append(cmds, cmd)
132
133 return m, tea.Batch(cmds...)
134}
135
136func (m *mainModel) View() string {
137 return m.current.View()
138}
139
140// sendEmail is a command that sends an email in the background.
141func sendEmail(cfg *config.Config, msg tui.SendEmailMsg) tea.Cmd {
142 return func() tea.Msg {
143 recipients := []string{msg.To}
144 err := sender.SendEmail(cfg, recipients, msg.Subject, msg.Body)
145 if err != nil {
146 log.Printf("Failed to send email: %v", err) // Log error
147 return tui.EmailResultMsg{Err: err}
148 }
149 time.Sleep(1 * time.Second) // Give user time to see the "Sending" message
150 return tui.EmailResultMsg{}
151 }
152}
153
154func fetchEmails(cfg *config.Config) tea.Cmd {
155 return func() tea.Msg {
156 emails, err := fetcher.FetchEmails(cfg)
157 if err != nil {
158 return tui.FetchErr(err)
159 }
160 return tui.EmailsFetchedMsg{Emails: emails}
161 }
162}
163
164func main() {
165 cfg, err := config.LoadConfig()
166 var initialModel *mainModel
167 if err != nil {
168 // If the config doesn't exist, we can guide the user to create one.
169 initialModel = newInitialModel(nil)
170 } else {
171 initialModel = newInitialModel(cfg)
172 }
173
174 p := tea.NewProgram(initialModel, tea.WithAltScreen())
175
176 if _, err := p.Run(); err != nil {
177 fmt.Printf("Alas, there's been an error: %v", err)
178 os.Exit(1)
179 }
180}