import React, { useState, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import Slider from 'react-slick';
import useResizeObserver from 'use-resize-observer';
import withListener from '../withListener';
import { generateUniqueID } from '../../tools/helpers';
import { DROPDOWN, TOGGLE } from '../../constants/schemaComponentTypes';
import { createNamespace } from '../../tools/namingTools';
import { MAX_THUMBNAILS_VISIBLE, LAYOUT_OPTIONS, LAYOUT_HORIZONTAL, LAYOUT_CARETS, LAYOUT_TITLES, LAYOUT_VERTICAL, ARROW_DEFAULT_PROPS as arrowDefaults } from '../../constants/components/product-image-viewer';
import { sliderArrowShape, screenshotShape, overviewShape } from '../../constants/components/prop-types/product-image-viewer';
import { getSlides, getVideoSlide } from '../../tools/components/productImageViewer';
import Icon from '../../atoms/icon/Icon';
import VideoPlayer from '../../atoms/video-player/VideoPlayer';
import ImageZoom from '../../atoms/image-zoom/ImageZoom';
import ProductImageViewerThumbnail from './atoms/product-image-viewer-thumbnail/ProductImageViewerThumbnail';
import { useDomRect } from '../../tools/dimensions/reportRect';
import './styles/ProductImageViewer.scss';

const n = createNamespace('ProductImageViewer');

const NextArrow = ({
    i18nWithDefaults,
    caretOnly,
    currentSlide,
    slideCount,
    ...otherProps
}) => {
    if (caretOnly && currentSlide === slideCount - 1) { return null; }
    const arrowType = caretOnly ? 'caretRightFull' : 'caretRight';
    return (
        <div {...otherProps}>
            <button type="button" {...n('arrow', caretOnly).props}>
                <Icon type={arrowType} {...n('arrowIcon').props} aria-hidden="true" />
                <span {...n('arrowText').props}>{i18nWithDefaults.goToNextSlide}</span>
            </button>
        </div>
    );
};

const PrevArrow = ({
    i18nWithDefaults,
    caretOnly,
    currentSlide,
    slideCount,
    ...otherProps
}) => {
    if (caretOnly && currentSlide === 0) { return null; }
    const arrowType = caretOnly ? 'caretLeftFull' : 'caretLeft';
    return (
        <div {...otherProps}>
            <button type="button" {...n('arrow', caretOnly).props}>
                <Icon type={arrowType} {...n('arrowIcon').props} aria-hidden="true" />
                <span {...n('arrowText').props}>{i18nWithDefaults.goToPrevSlide}</span>
            </button>
        </div>
    );
};

export const ProductImageViewer = ({
    data: {
        screenshots = {},
        overview = {}
    },
    settings: { layout, hasZoom },
    initialIndex
}) => {
    const sliderRef = useRef(null);
    const [currentIndex, setCurrentIndex] = useState(initialIndex);
    const [isChanging, setIsChanging] = useState(false);
    const [slides, setSlides] = useState([]);
    const [thumbnailSliderMargin, setThumbnailSliderMargin] = useState(0);
    const [thumbnail, setThumbnail] = useState(null);
    const [mainHorizontalSlide, setMainHorizontalSlide] = useState(null);

    const { ref } = useResizeObserver({
        ref: mainHorizontalSlide,
        onResize: ({ width }) => {
            const horizontalSliderMargins = 10;
            const computeThumbnailSliderMargin = thumbnail && (
                0.25 * ((width - thumbnail.offsetWidth * MAX_THUMBNAILS_VISIBLE) + horizontalSliderMargins)
            ) % 1;
            // check to prevent a glitch from occurring if resize event is fired too fast
            computeThumbnailSliderMargin <= 0
                ? setThumbnailSliderMargin(computeThumbnailSliderMargin)
                : setThumbnailSliderMargin(-1 + computeThumbnailSliderMargin);
        }
    });

    const [wrapperRefRect, wrapperRef] = useDomRect();

    // eslint-disable-next-line consistent-return
    useEffect(() => {
        if (layout === LAYOUT_HORIZONTAL) {
            setThumbnail(document.querySelector('.productImageViewer_thumbnailNav--horizontal .slick-slide'));
            setMainHorizontalSlide(document.querySelector('.productImageViewer_main--horizontal .slick-slide'));
            return () => {
                ref(null);
                setThumbnail(null);
                setMainHorizontalSlide(null);
                setThumbnailSliderMargin(0);
            };
        }
    }, [layout, ref]);

    useEffect(() => {
        async function fetchSlides() {
            const slideList = getSlides(screenshots);

            if (overview) {
                const { callToActions = {}, title, image, imageSrcSet } = overview;
                const { demo } = callToActions;

                if (demo && demo.iFrameFriendlyUrl) {
                    const videoSlide = await getVideoSlide(demo);
                    slideList.push(videoSlide);
                }

                if (image) {
                    slideList.unshift({
                        name: title,
                        imageUrl: image,
                        imageUrlSrcset: imageSrcSet
                    });
                }
            }

            setSlides(slideList);
        }

        fetchSlides();
    }, [screenshots, overview]);

    if (!slides || !slides.length) {
        return null;
    }

    const slidesCount = slides ? slides.length : 0;

    const componentVariables = {};

    const i18nWithDefaults = { ...ProductImageViewer.defaultProps.data.screenshots.i18n, ...screenshots.i18n };

    // set up layout variables depending on chosen layout
    const mainSliderHasArrows = layout === LAYOUT_TITLES || layout === LAYOUT_CARETS;
    const caretOnly = layout === LAYOUT_CARETS ? 'caretOnly' : null;
    const hasThumbnailNav = slidesCount > 1 && (layout === LAYOUT_HORIZONTAL || layout === LAYOUT_VERTICAL);
    const showSliderTitle = Boolean(layout === LAYOUT_TITLES && !slides[currentIndex].videoUrl);

    const beforeChange = () => {
        setIsChanging(true);
    };
    const afterChange = newIndex => {
        setIsChanging(false);
        setCurrentIndex(newIndex);
    };

    const renderSlide = ({ name, imageUrl, imageAlt = '', imageUrlSrcset = {}, videoUrl = '' }, idx) => (
        <div {...n('slide').withTestId().props} key={generateUniqueID([name, (imageUrl || videoUrl)])}>
            <div {...n('slideContent').props}>
                {videoUrl ? (
                    <VideoPlayer title={name} src={videoUrl} />
                ) : (
                    <ImageZoom
                        {...n('slideImage').withTestId().withE2EId().props}
                        src={imageUrl}
                        srcSet={imageUrlSrcset}
                        alt={imageAlt || name}
                        isActive={hasZoom && idx === currentIndex}
                        containerRect={wrapperRefRect}
                    />
                )}
            </div>
        </div>
    );

    // default flow style is stacked, unless vertical nav
    const layoutModifier = layout === LAYOUT_VERTICAL ? 'vertical' : 'horizontal';

    let thumbnailNav;
    if (hasThumbnailNav) {
        const navigateToSlide = index => {
            sliderRef.current.slickGoTo(index);
        };
        const navigateToSlideBefore = (prev, next) => {
            sliderRef.current.slickGoTo(next);
        };

        const slidesToShow = slidesCount < MAX_THUMBNAILS_VISIBLE ? slides.length : MAX_THUMBNAILS_VISIBLE;

        componentVariables['thumbnail-nav-max-width'] = `${slidesToShow * 100 / MAX_THUMBNAILS_VISIBLE}%`;
        componentVariables['thumbnail-slider-margin'] = thumbnailSliderMargin ? `${thumbnailSliderMargin}px` : null;

        // NOTE: the current implementation of slider moves active one to first position,
        // but it is not the ideal behaviour as per design's requirements, where slider should
        // stay at its position. There is no option in current version of Slick to such behaviour.
        // BUG: slider must be infinite, becaus eof a bug in Slick that does not report
        // correct current index when slider reaches end of non-infinite
        thumbnailNav = (
            <nav {...n('thumbnailNav', layoutModifier).props} aria-label={i18nWithDefaults.productScreenshots}>
                <Slider
                    slidesToShow={slidesToShow}
                    speed={350}
                    draggable={false}
                    initialSlide={currentIndex}
                    infinite
                    arrows={layout === LAYOUT_HORIZONTAL}
                    vertical={layout === LAYOUT_VERTICAL}
                    prevArrow={<PrevArrow i18nWithDefaults={i18nWithDefaults} caretOnly={caretOnly} />}
                    nextArrow={<NextArrow i18nWithDefaults={i18nWithDefaults} caretOnly={caretOnly} />}
                    beforeChange={navigateToSlideBefore}
                    afterChange={navigateToSlide}
                    focusOnSelect
                    {...n('thumbnailSlider', layoutModifier).props}
                >
                    {slides.map(item => (
                        <ProductImageViewerThumbnail
                            {...item}
                            key={generateUniqueID([item.name, item.imageUrl])}
                            isVideo={Boolean(item.videoUrl)}
                        />
                    ))}
                </Slider>
            </nav>
        );
    }

    return (
        <div ref={wrapperRef} {...n('wrapper', [layoutModifier, layout]).withTestId().withVariables(componentVariables).props}>
            <div {...n('main', [layoutModifier, hasThumbnailNav && 'with-thumbnail-nav']).props}>
                {slidesCount === 1 ? (
                    <ImageZoom
                        {...n('image').withTestId().withE2EId().props}
                        src={slides[0].imageUrl}
                        srcSet={slides[0].imageUrlSrcset}
                        alt={slides[0].name}
                        containerRect={wrapperRefRect}
                        isActive={hasZoom}
                    />
                ) : (
                    <Slider
                        ref={sliderRef}
                        infinite={false}
                        speed={250}
                        fade={hasThumbnailNav}
                        initialSlide={currentIndex}
                        slidesToShow={1}
                        draggable={false}
                        arrows={mainSliderHasArrows}
                        prevArrow={mainSliderHasArrows
                            && <PrevArrow i18nWithDefaults={i18nWithDefaults} caretOnly={caretOnly} />}
                        nextArrow={mainSliderHasArrows
                            && <NextArrow i18nWithDefaults={i18nWithDefaults} caretOnly={caretOnly} />}
                        beforeChange={beforeChange}
                        afterChange={afterChange}
                        dots={layout === LAYOUT_TITLES}
                    >
                        {slides.map(renderSlide)}
                    </Slider>
                )}
            </div>
            {thumbnailNav}
            {showSliderTitle && (
                <div {...n('title', isChanging && 'transition').props}>
                    {slides[currentIndex].name}
                </div>
            )}
        </div>
    );
};

ProductImageViewer.schema = () => ({
    name: 'ProductImageViewer',
    title: 'uieditor.sfbComponent.productImageViewer.title',
    iconName: 'image_viewer',
    form: {
        layout: {
            title: 'uieditor.sfbComponent.productImageViewer.layout.title',
            type: DROPDOWN,
            defaultValue: LAYOUT_OPTIONS[0].value,
            options: LAYOUT_OPTIONS
        },
        hasZoom: {
            labelOn: 'uieditor.sfbComponent.productImageViewer.hasZoom.labelOn',
            labelOff: 'uieditor.sfbComponent.productImageViewer.hasZoom.labelOff',
            caption: 'uieditor.sfbComponent.productImageViewer.hasZoom.caption',
            type: TOGGLE,
            defaultValue: false
        }
    },
    marketplaceData: {
        screenshots: 'application.screenshots',
        overview: 'application.overview'
    }
});

const SETTINGS = ProductImageViewer.schema().form;

PrevArrow.propTypes = sliderArrowShape;
PrevArrow.defaultProps = arrowDefaults;

NextArrow.propTypes = sliderArrowShape;
NextArrow.defaultProps = arrowDefaults;

ProductImageViewer.propTypes = {
    data: PropTypes.shape({
        screenshots: PropTypes.shape({
            items: PropTypes.arrayOf(PropTypes.shape(screenshotShape)),
            i18n: PropTypes.shape({
                sidebarTitle: PropTypes.string,
                title: PropTypes.string
            })
        }),
        overview: PropTypes.shape(overviewShape),
        i18n: PropTypes.shape({
            goToPrevSlide: PropTypes.string,
            goToNextSlide: PropTypes.string,
            productScreenshots: PropTypes.string
        })
    }),
    settings: PropTypes.shape({
        layout: PropTypes.oneOf(LAYOUT_OPTIONS.map(({ value }) => value)),
        hasZoom: PropTypes.bool
    }),
    initialIndex: PropTypes.number
};

ProductImageViewer.defaultProps = {
    data: {
        screenshots: {
            items: null,
            i18n: {
                goToPrevSlide: 'Go to previous slide',
                goToNextSlide: 'Go to next slide',
                productScreenshots: 'Product screenshots'
            }
        },
        overview: {
            callToActions: {}
        }
    },
    settings: {
        layout: SETTINGS.layout.defaultValue,
        hasZoom: SETTINGS.hasZoom.defaultValue
    },
    initialIndex: 0
};

export default withListener(ProductImageViewer);
