@@ -39,6 +39,10 @@ class MovieMetadataEnrichment extends Component
3939
4040 public int $ batchLimit = 100 ;
4141
42+ public string $ activeTab = 'all ' ;
43+
44+ public array $ orphanEpisodes = [];
45+
4246 protected const ENRICHABLE_FIELDS = ['description ' , 'poster_url ' , 'runtime_minutes ' , 'release_date ' , 'genres ' , 'director ' , 'show_name ' , 'season_number ' , 'episode_number ' ];
4347
4448 public function mount (): void
@@ -134,6 +138,8 @@ public function scanLibrary(): void
134138 ->values ()
135139 ->toArray ();
136140
141+ $ this ->scanOrphanEpisodes ();
142+
137143 $ this ->hasScanned = true ;
138144 $ this ->isScanning = false ;
139145 }
@@ -486,6 +492,114 @@ public function isJobCompleted(): bool
486492 return $ this ->jobStatus && $ this ->jobStatus ['status ' ] === 'completed ' ;
487493 }
488494
495+ public function setActiveTab (string $ tab ): void
496+ {
497+ $ this ->activeTab = $ tab ;
498+ }
499+
500+ public function getFilteredMovies (): array
501+ {
502+ if ($ this ->activeTab === 'tv ' ) {
503+ return collect ($ this ->moviesNeedingEnrichment )
504+ ->filter (fn ($ m ) => in_array ($ m ['title_type ' ] ?? '' , ['TV Series ' , 'TV Mini Series ' , 'TV Episode ' ]))
505+ ->values ()
506+ ->toArray ();
507+ }
508+
509+ if ($ this ->activeTab === 'orphans ' ) {
510+ return [];
511+ }
512+
513+ return $ this ->moviesNeedingEnrichment ;
514+ }
515+
516+ public function getTvCount (): int
517+ {
518+ return collect ($ this ->moviesNeedingEnrichment )
519+ ->filter (fn ($ m ) => in_array ($ m ['title_type ' ] ?? '' , ['TV Series ' , 'TV Mini Series ' , 'TV Episode ' ]))
520+ ->count ();
521+ }
522+
523+ public function scanOrphanEpisodes (): void
524+ {
525+ $ userId = Auth::id ();
526+
527+ // Get all TV Series / TV Mini Series titles for this user
528+ $ seriesTitles = Movie::query ()
529+ ->where ('user_id ' , $ userId )
530+ ->whereIn ('title_type ' , ['TV Series ' , 'TV Mini Series ' ])
531+ ->pluck ('title ' )
532+ ->toArray ();
533+
534+ // Find TV Episodes with no show_name, or whose show_name doesn't match any series title
535+ $ orphans = Movie::query ()
536+ ->where ('user_id ' , $ userId )
537+ ->where ('title_type ' , 'TV Episode ' )
538+ ->where (function ($ query ) use ($ seriesTitles ) {
539+ $ query ->whereNull ('show_name ' )
540+ ->orWhere ('show_name ' , '' );
541+ if (! empty ($ seriesTitles )) {
542+ $ query ->orWhereNotIn ('show_name ' , $ seriesTitles );
543+ }
544+ })
545+ ->orderBy ('title ' )
546+ ->get (['id ' , 'title ' , 'title_type ' , 'imdb_id ' , 'year ' , 'show_name ' , 'season_number ' , 'episode_number ' , 'poster_url ' ])
547+ ->map (fn ($ movie ) => [
548+ 'id ' => $ movie ->id ,
549+ 'title ' => $ movie ->title ,
550+ 'imdb_id ' => $ movie ->imdb_id ,
551+ 'year ' => $ movie ->year ,
552+ 'show_name ' => $ movie ->show_name ,
553+ 'season_episode ' => $ movie ->season_episode_label ,
554+ 'has_show_name ' => ! empty ($ movie ->show_name ),
555+ ])
556+ ->toArray ();
557+
558+ $ this ->orphanEpisodes = $ orphans ;
559+ }
560+
561+ public function linkOrphanToShow (int $ movieId , string $ showName ): void
562+ {
563+ $ movie = Movie::query ()
564+ ->where ('user_id ' , Auth::id ())
565+ ->where ('title_type ' , 'TV Episode ' )
566+ ->find ($ movieId );
567+
568+ if (! $ movie ) {
569+ return ;
570+ }
571+
572+ $ movie ->update (['show_name ' => $ showName ]);
573+
574+ // Propagate to siblings with same title prefix
575+ $ titlePrefix = str_contains ($ movie ->title , ': ' )
576+ ? trim (explode (': ' , $ movie ->title , 2 )[0 ])
577+ : null ;
578+
579+ $ propagated = 0 ;
580+ if ($ titlePrefix ) {
581+ $ propagated = Movie::query ()
582+ ->where ('user_id ' , Auth::id ())
583+ ->where ('title_type ' , 'TV Episode ' )
584+ ->where (function ($ q ) use ($ titlePrefix ) {
585+ $ q ->where ('title ' , 'like ' , $ titlePrefix . ':% ' );
586+ })
587+ ->where (function ($ q ) {
588+ $ q ->whereNull ('show_name ' )->orWhere ('show_name ' , '' );
589+ })
590+ ->update (['show_name ' => $ showName ]);
591+ }
592+
593+ // Re-scan orphans to reflect changes
594+ $ this ->scanOrphanEpisodes ();
595+
596+ $ msg = "Linked episode to \"{$ showName }\". " ;
597+ if ($ propagated > 0 ) {
598+ $ msg .= " Also linked {$ propagated } sibling episode(s). " ;
599+ }
600+ session ()->flash ('message ' , $ msg );
601+ }
602+
489603 public function render ()
490604 {
491605 if ($ this ->jobStatus && $ this ->jobStatus ['status ' ] === 'running ' ) {
0 commit comments