import React, { useState, useCallback, useEffect } from 'react';
import PropTypes from 'prop-types';
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';
import { isContentFromOneWorldSync } from '../../tools/contentTools';
import { getImageSrc } from '../../tools/images';
// IMPORTANT: (s)css should be imported after other atoms/components
import './styles/ImageZoom.scss';

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;

// keyboard navigation step size (as a percentage of viewport)
const KEYBOARD_STEP = 0.1;

const ImageZoom = ({
    src,
    srcSet,
    alt,
    isActive,
    containerRect,
    referUrl,
    ...otherProps
}) => {
    const [isZooming, setIsZooming] = useState(false);
    const [keyboardFocused, setKeyboardFocused] = 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 [keyboardPos, setKeyboardPos] = useState({ x: 0.5, y: 0.5 }); // Center position for keyboard

    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.right, referenceRect.top]);

    const largestSrc = getImageSrc(src, srcSet);

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

    const onSetZooming = useCallback((e, isKeyboard = false) => {
        if (!isKeyboard && e) {
            setThumbViewportPos({ x: e.pageX - imgRect.left, y: e.pageY - imgRect.top });
        } else if (isKeyboard) {
            // For keyboard activation, start at center
            setThumbViewportPos({ x: imgRect.width / 2, y: imgRect.height / 2 });
            setKeyboardPos({ x: 0.5, y: 0.5 });
        }

        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.left, imgRect.top, imgRect.width, imgRect.height, largestSrc, largestSrcSize.width]);

    const onKeyDown = useCallback(e => {
        if (!isActive || isMobile) { return; }

        switch (e.key) {
            case 'Enter':
            case ' ':
                e.preventDefault();
                if (isZooming) {
                    setIsZooming(false);
                } else {
                    onSetZooming(null, true);
                }
                break;
            case 'Escape':
                if (isZooming) {
                    e.preventDefault();
                    setIsZooming(false);
                }
                break;
            case 'ArrowLeft':
            case 'ArrowRight':
            case 'ArrowUp':
            case 'ArrowDown':
                if (isZooming && keyboardFocused) {
                    e.preventDefault();
                    const { x: currentX, y: currentY } = keyboardPos;
                    let newX = currentX;
                    let newY = currentY;

                    switch (e.key) {
                        case 'ArrowLeft':
                            newX = Math.max(0, currentX - KEYBOARD_STEP);
                            break;
                        case 'ArrowRight':
                            newX = Math.min(1, currentX + KEYBOARD_STEP);
                            break;
                        case 'ArrowUp':
                            newY = Math.max(0, currentY - KEYBOARD_STEP);
                            break;
                        case 'ArrowDown':
                            newY = Math.min(1, currentY + KEYBOARD_STEP);
                            break;
                        default:
                            // No action needed for other keys
                            break;
                    }

                    setKeyboardPos({ x: newX, y: newY });
                }
                break;
            default:
                // No action needed for other keys
                break;
        }
    }, [isActive, isMobile, isZooming, keyboardFocused, keyboardPos, onSetZooming]);

    const onFocus = useCallback(() => {
        setKeyboardFocused(true);
    }, []);

    const onBlur = useCallback(() => {
        setKeyboardFocused(false);
        if (isZooming && !isMobile) {
            setIsZooming(false);
        }
    }, [isZooming, isMobile]);

    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.width,
            largestSrcSize.height,
            zoomViewportSize.width,
            zoomViewportSize.height,
            isZooming,
            imgRect.right,
            imgRect.left,
            isMobile
        ]
    );

    useEffect(() => {
        if (isMobile) {
            return;
        }

        let posX; let
            posY;

        if (keyboardFocused && isZooming) {
            // Use keyboard position when keyboard is focused
            posX = keyboardPos.x * imgRect.width;
            posY = keyboardPos.y * imgRect.height;
        } else {
            // Use mouse position otherwise
            posX = mousePos.x;
            posY = mousePos.y;
        }

        const thLeft = Math.min(
            Math.max(0, posX - thumbViewportSize.width / 2), imgRect.width - thumbViewportSize.width
        );
        const thTop = Math.min(
            Math.max(0, posY - 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.width,
        imgRect.height,
        imgRect.offsetTop,
        thumbViewportSize.width,
        thumbViewportSize.height,
        mousePos.x,
        mousePos.y,
        keyboardPos.x,
        keyboardPos.y,
        keyboardFocused,
        isZooming,
        isMobile
    ]);

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

    // 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,
        onKeyDown,
        onFocus,
        onBlur,
        tabIndex: 0,
        role: 'button',
        'aria-label': isZooming
            ? `${alt || 'Image'} - Zoomed in. Use arrow keys to navigate, Enter or Space to toggle zoom, Escape to exit zoom.`
            : `${alt || 'Image'} - Press Enter or Space to zoom in`
    } : {};

    const img = (
        <Image
            src={getImageSrc(src, srcSet)}
            alt={alt}
            ref={imgRef}
            isCopyRighted={isContentFromOneWorldSync(referUrl)}
            {...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', isContentFromOneWorldSync(referUrl) && 'copyRighted').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,
    referUrl: PropTypes.string
};

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

export default (ImageZoom);
