import React, { forwardRef, ReactElement, Ref, useCallback, useContext, useRef, useState } from 'react';
import Cropper from 'react-easy-crop';
import isFunction from 'lodash/isFunction';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
import Modal, { ModalProps } from '../modal';
import { UploadFile, UploadProps } from '../upload/interface';
import defaultLocale from '../locale-provider/default';
import Button, { ButtonProps } from '../button';
import ButtonGroup from '../button/ButtonGroup';
import Upload from '../upload';
import AvatarUploader from './avatarUpload';
import { ShapeCroper } from './enum';
import { imageCrop } from '../locale-provider';
import ConfigContext from '../config-provider/ConfigContext';

// ssr
if (typeof window !== 'undefined') {
  // 兼容ie11 remove 方法
  (function (arr) {
    arr.forEach(function (item) {
      // eslint-disable-next-line no-prototype-builtins
      if (item.hasOwnProperty('remove')) {
        return;
      }
      Object.defineProperty(item, 'remove', {
        configurable: true,
        enumerable: true,
        writable: true,
        value: function remove() {
          if (this.parentNode === null) {
            return;
          }
          this.parentNode.removeChild(this);
        },
      });
    });

    // 兼容IE
    if (!HTMLCanvasElement.prototype.toBlob) {
      Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
        value(callback, type, quality) {
          setTimeout(() => {
            const binStr = atob(this.toDataURL(type, quality).split(',')[1]);
            const len = binStr.length;
            const arrArray = new Uint8Array(len);
            for (let i = 0; i < len; i++) {
              arrArray[i] = binStr.charCodeAt(i);
            }
            callback(new Blob([arrArray], { type: type || 'image/png' }));
          });
        },
      });
    }
  })([Element.prototype, CharacterData.prototype, DocumentType.prototype]);
}

const MIN_ZOOM = 1;
const MAX_ZOOM = 3;
const ZOOM_STEP = 0.1;

interface CompoundedComponent
  extends React.ForwardRefExoticComponent<ImgCropProps> {
  AvatarUploader: typeof AvatarUploader;
}

export interface ImageCropLocale {
  imageCrop?: string;
}

export {
  ShapeCroper,
};

export declare type Area = {
  width: number;
  height: number;
  x: number;
  y: number;
  zoom: number;
  rotation: number;
};

export interface BalanceRate {
  x: number;
  y: number;
}

export interface EasyCropProps {
  src?: string;
  aspect?: number;
  shape?: ShapeCroper;
  grid?: boolean;
  hasZoom?: boolean;
  zoomVal?: number;
  rotateVal?: number;
  setZoomVal?: (value: number) => void;
  setRotateVal?: (rotation: number) => void;
  onComplete: (croppedAreaPixels: Area) => void;
  prefixCls?: string;
}

export interface ImgCropProps {
  aspect?: number;
  shape?: ShapeCroper;
  zoom?: boolean;
  grid?: boolean;
  src?: string;
  rotate?: boolean;
  beforeCrop?: (file: UploadFile, uploadFiles: UploadFile[]) => boolean;
  /**
   * @deprecated
   */
  modalTitle?: string;
  /**
   * @deprecated
   */
  modalWidth?: number | string;
  /**
   * @deprecated
   */
  modalOk?: string;
  /**
   * @deprecated
   */
  modalCancel?: string;
  modalProps?: ModalProps;
  onCancel?: () => void;
  onOk?: ({ url: string, blob: Blob, area: Area }) => void;
  modalVisible?: boolean;
  children?: React.ReactElement<UploadProps> | React.ReactElement<any>;
  cropContent?: (crop: ReactElement<EasyCropProps>) => React.ReactElement<any>;
  onCropComplete?: ({ url: string, blob: Blob, area: Area }) => void;
  prefixCls?: string;
  serverCrop?: boolean;
  rotateStep?: number;
}

