88using System . Threading . Tasks ;
99using Bible . Alarm . Shared . Helpers ;
1010using Bible . Alarm . Shared . Models . Enums ;
11+ using Bible . Alarm . Shared . Models . Media . BiblePublications ;
1112using Bible . Alarm . Shared . Services . Media . Interfaces ;
1213using Microsoft . EntityFrameworkCore ;
1314using Quartz ;
@@ -163,11 +164,6 @@ public static async Task<AlarmSchedule> GetSampleSchedule(bool isNew, IBiblePubl
163164 throw new InvalidOperationException ( "No Bible publications found in database" ) ;
164165 }
165166
166- // Default to English, fallback to first available language with a sectioned publication
167- const string DefaultLanguageCode = "E" ;
168- string ? bibleLanguageCode = null ;
169- string ? biblePublicationCode = null ;
170-
171167 // Priority codes for Bible publications (lower = higher priority)
172168 // Prefer nwt (2013 NWT) over bi12 (1984 NWT)
173169 string [ ] priorityPublicationCodes = [ "nwt" , "bi12" ] ;
@@ -182,12 +178,37 @@ int GetPublicationSortPriority(string code)
182178 }
183179 return priorityPublicationCodes . Length ; // Others come after priority publications
184180 }
181+
182+ // Optimize: Load publications with sections in one call to get both publication info and sections
183+ // For new schedules, prefer "nwt" with English "E", then fallback to first available language with a sectioned publication
184+ const string DefaultLanguageCode = "E" ;
185+ const string PreferredPublicationCode = "nwt" ;
186+ string ? bibleLanguageCode = null ;
187+ string ? biblePublicationCode = null ;
188+ BiblePublication ? selectedBible = null ;
185189
186- // Try English first
190+ // Try English "E" with "nwt" first (for new schedules)
187191 if ( bibleLanguages . ContainsKey ( DefaultLanguageCode ) )
188192 {
189193 var englishPublications = await biblePublicationService . GetByLanguageCodeAsync ( DefaultLanguageCode ) ;
190- if ( englishPublications != null )
194+ if ( englishPublications != null && englishPublications . ContainsKey ( PreferredPublicationCode ) )
195+ {
196+ // Try "nwt" first
197+ if ( PublicationTypeHelper . HasSectionStructure ( PreferredPublicationCode ) )
198+ {
199+ var biblePub = await biblePublicationService . GetByLanguageAndCodeWithSectionsAsync (
200+ DefaultLanguageCode , PreferredPublicationCode ) ;
201+ if ( biblePub != null && biblePub . Sections != null && biblePub . Sections . Count > 0 )
202+ {
203+ bibleLanguageCode = DefaultLanguageCode ;
204+ biblePublicationCode = PreferredPublicationCode ;
205+ selectedBible = biblePub ;
206+ }
207+ }
208+ }
209+
210+ // If "nwt" not available, try other English publications
211+ if ( selectedBible == null && englishPublications != null )
191212 {
192213 // Sort publications by priority: nwt first, then bi12, then others
193214 var sortedPublications = englishPublications
@@ -199,16 +220,23 @@ int GetPublicationSortPriority(string code)
199220 {
200221 if ( PublicationTypeHelper . HasSectionStructure ( pub . Key ) )
201222 {
202- bibleLanguageCode = DefaultLanguageCode ;
203- biblePublicationCode = pub . Key ;
204- break ;
223+ // Load with sections in one call - this includes Category
224+ var biblePub = await biblePublicationService . GetByLanguageAndCodeWithSectionsAsync (
225+ DefaultLanguageCode , pub . Key ) ;
226+ if ( biblePub != null && biblePub . Sections != null && biblePub . Sections . Count > 0 )
227+ {
228+ bibleLanguageCode = DefaultLanguageCode ;
229+ biblePublicationCode = pub . Key ;
230+ selectedBible = biblePub ;
231+ break ;
232+ }
205233 }
206234 }
207235 }
208236 }
209237
210238 // Fallback: find any language with a sectioned publication
211- if ( bibleLanguageCode == null )
239+ if ( selectedBible == null )
212240 {
213241 foreach ( var lang in bibleLanguages )
214242 {
@@ -228,25 +256,32 @@ int GetPublicationSortPriority(string code)
228256 {
229257 if ( PublicationTypeHelper . HasSectionStructure ( pub . Key ) )
230258 {
231- bibleLanguageCode = lang . Key ;
232- biblePublicationCode = pub . Key ;
233- break ;
259+ // Load with sections in one call - this includes Category
260+ var biblePub = await biblePublicationService . GetByLanguageAndCodeWithSectionsAsync (
261+ lang . Key , pub . Key ) ;
262+ if ( biblePub != null && biblePub . Sections != null && biblePub . Sections . Count > 0 )
263+ {
264+ bibleLanguageCode = lang . Key ;
265+ biblePublicationCode = pub . Key ;
266+ selectedBible = biblePub ;
267+ break ;
268+ }
234269 }
235270 }
236271
237- if ( bibleLanguageCode != null )
272+ if ( selectedBible != null )
238273 {
239274 break ;
240275 }
241276 }
242277 }
243278
244- if ( bibleLanguageCode == null || biblePublicationCode == null )
279+ if ( selectedBible == null || bibleLanguageCode == null || biblePublicationCode == null )
245280 {
246281 throw new InvalidOperationException ( "No sectioned Bible publication found in database for sample schedule" ) ;
247282 }
248283
249- // Get first available melody music from database
284+ // Get first available melody music from database that has tracks
250285 var melodyQueryStart = DateTime . UtcNow ;
251286 var melodyReleases = await melodyMusicService . GetAllAsync ( ) ;
252287 Log . Information ( "[PERF] GetSampleSchedule: Melody releases query took {ElapsedMs}ms" , ( DateTime . UtcNow - melodyQueryStart ) . TotalMilliseconds ) ;
@@ -256,8 +291,22 @@ int GetPublicationSortPriority(string code)
256291 throw new InvalidOperationException ( "No melody music found in database" ) ;
257292 }
258293
259- var firstMelody = melodyReleases . FirstOrDefault ( ) ;
260- var melodyPublicationCode = firstMelody . Key ;
294+ // Find a melody music publication that has tracks
295+ string ? melodyPublicationCode = null ;
296+ foreach ( var melody in melodyReleases )
297+ {
298+ var musicWithTracks = await melodyMusicService . GetByCodeWithTracksAsync ( melody . Key ) ;
299+ if ( musicWithTracks ? . Tracks != null && musicWithTracks . Tracks . Count > 0 )
300+ {
301+ melodyPublicationCode = melody . Key ;
302+ break ;
303+ }
304+ }
305+
306+ if ( melodyPublicationCode == null )
307+ {
308+ throw new InvalidOperationException ( "No melody music with tracks found in database" ) ;
309+ }
261310
262311 // Create sample schedule disabled by default - user must explicitly enable it
263312 var sample = new AlarmSchedule
@@ -284,33 +333,38 @@ int GetPublicationSortPriority(string code)
284333 }
285334 } ;
286335
287- var bibleQueryStartTime = DateTime . UtcNow ;
288- var bible = await biblePublicationService . GetByLanguageAndCodeWithSectionsAsync (
289- sample . BiblePublicationSchedule . LanguageCode ,
290- sample . BiblePublicationSchedule . PublicationCode ) ;
291- var bibleQueryElapsed = ( DateTime . UtcNow - bibleQueryStartTime ) . TotalMilliseconds ;
292- Log . Information ( "[PERF] GetSampleSchedule: Bible sections query took {ElapsedMs}ms" , bibleQueryElapsed ) ;
293-
294- if ( bible == null )
295- {
296- throw new InvalidOperationException ( "Bible publication not found for sample schedule" ) ;
297- }
298-
299- if ( bible . Sections == null || bible . Sections . Count == 0 )
336+ // Use the already-loaded bible publication (no additional DB call needed)
337+ // selectedBible is guaranteed to be non-null here due to the check above
338+ if ( selectedBible . Sections == null || selectedBible . Sections . Count == 0 )
300339 {
301340 throw new InvalidOperationException ( $ "No sections found for Bible publication { biblePublicationCode } ") ;
302341 }
303342
304343 // Use Random.Shared for thread-safe random number generation
305344 // Safe for non-cryptographic use (selecting sample sections/tracks)
306- var section = bible . Sections [ Random . Shared . Next ( bible . Sections . Count ) ] ;
345+ // Only select sections that have tracks
346+ var sectionsWithTracks = selectedBible . Sections
347+ . Where ( s => s . Tracks != null && s . Tracks . Count > 0 )
348+ . ToList ( ) ;
349+
350+ if ( sectionsWithTracks . Count == 0 )
351+ {
352+ throw new InvalidOperationException ( $ "No sections with tracks found for Bible publication { biblePublicationCode } ") ;
353+ }
354+
355+ var section = sectionsWithTracks [ Random . Shared . Next ( sectionsWithTracks . Count ) ] ;
307356 if ( sample . BiblePublicationSchedule == null )
308357 {
309358 throw new InvalidOperationException ( "BiblePublicationSchedule is null in sample schedule" ) ;
310359 }
311360
312361 // Use SectionCode directly to match media index db
313362 sample . BiblePublicationSchedule . SectionCode = section . SectionCode ;
363+
364+ // Get the first track of the selected section (book)
365+ // We already verified the section has tracks above
366+ var firstTrack = section . Tracks ! . OrderBy ( t => t . Number ) . First ( ) ;
367+ sample . BiblePublicationSchedule . TrackNumber = firstTrack . Number ;
314368
315369 if ( sample . Music == null )
316370 {
@@ -322,13 +376,38 @@ int GetPublicationSortPriority(string code)
322376 var musicQueryElapsed = ( DateTime . UtcNow - musicQueryStartTime ) . TotalMilliseconds ;
323377 Log . Information ( "[PERF] GetSampleSchedule: Music tracks query took {ElapsedMs}ms" , musicQueryElapsed ) ;
324378
325- if ( music == null )
379+ if ( music == null || music . Publication == null )
326380 {
327381 throw new InvalidOperationException ( "Melody music not found for sample schedule" ) ;
328382 }
329383
330- var track = music . Tracks [ Random . Shared . Next ( music . Tracks . Count ) ] ;
331- sample . Music . TrackNumber = track . Number ;
384+ // Melody music is now sectioned (e.g., iam has sections like "iam-1", "iam-2")
385+ // Select a random section, then a random track from that section
386+ if ( music . Publication . Sections == null || music . Publication . Sections . Count == 0 )
387+ {
388+ // Fallback: if no sections, try direct tracks (for backward compatibility)
389+ if ( music . Tracks == null || music . Tracks . Count == 0 )
390+ {
391+ throw new InvalidOperationException ( $ "No sections or tracks found for melody music publication { sample . Music . PublicationCode } ") ;
392+ }
393+
394+ var track = music . Tracks [ Random . Shared . Next ( music . Tracks . Count ) ] ;
395+ sample . Music . TrackNumber = track . Number ;
396+ }
397+ else
398+ {
399+ // Select a random section
400+ var randomSection = music . Publication . Sections [ Random . Shared . Next ( music . Publication . Sections . Count ) ] ;
401+
402+ if ( randomSection . Tracks == null || randomSection . Tracks . Count == 0 )
403+ {
404+ throw new InvalidOperationException ( $ "No tracks found in section { randomSection . SectionCode } for melody music publication { sample . Music . PublicationCode } ") ;
405+ }
406+
407+ // Select a random track from the selected section
408+ var randomTrack = randomSection . Tracks [ Random . Shared . Next ( randomSection . Tracks . Count ) ] ;
409+ sample . Music . TrackNumber = randomTrack . Number ;
410+ }
332411
333412 var totalElapsed = ( DateTime . UtcNow - startTime ) . TotalMilliseconds ;
334413 Log . Information ( "[PERF] GetSampleSchedule: Completed in {ElapsedMs}ms" , totalElapsed ) ;
0 commit comments