import { Position, XYPosition } from "@xyflow/react";
import { ControlPoint, HandlePosition } from "./point";
import { uuid } from "../../utils/uuid";

export interface ILine {
  start: ControlPoint;
  end: ControlPoint;
}

/**
 * 判断给定位置是否为水平方向
 * @param position - 位置枚举值
 * @returns 如果是左侧或右侧位置则返回true,否则返回false
 */
export const isHorizontalFromPosition = (position: Position) => {
  return [Position.Left, Position.Right].includes(position);
};

/**
 * 判断连接是否为反向
 * 在图形布局中,通常希望连线从左到右或从上到下。当连线方向与此相反时,即为反向连接
 * @param props - 包含源点和目标点位置信息的对象
 * @param props.source - 源节点的位置信息
 * @param props.target - 目标节点的位置信息
 * @returns 如果是反向连接则返回true,否则返回false
 */
export const isConnectionBackward = (props: {
  source: HandlePosition;
  target: HandlePosition;
}) => {
  const { source, target } = props;
  // 判断是水平还是垂直方向的连接
  const isHorizontal = isHorizontalFromPosition(source.position);
  let isBackward = false;

  // 水平方向时,如果源点x坐标大于目标点,则为反向
  if (isHorizontal) {
    if (source.x > target.x) {
      isBackward = true;
    }
  }
  // 垂直方向时,如果源点y坐标大于目标点,则为反向
  else {
    if (source.y > target.y) {
      isBackward = true;
    }
  }
  return isBackward;
};

/**
 * 计算两点之间的欧几里得距离
 * 使用勾股定理(Math.hypot)计算两点间的直线距离
 * @param p1 - 第一个控制点
 * @param p2 - 第二个控制点
 * @returns 两点间的距离
 */
export const distance = (p1: ControlPoint, p2: ControlPoint) => {
  return Math.hypot(p2.x - p1.x, p2.y - p1.y);
};

/**
 * 计算线段的中点坐标
 * 通过取两端点坐标的算术平均值来确定中点位置
 * @param p1 - 第一个控制点
 * @param p2 - 第二个控制点
 * @returns 包含中点坐标和唯一标识的控制点对象
 */
export const getLineCenter = (
  p1: ControlPoint,
  p2: ControlPoint
): ControlPoint => {
  return {
    id: uuid(),
    x: (p1.x + p2.x) / 2,  // x坐标取两端点x坐标的平均值
    y: (p1.y + p2.y) / 2,  // y坐标取两端点y坐标的平均值
  };
};

/**
 * 判断点是否在线段上
 * 
 * @description
 * 该函数用于检测给定的点是否位于由两个控制点构成的线段上。
 * 判断逻辑分为两种情况:
 * 1. 垂直线段: 当起点和终点的x坐标相同时,判断目标点的x坐标是否等于线段x坐标,且y坐标在线段y坐标范围内
 * 2. 水平线段: 当起点和终点的y坐标相同时,判断目标点的y坐标是否等于线段y坐标,且x坐标在线段x坐标范围内
 * 
 * @param start - 线段起点坐标
 * @param end - 线段终点坐标  
 * @param p - 待检测点的坐标
 * @returns {boolean} 如果点在线段上返回true,否则返回false
 */
export const isLineContainsPoint = (
  start: ControlPoint,
  end: ControlPoint,
  p: ControlPoint
) => {
  return (
    // 判断垂直线段
    (start.x === end.x && // 起点终点x坐标相同
      p.x === start.x && // 目标点x坐标与线段相同
      p.y <= Math.max(start.y, end.y) && // 目标点y坐标不超过线段y坐标最大值
      p.y >= Math.min(start.y, end.y)) || // 目标点y坐标不小于线段y坐标最小值
    // 判断水平线段  
    (start.y === end.y && // 起点终点y坐标相同
      p.y === start.y && // 目标点y坐标与线段相同
      p.x <= Math.max(start.x, end.x) && // 目标点x坐标不超过线段x坐标最大值
      p.x >= Math.min(start.x, end.x)) // 目标点x坐标不小于线段x坐标最小值
  );
};
/**
/**
 * 生成带圆角转角的SVG路径
 * 
 * 该函数用于在图形编辑器中生成连接两点之间的边线路径。路径具有以下特点:
 * 1. 两个控制点之间为直线段
 * 2. 在转折点处生成圆角过渡
 * 3. 支持垂直和水平方向的转角
 * 
 * @param points 控制点数组,包含边的起点、终点和中间的转折点
 *   - 至少需要2个点(起点和终点)
 *   - 点的顺序应从输入端点开始到输出端点结束
 * @param radius 转角处的圆角半径
 * @returns 返回SVG路径字符串
 * @throws 当points数组长度小于2时抛出错误
 */
