import 'dart:async';
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:receive_sharing_intent/receive_sharing_intent.dart';

import 'pending_share.dart';

/// Bridges native share intents (Android ACTION_SEND, iOS Share Extension)
/// into a single [ValueNotifier] that the app routes off of.
class ShareIntentListener {
  ShareIntentListener._();
  static final ShareIntentListener instance = ShareIntentListener._();

  static final ValueNotifier<PendingShare?> pending = ValueNotifier(null);

  StreamSubscription<List<SharedMediaFile>>? _streamSub;
  bool _initialized = false;

  /// Reads the cold-start payload exactly once. Call from `main()` before
  /// `runApp` so the share is queued before the UI mounts.
  Future<void> initialize() async {
    if (_initialized) return;
    _initialized = true;
    try {
      final initial = await ReceiveSharingIntent.instance.getInitialMedia();
      final share = _toPendingShare(initial);
      if (share != null) _publish(share);
      await ReceiveSharingIntent.instance.reset();
    } catch (e) {
      debugPrint('ShareIntentListener.initialize failed: $e');
    }
  }

  /// Subscribes to warm-share stream events. Safe to call multiple times.
  void attach() {
    _streamSub ??= ReceiveSharingIntent.instance.getMediaStream().listen(
      (items) {
        final share = _toPendingShare(items);
        if (share != null) _publish(share);
      },
      onError: (Object e) =>
          debugPrint('ShareIntentListener stream error: $e'),
    );
  }

  /// The iOS Share Extension fires two `open(url)` requests per share, so the
  /// same payload can arrive twice in quick succession. Publishing the
  /// duplicate would re-trigger the share-flow navigation, pop the already
  /// open ShareTargetPage and thereby delete the temp files of the share that
  /// is still in flight — swallow it instead.
  void _publish(PendingShare share) {
    final current = pending.value;
    if (current != null && current.contentEquals(share)) return;
    pending.value = share;
  }

  /// Cancels the warm-share subscription. The singleton survives, so a
  /// subsequent [attach] re-subscribes.
  void detach() {
    _streamSub?.cancel();
    _streamSub = null;
  }

  /// Discards the current share and removes any temp files the plugin copied
  /// into the app cache. Idempotent.
  ///
  /// Pass [ifCurrent] from UI that owns a specific share (e.g. the
  /// ShareTargetPage pop handler): the call then only acts while that share
  /// is still the pending one. Without the guard, popping a stale share page
  /// after a new share arrived would delete the new share's temp files before
  /// its upload ran.
  void clear({PendingShare? ifCurrent}) {
    final current = pending.value;
    if (ifCurrent != null && !identical(current, ifCurrent)) return;
    pending.value = null;
    if (current != null) {
      for (final path in current.filePaths) {
        try {
          final f = File(path);
          if (f.existsSync()) f.deleteSync();
        } catch (_) {
          // best-effort cleanup; OS will reclaim cache eventually
        }
      }
    }
    unawaited(ReceiveSharingIntent.instance.reset());
  }

  PendingShare? _toPendingShare(List<SharedMediaFile> items) {
    if (items.isEmpty) return null;
    final files = <String>[];
    final texts = <String>[];
    for (final item in items) {
      switch (item.type) {
        case SharedMediaType.image:
        case SharedMediaType.video:
        case SharedMediaType.file:
          files.add(item.path);
        case SharedMediaType.text:
        case SharedMediaType.url:
          texts.add(item.path);
      }
    }
    if (files.isEmpty && texts.isEmpty) return null;
    return PendingShare(
      filePaths: files,
      text: texts.isEmpty ? null : texts.join('\n'),
      receivedAt: DateTime.now(),
    );
  }
}
