import 'dart:collection';

import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:get/get_navigation/src/routes/observers/route_observer.dart';
import 'package:guru_utils/controller/base_controller.dart';
import 'package:guru_utils/controller/controller.dart';
import 'package:guru_utils/extensions/extensions.dart';
import 'package:guru_utils/log/log.dart';
import 'package:guru_applifecycle_flutter/guru_applifecycle.dart';
import 'package:guru_utils/router/router.dart';

/// Created by Haoyi on 2022/7/18

class LifecycleSnapshot {
  final LifecycleState state;
  final bool foreground;
  final Routing routing;

  const LifecycleSnapshot(this.state, this.foreground, this.routing);

  static final LifecycleSnapshot invalid =
      LifecycleSnapshot(LifecycleState.create, false, Routing());

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is LifecycleSnapshot &&
          runtimeType == other.runtimeType &&
          state == other.state &&
          foreground == other.foreground &&
          routing == other.routing;

  @override
  int get hashCode => state.hashCode ^ foreground.hashCode ^ routing.hashCode;

  @override
  String toString() {
    return 'LifecycleSnapshot{state: $state, foreground: $foreground, routing: ${routing.current}}';
  }
}

mixin LifecycleObserver on BaseController {
  void onLifecycleChanged(LifecycleSnapshot snapshot) {}
}

class LifecycleManager with AppLifecycleCallback, WidgetsBindingObserver {
  static final LifecycleManager instance = LifecycleManager._();

  final BehaviorSubject<bool> foregroundSubject = BehaviorSubject.seeded(true);

  final BehaviorSubject<LifecycleState> lifecycleState =
      BehaviorSubject.seeded(LifecycleState.create);

  final BehaviorSubject<LifecycleSnapshot> lifecycleSnapshotSubject = BehaviorSubject();

  LifecycleSnapshot get currentLifecycleSnapshot => lifecycleSnapshotSubject.value;

  final PublishSubject<LifecycleEvent> lifecycleEventSubject = PublishSubject();

  final DoubleLinkedQueue<LifecycleObserver> _observer = DoubleLinkedQueue();

  final DoubleLinkedQueue<LifecycleObserver> _pendingObserver = DoubleLinkedQueue();

  Stream<bool> get observableAppLifecycle => foregroundSubject.stream;

  Stream<LifecycleEvent> get observableLifecycleEvent => lifecycleEventSubject.stream;

  static const debounceDuration = Duration(milliseconds: 50);

  static final verifyLifecycleDuration = debounceDuration * 2;

  bool isAppForeground() => foregroundSubject.value;

  LifecycleManager._() {
    if (kIsWeb) {
      onAppForeground();
    } else {
      listenApplifecycle();
    }
    WidgetsBinding.instance.addObserver(this);
  }

  void init() async {
    final state = WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed
        ? LifecycleState.resumed
        : LifecycleState.paused;
    lifecycleState.addEx(state);
    final foreground = LifecycleManager.instance.isAppForeground();
    final routing = RoutingObserver.currentRouting;
    onLifecycleChanged(LifecycleSnapshot(state, foreground, routing));

    final lifecycleStream = Rx.combineLatest3<LifecycleState, bool, Routing, LifecycleSnapshot>(
        lifecycleState.stream,
        LifecycleManager.instance.observableAppLifecycle,
        RoutingObserver.observableRouting,
        (state, foreground, routing) => LifecycleSnapshot(state, foreground, routing));

    lifecycleStream.debounceTime(debounceDuration).listen((info) {
      Log.w("[$runtimeType] lifecycle changed!! $info");
      onLifecycleChanged(info);
    });
  }

  void postEvent(LifecycleEvent event) {
    lifecycleEventSubject.addEx(event);
  }

  void addLifecycleObserver(LifecycleObserver observer) {
    _pendingObserver.add(observer);
    // _observer.addFirst(observer);
  }

  void removeLifecycleObserver(LifecycleObserver observer) {
    if (!_observer.remove(observer)) {
      _pendingObserver.remove(observer);
    }
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    Log.d("[$runtimeType] didChangeAppLifecycleState:$state");
    if (state == AppLifecycleState.resumed) {
      lifecycleState.addEx(LifecycleState.resumed);
    } else if (state != AppLifecycleState.resumed) {
      lifecycleState.addIfChanged(LifecycleState.paused);
    }
  }

  @override
  void onAppBackground() {
    Log.i("onAppBackground", tag: "Lifecycle");
    // _foreground = false;
    foregroundSubject.addIfChanged(false);
  }

  @override
  void onAppForeground() {
    Log.i("onAppForeground", tag: "Lifecycle");
    // _foreground = true;
    foregroundSubject.addIfChanged(true);
  }

  void onLifecycleChanged(LifecycleSnapshot snapshot) {
    Log.w("[$runtimeType] onLifecycleChanged:$snapshot");
    lifecycleSnapshotSubject.addEx(snapshot);
    for (var observer in _observer) {
      observer.onLifecycleChanged(snapshot);
    }
    if (_pendingObserver.isNotEmpty) {
      while (_pendingObserver.isNotEmpty) {
        final observer = _pendingObserver.removeFirst();
        observer.onLifecycleChanged(snapshot);
        _observer.addFirst(observer);
      }
    }
  }
}
