| 147 | // swing the due day by the parity of an arbitrary lifetime count — deleting or |
| 148 | // rest-toggling a day with 50 logged workouts must not make "today's workout" |
| 149 | // depend on 50 mod cycle length. Dropped rows are ignored entirely. No qualifying |
| 150 | // workout at all → the first workout day is due. A program with no workout days |
| 151 | // (every day is rest, or no days yet) has nothing to be due → 0. |
| 152 | func (s *ProgramStore) currentDayIndex(uid int64, programs []models.Program) (map[int64]int, error) { |
| 153 | result := make(map[int64]int, len(programs)) |
| 154 | workoutIdx := make(map[int64][]int, len(programs)) |
| 155 | progByID := make(map[int64]models.Program, len(programs)) |
| 156 | var ids []int64 |
| 157 | for _, p := range programs { |
| 158 | progByID[p.ID] = p |
| 159 | var idx []int |
| 160 | for i, d := range p.Days { |
| 161 | if !d.IsRestDay { |
| 162 | idx = append(idx, i) |
| 163 | } |
| 164 | } |
| 165 | if len(idx) == 0 { |
| 166 | result[p.ID] = 0 // no workout days at all — nothing to be due |
| 167 | continue |
| 168 | } |
| 169 | workoutIdx[p.ID] = idx |
| 170 | ids = append(ids, p.ID) |
| 171 | } |
| 172 | if len(ids) == 0 { |
| 173 | return result, nil |
| 174 | } |
| 175 | |
| 176 | idSelects := make([]string, len(ids)) |
| 177 | inPlaceholders := make([]string, len(ids)) |
| 178 | for i := range ids { |
| 179 | idSelects[i] = "SELECT ? AS program_id" |