/// External IDs (IMDb / TMDB / TVDB) extracted from a media server's /// metadata. Shared by the Trakt and tracker resolvers. /// /// - **Plex** stores them in a `Guid` array (`imdb://tt123`, /// `tmdb://456`, `tvdb://789`) — fetched via /// [PlexClient.fetchExternalGuids]. Use [ExternalIds.fromGuids]. /// - **Jellyfin** stores them inline as a `ProviderIds` map on every /// `BaseItemDto`. Use [ExternalIds.fromJellyfinProviderIds]. class ExternalIds { final String? imdb; final int? tmdb; final int? tvdb; const ExternalIds({this.imdb, this.tmdb, this.tvdb}); bool get hasAny => imdb != null || tmdb != null || tvdb != null; factory ExternalIds.fromGuids(List guids) { String? imdb; int? tmdb; int? tvdb; for (final g in guids) { if (g is! Map) continue; final id = g['id']; if (id is! String) continue; if (id.startsWith('imdb://')) { imdb = id.substring(7); } else if (id.startsWith('tmdb://')) { tmdb = int.tryParse(id.substring(7)); } else if (id.startsWith('tvdb://')) { tvdb = int.tryParse(id.substring(7)); } } return ExternalIds(imdb: imdb, tmdb: tmdb, tvdb: tvdb); } /// Build from a Jellyfin `ProviderIds` map. Jellyfin stores external IDs /// directly on every `BaseItemDto` so no extra fetch is needed. /// Keys are case-insensitive in practice (`Tmdb`, `Imdb`, `Tvdb`). factory ExternalIds.fromJellyfinProviderIds(Map providerIds) { String? imdb; int? tmdb; int? tvdb; providerIds.forEach((key, value) { if (value is! String || value.isEmpty) return; switch (key.toLowerCase()) { case 'imdb': imdb = value; break; case 'tmdb': tmdb = int.tryParse(value); break; case 'tvdb': tvdb = int.tryParse(value); break; } }); return ExternalIds(imdb: imdb, tmdb: tmdb, tvdb: tvdb); } }