"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const throttle_debounce_1 = require("throttle-debounce");
const threshold_1 = require("../../utils/threshold");
class InfiniteScroll extends react_1.Component {
    constructor(props) {
        super(props);
        this.lastScrollTop = 0;
        this.actionTriggered = false;
        // variables to keep track of pull down behaviour
        this.startY = 0;
        this.currentY = 0;
        this.dragging = false;
        // will be populated in componentDidMount
        // based on the height of the pull down element
        this.maxPullDownDistance = 0;
        this.getScrollableTarget = () => {
            if (this.props.scrollableTarget instanceof HTMLElement)
                return this.props.scrollableTarget;
            if (typeof this.props.scrollableTarget === 'string') {
                return document.getElementById(this.props.scrollableTarget);
            }
            if (this.props.scrollableTarget === null) {
                console.warn(`You are trying to pass scrollableTarget but it is null. This might
        happen because the element may not have been added to DOM yet.
        See https://github.com/ankeetmaini/react-infinite-scroll-component/issues/59 for more info.
      `);
            }
            return null;
        };
        this.onStart = (evt) => {
            if (this.lastScrollTop)
                return;
            this.dragging = true;
            if (evt instanceof MouseEvent) {
                this.startY = evt.pageY;
            }
            else if (evt instanceof TouchEvent) {
                this.startY = evt.touches[0].pageY;
            }
            this.currentY = this.startY;
            if (this._infScroll) {
                this._infScroll.style.willChange = 'transform';
                this._infScroll.style.transition = `transform 0.2s cubic-bezier(0,0,0.31,1)`;
            }
        };
        this.onMove = (evt) => {
            if (!this.dragging)
                return;
            if (evt instanceof MouseEvent) {
                this.currentY = evt.pageY;
            }
            else if (evt instanceof TouchEvent) {
                this.currentY = evt.touches[0].pageY;
            }
            // user is scrolling down to up
            if (this.currentY < this.startY)
                return;
            if (this.currentY - this.startY >= Number(this.props.pullDownToRefreshThreshold)) {
                this.setState({
                    pullToRefreshThresholdBreached: true
                });
            }
            // so you can drag upto 1.5 times of the maxPullDownDistance
            if (this.currentY - this.startY > this.maxPullDownDistance * 1.5)
                return;
            if (this._infScroll) {
                this._infScroll.style.overflow = 'visible';
                this._infScroll.style.transform = `translate3d(0px, ${this.currentY - this.startY}px, 0px)`;
            }
        };
        this.onEnd = () => {
            this.startY = 0;
            this.currentY = 0;
            this.dragging = false;
            if (this.state.pullToRefreshThresholdBreached) {
                this.props.refreshFunction && this.props.refreshFunction();
                this.setState({
                    pullToRefreshThresholdBreached: false
                });
            }
            requestAnimationFrame(() => {
                // this._infScroll
                if (this._infScroll) {
                    this._infScroll.style.overflow = 'auto';
                    this._infScroll.style.transform = 'none';
                    this._infScroll.style.willChange = 'unset';
                }
            });
        };
        this.onScrollListener = (event) => {
            if (typeof this.props.onScroll === 'function') {
                // Execute this callback in next tick so that it does not affect the
                // functionality of the library.
                setTimeout(() => this.props.onScroll && this.props.onScroll(event), 0);
            }
            const target = this.props.height || this._scrollableNode
                ? event.target
                : document.documentElement.scrollTop
                    ? document.documentElement
                    : document.body;
            // return immediately if the action has already been triggered,
            // prevents multiple triggers.
            if (this.actionTriggered)
                return;
            const atBottom = this.isElementAtBottom(target, this.props.scrollThreshold);
            const atTop = this.isElementAtTop(target, this.props.scrollThreshold);
            // call the `next` function in the props to trigger the next data fetch
            if (atBottom && this.props.hasMoreNext) {
                this.actionTriggered = true;
                this.setState({ showLoaderNext: true });
                this.props.next && this.props.next();
            }
            // call the `next` function in the props to trigger the previous data fetch
            if (atTop && this.props.hasMorePrevious) {
                this.actionTriggered = true;
                this.setState({ showLoaderPrevious: true });
                this.props.previous && this.props.previous();
            }
            this.lastScrollTop = target.scrollTop;
        };
        this.state = {
            showLoaderNext: false,
            showLoaderPrevious: false,
            pullToRefreshThresholdBreached: false,
            prevDataLength: props.dataLength
        };
        this.throttledOnScrollListener = (0, throttle_debounce_1.throttle)(150, this.onScrollListener).bind(this);
        this.onStart = this.onStart.bind(this);
        this.onMove = this.onMove.bind(this);
        this.onEnd = this.onEnd.bind(this);
    }
    componentDidMount() {
        if (typeof this.props.dataLength === 'undefined') {
            throw new Error(`mandatory prop "dataLength" is missing. The prop is needed` + ` when loading more content. Check README.md for usage`);
        }
        this._scrollableNode = this.getScrollableTarget();
        this.el = this.props.height ? this._infScroll : this._scrollableNode || window;
        if (this.el) {
            this.el.addEventListener('scroll', this.throttledOnScrollListener);
        }
        if (typeof this.props.initialScrollY === 'number' &&
            this.el &&
            this.el instanceof HTMLElement &&
            this.el.scrollHeight > this.props.initialScrollY) {
            this.el.scrollTo(0, this.props.initialScrollY);
        }
        if (this.props.pullDownToRefresh && this.el) {
            this.el.addEventListener('touchstart', this.onStart);
            this.el.addEventListener('touchmove', this.onMove);
            this.el.addEventListener('touchend', this.onEnd);
            this.el.addEventListener('mousedown', this.onStart);
            this.el.addEventListener('mousemove', this.onMove);
            this.el.addEventListener('mouseup', this.onEnd);
            // get BCR of pullDown element to position it above
            this.maxPullDownDistance =
                (this._pullDown && this._pullDown.firstChild && this._pullDown.firstChild.getBoundingClientRect().height) || 0;
            this.forceUpdate();
            if (typeof this.props.refreshFunction !== 'function') {
                throw new Error(`Mandatory prop "refreshFunction" missing.
          Pull Down To Refresh functionality will not work
          as expected. Check README.md for usage'`);
            }
        }
    }
    componentWillUnmount() {
        if (this.el) {
            this.el.removeEventListener('scroll', this.throttledOnScrollListener);
            if (this.props.pullDownToRefresh) {
                this.el.removeEventListener('touchstart', this.onStart);
                this.el.removeEventListener('touchmove', this.onMove);
                this.el.removeEventListener('touchend', this.onEnd);
                this.el.removeEventListener('mousedown', this.onStart);
                this.el.removeEventListener('mousemove', this.onMove);
                this.el.removeEventListener('mouseup', this.onEnd);
            }
        }
    }
    componentDidUpdate(prevProps) {
        // do nothing when dataLength is unchanged
        if (this.props.dataLength === prevProps.dataLength)
            return;
        this.actionTriggered = false;
        // update state when new data was sent in
        this.setState({
            showLoaderPrevious: false,
            showLoaderNext: false
        });
    }
    static getDerivedStateFromProps(nextProps, prevState) {
        const dataLengthChanged = nextProps.dataLength !== prevState.prevDataLength;
        // reset when data changes
        if (dataLengthChanged) {
            return Object.assign(Object.assign({}, prevState), { prevDataLength: nextProps.dataLength });
        }
        return null;
    }
    isElementAtTop(target, scrollThreshold = 0.8) {
        const clientHeight = target === document.body || target === document.documentElement ? window.screen.availHeight : target.clientHeight;
        const threshold = (0, threshold_1.parseThreshold)(scrollThreshold);
        if (threshold.unit === threshold_1.ThresholdUnits.Pixel) {
            // return target.scrollTop <= threshold.value + clientHeight - target.scrollHeight + 1;
            return (target.scrollTop <= threshold.value &&
                Math.abs(this.lastScrollTop - target.scrollTop) >= threshold.value / 3 &&
                target.scrollTop < this.lastScrollTop);
        }
        else if (this.props.inverse) {
            return target.scrollTop <= threshold.value / 100 + clientHeight - target.scrollHeight + 1;
        }
        return target.scrollTop <= threshold.value / 100 + clientHeight && target.scrollTop < this.lastScrollTop;
    }
    isElementAtBottom(target, scrollThreshold = 0.8) {
        const clientHeight = target === document.body || target === document.documentElement ? window.screen.availHeight : target.clientHeight;
        const threshold = (0, threshold_1.parseThreshold)(scrollThreshold);
        if (threshold.unit === threshold_1.ThresholdUnits.Pixel) {
            return target.scrollTop + clientHeight >= target.scrollHeight - threshold.value;
        }
        return target.scrollTop + clientHeight >= (threshold.value / 100) * target.scrollHeight;
    }
    render() {
        const style = Object.assign({ height: this.props.height || 'auto', overflow: 'auto', WebkitOverflowScrolling: 'touch' }, this.props.style);
        const hasChildren = this.props.hasChildren || !!(this.props.children && this.props.children instanceof Array && this.props.children.length);
        // because heighted infiniteScroll visualy breaks
        // on drag down as overflow becomes visible
        const outerDivStyle = this.props.pullDownToRefresh && this.props.height ? { overflow: 'auto' } : {};
        return ((0, jsx_runtime_1.jsx)("div", Object.assign({ style: outerDivStyle, className: "infinite-scroll-component__outerdiv" }, { children: (0, jsx_runtime_1.jsxs)("div", Object.assign({ className: `infinite-scroll-component ${this.props.className || ''}`, ref: (infScroll) => (this._infScroll = infScroll), style: style }, { children: [this.props.header, this.props.pullDownToRefresh && ((0, jsx_runtime_1.jsx)("div", Object.assign({ style: { position: 'relative' }, ref: (pullDown) => (this._pullDown = pullDown) }, { children: (0, jsx_runtime_1.jsx)("div", Object.assign({ style: {
                                position: 'absolute',
                                left: '50%',
                                transform: 'translateX(-50%)',
                                zIndex: 1
                            } }, { children: this.state.pullToRefreshThresholdBreached ? this.props.releaseToRefreshContent : this.props.pullDownToRefreshContent })) }))), !this.props.inverse && this.state.showLoaderPrevious && this.props.hasMorePrevious && this.props.loaderPrevious, this.props.children, this.props.inverse && this.state.showLoaderPrevious && this.props.hasMorePrevious && this.props.loaderPrevious, !this.state.showLoaderNext && !hasChildren && this.props.hasMoreNext && this.props.loaderNext, this.state.showLoaderNext && this.props.hasMoreNext && this.props.loaderNext, !this.props.hasMoreNext && this.props.endMessage, this.props.footer] })) })));
    }
}
exports.default = InfiniteScroll;
