import React, { Component, } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { Timer } from '../../services/time';
// import { ICON_TYPES } from '../icon';
import { SoftGlobalToastListItem } from './SoftGlobalToastListItem';
import { SoftToast } from './SoftToast';
export const TOAST_FADE_OUT_MS = 250;
export class SoftGlobalToastList extends Component {
    constructor(props) {
        super(props);
        this.state = {
            toastIdToDismissedMap: {},
        };
        this.onMouseEnter = () => {
            // Stop scrolling to bottom if we're in mid-scroll, because the user wants to interact with
            // the list.
            this.isScrollingToBottom = false;
            this.isUserInteracting = true;
            // Don't let toasts dismiss themselves while the user is interacting with them.
            for (const toastId in this.toastIdToTimerMap) {
                if (this.toastIdToTimerMap.hasOwnProperty(toastId)) {
                    const timer = this.toastIdToTimerMap[toastId];
                    timer.pause();
                }
            }
        };
        this.onMouseLeave = () => {
            this.isUserInteracting = false;
            for (const toastId in this.toastIdToTimerMap) {
                if (this.toastIdToTimerMap.hasOwnProperty(toastId)) {
                    const timer = this.toastIdToTimerMap[toastId];
                    timer.resume();
                }
            }
        };
        this.onScroll = () => {
            this.isScrolledToBottom =
                this.listElement.scrollHeight - this.listElement.scrollTop === this.listElement.clientHeight;
        };
        this.scheduleAllToastsForDismissal = () => {
            this.props.toasts.forEach(toast => {
                if (!this.toastIdToTimerMap[toast.id]) {
                    this.scheduleToastForDismissal(toast);
                }
            });
        };
        this.scheduleToastForDismissal = (toast) => {
            // Start fading the toast out once its lifetime elapses.
            this.toastIdToTimerMap[toast.id] =
                new Timer(this.dismissToast.bind(this, toast), toast.toastLifeTimeMs != null ? toast.toastLifeTimeMs : this.props.toastLifeTimeMs);
        };
        this.dismissToast = (toast) => {
            // Remove the toast after it's done fading out.
            this.dismissTimeoutIds.push(setTimeout(() => {
                // Because this is wrapped in a setTimeout, and because React does not guarantee when
                // state updates happen, it is possible to double-dismiss a toast
                // including by double-clicking the "x" button on the toast
                // so, first check to make sure we haven't already dismissed this toast
                if (this.toastIdToTimerMap.hasOwnProperty(toast.id)) {
                    this.props.dismissToast(toast);
                    this.toastIdToTimerMap[toast.id].clear();
                    delete this.toastIdToTimerMap[toast.id];
                    this.setState(prevState => {
                        // @ts-ignore
                        const toastIdToDismissedMap = { ...prevState.toastIdToDismissedMap };
                        delete toastIdToDismissedMap[toast.id];
                        return {
                            toastIdToDismissedMap,
                        };
                    });
                }
            }, TOAST_FADE_OUT_MS));
            this.setState((prevState) => {
                const toastIdToDismissedMap = {
                    ...prevState.toastIdToDismissedMap,
                    [toast.id]: true,
                };
                return {
                    toastIdToDismissedMap,
                };
            });
        };
        this.dismissTimeoutIds = [];
        this.toastIdToTimerMap = {};
        this.isScrollingToBottom = false;
        this.isScrolledToBottom = true;
        // See [Return Value](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame#Return_value)
        // for information on initial value of 0
        this.isScrollingAnimationFrame = 0;
        this.startScrollingAnimationFrame = 0;
    }
    startScrollingToBottom() {
        this.isScrollingToBottom = true;
        const scrollToBottom = () => {
            // Although we cancel the requestAnimationFrame in componentWillUnmount,
            // it's possible for this.listElement to become null in the meantime
            if (!this.listElement)
                return;
            const position = this.listElement.scrollTop;
            const destination = this.listElement.scrollHeight - this.listElement.clientHeight;
            const distanceToDestination = destination - position;
            if (distanceToDestination < 5) {
                this.listElement.scrollTop = destination;
                this.isScrollingToBottom = false;
                this.isScrolledToBottom = true;
                return;
            }
            this.listElement.scrollTop = position + distanceToDestination * 0.25;
            if (this.isScrollingToBottom) {
                this.isScrollingAnimationFrame = window.requestAnimationFrame(scrollToBottom);
            }
        };
        this.startScrollingAnimationFrame = window.requestAnimationFrame(scrollToBottom);
    }
    componentDidMount() {
        this.listElement.addEventListener('scroll', this.onScroll);
        this.listElement.addEventListener('mouseenter', this.onMouseEnter);
        this.listElement.addEventListener('mouseleave', this.onMouseLeave);
        this.scheduleAllToastsForDismissal();
    }
    componentDidUpdate(prevProps) {
        this.scheduleAllToastsForDismissal();
        if (!this.isUserInteracting) {
            // If the user has scrolled up the toast list then we don't want to annoy them by scrolling
            // all the way back to the bottom.
            if (this.isScrolledToBottom) {
                if (prevProps.toasts.length < this.props.toasts.length) {
                    this.startScrollingToBottom();
                }
            }
        }
    }
    componentWillUnmount() {
        if (this.isScrollingAnimationFrame !== 0) {
            window.cancelAnimationFrame(this.isScrollingAnimationFrame);
        }
        if (this.startScrollingAnimationFrame !== 0) {
            window.cancelAnimationFrame(this.startScrollingAnimationFrame);
        }
        this.listElement.removeEventListener('scroll', this.onScroll);
        this.listElement.removeEventListener('mouseenter', this.onMouseEnter);
        this.listElement.removeEventListener('mouseleave', this.onMouseLeave);
        this.dismissTimeoutIds.forEach(clearTimeout);
        for (const toastId in this.toastIdToTimerMap) {
            if (this.toastIdToTimerMap.hasOwnProperty(toastId)) {
                const timer = this.toastIdToTimerMap[toastId];
                timer.clear();
            }
        }
    }
    render() {
        const { className, toasts, dismissToast, // eslint-disable-line no-unused-vars
        toastLifeTimeMs, // eslint-disable-line no-unused-vars
        ...rest } = this.props;
        const renderedToasts = toasts.map(toast => {
            const { text, toastLifeTimeMs, // eslint-disable-line no-unused-vars
            ...rest } = toast;
            return (React.createElement(SoftGlobalToastListItem, { key: toast.id, isDismissed: this.state.toastIdToDismissedMap[toast.id] },
                React.createElement(SoftToast, Object.assign({ onClose: this.dismissToast.bind(this, toast), onFocus: this.onMouseEnter, onBlur: this.onMouseLeave }, rest), text)));
        });
        const classes = classNames('softGlobalToastList', className);
        return (React.createElement("div", Object.assign({ ref: element => {
                this.listElement = element;
            }, className: classes }, rest), renderedToasts));
    }
}
SoftGlobalToastList.propTypes = {
    className: PropTypes.string,
    toasts: PropTypes.arrayOf(PropTypes.shape({
        id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
        title: PropTypes.string,
        text: PropTypes.node,
        color: PropTypes.string,
        // iconType: PropTypes.oneOf(ICON_TYPES),
        toastLifeTimeMs: PropTypes.number,
    }).isRequired),
    dismissToast: PropTypes.func.isRequired,
    toastLifeTimeMs: PropTypes.number.isRequired,
};
SoftGlobalToastList.defaultProps = {
    toasts: [],
};
//# sourceMappingURL=SoftGlobalToastList.js.map