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 switch m.current.(type) {
65 case *tui.EmailView:
66 m.current = m.inbox
67 return m, nil
68 case *tui.Inbox, *tui.Composer:
69 m.current = tui.NewChoice()
70 return m, m.current.Init()
71 }
72 }
73
74 // --- Custom Messages for switching views ---
75 case tui.Credentials: // This is a struct, not a pointer
76 cfg := &config.Config{
77 ServiceProvider: msg.Provider,
78 Name: msg.Name,
79 Email: msg.Email,
80 Password: msg.Password,
81 }
82 if err := config.SaveConfig(cfg); err != nil {
83 // TODO: Show an error message to the user
84 log.Printf("could not save config: %v", err)
85 return m, tea.Quit
86 }
87 m.config = cfg
88 m.current = tui.NewChoice()
89 cmds = append(cmds, m.current.Init())
90
91 case tui.GoToInboxMsg:
92 m.current = tui.NewStatus("Fetching emails...")
93 return m, tea.Batch(m.current.Init(), fetchEmails(m.config))
94
95 case tui.EmailsFetchedMsg:
96 m.emails = msg.Emails
97 m.inbox = tui.NewInbox(m.emails)
98 m.current = m.inbox
99 // Manually set the size of the new view
100 m.current, _ = m.current.Update(tea.WindowSizeMsg{Width: m.width, Height: m.height})
101 cmds = append(cmds, m.current.Init())
102
103 case tui.GoToSendMsg:
104 composer := tui.NewComposer(m.config.Email)
105 m.current = &composer
106 m.current, _ = m.current.Update(tea.WindowSizeMsg{Width: m.width, Height: m.height})
107 cmds = append(cmds, m.current.Init())
108
109 case tui.ViewEmailMsg:
110 emailView := tui.NewEmailView(m.emails[msg.Index], m.width, m.height)
111 m.current = emailView
112 cmds = append(cmds, m.current.Init())
113
114 case tui.SendEmailMsg:
115 m.current = tui.NewStatus("Sending email...")
116 cmds = append(cmds, m.current.Init(), sendEmail(m.config, msg))
117
118 case tui.EmailResultMsg:
119 m.current = tui.NewChoice()
120 cmds = append(cmds, m.current.Init())
121 }
122
123 // Pass all other messages to the current view
124 m.current, cmd = m.current.Update(msg)
125 cmds = append(cmds, cmd)
126
127 return m, tea.Batch(cmds...)
128}
129
130func (m *mainModel) View() string {
131 return m.current.View()
132}
133
134// sendEmail is a command that sends an email in the background.
135func sendEmail(cfg *config.Config, msg tui.SendEmailMsg) tea.Cmd {
136 return func() tea.Msg {
137 recipients := []string{msg.To}
138 err := sender.SendEmail(cfg, recipients, msg.Subject, msg.Body)
139 if err != nil {
140 log.Printf("Failed to send email: %v", err) // Log error
141 return tui.EmailResultMsg{Err: err}
142 }
143 time.Sleep(1 * time.Second) // Give user time to see the "Sending" message
144 return tui.EmailResultMsg{}
145 }
146}
147
148func fetchEmails(cfg *config.Config) tea.Cmd {
149 return func() tea.Msg {
150 emails, err := fetcher.FetchEmails(cfg)
151 if err != nil {
152 return tui.FetchErr(err)
153 }
154 return tui.EmailsFetchedMsg{Emails: emails}
155 }
156}
157
158func main() {
159 cfg, err := config.LoadConfig()
160 var initialModel *mainModel
161 if err != nil {
162 // If the config doesn't exist, we can guide the user to create one.
163 initialModel = newInitialModel(nil)
164 } else {
165 initialModel = newInitialModel(cfg)
166 }
167
168 p := tea.NewProgram(initialModel, tea.WithAltScreen())
169
170 if _, err := p.Run(); err != nil {
171 fmt.Printf("Alas, there's been an error: %v", err)
172 os.Exit(1)
173 }
174}