import 'package:flutter/material.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, metadata /// edit, match). Backend-agnostic flows use the [_resolveMediaClient] /// helpers below. PlexClient? _resolveClient(String? 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(String? 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(String serverId) => _requireClient(serverId, fallback: false); PlexClient? tryGetPlexClientForServer(String? serverId) { if (serverId == null) return null; final provider = Provider.of(this, listen: false); return provider.getPlexClientForServer(serverId); } PlexClient getPlexClientForLibrary(MediaLibrary library) => _requireClient(library.serverId); PlexClient getPlexClientWithFallback(String? 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, // metadata editing, etc.). MediaServerClient? _resolveMediaClient(String? serverId) { final provider = Provider.of(this, listen: false); return _resolvePrioritized(serverId, provider.onlineServerIds, provider.getClientForServer); } MediaServerClient? tryGetMediaClientForServer(String? 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(String serverId) { final c = tryGetMediaClientForServer(serverId); if (c == null) throw Exception(t.errors.noClientAvailable); return c; } MediaServerClient getMediaClientForLibrary(MediaLibrary library) { final c = _resolveMediaClient(library.serverId); if (c == null) throw Exception(t.errors.noClientAvailable); return c; } /// 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(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(String? 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(String? serverId) => _resolveMediaClient(serverId); } /// 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(String) resolve) { if (preferred != null) { final c = resolve(preferred); if (c != null) return c; } for (final id in fallbacks) { final c = resolve(id); if (c != null) return c; } return null; }