package ui import ( "fmt" "strings" "termdoku/internal/database" "termdoku/internal/stats" "termdoku/internal/theme" "github.com/charmbracelet/lipgloss" ) // viewProfile displays a simple profile landing page with navigation options. func (a App) viewProfile() string { adaptiveColors := theme.NewAdaptiveColors(a.th) gradientColors := adaptiveColors.GetGradientColors() profileGrad := gradientColors["banner"] title := gradientText("User Profile", profileGrad[0], profileGrad[1]) var content strings.Builder content.WriteString(title + "\n\n") content.WriteString(a.renderProfileSummary()) content.WriteString("\n\n") instructionStyle := lipgloss.NewStyle(). Foreground(lipgloss.Color(a.th.Palette.Accent)). Bold(true) content.WriteString(instructionStyle.Render("Navigation:") + "\n") content.WriteString(a.styles.Status.Render(" Press 's' to view detailed profile data (Stats, Achievements, Leaderboard)")) content.WriteString("\n") content.WriteString(a.styles.Status.Render(" Press 'd' to view database info")) content.WriteString("\n") content.WriteString(a.styles.Status.Render(" Press 'm' or Enter to return to menu")) panel := a.styles.Panel.Render(content.String()) if a.width > 0 && a.height > 0 { return a.styles.App.Render(lipgloss.Place(a.width, a.height, lipgloss.Center, lipgloss.Center, panel)) } return a.styles.App.Render(panel) } // viewProfileSubmenu displays a dropdown menu for selecting profile data type. func (a App) viewProfileSubmenu() string { adaptiveColors := theme.NewAdaptiveColors(a.th) gradientColors := adaptiveColors.GetGradientColors() profileGrad := gradientColors["banner"] title := gradientText(">> Select Profile Data", profileGrad[0], profileGrad[1]) var content strings.Builder content.WriteString(title + "\n\n") var items []string accentColors := adaptiveColors.GetAccentColors() selectedStyle := lipgloss.NewStyle(). Foreground(lipgloss.Color(accentColors["selected"])). Bold(true) for i, name := range a.profileMenuItems { if i == a.profileSelectedIdx { prefix := "▶ " label := prefix + name items = append(items, selectedStyle.Render(label)) } else { prefix := " " label := prefix + name items = append(items, a.styles.MenuItem.Render(label)) } } menuContent := strings.Join(items, "\n") menuStyle := lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). BorderForeground(lipgloss.Color(profileGrad[0])). Padding(1, 2). MarginTop(1). MarginBottom(1) content.WriteString(menuStyle.Render(menuContent)) content.WriteString("\n\n") content.WriteString(a.styles.Status.Render("Use ↑/↓ or k/j to navigate, Enter to select")) content.WriteString("\n") content.WriteString(a.styles.Status.Render("Press 'm' or Esc to return to profile")) panel := a.styles.Panel.Render(content.String()) if a.width > 0 && a.height > 0 { return a.styles.App.Render(lipgloss.Place(a.width, a.height, lipgloss.Center, lipgloss.Center, panel)) } return a.styles.App.Render(panel) } // renderProfileSummary renders the user summary card. func (a App) renderProfileSummary() string { var sb strings.Builder totalGames := a.stats.TotalGames completed := a.stats.CompletedGames winRate := 0.0 if totalGames > 0 { winRate = float64(completed) / float64(totalGames) * 100 } rank := a.calculateRank(completed) rankEmoji := a.getRankEmoji(rank) summaryStyle := lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). BorderForeground(lipgloss.Color(a.th.Palette.Accent)). Padding(1, 2). MarginTop(1) summaryContent := fmt.Sprintf( "%s Rank: %s"+ "\n>> Total Games: %d"+ "\n>> Completed: %d"+ "\n>> Win Rate: %.1f%%"+ "\n>> Current Streak: %d days"+ "\n>> Best Streak: %d days", rankEmoji, rank, totalGames, completed, winRate, a.stats.CurrentStreak, a.stats.BestStreak, ) sb.WriteString(summaryStyle.Render(summaryContent)) return sb.String() } // calculateRank determines user rank based on completed games. func (a App) calculateRank(completed int) string { switch { case completed >= 1000: return "Grandmaster" case completed >= 500: return "Master" case completed >= 250: return "Expert" case completed >= 100: return "Advanced" case completed >= 50: return "Intermediate" case completed >= 25: return "Apprentice" case completed >= 10: return "Novice" case completed >= 1: return "Beginner" default: return "Newcomer" } } // getRankEmoji returns an emoji for the rank. func (a App) getRankEmoji(rank string) string { switch rank { case "Grandmaster": return "👑" case "Master": return "🎖️" case "Expert": return "🏅" case "Advanced": return "⭐" case "Intermediate": return "🌟" case "Apprentice": return "✨" case "Novice": return "🔰" case "Beginner": return "🌱" default: return "👤" } } // viewDatabaseInfo displays detailed database information. func (a App) viewDatabaseInfo() string { adaptiveColors := theme.NewAdaptiveColors(a.th) gradientColors := adaptiveColors.GetGradientColors() dbGrad := gradientColors["banner"] title := gradientText("Database Info", dbGrad[0], dbGrad[1]) var content strings.Builder content.WriteString(title + "\n\n") db, err := database.Open() if err != nil { content.WriteString(a.styles.Status.Render(fmt.Sprintf("Error opening database: %v\n", err))) } else { defer db.Close() dbStats, err := db.GetStats() if err != nil { content.WriteString(a.styles.Status.Render(fmt.Sprintf("Error reading stats: %v\n", err))) } else { statsStyle := lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). BorderForeground(lipgloss.Color(a.th.Palette.Accent)). Padding(1, 2) statsContent := fmt.Sprintf( "Total Games: %d"+ "\n>> Completed Games: %d"+ "\n>> Current Streak: %d"+ "\n>> Best Streak: %d"+ "\n>> Hints Used: %d"+ "\n>> Last Played: %s", dbStats.TotalGames, dbStats.CompletedGames, dbStats.CurrentStreak, dbStats.BestStreak, dbStats.HintsUsed, dbStats.LastPlayedDate, ) content.WriteString(statsStyle.Render(statsContent)) } content.WriteString("\n\n") headerStyle := lipgloss.NewStyle(). Bold(true). Foreground(lipgloss.Color(a.th.Palette.Accent)). Render("Achievements") content.WriteString(headerStyle + "\n") achievements, err := db.GetAchievements() if err != nil { content.WriteString(a.styles.Status.Render(fmt.Sprintf("Error reading achievements: %v\n", err))) } else { unlockedCount := 0 for _, ach := range achievements { if ach.Unlocked { unlockedCount++ } } content.WriteString(a.styles.Status.Render(fmt.Sprintf("Unlocked: %d/%d\n", unlockedCount, len(achievements)))) } content.WriteString("\n") headerStyle = lipgloss.NewStyle(). Bold(true). Foreground(lipgloss.Color(a.th.Palette.Accent)). Render("Recent Games") content.WriteString(headerStyle + "\n") recentGames, err := db.GetRecentGames(5) if err != nil { content.WriteString(a.styles.Status.Render(fmt.Sprintf("Error reading games: %v\n", err))) } else { if len(recentGames) == 0 { content.WriteString(a.styles.Status.Render("No games in database\n")) } else { for _, game := range recentGames { statusIcon := "✅" if !game.Completed { statusIcon = "❌" } timeStr := stats.FormatTime(game.TimeSeconds) line := fmt.Sprintf("%s %s - %s", statusIcon, game.Difficulty, timeStr) if game.IsDaily { line += " 📅" } content.WriteString(a.styles.Status.Render(line + "\n")) } } } } content.WriteString("\n" + a.styles.Status.Render("Press 'm' or Enter to return to menu")) panel := a.styles.Panel.Render(content.String()) if a.width > 0 && a.height > 0 { return a.styles.App.Render(lipgloss.Place(a.width, a.height, lipgloss.Center, lipgloss.Center, panel)) } return a.styles.App.Render(panel) }