part of '../di_providers.dart';

typedef SeriesSeasonsKey = ({String serverId, String seriesId});
typedef EpisodesKey = ({String serverId, String seriesId, String seasonId});
typedef CrossServerSearchKey = ({
  String activeServerId,
  String anchorType,
  String providerIdQuery,
  String seriesProviderIdQuery,
  int parentIndexNumber,
  int indexNumber,
  String itemName,
  String excludedServerId,
});

typedef MediaDetailKey = ({String serverId, String itemId});


final mediaDetailDataProvider =
    FutureProvider.family<MediaDetailRes, MediaDetailKey>((ref, key) async {
      final uc = await ref.read(mediaDetailUseCaseProvider.future);
      final sw = Stopwatch()..start();
      final res = await uc
          .execute(MediaDetailReq(itemId: key.itemId))
          .timeout(const Duration(seconds: 30));
      final rawSources = res.base.extra['MediaSources'];
      final srcCount = rawSources is List ? rawSources.length : 0;
      AppLogger.debug(
        'DetailTiming',
        'base.fetch type=${res.base.Type} sources=$srcCount ms=${sw.elapsedMilliseconds}',
      );
      return res;
    });

final similarItemsDataProvider =
    FutureProvider.family<List<EmbyRawItem>, String>((ref, itemId) async {
      final uc = await ref.read(similarItemsUseCaseProvider.future);
      final res = await uc.execute(SimilarItemsReq(itemId: itemId));
      return res.items;
    });

final additionalPartsDataProvider =
    FutureProvider.family<List<EmbyRawItem>, String>((ref, itemId) async {
      final uc = await ref.read(additionalPartsUseCaseProvider.future);
      final res = await uc.execute(AdditionalPartsReq(itemId: itemId));
      return res.items;
    });

final specialFeaturesDataProvider = FutureProvider.autoDispose
    .family<List<EmbyRawItem>, String>((ref, itemId) async {
      final uc = await ref.read(specialFeaturesUseCaseProvider.future);
      return uc.execute(itemId);
    });

final crossServerSearchDataProvider = StreamProvider.autoDispose
    .family<List<CrossServerSourceCard>, CrossServerSearchKey>((
      ref,
      key,
    ) async* {
      final uc = await ref.read(crossServerSearchUseCaseProvider.future);
      final cancelToken = CancellationToken();
      ref.onDispose(() => cancelToken.cancel('provider disposed'));

      final req = CrossServerSearchReq(
        anchorType: key.anchorType,
        itemName: key.itemName,
        providerIds: _decodeProviderIdQuery(key.providerIdQuery),
        seriesProviderIds: key.seriesProviderIdQuery.isEmpty
            ? null
            : _decodeProviderIdQuery(key.seriesProviderIdQuery),
        parentIndexNumber: key.parentIndexNumber < 0
            ? null
            : key.parentIndexNumber,
        indexNumber: key.indexNumber < 0 ? null : key.indexNumber,
        excludedServerId: key.excludedServerId.isEmpty
            ? null
            : key.excludedServerId,
      );

      yield const <CrossServerSourceCard>[];
      yield* uc.executeStream(req, cancelToken: cancelToken);
    });


final tmdbLibraryAvailabilityProvider = StreamProvider.autoDispose
    .family<
      List<TmdbLibraryHit>,
      ({String mediaType, String tmdbId, String imdbId})
    >((ref, key) async* {
      if (key.tmdbId.isEmpty) {
        yield const <TmdbLibraryHit>[];
        return;
      }
      final uc = await ref.read(tmdbAvailabilityUseCaseProvider.future);
      final cancelToken = CancellationToken();
      ref.onDispose(() => cancelToken.cancel('provider disposed'));
      yield* uc.executeStream(
        TmdbAvailabilityReq(
          tmdbId: key.tmdbId,
          mediaType: key.mediaType,
          imdbId: key.imdbId.isEmpty ? null : key.imdbId,
        ),
        cancelToken: cancelToken,
      );
    });


typedef ServerScopedItemKey = ({String serverId, String itemId});


final tmdbServerScopedDetailProvider = FutureProvider.autoDispose
    .family<MediaDetailRes, ServerScopedItemKey>((ref, key) async {
      final uc = await ref.read(mediaDetailUseCaseProvider.future);
      return uc.executeForServer(serverId: key.serverId, itemId: key.itemId);
    });


typedef ServerScopedAuth = ({
  String baseUrl,
  String token,
  String userId,
  Map<String, String> imageHeaders,
});

