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 } 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 } from '../../constants/components/prop-types/product-image-viewer';
import { getSlides, hasScreenshots, hasDemoVideo } from '../../tools/components/ProductImageViewer';
import Image from '../../atoms/image/Image';
import Icon from '../../atoms/icon/Icon';
import VideoPlayer from '../../atoms/video-player/VideoPlayer';
import './styles/ProductImageViewer.scss';

const n = createNamespace('ProductImageViewer');

// eslint-disable-next-line no-unused-vars
const { ResizeObserver } = window;

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} />
                <span {...n('arrowText').props}>{i18nWithDefaults.next}</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} />
                <span {...n('arrowText').props}>{i18nWithDefaults.previous}</span>
            </button>
        </div>
    );
};

export const ProductImageViewer = ({
    data: {
        screenshots = {},
        overview = {},
        i18n
    },
    settings: { layout }
}) => {
    const sliderRef = useRef(null);
    const [currentIndex, setCurrentIndex] = useState(0);
    const [isChanging, setIsChanging] = useState(false);
    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 = (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);
        }
    });

    // 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]);

    if (!hasScreenshots(screenshots) && !hasDemoVideo(overview)) {
        return null;
    }

    const slides = getSlides(screenshots, overview);
    const slidesCount = slides && slides.length;

    const i18nWithDefaults = { ...ProductImageViewer.defaultProps.data.i18n, ...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 = 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, imageUrlSrcset = {}, videoUrl = '' }) => (
        <div {...n('slide').props} key={generateUniqueID([name, (imageUrl || videoUrl)])}>
            <div {...n('slideContent').props}>
                {videoUrl ? (
                    <VideoPlayer title={name} src={videoUrl} />
                ) : (
                    <Image
                        {...n('slideImage').withTestId().withE2EId().props}
                        src={imageUrl}
                        srcSet={imageUrlSrcset}
                        alt={name}
                    />
                )}
            </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 renderThumbnail = ({ name, imageUrl, imageUrlSrcset, videoUrl }) => {
            const hasImage = imageUrl || imageUrlSrcset;

            return (
                <div {...n('thumbnail', hasImage && 'with-image').withE2EId().props} key={imageUrl}>
                    {videoUrl && (
                        <React.Fragment>
                            <span {...n('thumbText').props}>{name}</span>
                            <Icon {...n('thumbIcon').props} type="play" />
                        </React.Fragment>
                    )}
                    {hasImage && (
                        <Image
                            {...n('thumbImage', videoUrl && 'video-preview').withTestId().withE2EId().props}
                            alt={name}
                            src={imageUrl}
                            srcSet={imageUrlSrcset}
                        />
                    )}
                </div>
            );
        };

        const getSlidesToShow = () => {
            if (slidesCount < MAX_THUMBNAILS_VISIBLE) {
                return slides.length;
            }
            return MAX_THUMBNAILS_VISIBLE;
        };

        const 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 = (
            <div {...n('thumbnailNav', layoutModifier).withVariables(componentVariables).props}>
                <Slider
                    slidesToShow={getSlidesToShow()}
                    speed={350}
                    draggable={false}
                    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(renderThumbnail)}
                </Slider>
            </div>
        );
    }

    return Boolean(slidesCount) && (
        <div {...n('wrapper', [layoutModifier, layout]).props}>
            <div {...n('main', layoutModifier).props}>
                <Slider
                    ref={sliderRef}
                    infinite={false}
                    speed={250}
                    fade={hasThumbnailNav}
                    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.productListCardForm.title',
    iconName: 'image_viewer',
    form: {
        layout: {
            title: 'uieditor.sfbComponent.productListCardForm.layout.title',
            type: DROPDOWN,
            defaultValue: LAYOUT_OPTIONS[0].value,
            options: LAYOUT_OPTIONS,
            validation: {
                availableOptions: LAYOUT_OPTIONS.map(({ value }) => value),
                message: 'uieditor.sfbComponent.dropdown.validation.message',
                default: 'uieditor.sfbComponent.dropdown.defaultValue'
            }
        }
    },
    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({
            callToActions: PropTypes.shape({
                demo: PropTypes.shape({
                    href: PropTypes.string,
                    iFrameFriendlyUrl: PropTypes.string,
                    label: PropTypes.string
                })
            })
        }),
        i18n: PropTypes.shape({
            previous: PropTypes.string,
            next: PropTypes.string
        })
    }),
    settings: PropTypes.shape({
        layout: PropTypes.string
    })
};

ProductImageViewer.defaultProps = {
    data: {
        screenshots: {
            items: null,
            i18n: null
        },
        overview: {
            callToActions: {}
        },
        i18n: {
            previous: 'Previous',
            next: 'Next'
        }
    },
    settings: {
        layout: SETTINGS.layout.defaultValue
    }
};

export default withListener(ProductImageViewer);
