import React, { Children, PureComponent } from 'react';
import { st, classes, vars } from './CarouselWIP.st.css.js';
import { ChevronLeftSmall, ChevronRightSmall } from '@wix/wix-ui-icons-common';
import Loader from '../Loader';
import Control from './Control';
import Slide from './Slide';
import deprecationLog from '../utils/deprecationLog';
import { CONTROLS_START_END, SLIDING_TYPE, ALIGNMENT, DATA_HOOKS, AUTOPLAY_SPEED, SWIPE_THRESHOLD, } from './constants';
import { isWhollyInView, animate, nop, normalizeIndex } from './utils';
/**
 * The carousel component creates a slideshow for cycling through a series of content.
 * @deprecated CarouselWIP is deprecated and will be removed in the next major version. Please migrate to Carousel which now supports all CarouselWIP features. See the migration guide under Deprecated/CarouselWIP in Storybook.
 */
class CarouselWIP extends PureComponent {
    constructor(props) {
        super(props);
        this.loadingImagesCount = 0;
        this.childCount = 0;
        this.autoplayTimer = -1;
        this.dragStartX = null;
        this.initialScrollLeft = 0;
        this.capturedPointerId = null;
        this.hasSetPointerCapture = false;
        // Need to wait for images to load so we know which images are visible
        // Adding onLoad and onError callbacks to all images under the component
        this._setImagesOnLoadHandlers = () => {
            Array.from(this.carousel.children).forEach(child => {
                const childImages = Array.from(child.getElementsByTagName('img'));
                childImages.forEach(img => {
                    if (img.complete) {
                        return;
                    }
                    this.setState({ isLoading: true });
                    this.loadingImagesCount++;
                    img.onload = this._onImageLoad;
                    img.onerror = this._onImageLoad;
                });
            });
        };
        this._updateChildCount = () => {
            const { images } = this.props;
            this.childCount = this.carousel?.children?.length ?? images?.length ?? 0;
        };
        this._onImageLoad = () => {
            const { initialSlideIndex } = this.props;
            this.loadingImagesCount--;
            if (!this.loadingImagesCount) {
                this.setState({ isLoading: false });
                this._slideTo({
                    index: initialSlideIndex,
                    immediate: true,
                }).catch(nop);
            }
        };
        this._setAutoplayTimer = (active) => {
            clearInterval(this.autoplayTimer);
            if (active) {
                this.autoplayTimer = window.setInterval(this._next, AUTOPLAY_SPEED);
            }
        };
        this._setVisibleSlides = () => {
            const { props, carousel, childCount } = this;
            const { infinite } = props;
            const firstVisibleChild = Math.max(Array.from(carousel.children).findIndex(child => isWhollyInView(carousel)(child)), 0);
            const lastVisibleChild = Math.max(Array.from(carousel.children).findIndex((child, i, children) => isWhollyInView(carousel)(child) &&
                (i === children.length - 1 ||
                    !isWhollyInView(carousel)(children[i + 1]))), 0);
            this.setState({
                visibleSlides: [firstVisibleChild, lastVisibleChild],
                isLeftArrowDisabled: !infinite && firstVisibleChild === 0,
                isRightArrowDisabled: !infinite && lastVisibleChild === childCount - 1,
                isShowStartGradient: firstVisibleChild > 0,
                isShowEndGradient: lastVisibleChild < childCount - 1,
            });
        };
        this._slideTo = ({ index = 0, alignTo = ALIGNMENT.LEFT, immediate } = {
            index: 0,
            alignTo: ALIGNMENT.LEFT,
            immediate: false,
        }) => {
            if (this.childCount === 0) {
                return Promise.reject('No children to slide to');
            }
            if (!this.carousel) {
                return Promise.reject('The Carousel is not mounted');
            }
            const { afterChange, beforeChange, easing, animationDuration: duration, infinite, startEndOffset = 0, } = this.props;
            const { children, scrollLeft, offsetWidth } = this.carousel;
            const slideIndex = normalizeIndex(index, this.childCount, infinite);
            const { visibleSlides } = this.state;
            const [firstVisibleSlide] = visibleSlides;
            let delta;
            const child = children[slideIndex];
            if (alignTo === ALIGNMENT.RIGHT) {
                delta =
                    child.offsetWidth -
                        (offsetWidth - child.offsetLeft) -
                        scrollLeft +
                        startEndOffset;
            }
            else {
                delta = child.offsetLeft - scrollLeft - startEndOffset;
            }
            if (firstVisibleSlide !== slideIndex && beforeChange) {
                beforeChange(firstVisibleSlide, index);
            }
            this.setState({ isAnimating: true });
            return new Promise((res, _) => {
                if (immediate) {
                    this.carousel.scrollLeft = child.offsetLeft;
                    return res();
                }
                else {
                    const prop = 'scrollLeft';
                    return res(animate(this.carousel, {
                        prop,
                        delta,
                        easing,
                        duration,
                    }));
                }
            })
                .then(() => {
                this.setState({ isAnimating: false });
                this._setVisibleSlides();
                if (firstVisibleSlide !== slideIndex && afterChange) {
                    return afterChange(slideIndex);
                }
            })
                .catch(_ => {
                this._setVisibleSlides();
                this.setState({ isAnimating: false });
            });
        };
        this._next = () => {
            const { slidingType, infinite } = this.props;
            const { visibleSlides } = this.state;
            const [firstVisibleSlide, lastVisibleSlide] = visibleSlides;
            let nextSlide, alignTo;
            if ([SLIDING_TYPE.REVEAL_CHUNK, SLIDING_TYPE.REVEAL_ONE].includes(slidingType)) {
                if (lastVisibleSlide === this.childCount - 1) {
                    nextSlide = infinite ? 0 : lastVisibleSlide;
                }
                else {
                    nextSlide = lastVisibleSlide + 1;
                }
                alignTo =
                    slidingType === SLIDING_TYPE.REVEAL_CHUNK
                        ? ALIGNMENT.LEFT
                        : ALIGNMENT.RIGHT;
            }
            else {
                if (firstVisibleSlide === this.childCount - 1) {
                    nextSlide = infinite ? 0 : firstVisibleSlide;
                }
                else {
                    nextSlide = firstVisibleSlide + 1;
                }
                alignTo = ALIGNMENT.LEFT;
            }
            if (nextSlide === this.childCount - 1) {
                this.setState({ isRightArrowDisabled: true, isShowEndGradient: false });
            }
            if (firstVisibleSlide === 0) {
                this.setState({ isLeftArrowDisabled: false, isShowStartGradient: true });
            }
            return this._slideTo({ index: nextSlide, alignTo });
        };
        this._prev = () => {
            const { slidingType, infinite } = this.props;
            const { visibleSlides } = this.state;
            const [firstVisibleSlide, lastVisibleSlide] = visibleSlides;
            let prevSlide, alignTo;
            if ([SLIDING_TYPE.REVEAL_CHUNK, SLIDING_TYPE.REVEAL_ONE].includes(slidingType)) {
                if (firstVisibleSlide === 0) {
                    prevSlide = infinite ? this.childCount - 1 : firstVisibleSlide;
                }
                else {
                    prevSlide = firstVisibleSlide - 1;
                }
                alignTo =
                    slidingType === SLIDING_TYPE.REVEAL_CHUNK
                        ? ALIGNMENT.RIGHT
                        : ALIGNMENT.LEFT;
            }
            else {
                if (firstVisibleSlide === 0) {
                    prevSlide = infinite ? this.childCount - 1 : 0;
                }
                else {
                    prevSlide = firstVisibleSlide - 1;
                }
                alignTo = ALIGNMENT.LEFT;
            }
            if (prevSlide === 0) {
                this.setState({ isLeftArrowDisabled: true, isShowStartGradient: false });
            }
            if (lastVisibleSlide === this.childCount - 1) {
                this.setState({ isRightArrowDisabled: false, isShowEndGradient: true });
            }
            return this._slideTo({ index: prevSlide, alignTo });
        };
        this._setRef = (r) => {
            this.carousel = r;
        };
        this._renderLeftControl = () => {
            const { isLeftArrowDisabled } = this.state;
            const { controlsPosition, controlsStartEnd, controlsSize, controlsSkin } = this.props;
            return (controlsPosition !== 'none' &&
                (!isLeftArrowDisabled ||
                    controlsStartEnd === CONTROLS_START_END.DISABLED) && (React.createElement(Control, { dataHook: DATA_HOOKS.prevButton, onClick: this._prev, icon: React.createElement(ChevronLeftSmall, null), size: controlsSize, skin: controlsSkin, disabled: isLeftArrowDisabled, className: st(classes.control, classes.prev) })));
        };
        this._renderRightControl = () => {
            const { isRightArrowDisabled } = this.state;
            const { controlsPosition, controlsStartEnd, controlsSize, controlsSkin } = this.props;
            return (controlsPosition !== 'none' &&
                (!isRightArrowDisabled ||
                    controlsStartEnd === CONTROLS_START_END.DISABLED) && (React.createElement(Control, { dataHook: DATA_HOOKS.nextButton, onClick: this._next, icon: React.createElement(ChevronRightSmall, null), size: controlsSize, skin: controlsSkin, disabled: isRightArrowDisabled, className: st(classes.control, classes.next) })));
        };
        this._handlePointerDown = (e) => {
            if (!e.isPrimary)
                return;
            e.preventDefault();
            this.dragStartX = e.clientX;
            this.initialScrollLeft = this.carousel.scrollLeft;
            this.capturedPointerId = e.pointerId;
            this.setState({
                isDragging: true,
            });
        };
        this.requestAnimationFrameId = null;
        this._handlePointerMove = (e) => {
            const { isDragging } = this.state;
            if (!isDragging ||
                !this.dragStartX ||
                e.pointerId !== this.capturedPointerId) {
                return;
            }
            e.preventDefault();
            if (!this.hasSetPointerCapture) {
                e.currentTarget.setPointerCapture(e.pointerId);
                this.hasSetPointerCapture = true;
            }
            if (this.requestAnimationFrameId) {
                cancelAnimationFrame(this.requestAnimationFrameId);
            }
            const swipeDistance = e.clientX - this.dragStartX;
            this.requestAnimationFrameId = requestAnimationFrame(() => {
                this.carousel.scrollLeft = this.initialScrollLeft - swipeDistance;
            });
        };
        this._handlePointerUp = (e) => {
            const { isDragging } = this.state;
            if (!isDragging ||
                !this.dragStartX ||
                e.pointerId !== this.capturedPointerId)
                return;
            this._handleSwipeEnd(e);
            this._releasePointerCapture(e.pointerId);
            this._resetDragState();
        };
        this._handlePointerCancel = (e) => {
            if (e.pointerId !== this.capturedPointerId || !this.dragStartX) {
                return;
            }
            this._handleSwipeEnd(e);
            this._releasePointerCapture(e.pointerId);
            this._resetDragState();
        };
        this._handleKeyDown = (e) => {
            const { infinite } = this.props;
            const { isLeftArrowDisabled, isRightArrowDisabled } = this.state;
            if (e.key === 'ArrowLeft') {
                e.preventDefault();
                if (infinite || !isLeftArrowDisabled) {
                    this._prev();
                }
            }
            else if (e.key === 'ArrowRight') {
                e.preventDefault();
                if (infinite || !isRightArrowDisabled) {
                    this._next();
                }
            }
        };
        this._handleFocusCapture = (e) => {
            if (!this.carousel)
                return;
            const focusedElement = e.target;
            const slides = Array.from(this.carousel.children);
            const slideIndex = slides.findIndex(slide => slide.contains(focusedElement));
            if (slideIndex === -1)
                return;
            const slide = slides[slideIndex];
            if (!isWhollyInView(this.carousel)(slide)) {
                this._slideTo({ index: slideIndex, alignTo: ALIGNMENT.LEFT });
            }
        };
        this._handleSwipeEnd = (e) => {
            if (!this.dragStartX)
                return;
            const swipeDistance = e.clientX - this.dragStartX;
            if (Math.abs(swipeDistance) > SWIPE_THRESHOLD) {
                if (swipeDistance > 0) {
                    // Swiped right - go to previous slide
                    this._prev();
                }
                else {
                    // Swiped left - go to next slide
                    this._next();
                }
            }
            else {
                const { easing, animationDuration: duration } = this.props;
                // sliding back to the original position
                animate(this.carousel, {
                    prop: 'scrollLeft',
                    delta: swipeDistance,
                    easing,
                    duration,
                });
            }
        };
        this._releasePointerCapture = (capturedPointerId) => {
            if (this.carousel?.hasPointerCapture?.(capturedPointerId)) {
                this.carousel?.releasePointerCapture?.(capturedPointerId);
            }
        };
        this._resetDragState = () => {
            this.dragStartX = null;
            this.initialScrollLeft = 0;
            this.capturedPointerId = null;
            this.hasSetPointerCapture = false;
            this.setState({
                isDragging: false,
            });
        };
        this._renderSlides = () => {
            const { images = [], children, gutter, imagesPosition, imagesFit, } = this.props;
            const { visibleSlides } = this.state;
            const [firstVisibleSlide, lastVisibleSlide] = visibleSlides;
            const slide = ({ i, image, child, }) => {
                const isVisible = i >= firstVisibleSlide && i <= lastVisibleSlide;
                return (React.createElement(Slide, { dataHook: DATA_HOOKS.child, className: classes.slide, key: `slide-${i}`, width: "auto", gutter: i > 0 && gutter != null ? `${gutter}px` : undefined, image: image, children: child, imagePosition: imagesPosition, imageFit: imagesFit, isVisible: isVisible }));
            };
            return (React.createElement("div", { className: st(classes.carousel, { dragging: this.state.isDragging }), role: "list", ref: this._setRef, "data-hook": DATA_HOOKS.carousel, onPointerDown: this._handlePointerDown, onPointerMove: this._handlePointerMove, onPointerUp: this._handlePointerUp, onPointerCancel: this._handlePointerCancel, onFocusCapture: this._handleFocusCapture }, Children.count(children)
                ? Children.map(children, (child, i) => slide({ i, child }))
                : images.map((image, i) => slide({ i, image }))));
        };
        this._renderLoader = () => (React.createElement("div", { className: classes.loader },
            React.createElement(Loader, { dataHook: DATA_HOOKS.loader, size: "small" })));
        this._renderDots = () => {
            const { children, images } = this.props;
            const { visibleSlides } = this.state;
            const [firstVisibleSlide, lastVisibleSlide] = visibleSlides;
            const slidesCount = Children.count(children) || images?.length || 0;
            return (React.createElement("div", { className: classes.dots }, Array(slidesCount)
                .fill(0)
                .map((_, index) => (React.createElement("div", { "data-hook": DATA_HOOKS.pageNavigation(index), key: index, "aria-hidden": index !== firstVisibleSlide, className: st(classes.dot, {
                    active: index >= firstVisibleSlide && index <= lastVisibleSlide,
                }), onClick: () => {
                    if (index < firstVisibleSlide || index > lastVisibleSlide)
                        this._slideTo({
                            index,
                            alignTo: index > firstVisibleSlide
                                ? ALIGNMENT.RIGHT
                                : ALIGNMENT.LEFT,
                        });
                } })))));
        };
        this._renderStartGradient = () => React.createElement("div", { className: classes.start });
        this._renderEndGradient = () => React.createElement("div", { className: classes.end });
        this.loadingImagesCount = 0;
        this.state = {
            visibleSlides: [],
            isAnimating: false,
            isLoading: false,
            isLeftArrowDisabled: true,
            isRightArrowDisabled: true,
            isShowStartGradient: false,
            isShowEndGradient: false,
            isDragging: false,
        };
    }
    componentDidMount() {
        const { initialSlideIndex = 0, autoplay } = this.props;
        deprecationLog('<CarouselWIP/> is deprecated and will be removed in the next major version. Please migrate to <Carousel/> which now supports all CarouselWIP features.');
        this._updateChildCount();
        this._setImagesOnLoadHandlers();
        if (!this.loadingImagesCount) {
            this._slideTo({ index: initialSlideIndex, immediate: true }).catch(nop);
            this._setVisibleSlides();
        }
        this._setAutoplayTimer(autoplay);
    }
    componentDidUpdate(prevProps) {
        const { autoplay } = this.props;
        if (prevProps.autoplay !== autoplay)
            this._setAutoplayTimer(autoplay);
        const lastCount = this.childCount;
        this._updateChildCount();
        if (this.childCount && lastCount !== this.childCount) {
            this._setVisibleSlides();
        }
    }
    render() {
        const { dataHook, className, controlsPosition, controlsSize, showControlsShadow, sidesGradientColor, hideDots, } = this.props;
        const { isShowStartGradient, isShowEndGradient, isLoading } = this.state;
        const showSidesGradients = !!sidesGradientColor;
        return isLoading ? (this._renderLoader()) : (React.createElement("div", { "data-hook": dataHook, className: st(classes.root, {
                controlsPosition,
                controlsSize,
                showControlsShadow,
                showSidesGradients,
                hideDots,
            }, className), "aria-roledescription": "carousel", style: { [vars.sidesGradientColor]: sidesGradientColor }, onKeyDown: this._handleKeyDown },
            React.createElement("div", { style: { position: 'relative' } },
                React.createElement("div", { role: "group", "aria-label": "Carousel navigation" },
                    this._renderLeftControl(),
                    this._renderRightControl()),
                showSidesGradients &&
                    isShowStartGradient &&
                    this._renderStartGradient(),
                showSidesGradients && isShowEndGradient && this._renderEndGradient(),
                this._renderSlides()),
            !hideDots && this._renderDots()));
    }
    componentWillUnmount() {
        if (this.capturedPointerId) {
            this._releasePointerCapture(this.capturedPointerId);
        }
        this._setAutoplayTimer(false);
    }
}
CarouselWIP.displayName = 'CarouselWIP';
CarouselWIP.defaultProps = {
    children: [],
    infinite: true,
    controlsSkin: 'standard',
    controlsStartEnd: 'disabled',
    showControlsShadow: false,
    images: [],
    initialSlideIndex: 0,
    controlsPosition: 'sides',
    controlsSize: 'medium',
    slidingType: 'align-to-start',
    startEndOffset: 0,
    gutter: undefined,
    hideDots: false,
    autoplay: false,
};
export default CarouselWIP;
//# sourceMappingURL=CarouselWIP.js.map