package bot import ( "context" "fmt" "strconv" "strings" "time" "github.com/go-telegram/bot/models" "worktimeBot/internal/db" ) // handleSetReportTime parses a HH:MM argument and updates the user's report time. func (h *Handler) handleSetReportTime(ctx context.Context, msg *models.Message) { args := "" if len(msg.Text) > 16 { // "/setreporttime " is 15 chars args = strings.TrimSpace(msg.Text[15:]) } if args == "" { user, err := h.getOrCreateUser(msg.Chat.ID) if err != nil { h.sendText(ctx, msg.Chat.ID, "Error loading profile") return } h.sendText(ctx, msg.Chat.ID, fmt.Sprintf("Current report time: %s\nUsage: /setreporttime HH:MM (24-hour)", user.ReportTime)) return } if len(args) != 5 || args[2] != ':' { h.sendText(ctx, msg.Chat.ID, "Invalid format. Usage: /setreporttime HH:MM (24-hour)") return } hours, err1 := strconv.Atoi(args[:2]) mins, err2 := strconv.Atoi(args[3:]) if err1 != nil || err2 != nil || hours < 0 || hours > 23 || mins < 0 || mins > 59 { h.sendText(ctx, msg.Chat.ID, "Invalid time. Usage: /setreporttime HH:MM (24-hour)") return } user, err := h.getOrCreateUser(msg.Chat.ID) if err != nil { h.sendText(ctx, msg.Chat.ID, "Error updating report time") return } user.ReportTime = args if err := h.DB.UpdateUser(user); err != nil { h.sendText(ctx, msg.Chat.ID, "Error updating report time") return } h.sendText(ctx, msg.Chat.ID, fmt.Sprintf("Report time set to %s", args)) } // handleAccentMsg shows the accent color picker. func (h *Handler) handleAccentMsg(ctx context.Context, msg *models.Message) { user, err := h.getOrCreateUser(msg.Chat.ID) if err != nil { h.sendText(ctx, msg.Chat.ID, "Error loading profile") return } text, kb := h.buildAccentKeyboard(user) h.sendWithKB(ctx, msg.Chat.ID, text, kb) } // handleSetTimezone validates and updates the user's IANA timezone. func (h *Handler) handleSetTimezone(ctx context.Context, msg *models.Message) { args := "" if len(msg.Text) > 13 { // "/settimezone " is 12 chars args = strings.TrimSpace(msg.Text[13:]) } if args == "" { user, err := h.getOrCreateUser(msg.Chat.ID) if err != nil { h.sendText(ctx, msg.Chat.ID, "Error loading profile") return } h.sendText(ctx, msg.Chat.ID, fmt.Sprintf("Current timezone: %s\nUsage: /settimezone \nExamples: Asia/Tehran, Europe/London, America/New_York", user.Timezone)) return } loc, err := time.LoadLocation(args) if err != nil { h.sendText(ctx, msg.Chat.ID, fmt.Sprintf("Invalid timezone: %s\nUse IANA format, e.g. Asia/Tehran, Europe/London", args)) return } user, err := h.getOrCreateUser(msg.Chat.ID) if err != nil { h.sendText(ctx, msg.Chat.ID, "Error loading profile") return } user.Timezone = args if err := h.DB.UpdateUser(user); err != nil { h.sendText(ctx, msg.Chat.ID, "Error saving timezone") return } now := time.Now().In(loc) h.sendText(ctx, msg.Chat.ID, fmt.Sprintf("Timezone set to %s (current time: %s)", args, now.Format("15:04"))) } // getTodayBreakThreshold returns today's break threshold in seconds for the given chat. func (h *Handler) getTodayBreakThreshold(chatID int64) int64 { user, err := h.getOrCreateUser(chatID) if err != nil { return 300 } now := time.Now() date := now.Format("2006-01-02") day, err := h.DB.GetDay(user.ID, date) if err != nil { return 300 } return day.MinBreakThreshold } // handleSetBreakMsg sets the minimum break threshold for today. func (h *Handler) handleSetBreakMsg(ctx context.Context, msg *models.Message) { args := strings.TrimSpace(strings.TrimPrefix(msg.Text, "/setbreak")) if args == "" { _, day, err := h.getUserToday(msg.Chat.ID) if err != nil { h.sendText(ctx, msg.Chat.ID, "Error loading profile") return } h.sendText(ctx, msg.Chat.ID, fmt.Sprintf("Current break threshold: %dm\nUsage: /setbreak (e.g. /setbreak 15)", day.MinBreakThreshold/60)) return } mins, err := strconv.Atoi(args) if err != nil || mins < 0 || mins > 480 { h.sendText(ctx, msg.Chat.ID, "Invalid value. Must be between 0 and 480 minutes (8 hours).") return } user, err := h.getOrCreateUser(msg.Chat.ID) if err != nil { h.sendText(ctx, msg.Chat.ID, "Error loading profile") return } now := time.Now() date := now.Format("2006-01-02") day, err := h.DB.GetOrCreateDay(user.ID, date) if err != nil { h.sendText(ctx, msg.Chat.ID, "Error updating break threshold") return } day.MinBreakThreshold = int64(mins) * 60 if err := h.DB.UpdateDay(day); err != nil { h.sendText(ctx, msg.Chat.ID, "Error saving break threshold") return } h.sendText(ctx, msg.Chat.ID, fmt.Sprintf("Break threshold set to %d minutes", mins)) } // settingsCallback shows the settings inline menu. func (h *Handler) settingsCallback(ctx context.Context, chatID int64, msgID int, callbackID string) { h.answerCb(ctx, callbackID) user, err := h.getOrCreateUser(chatID) if err != nil { return } bt := h.getTodayBreakThreshold(chatID) text, kb := h.buildSettingsKeyboard(user, bt) h.editText(ctx, chatID, msgID, text, &kb) } // buildSettingsKeyboard returns the settings menu text and inline keyboard. func (h *Handler) buildSettingsKeyboard(user *db.User, breakThreshold int64) (string, models.InlineKeyboardMarkup) { reportStatus := "disabled" if user.ReportEnabled { reportStatus = "enabled" } rows := [][]models.InlineKeyboardButton{ {{Text: fmt.Sprintf("Timezone: %s", user.Timezone), CallbackData: "timezone"}}, {{Text: fmt.Sprintf("Accent: %s", user.ExportAccent), CallbackData: "accent"}}, {{Text: fmt.Sprintf("Calendar: %s", user.Calendar), CallbackData: "caltype"}}, {{Text: fmt.Sprintf("Report: %s", reportStatus), CallbackData: "reporttoggle"}}, {{Text: fmt.Sprintf("Report time: %s", user.ReportTime), CallbackData: "reporttime"}}, {{Text: fmt.Sprintf("Break threshold: %dm", breakThreshold/60), CallbackData: "breakthreshold"}}, {{Text: "History", CallbackData: "history"}}, {{Text: "Back to Menu", CallbackData: "back_menu"}}, } return "Settings:", models.InlineKeyboardMarkup{InlineKeyboard: rows} } // accentCallback shows the accent color picker (inline edit). func (h *Handler) accentCallback(ctx context.Context, chatID int64, msgID int, callbackID string) { h.answerCb(ctx, callbackID) user, err := h.getOrCreateUser(chatID) if err != nil { return } text, kb := h.buildAccentKeyboard(user) h.editText(ctx, chatID, msgID, text, &kb) } // timezoneCallback shows the current timezone and instructions to change it. func (h *Handler) timezoneCallback(ctx context.Context, chatID int64, msgID int, callbackID string) { h.answerCb(ctx, callbackID) user, err := h.getOrCreateUser(chatID) if err != nil { return } text := fmt.Sprintf("Current timezone: %s\n\nUse /settimezone to change it.\nExamples: Asia/Tehran, Europe/London, America/New_York", user.Timezone) kb := models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{ {{Text: "Back to Settings", CallbackData: "back_settings"}}, }, } h.editText(ctx, chatID, msgID, text, &kb) } // buildAccentKeyboard returns the accent color picker keyboard. func (h *Handler) buildAccentKeyboard(user *db.User) (string, models.InlineKeyboardMarkup) { type accentOption struct { name string color string } accents := []accentOption{ {"ocean", "Blue"}, {"beach", "Warm"}, {"rose", "Pink"}, {"catppuccin", "Purple"}, } rows := [][]models.InlineKeyboardButton{} for _, a := range accents { label := fmt.Sprintf("%s (%s)", a.name, a.color) if a.name == user.ExportAccent { label = "> " + label } rows = append(rows, []models.InlineKeyboardButton{ {Text: label, CallbackData: "accent_" + a.name}, }) } rows = append(rows, []models.InlineKeyboardButton{ {Text: "Back to Settings", CallbackData: "back_settings"}, }) return "Select accent color:", models.InlineKeyboardMarkup{InlineKeyboard: rows} } // selectAccent sets the user's export accent color after validation. func (h *Handler) selectAccent(ctx context.Context, chatID int64, msgID int, callbackID, name string) { h.answerCb(ctx, callbackID) user, err := h.getOrCreateUser(chatID) if err != nil { return } valid := map[string]bool{"ocean": true, "beach": true, "rose": true, "catppuccin": true} if !valid[name] { return } user.ExportAccent = name if err := h.DB.UpdateUser(user); err != nil { return } kb := models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{ {{Text: "Back to Settings", CallbackData: "back_settings"}}, }, } h.editText(ctx, chatID, msgID, fmt.Sprintf("Accent set to: %s", name), &kb) } // reportTimeCallback shows the current report time setting. func (h *Handler) reportTimeCallback(ctx context.Context, chatID int64, msgID int, callbackID string) { h.answerCb(ctx, callbackID) user, err := h.getOrCreateUser(chatID) if err != nil { return } text := fmt.Sprintf("Current report time: %s\nUse /setreporttime HH:MM to change it.", user.ReportTime) kb := models.InlineKeyboardMarkup{ InlineKeyboard: [][]models.InlineKeyboardButton{ {{Text: "Back to Settings", CallbackData: "back_settings"}}, }, } h.editText(ctx, chatID, msgID, text, &kb) } // breakThresholdCallback shows the break threshold picker. func (h *Handler) breakThresholdCallback(ctx context.Context, chatID int64, msgID int, callbackID string) { h.answerCb(ctx, callbackID) bt := h.getTodayBreakThreshold(chatID) text, kb := h.buildBreakThresholdKeyboard(bt) h.editText(ctx, chatID, msgID, text, &kb) } // buildBreakThresholdKeyboard returns the break threshold picker. func (h *Handler) buildBreakThresholdKeyboard(current int64) (string, models.InlineKeyboardMarkup) { values := []int{0, 5, 10, 15, 30, 60, 120, 240, 480} labels := map[int]string{ 0: "0 (off)", 5: "5 min", 10: "10 min", 15: "15 min", 30: "30 min", 60: "1h", 120: "2h", 240: "4h", 480: "8h", } rows := [][]models.InlineKeyboardButton{} for _, v := range values { label := labels[v] if int(current/60) == v { label = "> " + label } rows = append(rows, []models.InlineKeyboardButton{ {Text: label, CallbackData: fmt.Sprintf("breakthreshold_%d", v)}, }) } rows = append(rows, []models.InlineKeyboardButton{ {Text: "Back to Settings", CallbackData: "back_settings"}, }) return "Select break threshold:", models.InlineKeyboardMarkup{InlineKeyboard: rows} } // selectBreakThreshold saves the chosen break threshold and returns to settings. func (h *Handler) selectBreakThreshold(ctx context.Context, chatID int64, msgID int, callbackID, valStr string) { h.answerCb(ctx, callbackID) mins, err := strconv.Atoi(valStr) if err != nil || mins < 0 || mins > 480 { return } user, err := h.getOrCreateUser(chatID) if err != nil { return } now := time.Now() date := now.Format("2006-01-02") day, err := h.DB.GetOrCreateDay(user.ID, date) if err != nil { return } day.MinBreakThreshold = int64(mins) * 60 if err := h.DB.UpdateDay(day); err != nil { return } bt := h.getTodayBreakThreshold(chatID) text, kb := h.buildSettingsKeyboard(user, bt) h.editText(ctx, chatID, msgID, text, &kb) } // backToSettings returns to the main settings menu. func (h *Handler) backToSettings(ctx context.Context, chatID int64, msgID int, callbackID string) { h.answerCb(ctx, callbackID) user, err := h.getOrCreateUser(chatID) if err != nil { return } bt := h.getTodayBreakThreshold(chatID) text, kb := h.buildSettingsKeyboard(user, bt) h.editText(ctx, chatID, msgID, text, &kb) } // calTypeCallback shows the calendar type selector. func (h *Handler) calTypeCallback(ctx context.Context, chatID int64, msgID int, callbackID string) { h.answerCb(ctx, callbackID) user, err := h.getOrCreateUser(chatID) if err != nil { return } text, kb := h.buildCalendarKeyboard(user) h.editText(ctx, chatID, msgID, text, &kb) } // buildCalendarKeyboard returns the calendar type selector keyboard. func (h *Handler) buildCalendarKeyboard(user *db.User) (string, models.InlineKeyboardMarkup) { cals := []string{"gregorian", "jalali", "hijri"} calLabels := map[string]string{ "gregorian": "Gregorian", "jalali": "Jalali", "hijri": "Hijri", } rows := [][]models.InlineKeyboardButton{} for _, c := range cals { label := calLabels[c] if c == user.Calendar { label = "> " + label } rows = append(rows, []models.InlineKeyboardButton{ {Text: label, CallbackData: "caltype_" + c}, }) } rows = append(rows, []models.InlineKeyboardButton{ {Text: "Back to Settings", CallbackData: "back_settings"}, }) return "Select calendar type:", models.InlineKeyboardMarkup{InlineKeyboard: rows} } // selectCalendar sets the user's calendar after validation. func (h *Handler) selectCalendar(ctx context.Context, chatID int64, msgID int, callbackID, name string) { h.answerCb(ctx, callbackID) valid := map[string]bool{"gregorian": true, "jalali": true, "hijri": true} if !valid[name] { return } user, err := h.getOrCreateUser(chatID) if err != nil { return } user.Calendar = name if err := h.DB.UpdateUser(user); err != nil { return } bt := h.getTodayBreakThreshold(chatID) text, kb := h.buildSettingsKeyboard(user, bt) h.editText(ctx, chatID, msgID, text, &kb) }