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