/*
 * Copyright 2025 coze-dev Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
import { type FabricObject } from 'fabric';

import { type Snap } from '../../typings';

export const getObjectPoints = (
  object: FabricObject,
): Snap.ObjectPointsWithMiddle => {
  const {
    left,
    top,
    height: _height,
    width: _width,
    scaleX,
    scaleY,
    angle,
    strokeWidth,
  } = object;

  const height = _height * scaleY + strokeWidth;
  const width = _width * scaleX + strokeWidth;

  const tl = { x: left, y: top };
  const anglePI = angle * (Math.PI / 180);
  const tr = {
    x: left + width * Math.cos(anglePI),
    y: top + width * Math.sin(anglePI),
  };

  const bl = {
    x: left - height * Math.sin(anglePI),
    y: top + height * Math.cos(anglePI),
  };
  const br = {
    x: left - height * Math.sin(anglePI) + width * Math.cos(anglePI),
    y: top + height * Math.cos(anglePI) + width * Math.sin(anglePI),
  };

  return fixedMiddlePoint({ tl, tr, bl, br });
};

export const getBBoxWidth = (target: FabricObject) => {
  const { width: _width, scaleX, strokeWidth } = target;
  const width = _width * scaleX + strokeWidth;
  return width;
};

export const getBBoxHeight = (target: FabricObject) => {
  const { height: _height, scaleY, strokeWidth } = target;
  const height = _height * scaleY + strokeWidth;
  return height;
};

export const bboxWidthToWidth = ({
  nextWidth,
  target,
}: {
  nextWidth: number;
  target: FabricObject;
}) => {
  const width = (nextWidth - target.strokeWidth) / target.scaleX;
  return width;
};

export const bboxHeightToHeight = ({
  nextHeight,
  target,
}: {
  nextHeight: number;
  target: FabricObject;
}) => {
  const height = (nextHeight - target.strokeWidth) / target.scaleY;
  return height;
};

export const numberEqual = (a: number, b: number) => Math.abs(a - b) < 0.01;

export const fixedMiddlePoint = (
  objectPoints: Snap.ObjectPoints,
): Snap.ObjectPointsWithMiddle => {
  const { tl, tr, bl, br } = objectPoints;
  return {
    tl,
    tr,
    m: { x: tl.x + (br.x - tl.x) / 2, y: tl.y + (br.y - tl.y) / 2 },
    bl,
    br,
  };
};

// Find the specified point, the nearest element, and the point
export const findLatestObject = (
  otherPoints: Snap.ObjectPointsWithMiddle[],
  targets: number[],
  direction: 'x' | 'y' = 'y',
) => {
  let latestObject: Snap.ObjectPointsWithMiddle[] = [];
  let latestSnapPoint: Snap.Point[] = [];
  let latestDistance = Infinity;
  let latestDistanceAbs = Infinity;

  targets.forEach(target => {
    otherPoints.forEach(point => {
      Object.values(point).forEach(p => {
        const abs = Math.abs(p[direction] - target);
        if (numberEqual(abs, latestDistanceAbs)) {
          latestObject.push(point);
          latestSnapPoint.push(p);
        } else if (abs < latestDistanceAbs) {
          latestObject = [point];
          latestSnapPoint = [p];
          latestDistance = p[direction] - target;
          latestDistanceAbs = abs;
        }
      });
    });
  });
  return {
    object: latestObject,
    snapPoints: latestSnapPoint,
    distance: latestDistance,
    distanceAbs: latestDistanceAbs,
  };
};

export const getLatestSnapRs = (
  snapRs: (Snap.SnapLine | undefined)[] = [],
): Snap.SnapLine => {
  const snapRsFilterEmpty = snapRs.filter(Boolean) as Snap.SnapLine[];

  const sortedSnapRs = snapRsFilterEmpty.sort(
    (a, b) => a.snapDistance - b.snapDistance,
  );
  // Find the nearest distance
  const latestSnapRs = sortedSnapRs[0];

  // Find the helplines with the closest distance, there may be multiple closest distances, and merge the helplines.
  const helplinesRs = snapRsFilterEmpty
    .filter(rs => numberEqual(rs.snapDistance, latestSnapRs.snapDistance))
    .map(rs => rs.helplines)
    .flat();

  return {
    ...latestSnapRs,
    helplines: helplinesRs,
  };
};
