import React, { useCallback, useContext, useEffect, useRef } from 'react';
import { fabric } from 'fabric';
import { FabricTexture, AbstractMode, Texture } from 'fabric-texture';
import Warp from 'fabric-texture/modes/warp';
import { Canvas } from 'docs/components';
import type { CanvasHandlers } from 'docs/components/canvas';
import { DocsContext } from 'docs/docs';
import styles from './style.less';
import {
  calcFabricCanvasCoord,
  calcCoordDistance,
  calcRelativeCoord,
  calcFabricRelativeCoord,
} from '@utils';

export class ConvexMode extends AbstractMode {
  name = 'convex';

  convexCenterPoint: Coord | null = null;

  constructor(
    public radius: number,
    public level: number,
  ) {
    super();
  }

  execute(texture: Texture) {
    const points = Warp.execute(texture);
    const centerPoint = this.convexCenterPoint;
    if (!centerPoint) return points;
    return points.map((rows) =>
      rows.map((row) =>
        row.map((point) => {
          const distance = calcCoordDistance(centerPoint, point);
          if (distance <= this.radius) {
            return calcRelativeCoord(
              centerPoint,
              point,
              this.radius - this.radius * (1 - distance / this.radius) ** Math.max(0, this.level),
            );
          } else {
            return point;
          }
        }),
      ),
    );
  }

  dirtyRender(fabricTexture: FabricTexture) {
    const fabricCanvas = fabricTexture.canvas;
    if (!fabricCanvas) return;

    fabricCanvas.on('mouse:move', (e) => {
      if (!e.pointer) return;
      const pointer = calcFabricCanvasCoord(fabricCanvas, e.pointer);

      const path = fabricTexture.paths?.[0];
      if (!path) return;

      const coord = calcFabricRelativeCoord({ left: pointer.x, top: pointer.y }, path);
      this.convexCenterPoint = coord;

      fabricTexture.requestRender(false);
    });
  }
}

const Customization = () => {
  const { placeholder } = useContext(DocsContext);

  const _canvasRef = useRef<CanvasHandlers>(null);
  const _fabricTextureRef = useRef<FabricTexture>();

  const render = useCallback(async () => {
    const canvas = _canvasRef.current?.getCanvas();
    if (!canvas) return;

    const width = canvas.getWidth();
    const height = canvas.getHeight();

    if (!placeholder) return;

    // 限制画布不允许多选
    canvas.selection = false;

    // 缩放以控制在合理渲染区域内
    const scale =
      Math.min(200, canvas.getWidth()) /
      Math.min(placeholder.naturalWidth, placeholder.naturalHeight);

    // 创建元素
    const object = new fabric.Image(placeholder, {
      left: width / 2,
      top: height / 2,
      scaleX: scale,
      scaleY: scale,
      originX: 'center',
      originY: 'center',
    });

    // 进入交互态
    const fabricTexture = new FabricTexture(canvas);
    fabricTexture.enterEditing(object, null, new ConvexMode(100, 3));
    if (_fabricTextureRef.current) {
      _fabricTextureRef.current.leaveEditing();
    }
    _fabricTextureRef.current = fabricTexture;

    fabricTexture.texture?.setSplitUnit(0.02).useInstanceCanvas().setRenderOptions({ padding: 60 });
    fabricTexture.render();

    // 隐藏源元素
    object.set({ visible: false });
    canvas.renderAll();
  }, [placeholder]);

  useEffect(() => {
    render();
  }, [render]);

  return (
    <div className={styles.demo}>
      <Canvas className={styles.canvas} ref={_canvasRef} />
      <div className={styles.hint}>移动鼠标到图案上</div>
    </div>
  );
};

export default Customization;
