import 'dart:async';

import 'package:flutter/material.dart';

import '../../share_intent/remote_file_ref.dart';
import '../../utils/downloads/download_job.dart';
import '../../utils/downloads/download_manager.dart';
import '../confirm_dialog.dart';

/// Shared download trigger logic for the Files list and Talk chat bubbles.
///
/// Owns attaching to / detaching from a [DownloadJob] and starting/cancelling a
/// download. Rendering of inline progress stays with each screen (list subtitle
/// vs. bubble overlay) via [downloadJob]. Opening the finished file, the
/// completion/parallel behaviour and error surfacing are handled centrally by
/// the downloads tray/coordinator — not here.
mixin DownloadTrigger<T extends StatefulWidget> on State<T> {
  DownloadJob? _job;

  /// Current job for inline rendering, or null when idle.
  DownloadJob? get downloadJob => _job;

  /// Remote (WebDAV) path of the file this widget can download, or null when
  /// the widget currently has no downloadable file (e.g. a text message).
  String? get downloadRemotePath;

  /// True while a download for this widget's file is running.
  bool get isDownloading => _job?.status.value is DownloadInProgress;

  /// Reattach to an in-flight job on mount. Call from `initState`.
  void initDownloadTrigger() => _attach(_lookup());

  /// Re-resolve the job after the widget's file changed. Call from
  /// `didUpdateWidget` when [downloadRemotePath] may have changed.
  void refreshDownloadTrigger() {
    _detach();
    _attach(_lookup());
  }

  /// Detach listeners. Call from `dispose`.
  void disposeDownloadTrigger() => _detach();

  DownloadJob? _lookup() {
    final path = downloadRemotePath;
    return path == null ? null : DownloadManager.instance.jobFor(path);
  }

  void _attach(DownloadJob? job) {
    _job = job;
    job?.status.addListener(_onStatus);
  }

  void _detach() {
    _job?.status.removeListener(_onStatus);
    _job = null;
  }

  void _onStatus() {
    if (!mounted) return;
    final job = _job;
    setState(() {});
    // Once finished, the tray owns the job (open / retry / dismiss); drop our
    // reference so the inline progress clears and we never touch a job the
    // manager is about to dispose.
    if (job != null && job.isFinished) _detach();
  }

  /// Starts (or reattaches to) the download for this widget's file.
  Future<void> startDownload({
    required String name,
    RemoteFileRef? remoteFile,
  }) async {
    final path = downloadRemotePath;
    if (path == null) return;
    final job = await DownloadManager.instance.start(
      remotePath: path,
      name: name,
      remoteFile: remoteFile,
    );
    if (!mounted) return;
    if (_job != job) {
      _detach();
      _attach(job);
    }
    setState(() {});
  }

  /// Confirms and cancels the in-flight download.
  void confirmCancelDownload() {
    final job = _job;
    if (job == null || job.isFinished) return;
    ConfirmDialog(
      title: 'Download abbrechen?',
      content: 'Möchtest du den Download abbrechen?',
      confirmButton: 'Ja, Abbrechen',
      cancelButton: 'Nein',
      onConfirm: () => unawaited(DownloadManager.instance.cancel(job)),
    ).asDialog(context);
  }
}
