import * as tslib_1 from "tslib";
import * as React from "react";
import { useGestureResponder } from "react-gesture-responder";
import { animated, useSpring } from "react-spring";
import { useMeasure } from "./use-measure";
import useScrollLock from "use-scroll-lock";
import { usePrevious } from "./use-previous";
var Pager = function (_a, ref) {
    var children = _a.children, id = _a.id, index = _a.value, onRequestChange = _a.onRequestChange, _b = _a.focusOnChange, focusOnChange = _b === void 0 ? false : _b, _c = _a.enableScrollLock, enableScrollLock = _c === void 0 ? true : _c, _d = _a.enableGestures, enableGestures = _d === void 0 ? true : _d, _e = _a.enableMouse, enableMouse = _e === void 0 ? false : _e, _f = _a.lazyLoad, lazyLoad = _f === void 0 ? false : _f, onSetLazy = _a.onSetLazy, _g = _a.animationConfig, animationConfig = _g === void 0 ? { tension: 190, friction: 20, mass: 0.4 } : _g, onTerminationRequest = _a.onTerminationRequest, onMoveShouldSet = _a.onMoveShouldSet, style = _a.style, other = tslib_1.__rest(_a, ["children", "id", "value", "onRequestChange", "focusOnChange", "enableScrollLock", "enableGestures", "enableMouse", "lazyLoad", "onSetLazy", "animationConfig", "onTerminationRequest", "onMoveShouldSet", "style"]);
    var containerRef = React.useRef(null);
    var _h = React.useState(false), isDragging = _h[0], setIsDragging = _h[1];
    var _j = React.useState(function () { return new Set(onSetLazy ? onSetLazy(index) : [index]); }), loaded = _j[0], setLoaded = _j[1];
    var width = useMeasure(containerRef).width;
    var childrenRefs = React.useRef(new Map());
    var previousIndex = usePrevious(index);
    var shouldFocusRef = React.useRef(null);
    useScrollLock(isDragging && enableScrollLock);
    React.useEffect(function () {
        if (typeof previousIndex === "number" && previousIndex !== index) {
            shouldFocusRef.current = index;
        }
        else {
            shouldFocusRef.current = null;
        }
    }, [previousIndex, index]);
    function focusByIndex(i) {
        var el = childrenRefs.current.get(i);
        if (el) {
            el.focus();
        }
    }
    // expose an imperative focus function which focuses
    // the currently active index
    React.useImperativeHandle(ref, function () { return ({
        focus: function (i) {
            focusByIndex(i || index);
        }
    }); });
    var _k = useSpring(function () { return ({
        x: index * -100,
        config: animationConfig
    }); }), x = _k[0].x, set = _k[1];
    /**
     * Potentially autofocus after our animation
     */
    function onRest() {
        if (typeof shouldFocusRef.current === "number") {
            focusByIndex(shouldFocusRef.current);
        }
    }
    var renderableChildren = children.filter(function (child) { return child !== null; });
    // gesture view counts
    var childCount = renderableChildren.length;
    var maxIndex = childCount - 1;
    var minIndex = 0;
    /**
     * Prevent invalid indexes
     */
    function isValidNextIndex(index) {
        return index > 0 && index <= maxIndex;
    }
    /**
     * We keep a set of indexes that should
     * be loaded for lazy loading.
     */
    function addIndexToLoaded(index) {
        if (!isValidNextIndex(index)) {
            return;
        }
        var indexes = index;
        // allow the user to customize which indexes to load
        if (onSetLazy) {
            indexes = onSetLazy(index);
        }
        var indexesArray = Array.isArray(indexes) ? indexes : [indexes];
        var next = new Set(loaded);
        indexesArray.forEach(function (i) {
            // don't set items which are already loaded or are invalid
            if (loaded.has(i) || !isValidNextIndex(index)) {
                return;
            }
            next.add(i);
        });
        setLoaded(next);
    }
    // animate into position if our index changes
    React.useEffect(function () {
        set({
            x: index * -100,
            onRest: onRest
        });
        loaded.add(index);
    }, [index]);
    /**
     * Handle gesture end event (either touchend
     * or pan responder termination).
     */
    function releaseToPosition(x) {
        // if it's over 50% in either direction, move to that index.
        // otherwise, snap back to existing index.
        var threshold = width / 2;
        if (Math.abs(x) > threshold) {
            if (x < 0 && index < maxIndex) {
                onRequestChange(index + 1);
            }
            else if (x > 0 && index > minIndex) {
                onRequestChange(index - 1);
            }
            else {
                set({ x: index * -100 });
            }
        }
        else {
            // return back!
            set({ x: index * -100, onRest: onRest });
        }
    }
    function onTermination(_a) {
        var delta = _a.delta;
        setIsDragging(false);
        releaseToPosition(delta[0]);
    }
    function onEnd(_a) {
        var delta = _a.delta, velocity = _a.velocity, direction = _a.direction;
        var x = delta[0];
        setIsDragging(false);
        // 1. If the force is great enough, switch to the previous index
        if (velocity > 0.2 && direction[0] > 0 && index > minIndex) {
            return onRequestChange(index - 1);
        }
        // or the next index, depending on direction
        if (velocity > 0.2 && direction[0] < 0 && index < maxIndex) {
            return onRequestChange(index + 1);
        }
        releaseToPosition(x);
    }
    /**
     * Observe our pan-responder to enable gestures
     */
    var bind = useGestureResponder({
        onTerminationRequest: onTerminationRequest,
        onStartShouldSet: function () {
            if (!enableGestures) {
                return false;
            }
            return false;
        },
        onMoveShouldSet: function (state, e) {
            var initial = state.initial, xy = state.xy, initialDirection = state.initialDirection;
            if (!enableGestures) {
                return false;
            }
            var set = initialDirection[0] != 0;
            // allow the user to tap into this component to potentially
            // override it
            if (onMoveShouldSet) {
                return onMoveShouldSet(state, e, set);
            }
            return set;
        },
        onGrant: function () {
            setIsDragging(true);
        },
        onMove: function (_a) {
            var delta = _a.delta, direction = _a.direction;
            var x = delta[0];
            var xPos = (x / width) * 100 + index * -100;
            set({
                x: xPos,
                immediate: true,
                onRest: function () { }
            });
            // lazy load the item we are swiping towards
            addIndexToLoaded(direction[0] > 0 ? index - 1 : index + 1);
        },
        onRelease: onEnd,
        onTerminate: onTermination
    }, {
        uid: id,
        enableMouse: enableMouse
    }).bind;
    return (React.createElement("div", tslib_1.__assign({}, bind, { ref: containerRef, className: "Gesture-view", style: tslib_1.__assign({ display: "flex", flexDirection: "column", overflow: "hidden", width: "100%" }, style) }, other),
        React.createElement(animated.div, { className: "Gesture-view__animated-container", style: {
                flexDirection: "row",
                direction: "ltr",
                willChange: "transform",
                minHeight: 0,
                flex: 1,
                display: "flex",
                transform: x.interpolate(function (x) { return "translateX(" + taper(x, maxIndex * -100) + "%)"; })
            } }, renderableChildren.map(function (child, i) {
            var styles = {
                display: "flex",
                flexDirection: "column",
                width: "100%",
                alignSelf: "stretch",
                justifyContent: "flex-start",
                flexShrink: 0,
                height: "100%",
                overflow: "hidden",
                outline: "none"
            };
            var props = {
                key: i,
                tabIndex: index === i ? 0 : -1,
                style: styles,
                "aria-hidden": i !== index,
                ref: function (el) {
                    childrenRefs.current.set(i, el);
                }
            };
            var load = !lazyLoad || index === i || loaded.has(i);
            if (typeof child === "function") {
                return child(props, index === i, load);
            }
            return (React.createElement("div", tslib_1.__assign({ className: "Gesture-view__pane" }, props), load && child));
        }))));
};
export default React.forwardRef(Pager);
/**
 * Add some resistance when swiping in a direction
 * that doesn't contain another pane
 */
function taper(x, maxWidth) {
    if (x > 0) {
        return x * 0.3;
    }
    if (x < maxWidth) {
        var diff = x - maxWidth;
        return x - diff * 0.7;
    }
    return x;
}
