import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/gestures.dart' show DragStartBehavior;

class LLSTabBarView extends StatefulWidget {
  const LLSTabBarView({
    Key? key,
    required this.children,
    required this.controller,
    this.physics,
    this.height,
    this.dragStartBehavior = DragStartBehavior.start,
  }) : super(key: key);

  final TabController? controller;

  final List<Widget> children;

  final ScrollPhysics? physics;

  final DragStartBehavior dragStartBehavior;

  final double? height;

  @override
  _LLSTabBarViewState createState() => _LLSTabBarViewState();
}

final PageScrollPhysics _kLLSTabBarViewPhysics =
    const PageScrollPhysics().applyTo(const ClampingScrollPhysics());

class _LLSTabBarViewState extends State<LLSTabBarView> {
  TabController? _controller;
  PageController? _pageController;
  List<Widget>? _children;
  List<Widget>? _childrenWithKey;
  int? _currentIndex;
  int _warpUnderwayCount = 0;

  bool get _controllerIsValid => _controller?.animation != null;

  void _updateTabController() {
    final TabController? newController =
        widget.controller ?? DefaultTabController.of(context);
    assert(() {
      if (newController == null) {
        throw FlutterError('No TabController for ${widget.runtimeType}.\n'
            'When creating a ${widget.runtimeType}, you must either provide an explicit '
            'TabController using the "controller" property, or you must ensure that there '
            'is a DefaultTabController above the ${widget.runtimeType}.\n'
            'In this case, there was neither an explicit controller nor a default controller.');
      }
      return true;
    }());
    if (newController == _controller) {
      return;
    }
    if (_controllerIsValid) {
      _controller?.animation?.removeListener(_handleTabControllerAnimationTick);
    }
    _controller = newController;
    if (_controller != null) {
      _controller?.animation?.addListener(_handleTabControllerAnimationTick);
    }
  }

  @override
  void initState() {
    super.initState();
    _updateChildren();
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _updateTabController();
    _currentIndex = _controller?.index;
    _pageController = PageController(initialPage: _currentIndex ?? 0);
  }

  @override
  void didUpdateWidget(LLSTabBarView oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.controller != oldWidget.controller) {
      _updateTabController();
    }
    if (widget.children != oldWidget.children && _warpUnderwayCount == 0) {
      _updateChildren();
    }
  }

  @override
  void dispose() {
    if (_controllerIsValid) {
      _controller?.animation?.removeListener(_handleTabControllerAnimationTick);
    }
    _controller = null;
    // We don't own the _controller Animation, so it's not disposed here.
    super.dispose();
  }

  void _updateChildren() {
    _children = widget.children;
    _childrenWithKey = KeyedSubtree.ensureUniqueKeysForList(widget.children);
  }

  void _handleTabControllerAnimationTick() {
    if (_controller != null) {
      if (_warpUnderwayCount > 0 || !_controller!.indexIsChanging) {
        return;
      } // This widget is driving the controller's animation.
      if (_controller!.index != _currentIndex) {
        _currentIndex = _controller!.index;
        _warpToCurrentIndex();
      }
    }
  }

  Future<void> _warpToCurrentIndex() async {
    if (!mounted || _pageController == null || _currentIndex == null) {
      return Future<void>.value();
    }

    if (_pageController!.page == _currentIndex!.toDouble()) {
      return Future<void>.value();
    }

    final int previousIndex = _controller!.previousIndex;
    if ((_currentIndex! - previousIndex).abs() == 1) {
      return _pageController?.animateToPage(_currentIndex!,
          duration: kTabScrollDuration, curve: Curves.ease);
    }

    assert((_currentIndex! - previousIndex).abs() > 1);
    final int initialPage = _currentIndex! > previousIndex
        ? _currentIndex! - 1
        : _currentIndex! + 1;
    final List<Widget>? originalChildren = _childrenWithKey;
    setState(() {
      _warpUnderwayCount += 1;
      _childrenWithKey = List<Widget>.from(_childrenWithKey!, growable: false);
      final Widget temp = _childrenWithKey![initialPage];
      _childrenWithKey![initialPage] = _childrenWithKey![previousIndex];
      _childrenWithKey![previousIndex] = temp;
    });
    _pageController?.jumpToPage(initialPage);

    await _pageController?.animateToPage(_currentIndex!,
        duration: kTabScrollDuration, curve: Curves.ease);
    if (!mounted) {
      return Future<void>.value();
    }
    setState(() {
      _warpUnderwayCount -= 1;
      if (widget.children != _children) {
        _updateChildren();
      } else {
        _childrenWithKey = originalChildren;
      }
    });
  }

  // Called when the PageView scrolls
  bool _handleScrollNotification(ScrollNotification notification) {
    if (_warpUnderwayCount > 0) {
      return false;
    }
    if (notification.depth != 0) {
      return false;
    }
    if (_controller == null ||
        _pageController == null ||
        _pageController?.page != null ||
        _controller?.index == null) {
      return false;
    }

    _warpUnderwayCount += 1;
    if (notification is ScrollUpdateNotification &&
        !_controller!.indexIsChanging) {
      if ((_pageController!.page! - _controller!.index).abs() > 1.0) {
        _controller!.index = _pageController!.page!.floor();
        _currentIndex = _controller!.index;
      }
      _controller!.offset =
          (_pageController!.page! - _controller!.index).clamp(-1.0, 1.0);
    } else if (notification is ScrollEndNotification) {
      _controller!.index = _pageController!.page!.round();
      _currentIndex = _controller!.index;
    }
    _warpUnderwayCount -= 1;
    return false;
  }

  @override
  Widget build(BuildContext context) {
    assert(() {
      if (_controller?.length != widget.children.length) {
        throw FlutterError(
            'Controller\'s length property (${_controller?.length}) does not match the \n'
            'number of tabs (${widget.children.length}) present in TabBar\'s tabs property.');
      }
      return true;
    }());
    return NotificationListener<ScrollNotification>(
      onNotification: _handleScrollNotification,
      child: Container(
        height: widget.height ?? MediaQuery.of(context).size.height,
        child: PageView(
          dragStartBehavior: widget.dragStartBehavior,
          controller: _pageController,
          physics: widget.physics == null
              ? _kLLSTabBarViewPhysics
              : _kLLSTabBarViewPhysics.applyTo(widget.physics),
          children: _childrenWithKey!,
        ),
      ),
    );
  }
}
