import "./circled.css";
import Cropper from "cropperjs";
import { ElUpload } from "element-plus";
import type { CSSProperties } from "vue";
import { useResizeObserver } from "@vueuse/core";
import { longpress } from "@/directives/longpress";
import { useTippy, directive as tippy } from "vue-tippy";
import { delay, debounce, isArray, downloadByBase64 } from "@pureadmin/utils";
import {
  ref,
  unref,
  computed,
  PropType,
  onMounted,
  onUnmounted,
  defineComponent
} from "vue";
import {
  Reload,
  Upload,
  ArrowH,
  ArrowV,
  ArrowUp,
  ArrowDown,
  ArrowLeft,
  ChangeIcon,
  ArrowRight,
  RotateLeft,
  SearchPlus,
  RotateRight,
  SearchMinus,
  DownloadIcon
} from "./svg";

type Options = Cropper.Options;

const defaultOptions: Options = {
  aspectRatio: 1,
  zoomable: true,
  zoomOnTouch: true,
  zoomOnWheel: true,
  cropBoxMovable: true,
  cropBoxResizable: true,
  toggleDragModeOnDblclick: true,
  autoCrop: true,
  background: true,
  highlight: true,
  center: true,
  responsive: true,
  restore: true,
  checkCrossOrigin: true,
  checkOrientation: true,
  scalable: true,
  modal: true,
  guides: true,
  movable: true,
  rotatable: true
};

const props = {
  src: { type: String, required: true },
  alt: { type: String },
  circled: { type: Boolean, default: false },
  realTimePreview: { type: Boolean, default: true },
  height: { type: [String, Number], default: "360px" },
  crossorigin: {
    type: String as PropType<"" | "anonymous" | "use-credentials" | undefined>,
    default: undefined
  },
  imageStyle: { type: Object as PropType<CSSProperties>, default: () => ({}) },
  options: { type: Object as PropType<Options>, default: () => ({}) }
};

