import 'dart:math' as math;

import 'package:flutter/gestures.dart';
import 'package:flutter/physics.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';

import 'shop_scroll_coordinator.dart';

/// 滑动位置信息
class ShopScrollPosition extends ScrollPosition
    implements ScrollActivityDelegate {
  final ShopScrollCoordinator coordinator; // 协调器
  ScrollDragController? _currentDrag;
  double _heldPreviousVelocity = 0.0;

  ShopScrollPosition(
      {required ScrollPhysics physics,
      required ScrollContext context,
      double initialPixels = 0.0,
      bool keepScrollOffset = true,
      ScrollPosition? oldPosition,
      String? debugLabel,
      required this.coordinator})
      : super(
          physics: physics,
          context: context,
          keepScrollOffset: keepScrollOffset,
          oldPosition: oldPosition,
          debugLabel: debugLabel,
        ) {
    // 如果oldPosition不为null，则父级将首先调用Absorb()，它可以设置_pixels和_activity.
    if (!hasPixels && initialPixels != null) correctPixels(initialPixels);
    if (activity == null) goIdle();
    assert(activity != null);
  }

  @override
  AxisDirection get axisDirection => context.axisDirection;

  @override
  double setPixels(double newPixels) {
    assert(activity!.isScrolling);
    return super.setPixels(newPixels);
  }

  @override
  void absorb(ScrollPosition other) {
    super.absorb(other);
    if (other is! ScrollPositionWithSingleContext) {
      goIdle();
      return;
    }
    activity!.updateDelegate(this);
    final ShopScrollPosition typedOther = other as ShopScrollPosition;
    _userScrollDirection = typedOther._userScrollDirection;
    assert(_currentDrag == null);
    if (typedOther._currentDrag != null) {
      _currentDrag = typedOther._currentDrag;
      _currentDrag!.updateDelegate(this);
      typedOther._currentDrag = null;
    }
  }

  @override
  void applyNewDimensions() {
    super.applyNewDimensions();
    context.setCanDrag(physics.shouldAcceptUserOffset(this));
  }

  /// 返回未使用的增量。
  /// 正增量表示下降（在上方显示内容），负增量向上（在下方显示内容）。
  double applyClampedDragUpdate(double delta) {
    assert(delta != 0.0);
    // 如果我们要朝向maxScrollExtent（负滚动偏移），那么我们在minScrollExtent方向上
    // 可以达到的最大距离是负无穷大。 例如，如果我们已经过度滚动，则滚动以减少过度滚动不应禁止过度滚动。
    // 如果我们要朝minScrollExtent（正滚动偏移量）方向移动，那么我们在minScrollExtent方向
    // 上可以达到的最大距离是我们现在所处的位置（如果我们已经过度滚动）
    // （在这种情况下，像素小于minScrollExtent），或者如果minScrollExtent可以 我们不是。
    // 换句话说，我们不能通过applyClampedDragUpdate进入过滚动状态。
    // 尽管如此，可能通过多种方式进入了过度滚动的情况。 一种是物理是否允许通过
    // applyFullDragUpdate（请参见下文）。也可能会发生过度滚动的情况，例如 如果使用滚动控制器人工设置了滚动位置。
    final double min =
        delta < 0.0 ? -double.infinity : math.min(minScrollExtent, pixels);
    // max的逻辑是等效的，但反向。
    final double max =
        delta > 0.0 ? double.infinity : math.max(maxScrollExtent, pixels);
    final double oldPixels = pixels;
    final double newPixels = (pixels - delta).clamp(min, max) as double;
    final double clampedDelta = newPixels - pixels;
    if (clampedDelta == 0.0) return delta;
    final double overScroll = physics.applyBoundaryConditions(this, newPixels);
    final double actualNewPixels = newPixels - overScroll;
    final double offset = actualNewPixels - oldPixels;
    if (offset != 0.0) {
      forcePixels(actualNewPixels);
      didUpdateScrollPositionBy(offset);
    }
    return delta + offset;
  }

  // Returns the overScroll. 返回过度滚动。
  double applyFullDragUpdate(double delta) {
    assert(delta != 0.0);
    final double oldPixels = pixels;
    // Apply friction: 施加摩擦：
    final double newPixels =
        pixels - physics.applyPhysicsToUserOffset(this, delta);
    if (oldPixels == newPixels) return 0.0; // 增量一定很小，我们在添加浮点数时将其删除了
    // Check for overScroll: 检查过度滚动：
    final double overScroll = physics.applyBoundaryConditions(this, newPixels);
    final double actualNewPixels = newPixels - overScroll;
    if (actualNewPixels != oldPixels) {
      forcePixels(actualNewPixels);
      didUpdateScrollPositionBy(actualNewPixels - oldPixels);
    }
    return overScroll;
  }

  /// 当手指滑动时，该方法会获取到滑动距离
  /// [delta]滑动距离，正增量表示下滑，负增量向上滑
  /// 我们需要把子部件的 滑动数据 交给协调器处理，主部件无干扰
  @override
  void applyUserOffset(double delta) {
    ScrollDirection userScrollDirection =
        delta > 0.0 ? ScrollDirection.forward : ScrollDirection.reverse;
    if (debugLabel != coordinator.pageLabel)
      return coordinator.applyUserOffset(delta, userScrollDirection, this);
    updateUserScrollDirection(userScrollDirection);
    setPixels(pixels - physics.applyPhysicsToUserOffset(this, delta));
  }

  @override
  void beginActivity(ScrollActivity? newActivity) {
    _heldPreviousVelocity = 0.0;
    if (newActivity == null) return;
    assert(newActivity.delegate == this);
    super.beginActivity(newActivity);
    _currentDrag?.dispose();
    _currentDrag = null;
    if (!activity!.isScrolling) updateUserScrollDirection(ScrollDirection.idle);
  }

  /// 将[用户滚动方向]设置为给定值。
  /// 如果更改了该值，则将分派[User ScrollNotification]。
  @protected
  @visibleForTesting
  void updateUserScrollDirection(ScrollDirection value) {
    assert(value != null);
    if (userScrollDirection == value) return;
    _userScrollDirection = value;
    didUpdateScrollDirection(value);
  }

  @override
  ScrollHoldController hold(VoidCallback holdCancelCallback) {
    final double previousVelocity = activity!.velocity;
    final HoldScrollActivity holdActivity =
        HoldScrollActivity(delegate: this, onHoldCanceled: holdCancelCallback);
    beginActivity(holdActivity);
    _heldPreviousVelocity = previousVelocity;
    return holdActivity;
  }

  @override
  Drag drag(DragStartDetails details, VoidCallback dragCancelCallback) {
    final ScrollDragController drag = ScrollDragController(
      delegate: this,
      details: details,
      onDragCanceled: dragCancelCallback,
      carriedVelocity: physics.carriedMomentum(_heldPreviousVelocity),
      motionStartDistanceThreshold: physics.dragStartDistanceMotionThreshold,
    );
    beginActivity(DragScrollActivity(this, drag));
    assert(_currentDrag == null);
    _currentDrag = drag;
    return drag;
  }

  @override
  void goIdle() {
    beginActivity(IdleScrollActivity(this));
  }

  /// 以特定的速度开始一个物理驱动的模拟，该模拟确定[pixels]位置。
  /// 此方法遵从[ScrollPhysics.createBallisticSimulation]，该方法通常在当前位置超出
  /// 范围时提供滑动模拟，而在当前位置超出范围但具有非零速度时提供摩擦模拟。
  /// 速度应以每秒逻辑像素为单位。
  @override
  void goBallistic(double velocity, [bool fromCoordinator = false]) {
    if (debugLabel != coordinator.pageLabel) {
      if (velocity > 0.0) coordinator.goBallistic(velocity);
    } else {
      if (fromCoordinator && velocity <= 0.0) return;
      if (coordinator.pageExpand == PageExpandState.Expanding) return;
    }
    assert(pixels != null);
    final Simulation? simulation =
        physics.createBallisticSimulation(this, velocity);
    if (simulation != null) {
      beginActivity(BallisticScrollActivity(this, simulation, context.vsync, false));
    } else {
      goIdle();
    }
  }

  @override
  bool applyContentDimensions(double minScrollExtent, double maxScrollExtent,
      [bool fromCoordinator = false]) {
    if (debugLabel == coordinator.pageLabel && !fromCoordinator)
      return coordinator.applyContentDimensions(
          minScrollExtent, maxScrollExtent, this);
    return super.applyContentDimensions(minScrollExtent, maxScrollExtent);
  }

  @override
  ScrollDirection get userScrollDirection => _userScrollDirection;
  ScrollDirection _userScrollDirection = ScrollDirection.idle;

  @override
  Future<void> animateTo(
    double to, {
    required Duration duration,
    required Curve curve,
  }) {
    if (nearEqual(to, pixels, physics.tolerance.distance)) {
      // 跳过动画，直接移到我们已经靠近的位置。
      jumpTo(to);
      return Future<void>.value();
    }

    final DrivenScrollActivity activity = DrivenScrollActivity(
      this,
      from: pixels,
      to: to,
      duration: duration,
      curve: curve,
      vsync: context.vsync,
    );
    beginActivity(activity);
    return activity.done;
  }

  @override
  void jumpTo(double value) {
    goIdle();
    if (pixels != value) {
      final double oldPixels = pixels;
      forcePixels(value);
      notifyListeners();
      didStartScroll();
      didUpdateScrollPositionBy(pixels - oldPixels);
      didEndScroll();
    }
    goBallistic(0.0);
  }

  @Deprecated('This will lead to bugs.')
  @override
  void jumpToWithoutSettling(double value) {
    goIdle();
    if (pixels != value) {
      final double oldPixels = pixels;
      forcePixels(value);
      notifyListeners();
      didStartScroll();
      didUpdateScrollPositionBy(pixels - oldPixels);
      didEndScroll();
    }
  }

  @override
  void dispose() {
    _currentDrag?.dispose();
    _currentDrag = null;
    super.dispose();
  }

  @override
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('${context.runtimeType}');
    description.add('$physics');
    description.add('$activity');
    description.add('$userScrollDirection');
  }

  @override
  void pointerScroll(double delta) {
    // TODO: implement pointerScroll
  }
}