export function getPathWithRoundCorners(
  points: ControlPoint[],
  radius: number
): string {
  if (points.length < 2) {
    throw new Error("At least 2 points are required.");
  }

  /**
   * 计算两条线段交点处的圆角路径
   * @param center 转折点坐标
   * @param p1 前一个点的坐标
   * @param p2 后一个点的坐标
   * @param radius 圆角半径
   * @returns SVG路径命令字符串
   */
  function getRoundCorner(
    center: ControlPoint,
    p1: ControlPoint,
    p2: ControlPoint,
    radius: number
  ) {
    const { x, y } = center;

    // 如果两条线段不垂直,则直接返回直线路径
    if (!areLinesPerpendicular(p1, center, center, p2)) {
      return `L ${x} ${y}`;
    }

    // 计算实际可用的圆角半径,取三个值中的最小值:
    // 1. 与前一个点的距离的一半
    // 2. 与后一个点的距离的一半
    // 3. 传入的目标半径
    const d1 = distance(center, p1);
    const d2 = distance(center, p2);
    radius = Math.min(d1 / 2, d2 / 2, radius);

    // 判断第一条线段是否为水平线
    const isHorizontal = p1.y === y;

    // 根据点的相对位置确定圆角绘制方向
    const xDir = isHorizontal ? (p1.x < p2.x ? -1 : 1) : p1.x < p2.x ? 1 : -1;
    const yDir = isHorizontal ? (p1.y < p2.y ? 1 : -1) : p1.y < p2.y ? -1 : 1;

    // 根据线段方向生成不同的圆角路径
    if (isHorizontal) {
      return `L ${x + radius * xDir},${y}Q ${x},${y} ${x},${y + radius * yDir}`;
    }
    return `L ${x},${y + radius * yDir}Q ${x},${y} ${x + radius * xDir},${y}`;
  }

  // 构建完整的SVG路径
  const path: string[] = [];
  for (let i = 0; i < points.length; i++) {
    if (i === 0) {
      // 起点使用移动命令M
      path.push(`M ${points[i].x} ${points[i].y}`);
    } else if (i === points.length - 1) {
      // 终点使用直线命令L
      path.push(`L ${points[i].x} ${points[i].y}`);
    } else {
      // 中间点使用圆角转角
      path.push(
        getRoundCorner(points[i], points[i - 1], points[i + 1], radius)
      );
    }
  }

  // 将所有路径命令连接成完整的路径字符串
  return path.join(" ");
}
/**
 * 获取折线中最长的线段
 * @param points 控制点数组，每个点包含x和y坐标
 * @returns 返回最长线段的起点和终点坐标
 * 
 * 实现原理:
 * 1. 初始化第一条线段为最长线段
 * 2. 遍历所有相邻点对,计算线段长度
 * 3. 如果找到更长的线段,则更新最长线段记录
 * 4. 返回最长线段的两个端点
 */
export function getLongestLine(
  points: ControlPoint[]
): [ControlPoint, ControlPoint] {
  let longestLine: [ControlPoint, ControlPoint] = [points[0], points[1]];
  let longestDistance = distance(...longestLine);
  for (let i = 1; i < points.length - 1; i++) {
    const _distance = distance(points[i], points[i + 1]);
    if (_distance > longestDistance) {
      longestDistance = _distance;
      longestLine = [points[i], points[i + 1]];
    }
  }
  return longestLine;
}

/**
 * 计算折线上标签的位置
 * @param points 控制点数组
 * @param minGap 最小间隔距离,默认为20
 * @returns 标签的坐标位置
 * 
 * 计算逻辑:
 * 1. 如果折线点数为偶数:
 *    - 取中间两个点
 *    - 如果这两点间距大于最小间隔,返回它们的中点
 * 2. 如果折线点数为奇数或中间段太短:
 *    - 找出最长的线段
 *    - 返回最长线段的中点作为标签位置
 */
export function getLabelPosition(
  points: ControlPoint[],
  minGap = 20
): XYPosition {
  if (points.length % 2 === 0) {
    const middleP1 = points[points.length / 2 - 1];
    const middleP2 = points[points.length / 2];
    if (distance(middleP1, middleP2) > minGap) {
      return getLineCenter(middleP1, middleP2);
    }
  }
  const [start, end] = getLongestLine(points);
  return {
    x: (start.x + end.x) / 2,
    y: (start.y + end.y) / 2,
  };
}