const EasyCrop = forwardRef<unknown, EasyCropProps>((props, ref: Ref<Cropper>) => {
  const {
    src,
    aspect,
    shape,
    grid,
    hasZoom,
    zoomVal,
    rotateVal,
    setZoomVal,
    setRotateVal,
    onComplete,
    prefixCls,
  } = props;

  const [crop, setCrop] = useState({ x: 0, y: 0 });

  const onCropComplete = useCallback(
    (_croppedArea, croppedAreaPixels) => {
      croppedAreaPixels.zoom = zoomVal || 0;
      croppedAreaPixels.rotate = rotateVal || 0;
      onComplete(croppedAreaPixels);
    },
    [onComplete, zoomVal, rotateVal],
  );

  return (
    <Cropper
      ref={ref}
      image={src}
      aspect={aspect}
      cropShape={shape}
      showGrid={grid}
      zoomWithScroll={hasZoom}
      crop={crop}
      zoom={zoomVal}
      rotation={rotateVal}
      onCropChange={setCrop}
      onZoomChange={setZoomVal}
      onRotationChange={setRotateVal}
      onCropComplete={onCropComplete}
      classes={{ containerClassName: `${prefixCls}-container`, mediaClassName: `${prefixCls}-media` }}
    />
  );
});

// 图片转化为canvas
const imageToCanvas = (image) => {
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  canvas.width = image.naturalWidth;
  canvas.height = image.naturalHeight;
  if (ctx) {
    ctx.drawImage(image, 0, 0);
    return canvas;
  }
  return undefined;
};

