package services import ( "fmt" "sort" ) // Podcast episode ordering — THE single source of order truth (§0 item // 3 + item 5a, 2026-10-30). Every surface (sequence JSON, reader chain // context, SSR page list, download queueing) routes through // OrderEpisodes; per-surface sort blocks were the drift risk (three // near-identical comparators existed by hand). // PodcastEpisodeOrder values: // - native: feed position ASC, date ASC tiebreak (fetch-maintained order) // - reverse: feed position DESC, date DESC tiebreak ("Reverse feed // order" — no pub dates involved, so Scribl's messy/all-equal // migrated podiobooks dates can't break it) // - oldest / newest: chronological by pub date string (2006-01-02 or // anything lexical-comparable), position as same-date tiebreak func ValidPodcastEpisodeOrder(o string) bool { return o == "native" || o == "reverse" || o == "oldest" || o == "newest" } // OrderEpisodes sorts eps in place into the given order. Accessor // functions supply each episode's feed position and pub date key // (LEN lexical-comparable, e.g. RFC3339 or 2006-01-02). func OrderEpisodes[T any](eps []T, order string, pos func(T) int32, date func(T) string) { switch order { case "reverse": sort.SliceStable(eps, func(i, j int) bool { if pos(eps[i]) != pos(eps[j]) { return pos(eps[i]) > pos(eps[j]) } return date(eps[i]) > date(eps[j]) }) case "oldest", "newest": sort.SliceStable(eps, func(i, j int) bool { ka := date(eps[i]) + episodePosKey(pos(eps[i])) kb := date(eps[j]) + episodePosKey(pos(eps[j])) if order == "newest" { return ka > kb } return ka < kb }) default: // native sort.SliceStable(eps, func(i, j int) bool { if pos(eps[i]) != pos(eps[j]) { return pos(eps[i]) < pos(eps[j]) } return date(eps[i]) < date(eps[j]) }) } } // episodePosKey: zero-padded position so lexical date keys tiebreak // numerically. func episodePosKey(pos int32) string { return fmt.Sprintf("%06d", pos) }