import 'dart:async';

import 'package:flutter/material.dart';


mixin BannerCarouselMixin<T extends StatefulWidget>
    on State<T>, WidgetsBindingObserver {
  final PageController pageController = PageController();
  Timer? _autoPlayTimer;
  Timer? _resumeTimer;
  int currentIndex = 0;
  bool hovering = false;
  bool _manualPaused = false;
  bool _appPaused = false;
  bool _autoPageChangeInProgress = false;
  double? lastBannerWidth;
  bool _tickerVisible = true;

  static const autoInterval = Duration(seconds: 5);
  static const _manualPauseDuration = Duration(seconds: 8);
  static const slideDuration = Duration(milliseconds: 450);
  static const slideCurve = Curves.easeInOutCubic;

  int get itemCount;
  bool get isActive;
  void emitActiveItem();
  void precacheAdjacentSlides();

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!mounted) return;
      emitActiveItem();
      syncAutoPlay();
    });
  }

  @override
  void dispose() {
    _autoPlayTimer?.cancel();
    _resumeTimer?.cancel();
    pageController.dispose();
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    final visible = TickerMode.of(context);
    if (visible == _tickerVisible) return;
    _tickerVisible = visible;
    if (visible) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (!mounted || !_tickerVisible) return;
        emitActiveItem();
        precacheAdjacentSlides();
      });
      syncAutoPlay();
    } else {
      stopAutoPlay();
      _resumeTimer?.cancel();
      _manualPaused = false;
    }
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    final paused =
        state == AppLifecycleState.paused ||
        state == AppLifecycleState.inactive ||
        state == AppLifecycleState.hidden;
    if (paused == _appPaused) return;
    _appPaused = paused;
    paused ? stopAutoPlay() : syncAutoPlay();
  }

  bool get shouldAutoPlay =>
      mounted &&
      isActive &&
      _tickerVisible &&
      !_appPaused &&
      !hovering &&
      !_manualPaused &&
      itemCount > 1;

  void syncAutoPlay() {
    if (!shouldAutoPlay) {
      stopAutoPlay();
      return;
    }
    _autoPlayTimer ??= Timer.periodic(
      autoInterval,
      (_) => nextPage(manual: false),
    );
  }

  void stopAutoPlay() {
    _autoPlayTimer?.cancel();
    _autoPlayTimer = null;
  }


  void setHovering(bool value) {
    hovering = value;
    value ? stopAutoPlay() : syncAutoPlay();
    if (mounted) setState(() {});
  }

  void pauseForManual() {
    if (!_manualPaused) setState(() => _manualPaused = true);
    stopAutoPlay();
    _resumeTimer?.cancel();
    _resumeTimer = Timer(_manualPauseDuration, () {
      if (!mounted) return;
      setState(() => _manualPaused = false);
      syncAutoPlay();
    });
  }

  void onPageChanged(int index) {
    setState(() => currentIndex = index);
    emitActiveItem();
    precacheAdjacentSlides();
    if (_autoPageChangeInProgress) {
      _autoPageChangeInProgress = false;
      return;
    }
    pauseForManual();
  }

  void goToPage(int index) {
    if (index < 0 || index >= itemCount) return;
    if (!pageController.hasClients) return;
    pageController.animateToPage(
      index,
      duration: slideDuration,
      curve: slideCurve,
    );
    pauseForManual();
  }

  void nextPage({bool manual = true}) {
    if (itemCount <= 1) return;
    if (!pageController.hasClients) return;
    final nextIndex = (currentIndex + 1) % itemCount;
    if (!manual) _autoPageChangeInProgress = true;
    pageController.animateToPage(
      nextIndex,
      duration: slideDuration,
      curve: slideCurve,
    );
    if (manual) pauseForManual();
  }

  void prevPage() {
    if (itemCount <= 1) return;
    if (!pageController.hasClients) return;
    final previousIndex = (currentIndex - 1 + itemCount) % itemCount;
    pageController.animateToPage(
      previousIndex,
      duration: slideDuration,
      curve: slideCurve,
    );
    pauseForManual();
  }

  int? bannerCacheWidth() {
    final width = lastBannerWidth;
    if (width == null || !width.isFinite || width <= 0) return null;
    return (width * MediaQuery.devicePixelRatioOf(context)).ceil();
  }


  void resetCarouselItems() {
    stopAutoPlay();
    _resumeTimer?.cancel();
    _manualPaused = false;
    if (itemCount > 0 && pageController.hasClients) {
      pageController.jumpToPage(0);
    }
    currentIndex = 0;
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!mounted) return;
      emitActiveItem();
      precacheAdjacentSlides();
      syncAutoPlay();
    });
  }


  void activateCarousel() {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!mounted) return;
      emitActiveItem();
      precacheAdjacentSlides();
      syncAutoPlay();
    });
  }


  void deactivateCarousel() {
    stopAutoPlay();
    _resumeTimer?.cancel();
    _manualPaused = false;
  }
}