const ImgCrop = forwardRef(function ImgCrop(props: ImgCropProps, ref) {
  const {
    aspect,
    shape,
    grid,
    zoom,
    rotate,
    rotateStep = 90,
    beforeCrop,
    modalTitle,
    modalWidth,
    modalOk,
    modalCancel,
    modalVisible,
    children,
    onCancel: onModalCancel,
    onOk: onModalOk,
    src: imageSrc,
    serverCrop,
    modalProps,
    cropContent,
    onCropComplete,
    prefixCls: customizePrefixCls,
  } = props;
  const { getPrefixCls } = useContext(ConfigContext);

  const prefixCls = getPrefixCls('image-crop', customizePrefixCls);

  const prefixClsMedia = `${prefixCls}-media`;

  const hasZoom = zoom === true;
  const hasRotate = rotate === true;

  const [src, setSrc] = useState('');
  const [zoomVal, setZoomVal] = useState(1);
  const [rotateVal, setRotateVal] = useState(0);

  const beforeUploadRef = React.useRef<(file: UploadFile, FileList: UploadFile[]) => boolean | PromiseLike<any | Blob>>(); // 返回上传组件的上传之前的钩子函数
  const fileRef = React.useRef<UploadFile>(); // 记录文件的参数
  const resolveRef = useRef<(value?: any | PromiseLike<any>) => void>(); // 返回文件上传的成功数据的方法
  const rejectRef = useRef<(reason?: any) => void>(); // 返回失败数据的方法

  const cropPixelsRef = React.useRef<HTMLImageElement>();

  const imageCropCanvas = (naturalModalImg): HTMLCanvasElement | undefined => {
    const { naturalWidth, naturalHeight } = naturalModalImg;
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    if (ctx) {
      // create a max canvas to cover the source image after rotated
      const maxLen = Math.sqrt(naturalWidth ** 2 + naturalHeight ** 2);
      canvas.width = maxLen;
      canvas.height = maxLen;
      // rotate the image
      if (hasRotate && rotateVal > 0 && rotateVal < 360) {
        const halfMax = maxLen / 2;
        ctx.translate(halfMax, halfMax);
        ctx.rotate((rotateVal * Math.PI) / 180);
        ctx.translate(-halfMax, -halfMax);
      }
      // draw the source image in the center of the max canvas
      const left = (maxLen - naturalWidth) / 2;
      const top = (maxLen - naturalHeight) / 2;
      ctx.drawImage(naturalModalImg, left, top);

      // shrink the max canvas to the crop area size, then align two center points
      const maxImgData = ctx.getImageData(0, 0, maxLen, maxLen);
      if (cropPixelsRef && cropPixelsRef.current) {
        const { width, height, x, y } = cropPixelsRef.current;
        canvas.width = width;
        canvas.height = height;
        // get the new image
        ctx.putImageData(maxImgData, -left - x, -top - y);
        return canvas;
      }
    }
  };

  /**
   * Upload
   */
  const renderUpload = useCallback(() => {
    const upload: React.ReactElement<UploadProps> = Array.isArray(children) ? children[0] : children;
    if (upload && upload.props && upload.type === Upload) {
      const { beforeUpload, accept, ...restUploadProps } = upload.props;
      beforeUploadRef.current = beforeUpload;
      return {
        ...upload,
        props: {
          ...restUploadProps,
          accept: accept || 'image/*',
          beforeUpload: (file: UploadFile, fileList: UploadFile[]) =>
            new Promise((resolve, reject) => {
              if (beforeCrop && !beforeCrop(file, fileList)) {
                reject();
                return;
              }
              fileRef.current = file;
              resolveRef.current = resolve;
              rejectRef.current = reject;
              const reader = new FileReader();
              if (reader) {
                reader.addEventListener('load', () => {
                  if (reader.result && typeof reader.result === 'string') {
                    setSrc(reader.result);
                  }
                });
                if (file instanceof Blob) {
                  reader.readAsDataURL(file);
                }
              }
            }),
        },
      };
    }
    if (imageSrc && (typeof window !== 'undefined')) {
      const newimage = new Image();
      newimage.src = imageSrc;
      newimage.crossOrigin = 'anonymous';
      newimage.onload = function () {
        const canvas = imageToCanvas(newimage);
        if (canvas) {
          if (isFunction(onModalOk) && canvas) {
            setSrc(canvas.toDataURL());
          }
        }
      };
    }
  }, [beforeCrop, children]);

  /**
   * EasyCrop
   */
  const onComplete = useCallback((croppedAreaPixels) => {
    cropPixelsRef.current = croppedAreaPixels;
    if (isFunction(onCropComplete)) {
      const naturalModalImg: Element | HTMLImageElement | null = document.querySelector(`.${prefixClsMedia}`);
      const canvas = serverCrop ? imageToCanvas(naturalModalImg) : imageCropCanvas(naturalModalImg);
      if (canvas) {
        canvas.toBlob((blob) => {
          let area = {};
          if (cropPixelsRef.current) {
            area = cropPixelsRef.current;
          }
          onCropComplete({ url: canvas.toDataURL(), blob, area });
        });
      }
    }
  }, [rotateVal, hasRotate]);

  /**
   * Controls
   */
  const MIN_ROTATE = 0;
  const MAX_ROTATE = 360;

  const isMinZoom = zoomVal <= MIN_ZOOM;
  const isMaxZoom = zoomVal >= MAX_ZOOM;
  const isMinRotate = rotateVal === MIN_ROTATE;
  const isMaxRotate = rotateVal >= MAX_ROTATE;

  const subZoomVal = useCallback(() => {
    if (!isMinZoom) setZoomVal(zoomVal - ZOOM_STEP);
  }, [isMinZoom, zoomVal]);

  const addZoomVal = useCallback(() => {
    if (!isMaxZoom) setZoomVal(zoomVal + ZOOM_STEP);
  }, [isMaxZoom, zoomVal]);

  const addRotateVal = useCallback(() => {
    if (!isMaxRotate) {
      setRotateVal(rotateVal + rotateStep);
    } else {
      setRotateVal(MIN_ROTATE + rotateStep);
    }
  }, [isMaxRotate, rotateVal]);

  const initVal = useCallback(() => {
    if (!isMinZoom || !isMinRotate) {
      setZoomVal(MIN_ZOOM);
      setRotateVal(MIN_ROTATE);
    }
  }, [zoomVal, rotateVal]);

  /**
   * Modal
   */
  const closeModal = useCallback(() => {
    setSrc('');
    setZoomVal(1);
    setRotateVal(0);
  }, []);

  const onClose = useCallback(() => {
    closeModal();
    if (isFunction(onModalCancel)) {
      onModalCancel();
    }
  }, []);

  const onOk = useCallback(async () => {
    onClose();
    const naturalModalImg: Element | HTMLImageElement | null = document.querySelector(`.${prefixClsMedia}`);
    if (naturalModalImg) {
      if (naturalModalImg && naturalModalImg instanceof HTMLImageElement) {
        const canvas: HTMLCanvasElement | undefined = serverCrop ? imageToCanvas(naturalModalImg) : imageCropCanvas(naturalModalImg);
        if (fileRef.current && canvas) {
          const { type, name, uid } = fileRef.current;
          canvas.toBlob(
            async (blob) => {
              let newFile: any = blob;
              if (newFile) {
                newFile.lastModifiedDate = Date.now();
                newFile.name = name;
                newFile.uid = uid;
                newFile.imageCropArea = cropPixelsRef.current;
                if (resolveRef && rejectRef && resolveRef.current && rejectRef.current) {
                  if (typeof beforeUploadRef.current !== 'function') return resolveRef.current(newFile);

                  const res = beforeUploadRef.current(newFile, [newFile]);

                  if (typeof res !== 'boolean' && !res) {
                    console.error('beforeUpload must return a boolean or Promise');
                    return;
                  }
                  if (res === true) return resolveRef.current(newFile);
                  if (res === false) return rejectRef.current('not upload');
                  if (res && typeof res.then === 'function') {
                    try {
                      const passedFile = await res;
                      const passedFileType = Object.prototype.toString.call(passedFile);
                      if (passedFileType === '[object File]' || passedFileType === '[object Blob]') newFile = passedFile;
                      resolveRef.current(newFile);
                    } catch (err) {
                      rejectRef.current(err);
                    }
                  }
                }
              }
            },
            type,
            0.4,
          );
        }
        if (isFunction(onModalOk) && canvas) {
          canvas.toBlob((blob) => {
            let area = {};
            if (cropPixelsRef.current) {
              area = cropPixelsRef.current;
            }
            onModalOk({ url: canvas.toDataURL(), blob, area });
          });
        }
      }
    }

  }, [hasRotate, onClose, rotateVal]);

  const RenderCrop = (
    <EasyCrop
      ref={ref}
      src={src}
      aspect={aspect}
      shape={shape}
      grid={grid}
      hasZoom={hasZoom}
      zoomVal={zoomVal}
      rotateVal={rotateVal}
      setZoomVal={setZoomVal}
      setRotateVal={setRotateVal}
      onComplete={onComplete}
      prefixCls={prefixCls}
    />
  );

  const title = modalProps && modalProps.title || modalTitle;
  const cancelButtonProps: ButtonProps = { funcType: 'raised' };
  const okButtonProps: ButtonProps = { funcType: 'raised', type: 'primary' };
  return (
    <LocaleReceiver componentName="imageCrop" defaultLocale={defaultLocale.imageCrop}>
      {(locale: imageCrop) => {
        return (
          <>
            {renderUpload()}
            {src && modalVisible && (
              <Modal
                visible={modalVisible}
                wrapClassName={`${prefixCls}-modal`}
                title={title || (locale && locale.editImage ? locale.editImage : 'Edit image')} // 当不存在的语言使用英文
                width={modalWidth}
                destroyOnClose
                maskClosable={false}
                onCancel={onClose}
                onOk={onOk}
                cancelButtonProps={cancelButtonProps}
                okButtonProps={okButtonProps}
                okText={modalOk}
                cancelText={modalCancel}
                {...modalProps}
              >
                {cropContent ? cropContent(RenderCrop) : RenderCrop}
                <div className={`${prefixCls}-control`}>
                  {hasZoom && (
                    <ButtonGroup>
                      <Button funcType="raised" icon="zoom_in" onClick={addZoomVal} disabled={isMaxZoom} />
                      <Button funcType="raised" icon="zoom_out" onClick={subZoomVal} disabled={isMinZoom} />
                    </ButtonGroup>
                  )}
                  {
                    hasRotate
                    &&
                    <Button
                      funcType="raised"
                      icon={rotateStep === 90 ? 'play_90' : 'rotate_right'}
                      onClick={addRotateVal}
                    />
                  }
                  {hasZoom && <Button funcType="raised" onClick={initVal}>1:1</Button>}
                </div>
              </Modal>
            )}
          </>
        );
      }}
    </LocaleReceiver>
  );
}) as CompoundedComponent;

ImgCrop.displayName = 'ImgCrop';

ImgCrop.defaultProps = {
  shape: ShapeCroper.rect,
  grid: false,
  zoom: true,
  rotate: false,
  modalWidth: 600,
  modalVisible: true,
  serverCrop: false,
};

ImgCrop.AvatarUploader = AvatarUploader;

export default ImgCrop;