import {
  Matrix2d,
  StateNode,
  Vec2d,
  deepCopy,
  snapAngle,
  sortByIndex
} from "@cmpd/editor";
class DraggingHandle extends StateNode {
  static id = "dragging_handle";
  shapeId = "";
  initialHandle = {};
  initialAdjacentHandle = null;
  initialPagePoint = {};
  markId = "";
  initialPageTransform;
  initialPageRotation;
  info = {};
  isPrecise = false;
  isPreciseId = null;
  pointingId = null;
  onEnter = (info) => {
    const { shape, isCreating, handle } = info;
    this.info = info;
    this.parent.setCurrentToolIdMask(info.onInteractionEnd);
    this.shapeId = shape.id;
    this.markId = isCreating ? `creating:${shape.id}` : "dragging handle";
    if (!isCreating)
      this.editor.mark(this.markId);
    this.initialHandle = deepCopy(handle);
    this.initialPageTransform = this.editor.getShapePageTransform(shape);
    this.initialPageRotation = this.initialPageTransform.rotation();
    this.initialPagePoint = this.editor.inputs.originPagePoint.clone();
    this.editor.updateInstanceState(
      { cursor: { type: isCreating ? "cross" : "grabbing", rotation: 0 } },
      { ephemeral: true }
    );
    const handles = this.editor.getShapeHandles(shape).sort(sortByIndex);
    const index = handles.findIndex((h) => h.id === info.handle.id);
    this.initialAdjacentHandle = null;
    for (let i = index + 1; i < handles.length; i++) {
      const handle2 = handles[i];
      if (handle2.type === "vertex" && handle2.id !== "middle" && handle2.id !== info.handle.id) {
        this.initialAdjacentHandle = handle2;
        break;
      }
    }
    if (!this.initialAdjacentHandle) {
      for (let i = handles.length - 1; i >= 0; i--) {
        const handle2 = handles[i];
        if (handle2.type === "vertex" && handle2.id !== "middle" && handle2.id !== info.handle.id) {
          this.initialAdjacentHandle = handle2;
          break;
        }
      }
    }
    const initialTerminal = shape.props[info.handle.id];
    this.isPrecise = false;
    if (initialTerminal?.type === "binding") {
      this.editor.setHintingShapes([initialTerminal.boundShapeId]);
      this.isPrecise = initialTerminal.isPrecise;
      if (this.isPrecise) {
        this.isPreciseId = initialTerminal.boundShapeId;
      } else {
        this.resetExactTimeout();
      }
    } else {
      this.editor.setHintingShapes([]);
    }
    this.update();
    this.editor.select(this.shapeId);
  };
  // Only relevant to arrows
  exactTimeout = -1;
  // Only relevant to arrows
  resetExactTimeout() {
    if (this.exactTimeout !== -1) {
      this.clearExactTimeout();
    }
    this.exactTimeout = setTimeout(() => {
      if (this.getIsActive() && !this.isPrecise) {
        this.isPrecise = true;
        this.isPreciseId = this.pointingId;
        this.update();
      }
      this.exactTimeout = -1;
    }, 750);
  }
  // Only relevant to arrows
  clearExactTimeout() {
    if (this.exactTimeout !== -1) {
      clearTimeout(this.exactTimeout);
      this.exactTimeout = -1;
    }
  }
  onPointerMove = () => {
    this.update();
  };
  onKeyDown = () => {
    this.update();
  };
  onKeyUp = () => {
    this.update();
  };
  onPointerUp = () => {
    this.complete();
  };
  onComplete = () => {
    this.complete();
  };
  onCancel = () => {
    this.cancel();
  };
  onExit = () => {
    this.parent.setCurrentToolIdMask(void 0);
    this.editor.setHintingShapes([]);
    this.editor.snaps.clear();
    this.editor.updateInstanceState(
      { cursor: { type: "default", rotation: 0 } },
      { ephemeral: true }
    );
  };
  complete() {
    this.editor.snaps.clear();
    const { onInteractionEnd } = this.info;
    if (this.editor.getInstanceState().isToolLocked && onInteractionEnd) {
      this.editor.setCurrentTool(onInteractionEnd, { shapeId: this.shapeId });
      return;
    }
    this.parent.transition("idle");
  }
  cancel() {
    this.editor.bailToMark(this.markId);
    this.editor.snaps.clear();
    const { onInteractionEnd } = this.info;
    if (onInteractionEnd) {
      this.editor.setCurrentTool(onInteractionEnd, { shapeId: this.shapeId });
      return;
    }
    this.parent.transition("idle");
  }
  update() {
    const { editor, shapeId, initialPagePoint } = this;
    const { initialHandle, initialPageRotation, initialAdjacentHandle } = this;
    const hintingShapeIds = this.editor.getHintingShapeIds();
    const isSnapMode = this.editor.user.getIsSnapMode();
    const {
      snaps,
      inputs: { currentPagePoint, shiftKey, ctrlKey, altKey, pointerVelocity }
    } = editor;
    const initial = this.info.shape;
    const shape = editor.getShape(shapeId);
    if (!shape)
      return;
    const util = editor.getShapeUtil(shape);
    let point = currentPagePoint.clone().sub(initialPagePoint).rot(-initialPageRotation).add(initialHandle);
    if (shiftKey && initialAdjacentHandle && initialHandle.id !== "middle") {
      const angle = Vec2d.Angle(initialAdjacentHandle, point);
      const snappedAngle = snapAngle(angle, 24);
      const angleDifference = snappedAngle - angle;
      point = Vec2d.RotWith(point, initialAdjacentHandle, angleDifference);
    }
    editor.snaps.clear();
    if (initialHandle.canSnap && (isSnapMode ? !ctrlKey : ctrlKey)) {
      const pageTransform = editor.getShapePageTransform(shape.id);
      if (!pageTransform)
        throw Error("Expected a page transform");
      const handleIndex = editor.getShapeHandles(shape).filter(({ type }) => type === "vertex").sort(sortByIndex).findIndex(({ index }) => initialHandle.index === index);
      const additionalSegments = util.getOutlineSegments(shape).map((segment) => Matrix2d.applyToPoints(pageTransform, segment)).filter((_segment, i) => i !== handleIndex - 1 && i !== handleIndex);
      const snapDelta = snaps.getSnappingHandleDelta({
        additionalSegments,
        handlePoint: Matrix2d.applyToPoint(pageTransform, point)
      });
      if (snapDelta) {
        snapDelta.rot(-editor.getShapeParentTransform(shape).rotation());
        point.add(snapDelta);
      }
    }
    const changes = util.onHandleChange?.(shape, {
      handle: {
        ...initialHandle,
        x: point.x,
        y: point.y
      },
      isPrecise: this.isPrecise || altKey,
      initial
    });
    const next = { ...shape, ...changes };
    if (initialHandle.canBind) {
      const bindingAfter = next.props[initialHandle.id];
      if (bindingAfter?.type === "binding") {
        if (hintingShapeIds[0] !== bindingAfter.boundShapeId) {
          editor.setHintingShapes([bindingAfter.boundShapeId]);
          this.pointingId = bindingAfter.boundShapeId;
          this.isPrecise = pointerVelocity.len() < 0.5 || altKey;
          this.isPreciseId = this.isPrecise ? bindingAfter.boundShapeId : null;
          this.resetExactTimeout();
        }
      } else {
        if (hintingShapeIds.length > 0) {
          editor.setHintingShapes([]);
          this.pointingId = null;
          this.isPrecise = false;
          this.isPreciseId = null;
          this.resetExactTimeout();
        }
      }
    }
    if (changes) {
      editor.updateShapes([next], { squashing: true });
    }
  }
}
export {
  DraggingHandle
};
//# sourceMappingURL=DraggingHandle.mjs.map
