package valueobjects import ( "fmt" "knowfoolery/backend/shared/types" "knowfoolery/backend/shared/errors" ) // UserPreferences represents a user's preferences and settings type UserPreferences struct { // Game preferences preferredDifficulty types.DifficultyLevel preferredThemes []types.ThemeID enableHints bool enableTimer bool enableSound bool enableNotifications bool // Display preferences theme string // "light", "dark", "auto" language string // ISO 639-1 language code timezone string // IANA timezone // Privacy preferences showProfile bool showStatistics bool showLeaderboard bool allowFriendRequests bool // Notification preferences emailNotifications *EmailNotificationSettings pushNotifications *PushNotificationSettings // Accessibility preferences accessibility *AccessibilitySettings // Update tracking updatedAt types.Timestamp } // EmailNotificationSettings represents email notification preferences type EmailNotificationSettings struct { Enabled bool `json:"enabled"` GameInvitations bool `json:"game_invitations"` LeaderboardUpdates bool `json:"leaderboard_updates"` WeeklyDigest bool `json:"weekly_digest"` SecurityAlerts bool `json:"security_alerts"` ProductUpdates bool `json:"product_updates"` } // PushNotificationSettings represents push notification preferences type PushNotificationSettings struct { Enabled bool `json:"enabled"` GameInvitations bool `json:"game_invitations"` TurnReminders bool `json:"turn_reminders"` LeaderboardUpdates bool `json:"leaderboard_updates"` DailyReminders bool `json:"daily_reminders"` } // AccessibilitySettings represents accessibility preferences type AccessibilitySettings struct { HighContrast bool `json:"high_contrast"` LargeText bool `json:"large_text"` ReducedMotion bool `json:"reduced_motion"` ScreenReaderSupport bool `json:"screen_reader_support"` KeyboardNavigation bool `json:"keyboard_navigation"` FontSize float64 `json:"font_size"` // 0.8 - 2.0 multiplier } // NewDefaultUserPreferences creates default user preferences func NewDefaultUserPreferences() *UserPreferences { return &UserPreferences{ preferredDifficulty: types.DifficultyMedium, preferredThemes: []types.ThemeID{}, enableHints: true, enableTimer: true, enableSound: true, enableNotifications: true, theme: "auto", language: "en", timezone: "UTC", showProfile: true, showStatistics: true, showLeaderboard: true, allowFriendRequests: true, emailNotifications: &EmailNotificationSettings{ Enabled: true, GameInvitations: true, LeaderboardUpdates: false, WeeklyDigest: true, SecurityAlerts: true, ProductUpdates: false, }, pushNotifications: &PushNotificationSettings{ Enabled: true, GameInvitations: true, TurnReminders: true, LeaderboardUpdates: false, DailyReminders: false, }, accessibility: &AccessibilitySettings{ HighContrast: false, LargeText: false, ReducedMotion: false, ScreenReaderSupport: false, KeyboardNavigation: false, FontSize: 1.0, }, updatedAt: types.NewTimestamp(), } } // NewUserPreferences creates user preferences with specified values func NewUserPreferences( preferredDifficulty types.DifficultyLevel, preferredThemes []types.ThemeID, enableHints bool, enableTimer bool, ) (*UserPreferences, error) { preferences := NewDefaultUserPreferences() // Validate and set preferred difficulty if err := preferences.SetPreferredDifficulty(preferredDifficulty); err != nil { return nil, err } // Set preferred themes if err := preferences.SetPreferredThemes(preferredThemes); err != nil { return nil, err } preferences.enableHints = enableHints preferences.enableTimer = enableTimer return preferences, nil } // Getters // PreferredDifficulty returns the preferred difficulty level func (p *UserPreferences) PreferredDifficulty() types.DifficultyLevel { return p.preferredDifficulty } // PreferredThemes returns the preferred themes func (p *UserPreferences) PreferredThemes() []types.ThemeID { return append([]types.ThemeID(nil), p.preferredThemes...) // Return copy } // EnableHints returns true if hints are enabled func (p *UserPreferences) EnableHints() bool { return p.enableHints } // EnableTimer returns true if timer is enabled func (p *UserPreferences) EnableTimer() bool { return p.enableTimer } // EnableSound returns true if sound is enabled func (p *UserPreferences) EnableSound() bool { return p.enableSound } // EnableNotifications returns true if notifications are enabled func (p *UserPreferences) EnableNotifications() bool { return p.enableNotifications } // Theme returns the display theme func (p *UserPreferences) Theme() string { return p.theme } // Language returns the language code func (p *UserPreferences) Language() string { return p.language } // Timezone returns the timezone func (p *UserPreferences) Timezone() string { return p.timezone } // ShowProfile returns true if profile should be shown func (p *UserPreferences) ShowProfile() bool { return p.showProfile } // ShowStatistics returns true if statistics should be shown func (p *UserPreferences) ShowStatistics() bool { return p.showStatistics } // ShowLeaderboard returns true if leaderboard should be shown func (p *UserPreferences) ShowLeaderboard() bool { return p.showLeaderboard } // AllowFriendRequests returns true if friend requests are allowed func (p *UserPreferences) AllowFriendRequests() bool { return p.allowFriendRequests } // EmailNotifications returns email notification settings func (p *UserPreferences) EmailNotifications() *EmailNotificationSettings { if p.emailNotifications == nil { return nil } // Return copy return &EmailNotificationSettings{ Enabled: p.emailNotifications.Enabled, GameInvitations: p.emailNotifications.GameInvitations, LeaderboardUpdates: p.emailNotifications.LeaderboardUpdates, WeeklyDigest: p.emailNotifications.WeeklyDigest, SecurityAlerts: p.emailNotifications.SecurityAlerts, ProductUpdates: p.emailNotifications.ProductUpdates, } } // PushNotifications returns push notification settings func (p *UserPreferences) PushNotifications() *PushNotificationSettings { if p.pushNotifications == nil { return nil } // Return copy return &PushNotificationSettings{ Enabled: p.pushNotifications.Enabled, GameInvitations: p.pushNotifications.GameInvitations, TurnReminders: p.pushNotifications.TurnReminders, LeaderboardUpdates: p.pushNotifications.LeaderboardUpdates, DailyReminders: p.pushNotifications.DailyReminders, } } // Accessibility returns accessibility settings func (p *UserPreferences) Accessibility() *AccessibilitySettings { if p.accessibility == nil { return nil } // Return copy return &AccessibilitySettings{ HighContrast: p.accessibility.HighContrast, LargeText: p.accessibility.LargeText, ReducedMotion: p.accessibility.ReducedMotion, ScreenReaderSupport: p.accessibility.ScreenReaderSupport, KeyboardNavigation: p.accessibility.KeyboardNavigation, FontSize: p.accessibility.FontSize, } } // UpdatedAt returns the last update timestamp func (p *UserPreferences) UpdatedAt() types.Timestamp { return p.updatedAt } // Business methods // SetPreferredDifficulty sets the preferred difficulty level func (p *UserPreferences) SetPreferredDifficulty(difficulty types.DifficultyLevel) error { // Validate difficulty level validDifficulties := []types.DifficultyLevel{ types.DifficultyEasy, types.DifficultyMedium, types.DifficultyHard, types.DifficultyExpert, types.DifficultyImpossible, } isValid := false for _, valid := range validDifficulties { if difficulty == valid { isValid = true break } } if !isValid { return errors.ErrValidationFailed("preferred_difficulty", "invalid difficulty level") } p.preferredDifficulty = difficulty p.markUpdated() return nil } // SetPreferredThemes sets the preferred themes func (p *UserPreferences) SetPreferredThemes(themes []types.ThemeID) error { if len(themes) > types.MaxPreferredThemes { return errors.ErrValidationFailed("preferred_themes", fmt.Sprintf("too many preferred themes (max %d)", types.MaxPreferredThemes)) } // Validate theme IDs for i, themeID := range themes { if themeID.IsEmpty() { return errors.ErrValidationFailed("preferred_themes", fmt.Sprintf("theme ID at index %d is empty", i)) } } // Remove duplicates uniqueThemes := make([]types.ThemeID, 0, len(themes)) themeSet := make(map[types.ThemeID]bool) for _, themeID := range themes { if !themeSet[themeID] { uniqueThemes = append(uniqueThemes, themeID) themeSet[themeID] = true } } p.preferredThemes = uniqueThemes p.markUpdated() return nil } // AddPreferredTheme adds a theme to preferred themes func (p *UserPreferences) AddPreferredTheme(themeID types.ThemeID) error { if themeID.IsEmpty() { return errors.ErrValidationFailed("theme_id", "theme ID cannot be empty") } // Check if already in preferred themes for _, existing := range p.preferredThemes { if existing == themeID { return nil // Already exists } } if len(p.preferredThemes) >= types.MaxPreferredThemes { return errors.ErrValidationFailed("preferred_themes", fmt.Sprintf("too many preferred themes (max %d)", types.MaxPreferredThemes)) } p.preferredThemes = append(p.preferredThemes, themeID) p.markUpdated() return nil } // RemovePreferredTheme removes a theme from preferred themes func (p *UserPreferences) RemovePreferredTheme(themeID types.ThemeID) error { for i, existing := range p.preferredThemes { if existing == themeID { p.preferredThemes = append(p.preferredThemes[:i], p.preferredThemes[i+1:]...) p.markUpdated() return nil } } return errors.ErrNotFound("preferred_theme", string(themeID)) } // SetGamePreferences sets game-related preferences func (p *UserPreferences) SetGamePreferences( enableHints bool, enableTimer bool, enableSound bool, enableNotifications bool, ) { p.enableHints = enableHints p.enableTimer = enableTimer p.enableSound = enableSound p.enableNotifications = enableNotifications p.markUpdated() } // SetDisplayPreferences sets display-related preferences func (p *UserPreferences) SetDisplayPreferences( theme string, language string, timezone string, ) error { if err := p.SetTheme(theme); err != nil { return err } if err := p.SetLanguage(language); err != nil { return err } if err := p.SetTimezone(timezone); err != nil { return err } return nil } // SetTheme sets the display theme func (p *UserPreferences) SetTheme(theme string) error { validThemes := []string{"light", "dark", "auto"} isValid := false for _, valid := range validThemes { if theme == valid { isValid = true break } } if !isValid { return errors.ErrValidationFailed("theme", "theme must be 'light', 'dark', or 'auto'") } p.theme = theme p.markUpdated() return nil } // SetLanguage sets the language func (p *UserPreferences) SetLanguage(language string) error { if len(language) != 2 { return errors.ErrValidationFailed("language", "language must be a 2-character ISO 639-1 code") } // Could validate against known language codes p.language = language p.markUpdated() return nil } // SetTimezone sets the timezone func (p *UserPreferences) SetTimezone(timezone string) error { // Basic validation - could be more comprehensive if timezone == "" { timezone = "UTC" } p.timezone = timezone p.markUpdated() return nil } // SetPrivacyPreferences sets privacy-related preferences func (p *UserPreferences) SetPrivacyPreferences( showProfile bool, showStatistics bool, showLeaderboard bool, allowFriendRequests bool, ) { p.showProfile = showProfile p.showStatistics = showStatistics p.showLeaderboard = showLeaderboard p.allowFriendRequests = allowFriendRequests p.markUpdated() } // UpdateEmailNotifications updates email notification settings func (p *UserPreferences) UpdateEmailNotifications(settings *EmailNotificationSettings) error { if settings == nil { return errors.ErrValidationFailed("email_notifications", "email notification settings cannot be nil") } p.emailNotifications = &EmailNotificationSettings{ Enabled: settings.Enabled, GameInvitations: settings.GameInvitations, LeaderboardUpdates: settings.LeaderboardUpdates, WeeklyDigest: settings.WeeklyDigest, SecurityAlerts: settings.SecurityAlerts, ProductUpdates: settings.ProductUpdates, } p.markUpdated() return nil } // UpdatePushNotifications updates push notification settings func (p *UserPreferences) UpdatePushNotifications(settings *PushNotificationSettings) error { if settings == nil { return errors.ErrValidationFailed("push_notifications", "push notification settings cannot be nil") } p.pushNotifications = &PushNotificationSettings{ Enabled: settings.Enabled, GameInvitations: settings.GameInvitations, TurnReminders: settings.TurnReminders, LeaderboardUpdates: settings.LeaderboardUpdates, DailyReminders: settings.DailyReminders, } p.markUpdated() return nil } // UpdateAccessibility updates accessibility settings func (p *UserPreferences) UpdateAccessibility(settings *AccessibilitySettings) error { if settings == nil { return errors.ErrValidationFailed("accessibility", "accessibility settings cannot be nil") } // Validate font size if settings.FontSize < 0.5 || settings.FontSize > 3.0 { return errors.ErrValidationFailed("font_size", "font size must be between 0.5 and 3.0") } p.accessibility = &AccessibilitySettings{ HighContrast: settings.HighContrast, LargeText: settings.LargeText, ReducedMotion: settings.ReducedMotion, ScreenReaderSupport: settings.ScreenReaderSupport, KeyboardNavigation: settings.KeyboardNavigation, FontSize: settings.FontSize, } p.markUpdated() return nil } // Query methods // HasPreferredTheme checks if a theme is in preferred themes func (p *UserPreferences) HasPreferredTheme(themeID types.ThemeID) bool { for _, preferred := range p.preferredThemes { if preferred == themeID { return true } } return false } // IsAccessibilityEnabled returns true if any accessibility features are enabled func (p *UserPreferences) IsAccessibilityEnabled() bool { if p.accessibility == nil { return false } return p.accessibility.HighContrast || p.accessibility.LargeText || p.accessibility.ReducedMotion || p.accessibility.ScreenReaderSupport || p.accessibility.KeyboardNavigation || p.accessibility.FontSize != 1.0 } // AreEmailNotificationsEnabled returns true if email notifications are globally enabled func (p *UserPreferences) AreEmailNotificationsEnabled() bool { return p.emailNotifications != nil && p.emailNotifications.Enabled } // ArePushNotificationsEnabled returns true if push notifications are globally enabled func (p *UserPreferences) ArePushNotificationsEnabled() bool { return p.pushNotifications != nil && p.pushNotifications.Enabled } // Validation // Validate performs comprehensive validation func (p *UserPreferences) Validate() *types.ValidationResult { result := types.NewValidationResult() // Validate preferred difficulty validDifficulties := []types.DifficultyLevel{ types.DifficultyEasy, types.DifficultyMedium, types.DifficultyHard, types.DifficultyExpert, types.DifficultyImpossible, } isValidDifficulty := false for _, valid := range validDifficulties { if p.preferredDifficulty == valid { isValidDifficulty = true break } } if !isValidDifficulty { result.AddError("invalid preferred difficulty") } // Validate preferred themes count if len(p.preferredThemes) > types.MaxPreferredThemes { result.AddErrorf("too many preferred themes (max %d)", types.MaxPreferredThemes) } // Validate theme IDs for i, themeID := range p.preferredThemes { if themeID.IsEmpty() { result.AddErrorf("preferred theme at index %d is empty", i) } } // Validate theme validThemes := []string{"light", "dark", "auto"} isValidTheme := false for _, valid := range validThemes { if p.theme == valid { isValidTheme = true break } } if !isValidTheme { result.AddError("invalid theme") } // Validate language if len(p.language) != 2 { result.AddError("invalid language code") } // Validate accessibility settings if p.accessibility != nil { if p.accessibility.FontSize < 0.5 || p.accessibility.FontSize > 3.0 { result.AddError("font size must be between 0.5 and 3.0") } } return result } // Helper methods // markUpdated updates the timestamp func (p *UserPreferences) markUpdated() { p.updatedAt = types.NewTimestamp() } // ToSnapshot creates a read-only snapshot func (p *UserPreferences) ToSnapshot() *UserPreferencesSnapshot { return &UserPreferencesSnapshot{ PreferredDifficulty: p.preferredDifficulty, PreferredThemes: append([]types.ThemeID(nil), p.preferredThemes...), EnableHints: p.enableHints, EnableTimer: p.enableTimer, EnableSound: p.enableSound, EnableNotifications: p.enableNotifications, Theme: p.theme, Language: p.language, Timezone: p.timezone, ShowProfile: p.showProfile, ShowStatistics: p.showStatistics, ShowLeaderboard: p.showLeaderboard, AllowFriendRequests: p.allowFriendRequests, EmailNotifications: p.EmailNotifications(), PushNotifications: p.PushNotifications(), Accessibility: p.Accessibility(), UpdatedAt: p.updatedAt, } } // UserPreferencesSnapshot represents a read-only view of user preferences type UserPreferencesSnapshot struct { PreferredDifficulty types.DifficultyLevel `json:"preferred_difficulty"` PreferredThemes []types.ThemeID `json:"preferred_themes"` EnableHints bool `json:"enable_hints"` EnableTimer bool `json:"enable_timer"` EnableSound bool `json:"enable_sound"` EnableNotifications bool `json:"enable_notifications"` Theme string `json:"theme"` Language string `json:"language"` Timezone string `json:"timezone"` ShowProfile bool `json:"show_profile"` ShowStatistics bool `json:"show_statistics"` ShowLeaderboard bool `json:"show_leaderboard"` AllowFriendRequests bool `json:"allow_friend_requests"` EmailNotifications *EmailNotificationSettings `json:"email_notifications"` PushNotifications *PushNotificationSettings `json:"push_notifications"` Accessibility *AccessibilitySettings `json:"accessibility"` UpdatedAt types.Timestamp `json:"updated_at"` } // HasPreferredTheme checks if a theme is preferred in the snapshot func (ps *UserPreferencesSnapshot) HasPreferredTheme(themeID types.ThemeID) bool { for _, preferred := range ps.PreferredThemes { if preferred == themeID { return true } } return false }