final tmdbServerScopedAuthProvider = FutureProvider.autoDispose
    .family<ServerScopedAuth?, String>((ref, serverId) async {
      final sessionRepo = await ref.read(sessionRepositoryProvider.future);
      final serverRepo = await ref.read(serverRepositoryProvider.future);
      final session = await sessionRepo.getByServerId(serverId);
      if (session == null) return null;
      final server = await serverRepo.findById(serverId);
      if (server == null) return null;
      final deviceId = await ref.read(deviceIdProvider.future);
      return (
        baseUrl: server.baseUrl,
        token: session.token,
        userId: session.userId,
        imageHeaders: EmbyRequestHeaders.image(
          deviceId: deviceId,
          deviceName: kEmbyDeviceName,
          token: session.token,
          userId: session.userId,
        ),
      );
    });


final tmdbServerScopedAdditionalPartsProvider = FutureProvider.autoDispose
    .family<List<EmbyRawItem>, ServerScopedItemKey>((ref, key) async {
      final uc = await ref.read(additionalPartsUseCaseProvider.future);
      final res = await uc.executeForServer(
        serverId: key.serverId,
        itemId: key.itemId,
      );
      return res.items;
    });

typedef SeriesPrewarmKey = ({
  String activeServerId,
  String seriesProviderIdQuery,
});


final seriesPrewarmProvider = FutureProvider.autoDispose
    .family<void, SeriesPrewarmKey>((ref, key) async {
      if (key.seriesProviderIdQuery.isEmpty) return;
      final keepAlive = ref.keepAlive();
      final uc = await ref.read(crossServerSearchUseCaseProvider.future);
      final cancelToken = CancellationToken();
      var disposed = false;
      ref.onDispose(() {
        disposed = true;
        cancelToken.cancel('prewarm disposed');
      });
      try {
        await uc.prewarmSeriesIdentity(
          _decodeProviderIdQuery(key.seriesProviderIdQuery),
          cancelToken: cancelToken,
        );
      } finally {
        if (!disposed) keepAlive.close();
      }
    });

Map<String, String> _decodeProviderIdQuery(String q) {
  final out = <String, String>{};
  if (q.isEmpty) return out;
  for (final part in q.split(',')) {
    final dotIdx = part.indexOf('.');
    if (dotIdx > 0) {
      out[part.substring(0, dotIdx)] = part.substring(dotIdx + 1);
    }
  }
  return out;
}

final seriesSeasonsDataProvider =
    FutureProvider.family<List<EmbyRawSeason>, SeriesSeasonsKey>((
      ref,
      key,
    ) async {
      final uc = await ref.read(seriesSeasonsUseCaseProvider.future);
      final res = await uc.executeForServer(
        serverId: key.serverId,
        input: SeriesSeasonsReq(seriesId: key.seriesId),
      );
      return res.items;
    });

final seriesEpisodesDataProvider =
    FutureProvider.family<List<EmbyRawItem>, EpisodesKey>((ref, key) async {
      final uc = await ref.read(seriesEpisodesUseCaseProvider.future);
      final res = await uc.executeForServer(
        serverId: key.serverId,
        input: MediaEpisodesReq(seriesId: key.seriesId, seasonId: key.seasonId),
      );
      return res.items;
    });


typedef SeriesResumeTargetKey = ({String serverId, String seriesId});


final seriesResumeTargetProvider = FutureProvider.autoDispose
    .family<SeriesResumeTarget?, SeriesResumeTargetKey>((ref, key) async {
      try {
        final sessionRepo = await ref.read(sessionRepositoryProvider.future);
        final serverRepo = await ref.read(serverRepositoryProvider.future);
        final ctx = await resolveAuthedContextForServer(
          sessionRepository: sessionRepo,
          serverRepository: serverRepo,
          serverId: key.serverId,
        );
        if (ctx == null) return null;
        final gw = await ref.read(embyGatewayProvider.future);
        return resolveSeriesResumeTarget(gw, ctx, key.seriesId);
      } catch (e) {
        AppLogger.warn('SeriesResumeTarget', 'resolve failed', e);
        return null;
      }
    });


final tmdbTvRecentHitProvider = FutureProvider.autoDispose
    .family<
      TmdbLibraryHit?,
      ({String mediaType, String tmdbId, String imdbId})
    >((ref, key) async {
      final hits = await ref.watch(tmdbLibraryAvailabilityProvider(key).future);
      final seriesHits = hits
          .where((h) => h.itemType == 'Series')
          .toList(growable: false);
      if (seriesHits.length < 2) return null;
      final uc = await ref.read(mediaDetailUseCaseProvider.future);
      DateTime? bestDate;
      TmdbLibraryHit? best;
      for (final h in seriesHits) {
        try {
          final res = await uc.executeForServer(
            serverId: h.serverId,
            itemId: h.itemId,
          );
          final lp = parseEmbyDate(res.base.UserData?.extra['LastPlayedDate']);
          if (lp != null && (bestDate == null || lp.isAfter(bestDate))) {
            bestDate = lp;
            best = h;
          }
        } catch (_) {}
      }
      return best;
    });
