import { useState } from "react";
import { Circle, Rect } from "react-konva";
import { observer } from "mobx-react";
import { getParent, hasParent, types } from "mobx-state-tree";

import { guidGenerator } from "../core/Helpers";
import { useRegionStyles } from "../hooks/useRegionColor";
import { AnnotationMixin } from "../mixins/AnnotationMixin";

const PolygonPointModel = types
  .model("PolygonPoint", {
    id: types.optional(types.identifier, guidGenerator),

    x: types.number,
    y: types.number,

    index: types.number,

    style: "circle",
    size: "small",
  })
  .volatile(() => ({
    selected: false,
  }))
  .views((self) => ({
    get parent() {
      if (!hasParent(self, 2)) return null;
      return getParent(self, 2);
    },

    get stage() {
      return self.parent?.parent;
    },

    get canvasX() {
      return self.stage?.internalToCanvasX(self.x);
    },

    get canvasY() {
      return self.stage?.internalToCanvasY(self.y);
    },
  }))
  .actions((self) => ({
    /**
     * External function for Polygon Parent
     * @param {number} x
     * @param {number} y
     */

    movePoint(offsetX, offsetY) {
      const dx = self.stage.canvasToInternalX(offsetX);
      const dy = self.stage.canvasToInternalY(offsetY);

      self.x = self.x + dx;
      self.y = self.y + dy;
    },

    _setPos(x, y) {
      self.x = x;
      self.y = y;
    },
    _movePoint(canvasX, canvasY) {
      const point = self.parent.control?.getSnappedPoint({
        x: self.stage.canvasToInternalX(canvasX),
        y: self.stage.canvasToInternalY(canvasY),
      });

      self._setPos(point.x, point.y);
    },

    /**
     * Close polygon
     * @param {*} ev
     */
    closeStartPoint() {
      if (self.annotation.isReadOnly()) return;
      if (self.parent.closed) return;

      if (self.parent.mouseOverStartPoint) {
        self.parent.closePoly();
      }
    },

    handleMouseOverStartPoint(ev) {
      ev.cancelBubble = true;

      const stage = self.stage?.stageRef;

      if (!stage) return;
      stage.container().style.cursor = "crosshair";

      /**
       * Check if polygon > 2 points and closed point
       */
      if (self.parent.closed || self.parent.points.length < 3) return;

      const startPoint = ev.target;

      if (self.style === "rectangle") {
        startPoint.setX(startPoint.x() - startPoint.width() / 2);
        startPoint.setY(startPoint.y() - startPoint.height() / 2);
      }

      const scaleMap = {
        small: 2,
        medium: 3,
        large: 4,
      };

      const scale = scaleMap[self.size];

      startPoint.scale({
        x: scale / self.stage.zoomScale,
        y: scale / self.stage.zoomScale,
      });

      self.parent.setMouseOverStartPoint(true);
    },

    handleMouseOutStartPoint(ev) {
      const t = ev.target;

      const stage = self.stage?.stageRef;

      if (!stage) return;
      stage.container().style.cursor = "default";

      if (self.style === "rectangle") {
        t.setX(t.x() + t.width() / 2);
        t.setY(t.y() + t.height() / 2);
      }

      t.scale({
        x: 1 / self.stage.zoomScale,
        y: 1 / self.stage.zoomScale,
      });

      self.parent.setMouseOverStartPoint(false);
    },

    getSkipInteractions() {
      return self.parent.control.obj.getSkipInteractions();
    },
  }));

const PolygonPoint = types.compose("PolygonPoint", AnnotationMixin, PolygonPointModel);

const PolygonPointView = observer(({ item, name }) => {
  if (!item.parent) return;

  const [draggable, setDraggable] = useState(true);
  const regionStyles = useRegionStyles(item.parent);
  const sizes = {
    small: 4,
    medium: 8,
    large: 12,
  };

  const stroke = {
    small: 1,
    medium: 2,
    large: 3,
  };

  const w = sizes[item.size];

  const startPointAttr =
    item.index === 0
      ? {
          hitStrokeWidth: 12,
          fill: regionStyles.strokeColor || item.primary,
          onMouseOver: item.handleMouseOverStartPoint,
          onMouseOut: item.handleMouseOutStartPoint,
        }
      : null;

  const dragOpts = {
    onDragMove: (e) => {
      if (item.getSkipInteractions()) return false;
      if (e.target !== e.currentTarget) return;
      const shape = e.target;
      let { x, y } = shape.attrs;

      if (x < 0) x = 0;
      if (y < 0) y = 0;
      if (x > item.stage.stageWidth) x = item.stage.stageWidth;
      if (y > item.stage.stageHeight) y = item.stage.stageHeight;

      item._movePoint(x, y);
      shape.setAttr("x", item.canvasX);
      shape.setAttr("y", item.canvasY);
    },

    onDragStart: () => {
      if (item.getSkipInteractions()) {
        setDraggable(false);
        return false;
      }
      item.annotation.history.freeze();
    },

    onDragEnd: (e) => {
      setDraggable(true);
      item.annotation.history.unfreeze();
      e.cancelBubble = true;
    },

    onMouseOver: (e) => {
      e.cancelBubble = true;
      const stage = item.stage?.stageRef;

      if (!stage) return;
      stage.container().style.cursor = "crosshair";
    },

    onMouseOut: () => {
      const stage = item.stage?.stageRef;

      if (!stage) return;
      stage.container().style.cursor = "default";
    },

    onTransformEnd(e) {
      if (e.target !== e.currentTarget) return;
      const t = e.target;

      t.setAttr("x", 0);
      t.setAttr("y", 0);
      t.setAttr("scaleX", 1);
      t.setAttr("scaleY", 1);
    },
  };

  const fill = item.selected ? "green" : "white";

  if (item.style === "circle") {
    return (
      <Circle
        key={name}
        name={name}
        x={item.canvasX}
        y={item.canvasY}
        radius={w}
        fill={fill}
        stroke="black"
        strokeWidth={stroke[item.size]}
        dragOnTop={false}
        strokeScaleEnabled={false}
        perfectDrawEnabled={false}
        shadowForStrokeEnabled={false}
        scaleX={1 / (item.stage.zoomScale || 1)}
        scaleY={1 / (item.stage.zoomScale || 1)}
        onDblClick={() => {
          item.parent.deletePoint(item);
        }}
        onClick={(ev) => {
          if (ev.evt.altKey) return item.parent.deletePoint(item);
          if (item.parent.isDrawing && item.parent.points.length === 1) return;
          // don't unselect polygon on point click
          ev.evt.preventDefault();
          ev.cancelBubble = true;
          if (item.parent.mouseOverStartPoint) {
            item.closeStartPoint();
            item.parent.notifyDrawingFinished();
          } else {
            item.parent.setSelectedPoint(item);
          }
        }}
        {...dragOpts}
        {...startPointAttr}
        draggable={!item.parent.isReadOnly() && draggable}
      />
    );
  }
  return (
    <Rect
      name={name}
      key={name}
      x={item.x - w / 2}
      y={item.y - w / 2}
      width={w}
      height={w}
      fill={fill}
      stroke="black"
      strokeWidth={stroke[item.size]}
      strokeScaleEnabled={false}
      perfectDrawEnabled={false}
      shadowForStrokeEnabled={false}
      dragOnTop={false}
      {...dragOpts}
      {...startPointAttr}
      draggable={!item.parent.isReadOnly()}
    />
  );
});

export { PolygonPoint, PolygonPointView };
