package recognize import ( "sort" "strconv" "strings" ) // SeasonSummary собирает верхнеуровневую сводку сезонов сериальной раздачи по // эпизодным файлам плана. Сезон задан на файле (мультисезонные паки), поэтому // сводим множество различных сезонов; Season nil/0 — спецвыпуски. Примеры: // «Сезон 2», «Сезоны 1–3», «Сезоны 1, 3–4», «Спецвыпуски», «Сезоны 1–2, спецвыпуски». // Пустая строка — эпизодных файлов нет (нечего сводить). Общий хелпер для веб-UI // (сводка сезонов на карточке/ревью) и для ярлыка display_name (naming). func SeasonSummary(plan Plan) string { seen := map[int]bool{} specials := false for _, f := range plan.Files { if f.Role != RoleEpisode { continue } n := 0 if f.Season != nil { n = *f.Season } if n <= 0 { specials = true continue } seen[n] = true } nums := make([]int, 0, len(seen)) for n := range seen { nums = append(nums, n) } sort.Ints(nums) var parts []string switch { case len(nums) == 1: parts = append(parts, "Сезон "+strconv.Itoa(nums[0])) case len(nums) > 1: parts = append(parts, "Сезоны "+seasonRanges(nums)) } if specials { if len(parts) == 0 { parts = append(parts, "Спецвыпуски") } else { parts = append(parts, "спецвыпуски") } } return strings.Join(parts, ", ") } // seasonRanges схлопывает возрастающие номера сезонов в диапазоны: // [1,2,3] → «1–3», [1,3,4] → «1, 3–4». func seasonRanges(nums []int) string { var out []string for i := 0; i < len(nums); { j := i for j+1 < len(nums) && nums[j+1] == nums[j]+1 { j++ } if j == i { out = append(out, strconv.Itoa(nums[i])) } else { out = append(out, strconv.Itoa(nums[i])+"–"+strconv.Itoa(nums[j])) } i = j + 1 } return strings.Join(out, ", ") }