import { __awaiter } from "tslib";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useCallback, useContext, useMemo, useRef } from 'react';
import clsx from 'clsx';
import { getScrollTop, preventDefault } from '../utils';
import { getScrollParent } from '../hooks/use-scroll-parent';
import useEventListener from '../hooks/use-event-listener';
import Loading from '../loading';
import { useSetState, useTouch, useUpdateEffect } from '../hooks';
import ConfigProviderContext from '../config-provider/ConfigProviderContext';
const DEFAULT_HEAD_HEIGHT = 50;
const TEXT_STATUS = ['pulling', 'loosing', 'success'];

const PullRefresh = props => {
  const {
    prefixCls,
    createNamespace,
    locale
  } = useContext(ConfigProviderContext);
  const [bem] = createNamespace('pull-refresh', prefixCls);
  const {
    disabled,
    animationDuration
  } = props;
  const root = useRef();
  const [state, updateState] = useSetState({
    refreshing: false,
    status: 'normal',
    distance: 0,
    duration: 0
  });
  const track = useRef();
  const reachTop = useRef(null);
  const touch = useTouch();

  const getHeadStyle = () => {
    if (props.headHeight !== DEFAULT_HEAD_HEIGHT) {
      return {
        height: `${props.headHeight}px`
      };
    }

    return null;
  };

  const isTouchable = useCallback(() => {
    return state.status !== 'loading' && state.status !== 'success' && !disabled;
  }, [state.status, disabled]);

  const ease = distance => {
    const pullDistance = +(props.pullDistance || props.headHeight);

    if (distance > pullDistance) {
      if (distance < pullDistance * 2) {
        distance = pullDistance + (distance - pullDistance) / 2;
      } else {
        distance = pullDistance * 1.5 + (distance - pullDistance * 2) / 4;
      }
    }

    return Math.round(distance);
  };

  const setStatus = (distance, isLoading) => {
    const pullDistance = +(props.pullDistance || props.headHeight);
    const newState = {
      distance
    };

    if (isLoading) {
      newState.status = 'loading';
    } else if (distance === 0) {
      newState.status = 'normal';
    } else if (distance < pullDistance) {
      newState.status = 'pulling';
    } else {
      newState.status = 'loosing';
    }

    updateState(newState);
  };

  const getStatusText = () => {
    if (state.status === 'normal') {
      return '';
    }

    return props[`${state.status}Text`] || locale.vanPullRefresh[state.status];
  };

  const renderStatus = () => {
    var _a;

    const {
      status,
      distance
    } = state;

    if (typeof props[`${state.status}Text`] === 'function') {
      return (_a = props[`${state.status}Text`]) === null || _a === void 0 ? void 0 : _a.call(props, {
        distance
      });
    }

    const nodes = [];

    if (TEXT_STATUS.includes(status)) {
      nodes.push(_jsx("div", Object.assign({
        className: clsx(bem('text'))
      }, {
        children: getStatusText()
      }), "text"));
    }

    if (status === 'loading') {
      nodes.push(_jsx(Loading, Object.assign({
        className: clsx(bem('loading'))
      }, {
        children: getStatusText()
      }), "loading"));
    }

    return nodes;
  };

  const showSuccessTip = () => {
    updateState({
      status: 'success'
    });
    setTimeout(() => {
      setStatus(0);
    }, +props.successDuration);
  };

  const onRefresh = () => __awaiter(void 0, void 0, void 0, function* () {
    try {
      updateState({
        refreshing: true
      });
      yield props.onRefresh();
      updateState({
        refreshing: false
      });
    } catch (error) {
      updateState({
        refreshing: false
      });
    }
  });

  const checkPosition = event => {
    const scrollTarget = getScrollParent(event.target);
    reachTop.current = getScrollTop(scrollTarget) === 0;

    if (reachTop.current) {
      updateState({
        duration: 0
      });
      touch.start(event);
    }
  };

  const onTouchStart = event => {
    if (isTouchable()) {
      checkPosition(event.nativeEvent);
    }
  };

  const onTouchMove = useCallback(event => {
    if (isTouchable()) {
      if (!reachTop.current) {
        checkPosition(event);
      }

      touch.move(event);

      if (reachTop.current && touch.deltaY.current >= 0 && touch.isVertical()) {
        setStatus(ease(touch.deltaY.current));
        preventDefault(event);
      } else {
        /**
         * IN THIS CASE:
         * if component don't rerender after event.preventDefault
         * ios will hold `preventDefault` behavior when touchmoving
         * it will cause window unscrollable
         */
        setStatus(0);
      }
    }
  }, // eslint-disable-next-line react-hooks/exhaustive-deps
  [reachTop.current, touch.deltaY.current, isTouchable]);

  const onTouchEnd = () => {
    if (reachTop.current && touch.deltaY && isTouchable()) {
      updateState({
        duration: +animationDuration
      });

      if (state.status === 'loosing') {
        setStatus(+props.headHeight, true);
        onRefresh();
      } else {
        setStatus(0);
      }
    }
  };

  useEventListener('touchmove', onTouchMove, {
    target: track.current,
    depends: [reachTop.current, isTouchable(), touch.deltaY]
  });
  useUpdateEffect(() => {
    updateState({
      duration: +animationDuration
    });

    if (state.refreshing) {
      setStatus(+props.headHeight, true);
    } else if (props.successText) {
      showSuccessTip();
    } else {
      setStatus(0, false);
    }
  }, [state.refreshing]);
  const trackStyle = useMemo(() => ({
    transitionDuration: `${state.duration}ms`,
    transform: state.distance ? `translate3d(0,${state.distance}px, 0)` : ''
  }), [state.duration, state.distance]);
  return _jsx("div", Object.assign({
    ref: root,
    className: clsx(props.className, bem()),
    style: props.style
  }, {
    children: _jsxs("div", Object.assign({
      ref: track,
      className: clsx(bem('track')),
      style: trackStyle,
      onTouchStart: onTouchStart,
      onTouchEnd: onTouchEnd,
      onTouchCancel: onTouchEnd
    }, {
      children: [_jsx("div", Object.assign({
        className: clsx(bem('head')),
        style: getHeadStyle()
      }, {
        children: renderStatus()
      })), props.children]
    }))
  }));
};

PullRefresh.defaultProps = {
  headHeight: 50,
  animationDuration: 300,
  successDuration: 500
};
export default PullRefresh;