import 'dart:io';

import 'package:flutter/material.dart';

class AppNavigator {
  AppNavigator._();

  static final navigatorKey = GlobalKey<NavigatorState>();
  static final Set<String> _pushingRoutes = <String>{};

  static BuildContext? get context => navigatorKey.currentContext;

  static Future<T?> push<T>(
    Widget page, {
    BuildContext? context,
    Object? arguments,
    String? name,
    bool preventDuplicate = true,
  }) async {
    final routeName = name ?? page.runtimeType.toString();
    if (preventDuplicate && RouteTracker.contains(routeName)) {
      return null;
    }
    if (_pushingRoutes.contains(routeName)) {
      return null;
    }

    _pushingRoutes.add(routeName);
    final nav = Navigator.of(
      context ?? AppNavigator.context!,
      rootNavigator: true,
    );
    try {
      return await nav.push<T>(
        SlidePageRoute(
          builder: (_) => page,
          settings: RouteSettings(name: routeName, arguments: arguments),
        ),
      );
    } finally {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        _pushingRoutes.remove(routeName);
      });
    }
  }

  static Future<T?> pushReplacement<T, TO>(
    Widget page, {
    BuildContext? context,
    Object? arguments,
    String? name,
  }) {
    return Navigator.of(
      context ?? AppNavigator.context!,
      rootNavigator: true,
    ).pushReplacement<T, TO>(
      SlidePageRoute(
        builder: (_) => page,
        settings: RouteSettings(name: name, arguments: arguments),
      ),
    );
  }

  static Future<T?> pushAndRemoveUntil<T>(
    Widget page, {
    BuildContext? context,
    RoutePredicate? predicate,
    Object? arguments,
    String? name,
  }) {
    return Navigator.of(
      context ?? AppNavigator.context!,
      rootNavigator: true,
    ).pushAndRemoveUntil<T>(
      SlidePageRoute(
        builder: (_) => page,
        settings: RouteSettings(name: name, arguments: arguments),
      ),
      predicate ?? (_) => false,
    );
  }

  static Future<T?> pushTransparent<T>(
    Widget page, {
    BuildContext? context,
    Color barrierColor = Colors.black54,
    Duration duration = const Duration(milliseconds: 200),
    bool dismissible = true,
  }) {
    return Navigator.of(context ?? AppNavigator.context!).push<T>(
      PageRouteBuilder<T>(
        opaque: false,
        barrierColor: barrierColor,
        barrierDismissible: dismissible,
        transitionDuration: duration,
        reverseTransitionDuration: duration,
        pageBuilder: (_, _, _) => page,
        transitionsBuilder: (_, animation, _, child) {
          return FadeTransition(opacity: animation, child: child);
        },
      ),
    );
  }

  static void pop<T extends Object?>({BuildContext? context, T? result}) {
    Navigator.maybePop(context ?? AppNavigator.context!);
  }

  static void popTimes({BuildContext? context, int count = 1}) {
    var popped = 0;
    Navigator.of(context ?? AppNavigator.context!).popUntil((route) {
      if (popped < count) {
        popped++;
        return false;
      }
      return true;
    });
  }
}

class SlidePageRoute<T> extends MaterialPageRoute<T> {
  SlidePageRoute({required super.builder, super.settings});

  @override
  Widget buildTransitions(
    BuildContext context,
    Animation<double> animation,
    Animation<double> secondaryAnimation,
    Widget child,
  ) {
    if (Platform.isAndroid) {
      final tween = Tween(
        begin: const Offset(1, 0),
        end: Offset.zero,
      ).chain(CurveTween(curve: Curves.easeOutCubic));
      return SlideTransition(position: animation.drive(tween), child: child);
    }

    return Theme.of(context).pageTransitionsTheme.buildTransitions<T>(
      this,
      context,
      animation,
      secondaryAnimation,
      child,
    );
  }
}

class RouteTracker extends NavigatorObserver {
  static final List<String?> pageStack = [];

  bool _isPage(Route<dynamic> route) => route is PageRoute;

  @override
  void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
    if (_isPage(route)) {
      pageStack.add(route.settings.name);
    }
  }

  @override
  void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
    if (_isPage(route) && pageStack.isNotEmpty) {
      pageStack.removeLast();
    }
  }

  @override
  void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) {
    if (_isPage(route)) {
      pageStack.remove(route.settings.name);
    }
  }

  @override
  void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) {
    if (oldRoute != null && _isPage(oldRoute) && pageStack.isNotEmpty) {
      pageStack.removeLast();
    }
    if (newRoute != null && _isPage(newRoute)) {
      pageStack.add(newRoute.settings.name);
    }
  }

  static bool contains(String name) => pageStack.contains(name);
}