export default defineComponent({
  name: "ReCropper",
  props,
  setup(props, { attrs, emit }) {
    const tippyElRef = ref<ElRef<HTMLImageElement>>();
    const imgElRef = ref<ElRef<HTMLImageElement>>();
    const cropper = ref<Nullable<Cropper>>();
    const isReady = ref(false);
    const imgBase64 = ref();
    const inCircled = ref(props.circled);
    const inSrc = ref(props.src);
    let scaleX = 1;
    let scaleY = 1;

    const debounceRealTimeCroppered = debounce(realTimeCroppered, 80);

    const getImageStyle = computed((): CSSProperties => {
      return {
        height: props.height,
        maxWidth: "100%",
        ...props.imageStyle
      };
    });

    const getClass = computed(() => {
      return [
        attrs.class,
        {
          ["re-circled"]: inCircled.value
        }
      ];
    });

    const iconClass = computed(() => {
      return [
        "p-[6px]",
        "h-[30px]",
        "w-[30px]",
        "outline-none",
        "rounded-[4px]",
        "cursor-pointer",
        "hover:bg-[rgba(0,0,0,0.06)]"
      ];
    });

    const getWrapperStyle = computed((): CSSProperties => {
      return { height: `${props.height}`.replace(/px/, "") + "px" };
    });

    onMounted(init);

    onUnmounted(() => {
      cropper.value?.destroy();
    });

    useResizeObserver(tippyElRef, () => {
      handCropper("reset");
    });

    async function init() {
      const imgEl = unref(imgElRef);
      if (!imgEl) return;
      cropper.value = new Cropper(imgEl, {
        ...defaultOptions,
        ready: () => {
          isReady.value = true;
          realTimeCroppered();
          delay(400).then(() => emit("readied", cropper.value));
        },
        crop() {
          debounceRealTimeCroppered();
        },
        zoom() {
          debounceRealTimeCroppered();
        },
        cropmove() {
          debounceRealTimeCroppered();
        },
        ...props.options
      });
    }

    function realTimeCroppered() {
      props.realTimePreview && croppered();
    }

    function croppered() {
      if (!cropper.value) return;
      const canvas = inCircled.value
        ? getRoundedCanvas()
        : cropper.value.getCroppedCanvas();
      // https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/toBlob
      canvas.toBlob(blob => {
        if (!blob) return;
        const fileReader: FileReader = new FileReader();
        fileReader.readAsDataURL(blob);
        fileReader.onloadend = e => {
          if (!e.target?.result || !blob) return;
          imgBase64.value = e.target.result;
          emit("cropper", {
            base64: e.target.result,
            blob,
            info: { size: blob.size, ...cropper.value.getData() }
          });
        };
        fileReader.onerror = () => {
          emit("error");
        };
      });
    }

    function getRoundedCanvas() {
      const sourceCanvas = cropper.value!.getCroppedCanvas();
      const canvas = document.createElement("canvas");
      const context = canvas.getContext("2d")!;
      const width = sourceCanvas.width;
      const height = sourceCanvas.height;
      canvas.width = width;
      canvas.height = height;
      context.imageSmoothingEnabled = true;
      context.drawImage(sourceCanvas, 0, 0, width, height);
      context.globalCompositeOperation = "destination-in";
      context.beginPath();
      context.arc(
        width / 2,
        height / 2,
        Math.min(width, height) / 2,
        0,
        2 * Math.PI,
        true
      );
      context.fill();
      return canvas;
    }

    function handCropper(event: string, arg?: number | Array<number>) {
      if (event === "scaleX") {
        scaleX = arg = scaleX === -1 ? 1 : -1;
      }
      if (event === "scaleY") {
        scaleY = arg = scaleY === -1 ? 1 : -1;
      }
      arg && isArray(arg)
        ? cropper.value?.[event]?.(...arg)
        : cropper.value?.[event]?.(arg);
    }

    function beforeUpload(file) {
      const reader = new FileReader();
      reader.readAsDataURL(file);
      inSrc.value = "";
      reader.onload = e => {
        inSrc.value = e.target?.result as string;
      };
      reader.onloadend = () => {
        init();
      };
      return false;
    }

    const menuContent = defineComponent({
      directives: {
        tippy,
        longpress
      },
      setup() {
        return () => (
          <div class="flex flex-wrap w-[60px] justify-between">
            <ElUpload
              accept="image/*"
              show-file-list={false}
              before-upload={beforeUpload}
            >
              <Upload
                class={iconClass.value}
                v-tippy={{
                  content: "上传",
                  placement: "left-start"
                }}
              />
            </ElUpload>
            <DownloadIcon
              class={iconClass.value}
              v-tippy={{
                content: "下载",
                placement: "right-start"
              }}
              onClick={() => downloadByBase64(imgBase64.value, "cropping.png")}
            />
            <ChangeIcon
              class={iconClass.value}
              v-tippy={{
                content: "圆形、矩形裁剪",
                placement: "left-start"
              }}
              onClick={() => {
                inCircled.value = !inCircled.value;
                realTimeCroppered();
              }}
            />
            <Reload
              class={iconClass.value}
              v-tippy={{
                content: "重置",
                placement: "right-start"
              }}
              onClick={() => handCropper("reset")}
            />
            <ArrowUp
              class={iconClass.value}
              v-tippy={{
                content: "上移（可长按）",
                placement: "left-start"
              }}
              v-longpress={[() => handCropper("move", [0, -10]), "0:100"]}
            />
            <ArrowDown
              class={iconClass.value}
              v-tippy={{
                content: "下移（可长按）",
                placement: "right-start"
              }}
              v-longpress={[() => handCropper("move", [0, 10]), "0:100"]}
            />
            <ArrowLeft
              class={iconClass.value}
              v-tippy={{
                content: "左移（可长按）",
                placement: "left-start"
              }}
              v-longpress={[() => handCropper("move", [-10, 0]), "0:100"]}
            />
            <ArrowRight
              class={iconClass.value}
              v-tippy={{
                content: "右移（可长按）",
                placement: "right-start"
              }}
              v-longpress={[() => handCropper("move", [10, 0]), "0:100"]}
            />
            <ArrowH
              class={iconClass.value}
              v-tippy={{
                content: "水平翻转",
                placement: "left-start"
              }}
              onClick={() => handCropper("scaleX", -1)}
            />
            <ArrowV
              class={iconClass.value}
              v-tippy={{
                content: "垂直翻转",
                placement: "right-start"
              }}
              onClick={() => handCropper("scaleY", -1)}
            />
            <RotateLeft
              class={iconClass.value}
              v-tippy={{
                content: "逆时针旋转",
                placement: "left-start"
              }}
              onClick={() => handCropper("rotate", -45)}
            />
            <RotateRight
              class={iconClass.value}
              v-tippy={{
                content: "顺时针旋转",
                placement: "right-start"
              }}
              onClick={() => handCropper("rotate", 45)}
            />
            <SearchPlus
              class={iconClass.value}
              v-tippy={{
                content: "放大（可长按）",
                placement: "left-start"
              }}
              v-longpress={[() => handCropper("zoom", 0.1), "0:100"]}
            />
            <SearchMinus
              class={iconClass.value}
              v-tippy={{
                content: "缩小（可长按）",
                placement: "right-start"
              }}
              v-longpress={[() => handCropper("zoom", -0.1), "0:100"]}
            />
          </div>
        );
      }
    });

    function onContextmenu(event) {
      event.preventDefault();

      const { show, setProps } = useTippy(tippyElRef, {
        content: menuContent,
        arrow: false,
        theme: "light",
        trigger: "manual",
        interactive: true,
        appendTo: "parent",
        // hideOnClick: false,
        animation: "perspective",
        placement: "bottom-start"
      });

      setProps({
        getReferenceClientRect: () => ({
          width: 0,
          height: 0,
          top: event.clientY,
          bottom: event.clientY,
          left: event.clientX,
          right: event.clientX
        })
      });

      show();
    }

    return {
      inSrc,
      props,
      imgElRef,
      tippyElRef,
      getClass,
      getWrapperStyle,
      getImageStyle,
      isReady,
      croppered,
      onContextmenu
    };
  },

  render() {
    const {
      inSrc,
      isReady,
      getClass,
      getImageStyle,
      onContextmenu,
      getWrapperStyle
    } = this;
    const { alt, crossorigin } = this.props;

    return inSrc ? (
      <div
        ref="tippyElRef"
        class={getClass}
        style={getWrapperStyle}
        onContextmenu={event => onContextmenu(event)}
      >
        <img
          v-show={isReady}
          ref="imgElRef"
          style={getImageStyle}
          src={inSrc}
          alt={alt}
          crossorigin={crossorigin}
        />
      </div>
    ) : null;
  }
});
