import 'dart:async';

import 'package:nullability/nullability.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:rxdart/rxdart.dart';
import 'package:weblibre/features/web_feed/data/database/database.dart';
import 'package:weblibre/features/web_feed/data/models/feed_article.dart';
import 'package:weblibre/features/web_feed/data/models/feed_parse_result.dart';
import 'package:weblibre/features/web_feed/data/providers.dart';
import 'package:weblibre/features/web_feed/domain/providers/article_filter.dart';
import 'package:weblibre/features/web_feed/domain/repositories/feed_repository.dart';
import 'package:weblibre/features/web_feed/domain/services/feed_reader.dart';

part 'providers.g.dart';

@Riverpod()
class ArticleSearch extends _$ArticleSearch {
  late StreamController<List<FeedArticle>> _streamController;

  Future<void> search(
    String input, {
    int snippetLength = 120,
    String matchPrefix = '***',
    String matchSuffix = '***',
    String ellipsis = '…',
  }) async {
    if (input.isNotEmpty) {
      await ref
          .read(feedDatabaseProvider)
          .articleDao
          .queryArticles(
            matchPrefix: matchPrefix,
            matchSuffix: matchSuffix,
            ellipsis: ellipsis,
            snippetLength: snippetLength,
            searchString: input,
            feedId: feedId,
          )
          .get()
          .then((value) {
            if (!_streamController.isClosed) {
              _streamController.add(value);
            }
          });
    }
  }

  @override
  Stream<List<FeedArticle>> build(Uri? feedId) {
    _streamController = StreamController();

    ref.onDispose(() async {
      await _streamController.close();
    });

    return ConcatStream([Stream.value([]), _streamController.stream]);
  }
}

@Riverpod()
Stream<List<FeedData>> feedList(Ref ref) {
  final repository = ref.watch(feedRepositoryProvider.notifier);
  return repository.watchFeeds();
}

@Riverpod()
Stream<FeedData?> feedData(Ref ref, Uri? feedId) {
  final repository = ref.watch(feedRepositoryProvider.notifier);

  if (feedId == null) {
    return Stream.value(null);
  }

  return repository.watchFeed(feedId);
}

@Riverpod()
Stream<List<FeedArticle>> feedArticleList(Ref ref, Uri? feedId) {
  final repository = ref.watch(feedRepositoryProvider.notifier);
  return repository.watchFeedArticles(feedId);
}

@Riverpod()
class FilteredArticleList extends _$FilteredArticleList {
  bool _hasSearch = false;

  void search(String input) {
    if (input.isNotEmpty) {
      if (!_hasSearch) {
        _hasSearch = true;
        ref.invalidateSelf();
      }

      //Don't block
      unawaited(ref.read(articleSearchProvider(feedId).notifier).search(input));
    } else if (_hasSearch) {
      _hasSearch = false;
      ref.invalidateSelf();
    }
  }

  @override
  AsyncValue<List<FeedArticle>> build(Uri? feedId) {
    final filterTags = ref.watch(articleFilterProvider);

    final articlesAsync = _hasSearch
        ? ref.watch(articleSearchProvider(feedId))
        : ref.watch(feedArticleListProvider(feedId));

    return articlesAsync.whenData((articles) {
      if (filterTags.isNotEmpty) {
        return articles.where((article) {
          final tags = article.tags?.map((tag) => tag.id).toSet();

          final authors = article.authors
              ?.map((author) => author.name.whenNotEmpty)
              .nonNulls
              .toSet();

          return filterTags.every(
            (filter) =>
                (tags?.contains(filter) ?? false) ||
                (authors?.contains(filter) ?? false),
          );
        }).toList();
      }

      return articles;
    });
  }
}

@Riverpod()
Stream<FeedArticle?> feedArticle(
  Ref ref,
  String articleId, {
  required bool updateReadDate,
}) async* {
  final repository = ref.watch(feedRepositoryProvider.notifier);

  if (updateReadDate) {
    await repository.touchArticleRead(articleId);
  }

  yield* repository.watchArticle(articleId);
}

@Riverpod()
Raw<Stream<Map<String, int>>> unreadArticleCount(Ref ref) {
  final repository = ref.watch(feedRepositoryProvider.notifier);
  return repository.watchUnreadFeedArticleCount();
}

@Riverpod()
Stream<int?> unreadFeedArticleCount(Ref ref, Uri feedId) {
  final stream = ref.watch(unreadArticleCountProvider);
  return stream.map((counts) => counts[feedId.toString()]);
}

@Riverpod()
Future<FeedParseResult> fetchWebFeed(Ref ref, Uri url) {
  return ref.read(feedReaderProvider.notifier).parseFeed(url);
}
