import 'package:flutter/material.dart'; import '../media/ids.dart'; import 'package:provider/provider.dart'; import '../media/media_item.dart'; import '../media/media_library.dart'; import '../media/media_server_client.dart'; import '../media/media_server_user_profile.dart'; import '../services/plex_client.dart'; import '../i18n/strings.g.dart'; import '../providers/hidden_libraries_provider.dart'; import '../providers/multi_server_provider.dart'; import '../providers/user_profile_provider.dart'; import 'app_logger.dart'; extension ProviderExtensions on BuildContext { UserProfileProvider get userProfile => Provider.of(this, listen: false); HiddenLibrariesProvider get hiddenLibraries => Provider.of(this, listen: false); MediaServerUserProfile? get profileSettings => userProfile.profileSettings; /// Internal: resolve a [PlexClient] from a serverId or fall back to the /// first online server. Returns null if neither yields a Plex client. /// Non-Plex servers (Jellyfin) are skipped — these helpers exist for /// Plex-only flows that have no neutral equivalent (DVR tuning, match). /// Backend-agnostic flows use the [_resolveMediaClient] /// helpers below. PlexClient? _resolveClient(ServerId? serverId) { final provider = Provider.of(this, listen: false); return _resolvePrioritized(serverId, provider.onlineServerIds, provider.getPlexClientForServer); } /// Internal: like [_resolveClient] but throws a localized exception when /// no client is available. The thrown message is the canonical /// `t.errors.noClientAvailable` so callers can surface it directly. PlexClient _requireClient(ServerId? serverId, {bool fallback = true}) { final provider = Provider.of(this, listen: false); if (serverId != null) { final client = provider.getPlexClientForServer(serverId); if (client != null) return client; if (!fallback) { appLogger.e('No Plex client found for server $serverId'); throw Exception(t.errors.noClientAvailable); } } final client = _resolveClient(null); if (client == null) { throw Exception(t.errors.noClientAvailable); } return client; } PlexClient getPlexClientForServer(ServerId serverId) => _requireClient(serverId, fallback: false); PlexClient? tryGetPlexClientForServer(ServerId? serverId) { if (serverId == null) return null; final provider = Provider.of(this, listen: false); return provider?.getPlexClientForServer(serverId); } PlexClient getPlexClientForLibrary(MediaLibrary library) { final serverId = serverIdOrNull(library.serverId); if (serverId == null) throw Exception(t.errors.noClientAvailable); return getPlexClientForServer(serverId); } PlexClient getPlexClientWithFallback(ServerId? serverId) => _requireClient(serverId); // ── Backend-neutral helpers ────────────────────────────────────── // These return [MediaServerClient] regardless of backend kind so callers // that consume only the [MediaServerClient] surface don't need to type- // check the result. Use [getPlexClientForServer] / [getPlexClientForLibrary] // when you specifically need a [PlexClient] (Plex-only flows like Live TV or // match/fix-match). MediaServerClient? _resolveMediaClient(ServerId? serverId) { final provider = Provider.of(this, listen: false); return _resolvePrioritized(serverId, provider.onlineServerIds, provider.getClientForServer); } MediaServerClient? tryGetMediaClientForServer(ServerId? serverId) { if (serverId == null) return null; final provider = Provider.of(this, listen: false); return provider?.getClientForServer(serverId); } /// Get a [MediaServerClient] for the given serverId. Throws when the /// server isn't registered or is offline. Mirrors the throwing variant of /// the Plex-typed [getPlexClientForServer] helpers. MediaServerClient getMediaClientForServer(ServerId serverId) { final provider = Provider.of(this, listen: false); final c = provider.getClientForServer(serverId); if (c == null) throw Exception(t.errors.noClientAvailable); return c; } MediaServerClient getMediaClientForLibrary(MediaLibrary library) { final serverId = serverIdOrNull(library.serverId); if (serverId == null) throw Exception(t.errors.noClientAvailable); return getMediaClientForServer(serverId); } /// Get a [MediaServerClient] for a [MediaItem], or null in offline mode / /// when the server isn't online. MediaServerClient? getMediaClientForItemOrNull(MediaItem item, {bool isOffline = false}) { if (isOffline) return null; return tryGetMediaClientForServer(serverIdOrNull(item.serverId)); } /// Get a [MediaServerClient] for [serverId], falling back to the first /// online server when not found. Throws if no client is available. MediaServerClient getMediaClientWithFallback(ServerId? serverId) { final c = _resolveMediaClient(serverId); if (c == null) throw Exception(t.errors.noClientAvailable); return c; } /// Like [getMediaClientWithFallback] but returns null instead of throwing /// when no client is registered. Use this for non-critical surfaces (image /// loaders, list cards) that can render a fallback when the client isn't /// available — throwing during `build` would crash the widget instead. MediaServerClient? tryGetMediaClientWithFallback(ServerId? serverId) { final provider = Provider.of(this, listen: false); if (provider == null) return null; return _resolvePrioritized(serverId, provider.onlineServerIds, provider.getClientForServer); } } /// Try [preferred] first, then fall back through [fallbacks] in order. Returns /// the first non-null result from [resolve], or `null` if every candidate /// resolves to null. T? _resolvePrioritized(String? preferred, Iterable fallbacks, T? Function(ServerId) resolve) { if (preferred != null) { final c = resolve(ServerId(preferred)); if (c != null) return c; } for (final id in fallbacks) { final c = resolve(ServerId(id)); if (c != null) return c; } return null; }