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 Loading from "components/Loading";
import useBreakpoint from "antd/lib/grid/hooks/useBreakpoint";
import { resizeFile } from "utils/file";
import { useEffect } from "react";

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

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

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

    hasZoom,
    zoomVal,
    setZoomVal,

    minZoom,
    maxZoom,
    onComplete,

    cropperProps,
  } = props;

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

  const onCropComplete = (croppedArea: any, croppedAreaPixels: any) => {
    onComplete({ ...croppedAreaPixels, cropperParams: { ...crop, zoomVal } });
  };

  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]
  );

  const handleZoomChange = (zoom: number) => {
    setZoomVal(zoom < maxZoom ? zoom : maxZoom);
  };

  return (
    <Cropper
      {...cropperProps}
      ref={ref}
      image={src}
      crop={crop}
      cropSize={cropSize}
      onCropChange={setCrop}
      aspect={aspect}
      cropShape={shape}
      showGrid={grid}
      zoomWithScroll={hasZoom}
      zoom={zoomVal}
      onZoomChange={handleZoomChange}
      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;
  setZoomVal: any;
  rotateVal?: number;
  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,
    resizeMaxSize,
    fillColor = "white",

    modalTitle,
    modalWidth,
    modalOk,
    modalCancel,

    beforeCrop,
    children,

    cropperProps,
    handleCompletedCrop,
  } = props;

  const hasZoom = zoom === true;

  const [src, setSrc] = useState("");
  const [zoomVal, setZoomVal] = useState(1);
  const [loading, setLoading] = useState(false);
  const [maxZoom, setMaxZoom] = useState(1);
  const [zoomStep, setZoomStep] = useState(0.1);

  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();

  // Calcualte zoomMax and zoomStep when img change
  useEffect(() => {
    const img = new Image();
    img.onload = () => {
      const { naturalHeight, naturalWidth } = img;
      const maxHeighRatio = naturalHeight / 710;
      const maxWidthRatio = naturalWidth / 564;
      const calculatedMaxZoom =
        maxHeighRatio > maxWidthRatio ? maxWidthRatio : maxHeighRatio;

      setMaxZoom(calculatedMaxZoom);
      setZoomStep((calculatedMaxZoom - 1) / 30);
    };
    img.src = src; // load image
  }, [src]);

  /**
   * 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 <= minZoom;
  const isMaxZoom = zoomVal >= maxZoom;

  const subZoomVal = useCallback(() => {
    const zoom = zoomVal - zoomStep;
    setZoomVal(zoom < minZoom ? minZoom : zoom);
  }, [isMinZoom, zoomVal, zoomStep]);

  const addZoomVal = useCallback(() => {
    const zoom = zoomVal + zoomStep;
    setZoomVal(zoom > maxZoom ? maxZoom : zoom);
  }, [isMaxZoom, zoomVal, zoomStep]);

  /**
   * 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);
  }, []);

  const onOk = useCallback(async () => {
    onClose();
    const naturalImg: any = document.querySelector(`.${MEDIA_CLASS}`);
    const { width, height, x, y } = cropPixelsRef.current;

    // Process and upload origin file
    const uploadFile = new File([fileRef.current], fileRef.current?.name, {
      type: fileRef.current?.type,
    });

    // Resolve for handleOnChange of Dragger process next
    if (typeof beforeUploadRef.current !== "function") {
      handleCompletedCrop({ cropParams: { ...cropPixelsRef.current } });
      return resolveRef.current(uploadFile);
    }

    // Check valid img and resolve for dragger onChange
    const res = beforeUploadRef.current(uploadFile, [uploadFile]);

    if (typeof res !== "boolean" && !res) {
      console.error("beforeUpload must return a boolean or Promise");
      return;
    }

    if (res === true) {
      handleCompletedCrop({ cropParams: cropPixelsRef.current });
      return resolveRef.current(uploadFile);
    }
    if (res === false) return rejectRef.current("not upload");
  }, [onClose, quality, fillColor]);

  const renderComponent = (titleOfModal: string) => (
    <>
      {renderUpload()}
      {src ? (
        <Modal
          visible={true}
          wrapClassName={`${pkg}-modal`}
          title={titleOfModal}
          onOk={onOk}
          onCancel={onClose}
          maskClosable={false}
          destroyOnClose
          centered
          data-testid="image-crop__upload-modal"
          {...modalProps}
        >
          <EasyCrop
            ref={ref}
            src={src}
            aspect={aspect}
            shape={shape}
            grid={grid}
            hasZoom={hasZoom}
            zoomVal={zoomVal}
            setZoomVal={setZoomVal}
            minZoom={minZoom}
            maxZoom={maxZoom}
            onComplete={onComplete}
            cropperProps={cropperProps}
          />
          {hasZoom && (
            <div className={`${pkg}-control zoom`}>
              <button onClick={subZoomVal} disabled={isMinZoom}>
                －
              </button>
              <Slider
                tipFormatter={(tip) => tip?.toFixed(2)}
                min={minZoom}
                max={maxZoom}
                step={zoomStep}
                value={zoomVal}
                onChange={(zoom: any) => {
                  setZoomVal(zoom > maxZoom ? maxZoom : zoom);
                }}
              />
              <button onClick={addZoomVal} disabled={isMaxZoom}>
                ＋
              </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;
  handleCompletedCrop?: any;
}

export default forwardRef(ImgCrop);
