import React, { useEffect, useState, useMemo, useContext } from 'react';
import './index.less';
import { pxOrStrToNumber } from '../../config/utils';
import classNames from 'classnames';
import { ConfigContext } from '../../config/globalConfig';

type Position = 'right' | 'left' | 'top' | 'bottom';
interface LoupeProps {
  scale?: number;
  position?: Position;
  src: string;
  distance?: number;
  maskBlockStyle?: React.CSSProperties;
  maskBlockClassName?: string;
  zIndex?: number;
  width?: number | string;
  height?: number | string;
  alt?: string;
  onError?: (e: React.SyntheticEvent<HTMLImageElement, Event>) => string;
  fallback?: string;
  loupe?: boolean;
}

const Index: React.FC<LoupeProps> = (props) => {
  const {
    src,
    position = 'right',
    scale = 2,
    maskBlockStyle = {},
    zIndex = 200,
    width = 300,
    height = 300,
    alt,
    distance = 14,
    maskBlockClassName,
    fallback,
    loupe = true,
    onError: onImageError,
    ...imgPorps
  } = props;

  const getPrefixCls = useContext(ConfigContext);
  const prefixCls = getPrefixCls('loupe');

  const PARAMS = {
    scale: scale,
    width: pxOrStrToNumber(width),
    height: pxOrStrToNumber(height),
  };

  const mouseRadiusW = PARAMS.width / PARAMS.scale / 2;
  const mouseRadiusH = PARAMS.height / PARAMS.scale / 2;

  const ClassObj = {
    imgContainer: {
      width: `${PARAMS.width}px`,
      height: `${PARAMS.height}px`,
    },

    mouseBlock: {
      width: `${mouseRadiusW * 2}px`,
      height: `${mouseRadiusH * 2}px`,
    },
    magnifierContainer: {
      left: `${PARAMS.width}px`,
      width: `${PARAMS.width}px`,
      height: `${PARAMS.height}px`,
    },

    imgStyle: {
      width: `${PARAMS.width}px`,
      height: `${PARAMS.height}px`,
      transform: `scale(${PARAMS.scale})`,
    },
  };

  const isLoaded = React.useRef(false);
  const [showLoupe, setShowLoupe] = useState<boolean>(false);
  const [{ mouseBlock, imgStyle }, setMouseImg] = useState({
    mouseBlock: ClassObj.mouseBlock,
    imgStyle: ClassObj.imgStyle,
  });
  const [{ mouseX, mouseY }, setMousePosition] = useState<{
    mouseX: number;
    mouseY: number;
  }>({ mouseX: 0, mouseY: 0 });
  const [status, setStatus] = useState<string>('error');
  const isError = status === 'error';

  const calculationBlock = (offsetX: number, offsetY: number) => {
    const cssStyle: any = {
      mouseBlock: { ...mouseBlock },
      imgStyle: { ...imgStyle },
    };
    let offsetW = offsetX;
    let offsetH = offsetY;
    if (offsetX < mouseRadiusW) {
      offsetW = mouseRadiusW;
    } else if (offsetX > PARAMS.width - mouseRadiusW) {
      offsetW = PARAMS.width - mouseRadiusW;
    }
    if (offsetY < mouseRadiusH) {
      offsetH = mouseRadiusH;
    } else if (offsetY > PARAMS.height - mouseRadiusH) {
      offsetH = PARAMS.height - mouseRadiusH;
    }
    const left = offsetW - mouseRadiusW;
    const top = offsetH - mouseRadiusH;

    cssStyle.mouseBlock.left = left;
    cssStyle.mouseBlock.top = top;

    cssStyle.imgStyle.left = -left * PARAMS.scale;
    cssStyle.imgStyle.top = -top * PARAMS.scale;

    setMouseImg(cssStyle);
  };

  const onLoad = () => {
    setStatus('normal');
  };

  const getImgRef = (img: HTMLImageElement) => {
    isLoaded.current = false;
    if (status !== 'loading') return;
    if (img?.complete && (img.naturalWidth || img.naturalHeight)) {
      isLoaded.current = true;
      onLoad();
    }
  };

  React.useEffect(() => {
    if (isError) {
      setStatus('normal');
    }
    if (!isLoaded.current) {
      setStatus('loading');
    }
  }, [src]);

  const mouseEnter = () => {
    setShowLoupe(true);
  };

  const mouseLeave = () => {
    setShowLoupe(false);
  };

  const mouseMove: React.MouseEventHandler<HTMLDivElement> = (event) => {
    const e = event.nativeEvent;
    setMousePosition({ mouseX: e.offsetX, mouseY: e.offsetY });
  };

  useMemo(() => {
    calculationBlock(mouseX, mouseY);
  }, [mouseX, mouseY]);

  const magnifierMemo = useMemo(() => {
    const translateDis = pxOrStrToNumber(distance);
    const positionObj: any = {};
    switch (position) {
      case 'left':
        positionObj['left'] = -translateDis - pxOrStrToNumber(width) + `px`;
        break;
      case 'right':
        positionObj['left'] = translateDis + pxOrStrToNumber(width) + `px`;
        break;
      case 'top':
        positionObj['top'] = -translateDis - pxOrStrToNumber(width) + `px`;
        positionObj['left'] = `0px`;
        break;
      case 'bottom':
        positionObj['bottom'] = -distance - Number(width) + `px`;
        positionObj['top'] = 'auto';
        positionObj['left'] = 0;
        break;
      default:
        break;
    }

    return { ...ClassObj.magnifierContainer, ...positionObj };
  }, [position]);

  const onError = (e: React.SyntheticEvent<HTMLImageElement, Event>) => {
    if (onImageError) {
      onImageError(e);
    }
    setStatus('error');
  };

  const maskBlockClass = classNames({
    [`${prefixCls}-mouse-mask`]: true,
    maskBlockClassName: maskBlockClassName ? maskBlockClassName : '',
  });

  const wrapClassname = classNames({
    [`${prefixCls}`]: true,
  });

  const image = (
    <img
      alt={alt}
      {...imgPorps}
      {...(isError && fallback ? { src: fallback } : { onLoad, onError, src })}
      ref={getImgRef}
    />
  );

  return (
    <div className={wrapClassname} style={ClassObj.imgContainer}>
      <div
        className={`${prefixCls}-img-container`}
        style={{ cursor: loupe ? 'move' : 'default' }}
      >
        {image}
        <div
          className={`${prefixCls}-img-mask`}
          onMouseEnter={mouseEnter}
          onMouseLeave={mouseLeave}
          onMouseMove={mouseMove}
        />
        {showLoupe && loupe && (
          <div
            style={{ ...mouseBlock, ...maskBlockStyle }}
            className={maskBlockClass}
          />
        )}
      </div>

      {showLoupe && loupe && (
        <div
          style={Object.assign({ ...magnifierMemo }, { zIndex: zIndex })}
          className={`${prefixCls}-scale`}
        >
          <img
            style={imgStyle}
            alt={alt}
            src={isError && fallback ? fallback : src}
          />
        </div>
      )}
    </div>
  );
};
export default Index;
