import React, {
  useState,
  useCallback,
  useMemo,
  useRef,
  forwardRef,
} from "react";
import Cropper from "react-easy-crop";
import LocaleReceiver from "antd/lib/locale-provider/LocaleReceiver";
import { Slider, Modal } from "antd";
import "./index.less";
// import ImageBlobReduce from "image-blob-reduce";
import Loading from "components/Loading";
import useBreakpoint from "antd/lib/grid/hooks/useBreakpoint";
import { resizeFile } from "utils/file";

const pkg = "antd-img-crop";
const noop = () => {
  console.log("");
};

const MEDIA_CLASS = `${pkg}-media`;

const ZOOM_STEP = 0.1;

const MIN_ROTATE = 0;
const MAX_ROTATE = 360;
const ROTATE_STEP = 1;

// eslint-disable-next-line react/display-name
const EasyCrop = forwardRef((props: IEasyCropProps, ref) => {
  const {
    src,
    aspect,
    shape,
    grid,

    hasZoom,
    zoomVal,
    rotateVal,
    setZoomVal,
    setRotateVal,

    minZoom,
    maxZoom,
    onComplete,

    cropperProps,
  } = props;

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

  const onCropComplete = useCallback(
    (croppedArea, croppedAreaPixels) => {
      onComplete(croppedAreaPixels);
    },
    [onComplete]
  );

  const onMediaLoaded = useCallback(
    (mediaSize) => {
      const { width, height } = mediaSize;
      const ratioWidth = height * aspect;

      if (width > ratioWidth) {
        setCropSize({ width: ratioWidth, height });
      } else {
        setCropSize({ width, height: width / aspect });
      }
    },
    [aspect]
  );

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

interface IEasyCropProps {
  src: string;
  aspect: number;
  shape: string;
  grid: boolean;
  hasZoom: boolean;
  zoomVal: number;
  rotateVal: number;
  setZoomVal: any;
  setRotateVal: any;

  minZoom: number;
  maxZoom: number;
  onComplete: any;
  cropperProps: any;
}

const ImgCrop = (props: ImgCropProps, ref: any) => {
  const {
    aspect = 1,
    shape = "rect",
    grid = false,
    quality = 0.4,

    zoom = true,
    rotate = false,
    minZoom = 1,
    maxZoom = 3,
    resizeMaxSize,
    fillColor = "white",

    modalTitle,
    modalWidth,
    modalOk,
    modalCancel,

    beforeCrop,
    children,

    cropperProps,
  } = props;

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

  const [src, setSrc] = useState("");
  const [zoomVal, setZoomVal] = useState(1);
  const [rotateVal, setRotateVal] = useState(0);
  const [loading, setLoading] = useState(false);

  const beforeUploadRef: any = useRef();
  const fileRef: any = useRef();
  const resolveRef: any = useRef(noop);
  const rejectRef: any = useRef(noop);

  const cropPixelsRef: any = useRef();
  const screens = useBreakpoint();

  /**
   * Upload
   */
  const renderUpload = useCallback(() => {
    const upload = Array.isArray(children) ? children[0] : children;
    const { beforeUpload, accept, ...restUploadProps } = upload.props;
    beforeUploadRef.current = beforeUpload;

    return {
      ...upload,
      props: {
        ...restUploadProps,
        accept: accept || "image/png,image/jpeg,image/webp",
        beforeUpload: (file: any, fileList: any) =>
          new Promise(async (resolve, reject) => {
            const isValid = beforeCrop
              ? await beforeCrop(file, fileList)
              : true;
            if (!isValid) {
              reject();
              return;
            }

            let finalFile;

            if (resizeMaxSize) {
              //   const reduce = new ImageBlobReduce();
              setLoading(true);
              //   finalFile = await reduce.toBlob(file, { max: resizeMaxSize });
              finalFile = await resizeFile(file);
              finalFile.name = file.name;
              finalFile.lastModifiedDate = new Date();

              fileRef.current = finalFile;
              resolveRef.current = resolve;
              rejectRef.current = reject;
            } else {
              finalFile = file;
              fileRef.current = file;
              resolveRef.current = resolve;
              rejectRef.current = reject;
            }
            const reader = new FileReader();
            reader.addEventListener("load", () => {
              setLoading(false);
              setSrc(reader.result as string);
            });
            reader.readAsDataURL(finalFile);
          }),
      },
    };
  }, [beforeCrop, resizeMaxSize, children]);

  /**
   * EasyCrop
   */
  const onComplete = useCallback((croppedAreaPixels) => {
    cropPixelsRef.current = croppedAreaPixels;
  }, []);

  /**
   * Controls
   */
  const isMinZoom = zoomVal - ZOOM_STEP < minZoom;
  const isMaxZoom = zoomVal + ZOOM_STEP > maxZoom;
  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 subRotateVal = useCallback(() => {
    if (!isMinRotate) setRotateVal(rotateVal - ROTATE_STEP);
  }, [isMinRotate, rotateVal]);

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

  /**
   * Modal
   */
  const modalProps = useMemo(() => {
    const obj: any = {
      width: modalWidth,
      okText: modalOk,
      cancelText: modalCancel,
    };
    Object.keys(obj).forEach((key) => {
      if (!obj[key]) delete obj[key];
    });
    return obj;
  }, [modalCancel, modalOk, modalWidth]);

  const onClose = useCallback(() => {
    setSrc("");
    setZoomVal(1);
    setRotateVal(0);
  }, []);

  const onOk = useCallback(async () => {
    onClose();

    const naturalImg: any = document.querySelector(`.${MEDIA_CLASS}`);
    const { naturalWidth, naturalHeight } = naturalImg;
    const canvas = document.createElement("canvas");
    const ctx: any = canvas.getContext("2d");

    // create a max canvas to cover the source image after rotated
    const maxLen = Math.sqrt(
      Math.pow(naturalWidth, 2) + Math.pow(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);
    }

    ctx.fillStyle = fillColor;
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // draw the source image in the center of the max canvas
    const left = (maxLen - naturalWidth) / 2;
    const top = (maxLen - naturalHeight) / 2;
    ctx.drawImage(naturalImg, left, top);

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

    // get the new image
    const { type, name, uid } = fileRef.current;
    canvas.toBlob(
      async (blob: any) => {
        let newFile: any = new File([blob], name, { type });
        newFile.uid = uid;

        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 type = Object.prototype.toString.call(passedFile);
            if (type === "[object File]" || type === "[object Blob]")
              newFile = passedFile;
            resolveRef.current(newFile);
          } catch (err) {
            rejectRef.current(err);
          }
        }
      },
      type,
      quality
    );
  }, [hasRotate, onClose, quality, rotateVal, fillColor]);

  const renderComponent = (titleOfModal: string) => (
    <>
      {renderUpload()}
      {src ? (
        <Modal
          visible={true}
          wrapClassName={`${pkg}-modal`}
          title={titleOfModal}
          onOk={onOk}
          onCancel={onClose}
          maskClosable={false}
          destroyOnClose
          centered
          {...modalProps}
        >
          <EasyCrop
            ref={ref}
            src={src}
            aspect={aspect}
            shape={shape}
            grid={grid}
            hasZoom={hasZoom}
            zoomVal={zoomVal}
            rotateVal={rotateVal}
            setZoomVal={setZoomVal}
            setRotateVal={setRotateVal}
            minZoom={minZoom}
            maxZoom={maxZoom}
            onComplete={onComplete}
            cropperProps={cropperProps}
          />
          {hasZoom && (
            <div className={`${pkg}-control zoom`}>
              <button onClick={subZoomVal} disabled={isMinZoom}>
                －
              </button>
              <Slider
                min={minZoom}
                max={maxZoom}
                step={ZOOM_STEP}
                value={zoomVal}
                onChange={setZoomVal}
              />
              <button onClick={addZoomVal} disabled={isMaxZoom}>
                ＋
              </button>
            </div>
          )}
          {hasRotate && (
            <div className={`${pkg}-control rotate`}>
              <button onClick={subRotateVal} disabled={isMinRotate}>
                ↺
              </button>
              <Slider
                min={MIN_ROTATE}
                max={MAX_ROTATE}
                step={ROTATE_STEP}
                value={rotateVal}
                onChange={setRotateVal}
              />
              <button onClick={addRotateVal} disabled={isMaxRotate}>
                ↻
              </button>
            </div>
          )}
        </Modal>
      ) : loading && screens["xs"] ? (
        <Loading />
      ) : (
        <></>
      )}
    </>
  );

  if (modalTitle) return renderComponent(modalTitle);

  return (
    <LocaleReceiver>
      {(locale, localeCode) =>
        renderComponent(localeCode === "zh-cn" ? "编辑图片" : "Edit image")
      }
    </LocaleReceiver>
  );
};

interface ImgCropProps {
  aspect?: number;
  shape?: "rect" | "round";
  grid?: boolean;
  quality?: number;

  zoom?: boolean;
  rotate?: boolean;
  minZoom?: number;
  maxZoom?: number;
  fillColor?: string;
  resizeMaxSize?: number;

  modalTitle?: string;
  modalWidth?: number | string;
  modalOk?: string;
  modalCancel?: string;

  beforeCrop?: any;
  cropperProps?: any;

  children?: any;
}

export default forwardRef(ImgCrop);
