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:
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.ViewEmailMsg:
111 emailView := tui.NewEmailView(m.emails[msg.Index], m.width, m.height)
112 m.current = emailView
113 cmds = append(cmds, m.current.Init())
114
115 case tui.SendEmailMsg:
116 m.current = tui.NewStatus("Sending email...")
117 cmds = append(cmds, m.current.Init(), sendEmail(m.config, msg))
118
119 case tui.EmailResultMsg:
120 m.current = tui.NewChoice()
121 cmds = append(cmds, m.current.Init())
122 }
123
124 // Pass all other messages to the current view
125 m.current, cmd = m.current.Update(msg)
126 cmds = append(cmds, cmd)
127
128 return m, tea.Batch(cmds...)
129}
130
131func (m *mainModel) View() string {
132 return m.current.View()
133}
134
135// sendEmail is a command that sends an email in the background.
136func sendEmail(cfg *config.Config, msg tui.SendEmailMsg) tea.Cmd {
137 return func() tea.Msg {
138 recipients := []string{msg.To}
139 err := sender.SendEmail(cfg, recipients, msg.Subject, msg.Body)
140 if err != nil {
141 log.Printf("Failed to send email: %v", err) // Log error
142 return tui.EmailResultMsg{Err: err}
143 }
144 time.Sleep(1 * time.Second) // Give user time to see the "Sending" message
145 return tui.EmailResultMsg{}
146 }
147}
148
149func fetchEmails(cfg *config.Config) tea.Cmd {
150 return func() tea.Msg {
151 emails, err := fetcher.FetchEmails(cfg)
152 if err != nil {
153 return tui.FetchErr(err)
154 }
155 return tui.EmailsFetchedMsg{Emails: emails}
156 }
157}
158
159func main() {
160 cfg, err := config.LoadConfig()
161 var initialModel *mainModel
162 if err != nil {
163 // If the config doesn't exist, we can guide the user to create one.
164 initialModel = newInitialModel(nil)
165 } else {
166 initialModel = newInitialModel(cfg)
167 }
168
169 p := tea.NewProgram(initialModel, tea.WithAltScreen())
170
171 if _, err := p.Run(); err != nil {
172 fmt.Printf("Alas, there's been an error: %v", err)
173 os.Exit(1)
174 }
175}