import 'dart:convert';

import 'package:crypto/crypto.dart';
import 'package:localstore/localstore.dart';

import '../../../../../utils/cache_invalidation_bus.dart';
import '../../../../request_cache.dart';
import 'list_files.dart';
import 'list_files_params.dart';
import 'list_files_response.dart';

class ListFilesCache extends SimpleCache<ListFilesResponse> {
  ListFilesCache({
    required void Function(ListFilesResponse) onUpdate,
    super.onCacheData,
    super.onNetworkData,
    super.onError,
    required String path,
    super.renew = false,
    void Function(int nextAttempt, int maxAttempts)? onRetry,
  }) : super(
         cacheTime: _cacheTimeFor(path),
         loader: () => ListFiles(ListFilesParams(path), onRetry: onRetry).run(),
         fromJson: ListFilesResponse.fromJson,
         onUpdate: onUpdate,
       ) {
    start(_documentId(path));
  }

  /// The Nextcloud root listing is much slower than subfolders on our instance
  /// and frequently returns HTTP 500, but its content rarely changes — so it is
  /// cached for a full day to keep app-resume/connectivity auto-refetches off
  /// the slow root endpoint. [prefetchRootListing] warms it up in the background
  /// after login. Subfolders keep the "always refetch on visit" TTL; explicit
  /// user refreshes bypass the TTL via the inherited [renew] flag or [invalidate].
  static int _cacheTimeFor(String path) {
    final stripped = path.replaceAll('/', '').trim();
    return stripped.isEmpty ? RequestCache.cacheDay : RequestCache.cacheNothing;
  }

  /// Triggers a root-listing fetch in the background if no cached payload
  /// exists yet. Intended to be called once after login from `main` so the
  /// (slow) root listing is already populated by the time the user
  /// navigates to the Files module.
  ///
  /// No-ops when a cached root payload is already present in localstore —
  /// the regular TTL handling in [RequestCache] takes over from there.
  static Future<void> prefetchRootListing() async {
    const rootPath = '';
    final cached = await Localstore.instance
        .collection(RequestCache.collection)
        .doc(_documentId(rootPath))
        .get();
    if (cached != null) return;
    // Drive the same code path as a regular fetch so the result lands in
    // the cache; we don't care about the in-memory callback here.
    ListFilesCache(path: rootPath, onUpdate: (_) {});
  }

  static String _documentId(String path) {
    final cacheName = md5
        .convert(utf8.encode('MarianumMobile-$path'))
        .toString();
    return 'wd-folder-$cacheName';
  }

  /// Drops the cached listing for [path] from local storage so the next
  /// `listFiles` call cannot serve stale data, and notifies any live
  /// `_FilesView` for that path via [CacheInvalidationBus] so it refetches
  /// even while it is sitting in the background of the navigation stack.
  static Future<void> invalidate(String path) async {
    await Localstore.instance
        .collection(RequestCache.collection)
        .doc(_documentId(path))
        .delete();
    CacheInvalidationBus.notifyListFiles(path);
  }
}
