import React, { useState, useCallback, useEffect } from 'react';
import PropTypes from 'prop-types';
import './styles/ImageZoom.scss';
import { createNamespace } from '../../tools/namingTools';
import { useDomRect } from '../../tools/dimensions/reportRect';
import { useResponsive } from '../../tools/widths/useResponsive';
import { getWindowDimensions } from '../../tools/dimensions/getWindowDimensions';
import { DEFAULT_IMG } from '../../constants/images';
import Image from '../image/Image';
import Zoom from './atoms/Zoom';

const n = createNamespace('ImageZoom');
// offset of the zoom from the image
const X_OFFSET = 40;
const Y_OFFSET = 0;
// margin to the edge of the screen
const X_MARGIN = 20;
const Y_MARGIN = 20;

// minimum zoom viewport height ratio of window height
const MIN_ZOOM_HEIGHT = 0.5;

const ImageZoom = ({
    src,
    srcSet,
    alt,
    isActive,
    containerRect,
    ...otherProps
}) => {
    const [isZooming, setIsZooming] = useState(false);

    const [zoomViewportSize, setZoomViewportSize] = useState({});
    const [zoomAvailableSize, setZoomAvailableSize] = useState({});

    const [thumbViewportSize, setThumbViewportSize] = useState({});
    const [thumbViewportPos, setThumbViewportPos] = useState({});

    const [largestSrcSize, setLargestSrcSize] = useState({});
    const [zoomPos, setZoomPos] = useState({});
    const [mousePos, setMousePos] = useState({});

    const { mediumDown } = useResponsive();
    const isMobile = mediumDown;

    const [imgRect, imgRef] = useDomRect();

    // if container rect is null, use image rect instead, to place the zoom right next to the image
    const referenceRect = containerRect || imgRect;

    useEffect(() => {
        // check for available dimensions in the window right/down of the image
        const { width, height } = getWindowDimensions();
        const xSpace = width - (referenceRect.right + X_MARGIN + X_OFFSET);
        const ySpace = Math.max(height - (referenceRect.top + Y_MARGIN + Y_OFFSET), height * MIN_ZOOM_HEIGHT);
        setZoomAvailableSize({
            width: xSpace,
            height: ySpace
        });
    }, [referenceRect, imgRef]);

    const largestSrc = (srcSet && (srcSet['2x'] || srcSet['1x'] || srcSet.default)) || src;

    const onMouseLeave = useCallback(() => {
        if (!isMobile) {
            setIsZooming(false);
        }
    }, [isMobile]);

    const onSetZooming = useCallback(e => {
        setThumbViewportPos({ x: e.pageX - imgRect.left, y: e.pageY - imgRect.top });

        if (!largestSrcSize.width) {
            // when zooming, we need to get information about the largest image's dimensions
            const img = document.createElement('img');
            const onImageLoaded = () => {
                setLargestSrcSize({ width: img.naturalWidth, height: img.naturalHeight });
            };
            img.onload = onImageLoaded;
            img.src = largestSrc;
        }

        setIsZooming(true);
    }, [imgRect, largestSrc, largestSrcSize]);

    useEffect(
        () => {
            if (!isZooming || isMobile) {
                return;
            }
            // constrain zoom viewport so it is not larger than zoomed image
            const zoomViewportWidth = Math.min(largestSrcSize.width, zoomAvailableSize.width);
            const zoomViewportHeight = Math.min(largestSrcSize.height, zoomAvailableSize.height);
            setZoomViewportSize({
                width: zoomViewportWidth,
                height: zoomViewportHeight
            });
        },
        [
            largestSrcSize,
            zoomAvailableSize,
            isZooming,
            isMobile
        ]
    );
    useEffect(
        () => {
            if (!isZooming || isMobile) {
                return;
            }
            // make the thumb viewport in the same ratio as the viewport
            const viewportAspectRatio = zoomViewportSize.width / zoomViewportSize.height;

            // visible width of the zoomed image in viewport
            const visibleImgRatio = zoomViewportSize.width / largestSrcSize.width;

            // viewport thumbnail ratio to the non-zoomed image dimension
            const imgWidth = imgRect.right - imgRect.left;

            const thumbViewportWidthComputed = imgWidth * visibleImgRatio;
            const thumbViewportHeightComputed = thumbViewportWidthComputed / viewportAspectRatio;

            setThumbViewportSize({ width: thumbViewportWidthComputed, height: thumbViewportHeightComputed });
        },
        [
            largestSrcSize,
            zoomViewportSize,
            isZooming,
            imgRect,
            isMobile
        ]
    );

    useEffect(() => {
        if (isMobile) {
            return;
        }
        const thLeft = Math.min(
            Math.max(0, mousePos.x - thumbViewportSize.width / 2), imgRect.width - thumbViewportSize.width
        );
        const thTop = Math.min(
            Math.max(0, mousePos.y - thumbViewportSize.height / 2), imgRect.height - thumbViewportSize.height
        );
        setThumbViewportPos({ x: thLeft, y: thTop + imgRect.offsetTop });
        const prcLeft = thLeft / imgRect.width;
        const prcTop = thTop / imgRect.height;
        setZoomPos({ x: prcLeft, y: prcTop });
    }, [
        imgRect,
        thumbViewportSize,
        mousePos,
        isMobile
    ]);

    const onMouseMove = useCallback(e => {
        if (!isZooming || isMobile) {
            return;
        }
        setMousePos({ x: e.pageX - imgRect.absoluteLeft, y: e.pageY - imgRect.absoluteTop });
    }, [isZooming, imgRect, isMobile]);

    // If this image is in a carousel, deactivate zooming when this image is not active
    useEffect(() => {
        if (!isActive) {
            setIsZooming(false);
        }
    }, [isActive]);

    const imgEvents = !isMobile && isActive ? {
        onClick: onSetZooming,
        onMouseEnter: onSetZooming,
        onMouseLeave,
        onMouseMove
    } : {};

    const img = (
        <Image
            src={src}
            srcSet={srcSet}
            alt={alt}
            ref={imgRef}
            {...imgEvents}
            {...otherProps}
        />
    );

    let zoomed;
    let viewport;
    if (isZooming) {
        zoomed = (
            <Zoom
                top={referenceRect.absoluteTop + Y_OFFSET}
                left={referenceRect.absoluteLeft + referenceRect.width + X_OFFSET}
                width={zoomViewportSize.width || 0}
                height={zoomViewportSize.height || 0}
                x={zoomPos.x}
                y={zoomPos.y}
                isMobile={isMobile}
                src={largestSrc}
                imgDimensions={largestSrcSize}
                onClick={onMouseLeave}
            />
        );
        const viewportVars = {
            x: `${thumbViewportPos.x}px`,
            y: `${thumbViewportPos.y}px`,
            height: `${thumbViewportSize.height}px`,
            width: `${thumbViewportSize.width}px`
        };
        viewport = (
            <div {...n('thumbViewport').withVariables(viewportVars).props} />
        );
    }
    return (
        <div {...n('imageWrapper').props}>
            {img}
            {viewport}
            {zoomed}
        </div>
    );
};


ImageZoom.propTypes = {
    alt: PropTypes.string,
    src: PropTypes.string,
    srcSet: PropTypes.shape({
        default: PropTypes.string,
        '1x': PropTypes.string,
        '2x': PropTypes.string
    }),
    containerRect: PropTypes.shape({
        right: PropTypes.number,
        left: PropTypes.number,
        top: PropTypes.number,
        bottom: PropTypes.number
    }),
    isActive: PropTypes.bool
};

ImageZoom.defaultProps = {
    alt: '',
    src: DEFAULT_IMG,
    srcSet: null,
    isActive: true,
    containerRect: null
};

export default (ImageZoom);
