import {
  Matrix2d,
  PI,
  PI2,
  StateNode,
  TAU,
  Vec2d,
  areAnglesCompatible,
  compact
} from "@cmpd/editor";
class Resizing extends StateNode {
  static id = "resizing";
  info = {};
  markId = "";
  // A switch to detect when the user is holding ctrl
  didHoldCommand = false;
  // we transition into the resizing state from the geo pointing state, which starts with a shape of size w: 1, h: 1,
  // so if the user drags x: +50, y: +50 after mouseDown, the shape will be w: 51, h: 51, which is too many pixels, alas
  // so we allow passing a further offset into this state to negate such issues
  creationCursorOffset = { x: 0, y: 0 };
  snapshot = {};
  onEnter = (info) => {
    const { isCreating = false, creationCursorOffset = { x: 0, y: 0 } } = info;
    this.info = info;
    this.didHoldCommand = false;
    this.parent.setCurrentToolIdMask(info.onInteractionEnd);
    this.creationCursorOffset = creationCursorOffset;
    this.snapshot = this._createSnapshot();
    if (isCreating) {
      this.markId = `creating:${this.editor.getOnlySelectedShape().id}`;
      this.editor.updateInstanceState(
        { cursor: { type: "cross", rotation: 0 } },
        { ephemeral: true }
      );
    } else {
      this.markId = "starting resizing";
      this.editor.mark(this.markId);
    }
    this.handleResizeStart();
    this.updateShapes();
  };
  onPointerMove = () => {
    this.updateShapes();
  };
  onKeyDown = () => {
    this.updateShapes();
  };
  onKeyUp = () => {
    this.updateShapes();
  };
  onPointerUp = () => {
    this.complete();
  };
  onComplete = () => {
    this.complete();
  };
  onCancel = () => {
    this.cancel();
  };
  cancel() {
    this.editor.bailToMark(this.markId);
    if (this.info.onInteractionEnd) {
      this.editor.setCurrentTool(this.info.onInteractionEnd, {});
    } else {
      this.parent.transition("idle");
    }
  }
  complete() {
    this.handleResizeEnd();
    if (this.info.isCreating && this.info.onCreate) {
      this.info.onCreate?.(this.editor.getOnlySelectedShape());
      return;
    }
    if (this.editor.getInstanceState().isToolLocked && this.info.onInteractionEnd) {
      this.editor.setCurrentTool(this.info.onInteractionEnd, {});
      return;
    }
    this.parent.transition("idle");
  }
  handleResizeStart() {
    const { shapeSnapshots } = this.snapshot;
    const changes = [];
    shapeSnapshots.forEach(({ shape }) => {
      const util = this.editor.getShapeUtil(shape);
      const change = util.onResizeStart?.(shape);
      if (change) {
        changes.push(change);
      }
    });
    if (changes.length > 0) {
      this.editor.updateShapes(changes);
    }
  }
  handleResizeEnd() {
    const { shapeSnapshots } = this.snapshot;
    const changes = [];
    shapeSnapshots.forEach(({ shape }) => {
      const current = this.editor.getShape(shape.id);
      const util = this.editor.getShapeUtil(shape);
      const change = util.onResizeEnd?.(shape, current);
      if (change) {
        changes.push(change);
      }
    });
    if (changes.length > 0) {
      this.editor.updateShapes(changes);
    }
  }
  updateShapes() {
    const { altKey, shiftKey } = this.editor.inputs;
    const {
      frames,
      shapeSnapshots,
      selectionBounds,
      cursorHandleOffset,
      selectedShapeIds,
      selectionRotation,
      canShapesDeform
    } = this.snapshot;
    const isAspectRatioLocked = shiftKey || !canShapesDeform;
    const { ctrlKey } = this.editor.inputs;
    const currentPagePoint = this.editor.inputs.currentPagePoint.clone().sub(cursorHandleOffset).sub(this.creationCursorOffset);
    const originPagePoint = this.editor.inputs.originPagePoint.clone().sub(cursorHandleOffset);
    if (this.editor.getInstanceState().isGridMode && !ctrlKey) {
      const { gridSize } = this.editor.getDocumentSettings();
      currentPagePoint.snapToGrid(gridSize);
    }
    const dragHandle = this.info.handle;
    const scaleOriginHandle = rotateSelectionHandle(dragHandle, Math.PI);
    this.editor.snaps.clear();
    const shouldSnap = this.editor.user.getIsSnapMode() ? !ctrlKey : ctrlKey;
    if (shouldSnap && selectionRotation % TAU === 0) {
      const { nudge } = this.editor.snaps.snapResize({
        dragDelta: Vec2d.Sub(currentPagePoint, originPagePoint),
        initialSelectionPageBounds: this.snapshot.initialSelectionPageBounds,
        handle: rotateSelectionHandle(dragHandle, selectionRotation),
        isAspectRatioLocked,
        isResizingFromCenter: altKey
      });
      currentPagePoint.add(nudge);
    }
    const scaleOriginPage = Vec2d.RotWith(
      altKey ? selectionBounds.center : selectionBounds.getHandlePoint(scaleOriginHandle),
      selectionBounds.point,
      selectionRotation
    );
    const distanceFromScaleOriginNow = Vec2d.Sub(currentPagePoint, scaleOriginPage).rot(
      -selectionRotation
    );
    const distanceFromScaleOriginAtStart = Vec2d.Sub(originPagePoint, scaleOriginPage).rot(
      -selectionRotation
    );
    const scale = Vec2d.DivV(distanceFromScaleOriginNow, distanceFromScaleOriginAtStart);
    if (!Number.isFinite(scale.x))
      scale.x = 1;
    if (!Number.isFinite(scale.y))
      scale.y = 1;
    const isXLocked = dragHandle === "top" || dragHandle === "bottom";
    const isYLocked = dragHandle === "left" || dragHandle === "right";
    if (isAspectRatioLocked) {
      if (isYLocked) {
        scale.y = Math.abs(scale.x);
      } else if (isXLocked) {
        scale.x = Math.abs(scale.y);
      } else if (Math.abs(scale.x) > Math.abs(scale.y)) {
        scale.y = Math.abs(scale.x) * (scale.y < 0 ? -1 : 1);
      } else {
        scale.x = Math.abs(scale.y) * (scale.x < 0 ? -1 : 1);
      }
    } else {
      if (isXLocked) {
        scale.x = 1;
      }
      if (isYLocked) {
        scale.y = 1;
      }
    }
    if (!this.info.isCreating) {
      this.updateCursor({
        dragHandle,
        isFlippedX: scale.x < 0,
        isFlippedY: scale.y < 0,
        rotation: selectionRotation
      });
    }
    for (const id of shapeSnapshots.keys()) {
      const snapshot = shapeSnapshots.get(id);
      this.editor.resizeShape(id, scale, {
        initialShape: snapshot.shape,
        initialBounds: snapshot.bounds,
        initialPageTransform: snapshot.pageTransform,
        dragHandle,
        mode: selectedShapeIds.length === 1 && id === selectedShapeIds[0] ? "resize_bounds" : "scale_shape",
        scaleOrigin: scaleOriginPage,
        scaleAxisRotation: selectionRotation
      });
    }
    if (this.editor.inputs.ctrlKey) {
      this.didHoldCommand = true;
      for (const { id, children } of frames) {
        if (!children.length)
          continue;
        const initial = shapeSnapshots.get(id).shape;
        const current = this.editor.getShape(id);
        if (!(initial && current))
          continue;
        const dx = current.x - initial.x;
        const dy = current.y - initial.y;
        const delta = new Vec2d(dx, dy).rot(-initial.rotation);
        if (delta.x !== 0 || delta.y !== 0) {
          for (const child of children) {
            this.editor.updateShape({
              id: child.id,
              type: child.type,
              x: child.x - delta.x,
              y: child.y - delta.y
            });
          }
        }
      }
    } else if (this.didHoldCommand) {
      this.didHoldCommand = false;
      for (const { children } of frames) {
        if (!children.length)
          continue;
        for (const child of children) {
          this.editor.updateShape({
            id: child.id,
            type: child.type,
            x: child.x,
            y: child.y
          });
        }
      }
    }
  }
  // ---
  updateCursor({
    dragHandle,
    isFlippedX,
    isFlippedY,
    rotation
  }) {
    const nextCursor = { ...this.editor.getInstanceState().cursor };
    switch (dragHandle) {
      case "top_left":
      case "bottom_right": {
        nextCursor.type = "nwse-resize";
        if (isFlippedX !== isFlippedY) {
          nextCursor.type = "nesw-resize";
        }
        break;
      }
      case "top_right":
      case "bottom_left": {
        nextCursor.type = "nesw-resize";
        if (isFlippedX !== isFlippedY) {
          nextCursor.type = "nwse-resize";
        }
        break;
      }
    }
    nextCursor.rotation = rotation;
    this.editor.setCursor(nextCursor);
  }
  onExit = () => {
    this.parent.setCurrentToolIdMask(void 0);
    this.editor.updateInstanceState(
      { cursor: { type: "default", rotation: 0 } },
      { ephemeral: true }
    );
    this.editor.snaps.clear();
  };
  _createSnapshot = () => {
    const selectedShapeIds = this.editor.getSelectedShapeIds();
    const selectionRotation = this.editor.getSelectionRotation();
    const {
      inputs: { originPagePoint }
    } = this.editor;
    const selectionBounds = this.editor.getSelectionRotatedPageBounds();
    const dragHandlePoint = Vec2d.RotWith(
      selectionBounds.getHandlePoint(this.info.handle),
      selectionBounds.point,
      selectionRotation
    );
    const cursorHandleOffset = Vec2d.Sub(originPagePoint, dragHandlePoint);
    const shapeSnapshots = /* @__PURE__ */ new Map();
    const frames = [];
    selectedShapeIds.forEach((id) => {
      const shape = this.editor.getShape(id);
      if (shape) {
        if (shape.type === "frame") {
          frames.push({
            id,
            children: compact(
              this.editor.getSortedChildIdsForParent(shape).map((id2) => this.editor.getShape(id2))
            )
          });
        }
        shapeSnapshots.set(shape.id, this._createShapeSnapshot(shape));
        if (this.editor.isShapeOfType(shape, "frame") && selectedShapeIds.length === 1)
          return;
        this.editor.visitDescendants(shape.id, (descendantId) => {
          const descendent = this.editor.getShape(descendantId);
          if (descendent) {
            shapeSnapshots.set(descendent.id, this._createShapeSnapshot(descendent));
            if (this.editor.isShapeOfType(descendent, "frame")) {
              return false;
            }
          }
        });
      }
    });
    const canShapesDeform = ![...shapeSnapshots.values()].some(
      (shape) => !areAnglesCompatible(shape.pageRotation, selectionRotation) || shape.isAspectRatioLocked
    );
    return {
      shapeSnapshots,
      selectionBounds,
      cursorHandleOffset,
      selectionRotation,
      selectedShapeIds,
      canShapesDeform,
      initialSelectionPageBounds: this.editor.getSelectionPageBounds(),
      frames
    };
  };
  _createShapeSnapshot = (shape) => {
    const pageTransform = this.editor.getShapePageTransform(shape);
    const util = this.editor.getShapeUtil(shape);
    return {
      shape,
      bounds: this.editor.getShapeGeometry(shape).bounds,
      pageTransform,
      pageRotation: Matrix2d.Decompose(pageTransform).rotation,
      isAspectRatioLocked: util.isAspectRatioLocked(shape)
    };
  };
}
const ORDERED_SELECTION_HANDLES = [
  "top",
  "top_right",
  "right",
  "bottom_right",
  "bottom",
  "bottom_left",
  "left",
  "top_left"
];
function rotateSelectionHandle(handle, rotation) {
  rotation = rotation % PI2;
  const numSteps = Math.round(rotation / (PI / 4));
  const currentIndex = ORDERED_SELECTION_HANDLES.indexOf(handle);
  return ORDERED_SELECTION_HANDLES[(currentIndex + numSteps) % ORDERED_SELECTION_HANDLES.length];
}
export {
  Resizing,
  rotateSelectionHandle
};
//# sourceMappingURL=Resizing.mjs.map
