import { Position, Node, InternalNode } from "@xyflow/react";

/**
 * 定义交点的接口类型
 * 用于表示两条线段相交的坐标点
 */
interface IntersectionPoint {
    x: number;  // 交点的x坐标
    y: number;  // 交点的y坐标
}

/**
 * 定义边缘连接参数的接口类型
 * 包含源节点和目标节点的连接位置信息
 */
interface EdgeParams {
    sx: number;      // 源节点连接点x坐标
    sy: number;      // 源节点连接点y坐标
    tx: number;      // 目标节点连接点x坐标
    ty: number;      // 目标节点连接点y坐标
    sourcePos: Position;  // 源节点连接位置（上下左右）
    targetPos: Position;  // 目标节点连接位置（上下左右）
}

/**
 * 计算节点之间的交点坐标
 * 
 * 功能说明:
 * 该函数用于计算两个节点之间连线与节点边界的精确交点位置。这在绘制流程图等图形时,
 * 确保连接线能够准确地从节点边界开始和结束,而不是从节点中心点开始。
 * 
 * 算法原理:
 * 1. 首先获取两个节点的位置和尺寸信息
 * 2. 计算节点的中心点坐标
 * 3. 使用几何算法计算连线与节点矩形边界的交点
 * 4. 返回交点的精确坐标
 * 
 * @param intersectionNode - 起始节点,需要计算交点的源节点
 * @param targetNode - 目标节点,与源节点相连的终点节点
 * @returns {IntersectionPoint} 返回交点坐标 {x, y}
 */
function getNodeIntersection(intersectionNode: InternalNode, targetNode: InternalNode): IntersectionPoint {
    // 获取起始节点的宽度和高度
    const { width: intersectionNodeWidth, height: intersectionNodeHeight } = intersectionNode.measured;
    // 获取两个节点的绝对位置信息
    const intersectionNodePosition = intersectionNode.internals.positionAbsolute;
    const targetPosition = targetNode.internals.positionAbsolute;

    // 计算起始节点的半宽和半高,用于后续的坐标计算
    const w = intersectionNodeWidth / 2;
    const h = intersectionNodeHeight / 2;

    // 计算两个节点的中心点坐标
    // (x2,y2)为起始节点的中心点
    const x2 = intersectionNodePosition.x + w;
    const y2 = intersectionNodePosition.y + h;
    // (x1,y1)为目标节点的中心点
    const x1 = targetPosition.x + targetNode.measured.width / 2;
    const y1 = targetPosition.y + targetNode.measured.height / 2;

    // 使用数学公式计算交点坐标
    // 这里使用的是参数化方程,将节点边界视为矩形来计算交点
    const xx1 = (x1 - x2) / (2 * w) - (y1 - y2) / (2 * h);
    const yy1 = (x1 - x2) / (2 * w) + (y1 - y2) / (2 * h);
    // 通过标准化确保交点在节点边界上
    const a = 1 / (Math.abs(xx1) + Math.abs(yy1));
    const xx3 = a * xx1;
    const yy3 = a * yy1;
    // 计算最终的交点坐标
    const x = w * (xx3 + yy3) + x2;
    const y = h * (-xx3 + yy3) + y2;

    return { x, y };
}
/**
 * 确定边缘连接点的位置（上下左右）
 * 
 * 功能说明:
 * 根据节点和交点的位置关系,计算边缘线应该连接到节点的哪个位置(上/下/左/右)
 * 
 * 实现原理:
 * 1. 获取节点的绝对定位信息和尺寸信息
 * 2. 将节点四条边界划分为不同区域
 * 3. 通过比较交点坐标与边界位置,确定最合适的连接点
 * 
 * @param node - 需要确定连接位置的节点对象
 *              包含节点的位置信息(x,y)和尺寸信息(width,height) 
 * @param intersectionPoint - 交点坐标对象
 *                         包含交点的x,y坐标值
 * @returns Position - 返回枚举值,表示连接位置(Top/Right/Bottom/Left)
 */
function getEdgePosition(node: InternalNode, intersectionPoint: IntersectionPoint): Position {
    // 合并节点的绝对定位信息,确保获取准确的节点位置
    const n = { ...node.internals.positionAbsolute, ...node };
    
    // 对坐标进行取整,避免浮点数计算误差
    const nx = Math.round(n.x);  // 节点左边界x坐标
    const ny = Math.round(n.y);  // 节点上边界y坐标
    const px = Math.round(intersectionPoint.x);  // 交点x坐标
    const py = Math.round(intersectionPoint.y);  // 交点y坐标

    // 判断逻辑:通过比较交点与节点各边界的位置关系确定连接位置
    // 添加1px的容差值,增强判断的容错性
    if (px <= nx + 1) {
        return Position.Left;  // 交点在节点左侧
    }
    if (px >= nx + n.measured.width - 1) {
        return Position.Right;  // 交点在节点右侧
    }
    if (py <= ny + 1) {
        return Position.Top;  // 交点在节点上方
    }
    if (py >= n.y + n.measured.height - 1) {
        return Position.Bottom;  // 交点在节点下方
    }

    // 若都不满足,默认返回顶部位置作为连接点
    return Position.Top;
}

/**
 * 计算两个节点之间边缘连接的所有必要参数
 * @param source - 源节点
 * @param target - 目标节点
 * @returns 返回包含边缘连接所需所有参数的对象
 * 
 * 这是主要的导出函数，用于获取创建边缘连接线所需的所有参数
 */
export function getEdgeParams(source: InternalNode, target: InternalNode): EdgeParams {
    // 计算源节点和目标节点的交点
    const sourceIntersectionPoint = getNodeIntersection(source, target);
    const targetIntersectionPoint = getNodeIntersection(target, source);

    // 确定连接点在各自节点上的位置
    const sourcePos = getEdgePosition(source, sourceIntersectionPoint);
    const targetPos = getEdgePosition(target, targetIntersectionPoint);

    // 返回所有必要的参数
    return {
        sx: sourceIntersectionPoint.x,
        sy: sourceIntersectionPoint.y,
        tx: targetIntersectionPoint.x,
        ty: targetIntersectionPoint.y,
        sourcePos,
        targetPos,
    };
}