import 'dart:async';

import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import '../../../../../extensions/date_time.dart';
import 'loadable_state_event.dart';
import 'loadable_state_state.dart';

class LoadableStateBloc extends Bloc<LoadableStateEvent, LoadableStateState>
    with WidgetsBindingObserver {
  late StreamSubscription<List<ConnectivityResult>> _updateStream;
  void Function()? reFetch;

  /// Last time [reFetch] was triggered by an [AppLifecycleState.resumed]
  /// event. Used to coalesce rapid foreground/background flips so we don't
  /// spam the network when the user briefly checks notifications.
  DateTime _lastResumeRefetch = DateTime.fromMillisecondsSinceEpoch(0);

  LoadableStateBloc() : super(const LoadableStateState(connections: null)) {
    on<ConnectivityChanged>((event, emit) {
      emit(event.state);
      if (connectivityStatusKnown() && isConnected()) {
        if (reFetch == null) return;
        reFetch!();
      }
    });

    void emitConnectivity(List<ConnectivityResult> result) {
      // The initial checkConnectivity() future is not cancellable and may
      // resolve after the bloc was disposed, so guard against a closed sink.
      if (isClosed) return;
      add(ConnectivityChanged(LoadableStateState(connections: result)));
    }

    Connectivity().checkConnectivity().then(emitConnectivity);
    _updateStream = Connectivity().onConnectivityChanged.listen(
      emitConnectivity,
    );
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state != AppLifecycleState.resumed) return;
    final now = DateTime.now();
    if (now.difference(_lastResumeRefetch) < const Duration(seconds: 10)) {
      return;
    }
    _lastResumeRefetch = now;
    // Re-check connectivity so the resulting [ConnectivityChanged] handler
    // clears a stale error bar and triggers [reFetch] once reachable again.
    unawaited(
      Connectivity().checkConnectivity().then((result) {
        if (isClosed) return;
        add(ConnectivityChanged(LoadableStateState(connections: result)));
      }),
    );
  }

  bool connectivityStatusKnown() => state.connections != null;
  bool isConnected() =>
      !(state.connections?.contains(ConnectivityResult.none) ?? true);
  bool allowRetry() => reFetch != null;

  IconData connectionIcon() => connectivityStatusKnown()
      ? isConnected()
            ? Icons.nearby_error
            : Icons.signal_wifi_connected_no_internet_4
      : Icons.device_unknown;

  Color connectionColor(BuildContext context) =>
      connectivityStatusKnown() && !isConnected()
      ? Colors.grey.shade600
      : Theme.of(context).primaryColor;

  Color connectionForegroundColor(BuildContext context) =>
      connectivityStatusKnown() && !isConnected()
      ? Colors.white
      : ThemeData.estimateBrightnessForColor(Theme.of(context).primaryColor) ==
            Brightness.dark
      ? Colors.white
      : Colors.black;

  String connectionText({int? lastUpdated}) => connectivityStatusKnown()
      ? isConnected()
            ? 'Verbindung fehlgeschlagen'
            : 'Offline${lastUpdated == null ? '' : ' - Stand von ${DateTime.fromMillisecondsSinceEpoch(lastUpdated).formatRelative()}'}'
      : 'Unbekannte Fehlerursache';

  @override
  Future<void> close() {
    WidgetsBinding.instance.removeObserver(this);
    _updateStream.cancel();
    return super.close();
  }
}