/**
 * 判断两条线段是否垂直
 * @param p1,p2 第一条线段的起点和终点
 * @param p3,p4 第二条线段的起点和终点
 * @returns 如果两线段垂直则返回true
 * 
 * 判断依据:
 * - 假设线段要么水平要么垂直
 * - 当一条线段水平(y相等)而另一条垂直(x相等)时,两线段垂直
 */
export function areLinesPerpendicular(
  p1: ControlPoint,
  p2: ControlPoint,
  p3: ControlPoint,
  p4: ControlPoint
): boolean {
  return (p1.x === p2.x && p3.y === p4.y) || (p1.y === p2.y && p3.x === p4.x);
}

/**
 * 判断两条线段是否平行
 * @param p1,p2 第一条线段的起点和终点
 * @param p3,p4 第二条线段的起点和终点
 * @returns 如果两线段平行则返回true
 * 
 * 判断依据:
 * - 假设线段要么水平要么垂直
 * - 当两条线段都是水平的(x相等)或都是垂直的(y相等)时,两线段平行
 */
export function areLinesParallel(
  p1: ControlPoint,
  p2: ControlPoint,
  p3: ControlPoint,
  p4: ControlPoint
) {
  return (p1.x === p2.x && p3.x === p4.x) || (p1.y === p2.y && p3.y === p4.y);
}

/**
 * 判断两条线段是否同向
 * @param p1 第一条线段的起点
 * @param p2 第一条线段的终点  
 * @param p3 第二条线段的起点
 * @param p4 第二条线段的终点
 * @returns boolean 如果两线段同向返回true,否则返回false
 * 
 * 判断逻辑:
 * 1. 对于水平线段(y坐标相等),判断x方向的变化是否同向
 * 2. 对于垂直线段(x坐标相等),判断y方向的变化是否同向
 */
export function areLinesSameDirection(
  p1: ControlPoint,
  p2: ControlPoint,
  p3: ControlPoint,
  p4: ControlPoint
) {
  return (
    // 判断垂直线段是否同向
    (p1.x === p2.x && p3.x === p4.x && (p1.y - p2.y) * (p3.y - p4.y) > 0) ||
    // 判断水平线段是否同向 
    (p1.y === p2.y && p3.y === p4.y && (p1.x - p2.x) * (p3.x - p4.x) > 0)
  );
}

/**
 * 判断两条线段是否反向
 * @param p1 第一条线段的起点
 * @param p2 第一条线段的终点
 * @param p3 第二条线段的起点  
 * @param p4 第二条线段的终点
 * @returns boolean 如果两线段反向返回true,否则返回false
 * 
 * 判断逻辑:
 * 1. 对于水平线段(y坐标相等),判断x方向的变化是否反向
 * 2. 对于垂直线段(x坐标相等),判断y方向的变化是否反向
 */
export function areLinesReverseDirection(
  p1: ControlPoint,
  p2: ControlPoint,
  p3: ControlPoint,
  p4: ControlPoint
) {
  return (
    // 判断垂直线段是否反向
    (p1.x === p2.x && p3.x === p4.x && (p1.y - p2.y) * (p3.y - p4.y) < 0) ||
    // 判断水平线段是否反向
    (p1.y === p2.y && p3.y === p4.y && (p1.x - p2.x) * (p3.x - p4.x) < 0)
  );
}

/**
 * 计算两条线段之间的夹角
 * @param p1 第一条线段的起点
 * @param p2 第一条线段的终点
 * @param p3 第二条线段的起点
 * @param p4 第二条线段的终点
 * @returns number 两线段之间的夹角(单位:度)
 * 
 * 计算步骤:
 * 1. 计算两个向量
 * 2. 计算向量的点积
 * 3. 计算向量的模长
 * 4. 使用反余弦函数计算弧度
 * 5. 将弧度转换为角度
 */
export function getAngleBetweenLines(
  p1: ControlPoint,
  p2: ControlPoint,
  p3: ControlPoint,
  p4: ControlPoint
) {
  // 计算两条线段对应的向量
  const v1 = { x: p2.x - p1.x, y: p2.y - p1.y };
  const v2 = { x: p4.x - p3.x, y: p4.y - p3.y };

  // 计算向量的点积
  const dotProduct = v1.x * v2.x + v1.y * v2.y;

  // 计算两个向量的模长
  const magnitude1 = Math.sqrt(v1.x ** 2 + v1.y ** 2);
  const magnitude2 = Math.sqrt(v2.x ** 2 + v2.y ** 2);

  // 计算夹角的余弦值
  const cosine = dotProduct / (magnitude1 * magnitude2);

  // 使用反余弦函数计算弧度
  const angleInRadians = Math.acos(cosine);

  // 将弧度转换为角度并返回
  const angleInDegrees = (angleInRadians * 180) / Math.PI;

  return angleInDegrees;
}