package ui import ( "fmt" "strings" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/atridad/gitea-wrapped/pkg/models" ) type ReportScreen struct { stats *models.UserStats page int maxPages int } func NewReportScreen(stats *models.UserStats) *ReportScreen { return &ReportScreen{ stats: stats, page: 0, maxPages: 3, } } func (rs *ReportScreen) Init() tea.Cmd { return nil } func (rs *ReportScreen) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch msg.String() { case "q", "ctrl+c": return rs, tea.Quit case "right", "l", "n": if rs.page < rs.maxPages-1 { rs.page++ } case "left", "h", "p": if rs.page > 0 { rs.page-- } } } return rs, nil } func (rs *ReportScreen) View() string { header := lipgloss.NewStyle(). Foreground(lipgloss.Color("212")). Bold(true). Render(fmt.Sprintf("Gitea Wrapped %d", 2026)) + "\n" var content string switch rs.page { case 0: content = rs.renderOverviewPage() case 1: content = rs.renderLanguagesPage() case 2: content = rs.renderActivityPage() } footer := lipgloss.NewStyle(). Foreground(lipgloss.Color("8")). Render(fmt.Sprintf("Page %d/%d | ← → to navigate | q to quit", rs.page+1, rs.maxPages)) return header + "\n" + content + "\n\n" + footer } func (rs *ReportScreen) renderOverviewPage() string { var s strings.Builder s.WriteString(lipgloss.NewStyle(). Foreground(lipgloss.Color("14")). Bold(true). Render("Your 2026 Overview") + "\n\n") s.WriteString(fmt.Sprintf("Username: %s\n", rs.stats.Username)) s.WriteString(fmt.Sprintf("Total Commits: %d\n", rs.stats.TotalCommits)) s.WriteString(fmt.Sprintf("Total Repositories: %d\n", rs.stats.TotalRepositories)) s.WriteString(fmt.Sprintf("Average Commits/Day: %.2f\n", rs.stats.AverageCommitsPerDay)) s.WriteString(fmt.Sprintf("Most Active Day: %s\n", rs.stats.MostActiveDay)) s.WriteString(fmt.Sprintf("Most Active Month: %s\n", rs.stats.MostActiveMonth)) s.WriteString("\nTop Repositories:\n") for i, repo := range rs.stats.TopRepositories { if i >= 5 { break } s.WriteString(fmt.Sprintf(" %d. %s\n", i+1, repo.Name)) } return s.String() } func (rs *ReportScreen) renderLanguagesPage() string { var s strings.Builder s.WriteString(lipgloss.NewStyle(). Foreground(lipgloss.Color("14")). Bold(true). Render("Languages You Love") + "\n\n") if len(rs.stats.Languages) == 0 { s.WriteString("No language data available.\n") return s.String() } maxLength := 30 maxCount := 0 for _, lang := range rs.stats.Languages { if lang.Count > maxCount { maxCount = lang.Count } } for i, lang := range rs.stats.Languages { if i >= 10 { break } barLength := int(float64(lang.Count) / float64(maxCount) * float64(maxLength)) bar := strings.Repeat("█", barLength) + strings.Repeat("░", maxLength-barLength) s.WriteString(fmt.Sprintf("%-12s %s %d (%.1f%%)\n", lang.Language, bar, lang.Count, lang.Percent)) } return s.String() } func (rs *ReportScreen) renderActivityPage() string { var s strings.Builder s.WriteString(lipgloss.NewStyle(). Foreground(lipgloss.Color("14")). Bold(true). Render("Your Activity Patterns") + "\n\n") s.WriteString("Most Active Days of the Week:\n") if len(rs.stats.CommitsByWeekday) > 0 { maxCount := rs.stats.CommitsByWeekday[0].Count for i, day := range rs.stats.CommitsByWeekday { if i >= 7 { break } barLength := int(float64(day.Count) / float64(maxCount) * 20) bar := strings.Repeat("█", barLength) + strings.Repeat("░", 20-barLength) s.WriteString(fmt.Sprintf("%-12s %s %d\n", day.Day, bar, day.Count)) } } else { s.WriteString("No weekday data available.\n") } s.WriteString("\nMost Active Months:\n") if len(rs.stats.CommitsByMonth) > 0 { maxCount := rs.stats.CommitsByMonth[0].Count for i, month := range rs.stats.CommitsByMonth { if i >= 12 { break } barLength := int(float64(month.Count) / float64(maxCount) * 20) bar := strings.Repeat("█", barLength) + strings.Repeat("░", 20-barLength) monthStr := month.Date.Format("Jan 2006") s.WriteString(fmt.Sprintf("%-12s %s %d\n", monthStr, bar, month.Count)) } } else { s.WriteString("No monthly data available.\n") } return s.String() }