import 'dart:async';

import 'package:nextcloud/nextcloud.dart';

import '../../../../retry.dart';
import '../../webdav_api.dart';
import 'cacheable_file.dart';
import 'list_files_params.dart';
import 'list_files_response.dart';

class ListFiles extends WebdavApi<ListFilesParams> {
  ListFilesParams params;

  /// Forwarded to [retryOnTransientError] so the UI can surface "Erneuter
  /// Versuch (2 von 3)" while a 5xx-plagued listing self-heals.
  final void Function(int nextAttempt, int maxAttempts)? onRetry;

  ListFiles(this.params, {this.onRetry}) : super(params);

  // The root listing is much slower than subdirectories on our instance, so it
  // gets a longer timeout ceiling; subfolders stay tighter to keep the UI snappy.
  static const Duration _rootTimeout = Duration(minutes: 3);
  static const Duration _subfolderTimeout = Duration(seconds: 30);

  bool get _isRoot {
    final p = params.path.replaceAll('/', '').trim();
    return p.isEmpty;
  }

  @override
  Future<ListFilesResponse> run() async {
    final webdav = await WebdavApi.webdav;
    final timeout = _isRoot ? _rootTimeout : _subfolderTimeout;
    // Explicit prop list — without it the server omits OC-namespaced
    // properties like oc:fileid, which the preview endpoint relies on.
    final prop = WebDavPropWithoutValues.fromBools(
      davgetlastmodified: true,
      davgetetag: true,
      davgetcontenttype: true,
      davgetcontentlength: true,
      davresourcetype: true,
      ocfileid: true,
      ocsize: true,
      nccreationtime: true,
      nchaspreview: true,
      // 'shared' here means an incoming share (mounted into the user's files
      // by someone else); used to badge those entries in the list.
      ncmounttype: true,
    );

    // Our instance intermittently answers the (slow) root PROPFIND with a
    // quick empty 500 — retry those transparently before surfacing an error.
    var files = await retryOnTransientError(
      () => _fetch(webdav, prop, timeout),
      onRetry: onRetry,
    );
    // A freshly-entered incoming share sometimes answers its first PROPFIND
    // without the OC/NC props (no fileid / has-preview / mount-type) while the
    // share mount warms up server-side — which drops thumbnails AND share
    // badges together. Retry a couple of times so the folder self-heals
    // instead of needing manual re-entry. Best-effort: a failure here must
    // not discard the listing that was already fetched successfully.
    for (var attempt = 0; attempt < 2 && _looksIncomplete(files); attempt++) {
      await Future<void>.delayed(const Duration(milliseconds: 700));
      try {
        files = await _fetch(webdav, prop, timeout);
      } on Exception {
        break;
      }
    }

    return ListFilesResponse(files);
  }

  Future<Set<CacheableFile>> _fetch(
    WebDavClient webdav,
    WebDavPropWithoutValues prop,
    Duration timeout,
  ) async {
    final davFiles =
        (await webdav
                .propfind(
                  PathUri.parse(params.path),
                  prop: prop,
                  depth: WebDavDepth.one,
                )
                .timeout(timeout))
            .toWebDavFiles();
    final files = davFiles.map(CacheableFile.fromDavFile).toSet();
    // somehow the current working folder is also listed, it is filtered here.
    files.removeWhere(
      (element) => element.path == '/${params.path}/' || element.path == '/',
    );
    return files;
  }

  /// True when the server returned entries but none carry a `fileId` — a sign
  /// the OC/NC properties were omitted (cold share mount), so thumbnails and
  /// share badges would be missing for the whole folder.
  bool _looksIncomplete(Set<CacheableFile> files) =>
      files.isNotEmpty && files.every((file) => file.fileId == null);
}
