import { snapToGrid } from "../../../utils/NodeUtils.js";
import { ARROW_OFFSETS, EDGE_CONSTANTS } from "./constants.js";
const { BORDER_RADIUS, MIN_SEGMENT_LENGTH, COLLINEAR_TOLERANCE: TOL } = EDGE_CONSTANTS;
function snapPointToGrid(point) {
    return {
        x: snapToGrid(point.x),
        y: snapToGrid(point.y)
    };
}
function getDirection(position) {
    switch(position){
        case 'left':
            return {
                dx: -1,
                dy: 0
            };
        case 'right':
            return {
                dx: 1,
                dy: 0
            };
        case 'top':
            return {
                dx: 0,
                dy: -1
            };
        case 'bottom':
            return {
                dx: 0,
                dy: 1
            };
        default:
            return {
                dx: 1,
                dy: 0
            };
    }
}
function isHorizontalPosition(position) {
    return 0 !== getDirection(position).dx;
}
function calculateAutoWaypoints(sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition) {
    const sourceOffset = ARROW_OFFSETS[sourcePosition];
    const targetOffset = ARROW_OFFSETS[targetPosition];
    const startX = sourceX + sourceOffset.x;
    const startY = sourceY + sourceOffset.y;
    const endX = targetX + targetOffset.x;
    const endY = targetY + targetOffset.y;
    const isSourceHorizontal = isHorizontalPosition(sourcePosition);
    const isTargetHorizontal = isHorizontalPosition(targetPosition);
    const waypoints = [];
    if (isSourceHorizontal && isTargetHorizontal) {
        const midX = snapToGrid((startX + endX) / 2);
        if (Math.abs(startY - endY) > MIN_SEGMENT_LENGTH / 2) {
            waypoints.push({
                x: midX,
                y: startY
            });
            waypoints.push({
                x: midX,
                y: endY
            });
        }
    } else if (isSourceHorizontal || isTargetHorizontal) if (isSourceHorizontal && !isTargetHorizontal) waypoints.push({
        x: endX,
        y: startY
    });
    else waypoints.push({
        x: startX,
        y: endY
    });
    else {
        const midY = snapToGrid((startY + endY) / 2);
        if (Math.abs(startX - endX) > MIN_SEGMENT_LENGTH / 2) {
            waypoints.push({
                x: startX,
                y: midY
            });
            waypoints.push({
                x: endX,
                y: midY
            });
        }
    }
    return waypoints;
}
function routeAnchorToPoint(anchor, anchorPosition, point) {
    const dir = getDirection(anchorPosition);
    const isHorizontal = 0 !== dir.dx;
    const forward = isHorizontal ? (point.x - anchor.x) * dir.dx : (point.y - anchor.y) * dir.dy;
    let elbows;
    if (forward >= 0) elbows = isHorizontal ? [
        {
            x: point.x,
            y: anchor.y
        }
    ] : [
        {
            x: anchor.x,
            y: point.y
        }
    ];
    else {
        const { STUB_OFFSET } = EDGE_CONSTANTS;
        elbows = isHorizontal ? [
            {
                x: anchor.x + dir.dx * STUB_OFFSET,
                y: anchor.y
            },
            {
                x: anchor.x + dir.dx * STUB_OFFSET,
                y: point.y
            }
        ] : [
            {
                x: anchor.x,
                y: anchor.y + dir.dy * STUB_OFFSET
            },
            {
                x: point.x,
                y: anchor.y + dir.dy * STUB_OFFSET
            }
        ];
    }
    const result = [];
    let prev = anchor;
    for (const elbow of elbows){
        const samePrev = Math.abs(elbow.x - prev.x) < TOL && Math.abs(elbow.y - prev.y) < TOL;
        const samePoint = Math.abs(elbow.x - point.x) < TOL && Math.abs(elbow.y - point.y) < TOL;
        if (!samePrev && !samePoint) {
            result.push(elbow);
            prev = elbow;
        }
    }
    return result;
}
function connectorElbow(from, to, incoming) {
    const alignedX = Math.abs(from.x - to.x) < TOL;
    const alignedY = Math.abs(from.y - to.y) < TOL;
    if (alignedX || alignedY) return null;
    return 'horizontal' === incoming ? {
        x: from.x,
        y: to.y
    } : {
        x: to.x,
        y: from.y
    };
}
function buildPathVertices(sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition, waypoints = []) {
    const sourceOffset = ARROW_OFFSETS[sourcePosition];
    const targetOffset = ARROW_OFFSETS[targetPosition];
    const start = {
        x: sourceX + sourceOffset.x,
        y: sourceY + sourceOffset.y
    };
    const end = {
        x: targetX + targetOffset.x,
        y: targetY + targetOffset.y
    };
    const derived = (p)=>({
            x: p.x,
            y: p.y,
            waypointIndex: -1
        });
    if (0 === waypoints.length) {
        const autoWaypoints = calculateAutoWaypoints(sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition);
        return [
            start,
            ...autoWaypoints,
            end
        ].map(derived);
    }
    const startVertex = derived(start);
    const endVertex = derived(end);
    const path = [
        startVertex
    ];
    for (const elbow of routeAnchorToPoint(start, sourcePosition, waypoints[0]))path.push(derived(elbow));
    path.push({
        x: waypoints[0].x,
        y: waypoints[0].y,
        waypointIndex: 0
    });
    for(let i = 1; i < waypoints.length; i++){
        const from = path[path.length - 1];
        const incoming = path.length >= 2 ? getSegmentOrientation(path[path.length - 2], from) : null;
        const elbow = connectorElbow(from, waypoints[i], incoming);
        if (elbow) path.push(derived(elbow));
        path.push({
            x: waypoints[i].x,
            y: waypoints[i].y,
            waypointIndex: i
        });
    }
    const targetStub = routeAnchorToPoint(end, targetPosition, waypoints[waypoints.length - 1]);
    for(let j = targetStub.length - 1; j >= 0; j--)path.push(derived(targetStub[j]));
    const interior = consolidateWaypoints(path.slice(1), start, end, (v)=>v.waypointIndex >= 0);
    return [
        startVertex,
        ...interior,
        endVertex
    ];
}
function consolidateWaypoints(points, pathStart, pathEnd, isProtected = ()=>false) {
    let current = points;
    let changed = true;
    while(changed && current.length > 0){
        changed = false;
        const before = current.length;
        if (current.length > 1) {
            const deduped = [
                current[0]
            ];
            for(let i = 1; i < current.length; i++){
                const prev = deduped[deduped.length - 1];
                const curr = current[i];
                if (Math.abs(curr.x - prev.x) > TOL || Math.abs(curr.y - prev.y) > TOL) deduped.push(curr);
                else if (isProtected(curr) && !isProtected(prev)) deduped[deduped.length - 1] = curr;
            }
            current = deduped;
        }
        if (current.length > 2) {
            const result = [
                current[0]
            ];
            for(let i = 1; i < current.length - 1; i++){
                const prev = result[result.length - 1];
                const curr = current[i];
                const next = current[i + 1];
                const horizontalLine = Math.abs(prev.y - curr.y) <= TOL && Math.abs(curr.y - next.y) <= TOL;
                const verticalLine = Math.abs(prev.x - curr.x) <= TOL && Math.abs(curr.x - next.x) <= TOL;
                if (!horizontalLine && !verticalLine || isProtected(curr)) result.push(curr);
            }
            result.push(current[current.length - 1]);
            current = result;
        }
        if (current.length >= 2) {
            const first = current[0];
            const second = current[1];
            const collinearH = Math.abs(pathStart.y - first.y) <= TOL && Math.abs(first.y - second.y) <= TOL;
            const collinearV = Math.abs(pathStart.x - first.x) <= TOL && Math.abs(first.x - second.x) <= TOL;
            if ((collinearH || collinearV) && !isProtected(first)) current = current.slice(1);
        }
        if (current.length >= 2) {
            const last = current[current.length - 1];
            const secondLast = current[current.length - 2];
            const collinearH = Math.abs(secondLast.y - last.y) <= TOL && Math.abs(last.y - pathEnd.y) <= TOL;
            const collinearV = Math.abs(secondLast.x - last.x) <= TOL && Math.abs(last.x - pathEnd.x) <= TOL;
            if ((collinearH || collinearV) && !isProtected(last)) current = current.slice(0, -1);
        }
        if (1 === current.length) {
            const wp = current[0];
            const collinearH = Math.abs(pathStart.y - wp.y) <= TOL && Math.abs(wp.y - pathEnd.y) <= TOL;
            const collinearV = Math.abs(pathStart.x - wp.x) <= TOL && Math.abs(wp.x - pathEnd.x) <= TOL;
            if ((collinearH || collinearV) && !isProtected(wp)) current = [];
        }
        if (current.length >= 2) {
            const cleaned = [];
            for(let i = 0; i < current.length; i++){
                const curr = current[i];
                const prev = cleaned[cleaned.length - 1];
                const next = current[i + 1];
                if (!isProtected(curr)) {
                    if (prev && Math.abs(curr.x - prev.x) <= TOL && Math.abs(curr.y - prev.y) <= TOL) continue;
                    if (next && Math.abs(curr.x - next.x) <= TOL && Math.abs(curr.y - next.y) <= TOL) continue;
                }
                cleaned.push(curr);
            }
            current = cleaned;
        }
        changed = current.length !== before;
    }
    return current;
}
function getSegmentOrientation(start, end) {
    return Math.abs(end.x - start.x) > Math.abs(end.y - start.y) ? 'horizontal' : 'vertical';
}
function isSegmentPerpendicular(segment) {
    const dx = Math.abs(segment.end.x - segment.start.x);
    const dy = Math.abs(segment.end.y - segment.start.y);
    return dx < TOL || dy < TOL;
}
function extractSegments(pathPoints) {
    const segments = [];
    const interiorCount = Math.max(0, pathPoints.length - 2);
    for(let i = 0; i < pathPoints.length - 1; i++){
        const start = pathPoints[i];
        const end = pathPoints[i + 1];
        segments.push({
            id: `seg-${i}`,
            start: {
                x: start.x,
                y: start.y
            },
            end: {
                x: end.x,
                y: end.y
            },
            orientation: getSegmentOrientation(start, end),
            waypointIndexBefore: i - 1,
            waypointIndexAfter: i < interiorCount ? i : interiorCount
        });
    }
    return segments;
}
function createRoundedPath(points, borderRadius = BORDER_RADIUS) {
    if (points.length < 2) return '';
    const firstPoint = points[0];
    const path = [
        `M ${firstPoint.x} ${firstPoint.y}`
    ];
    if (2 === points.length) {
        const secondPoint = points[1];
        path.push(`L ${secondPoint.x} ${secondPoint.y}`);
        return path.join(' ');
    }
    for(let i = 1; i < points.length - 1; i++){
        const prev = points[i - 1];
        const curr = points[i];
        const next = points[i + 1];
        const v1 = {
            x: curr.x - prev.x,
            y: curr.y - prev.y
        };
        const v2 = {
            x: next.x - curr.x,
            y: next.y - curr.y
        };
        const len1 = Math.sqrt(v1.x * v1.x + v1.y * v1.y);
        const len2 = Math.sqrt(v2.x * v2.x + v2.y * v2.y);
        const radius = Math.min(borderRadius, Math.min(len1, len2) / 2);
        if (radius < 1) {
            path.push(`L ${curr.x} ${curr.y}`);
            continue;
        }
        const n1 = {
            x: v1.x / len1,
            y: v1.y / len1
        };
        const n2 = {
            x: v2.x / len2,
            y: v2.y / len2
        };
        const cornerStart = {
            x: curr.x - n1.x * radius,
            y: curr.y - n1.y * radius
        };
        const cornerEnd = {
            x: curr.x + n2.x * radius,
            y: curr.y + n2.y * radius
        };
        path.push(`L ${cornerStart.x} ${cornerStart.y}`);
        path.push(`Q ${curr.x} ${curr.y} ${cornerEnd.x} ${cornerEnd.y}`);
    }
    const lastPoint = points[points.length - 1];
    path.push(`L ${lastPoint.x} ${lastPoint.y}`);
    return path.join(' ');
}
function getSegmentMidpoint(segment) {
    return {
        x: (segment.start.x + segment.end.x) / 2,
        y: (segment.start.y + segment.end.y) / 2
    };
}
function getSegmentLength(segment) {
    const dx = segment.end.x - segment.start.x;
    const dy = segment.end.y - segment.start.y;
    return Math.sqrt(dx * dx + dy * dy);
}
function isSegmentInteractable(segment) {
    return getSegmentLength(segment) >= MIN_SEGMENT_LENGTH;
}
function getPathArcMidpoint(points) {
    if (0 === points.length) return {
        x: 0,
        y: 0
    };
    if (1 === points.length) return {
        x: points[0].x,
        y: points[0].y
    };
    const lengths = [];
    let total = 0;
    for(let i = 0; i < points.length - 1; i++){
        const dx = points[i + 1].x - points[i].x;
        const dy = points[i + 1].y - points[i].y;
        const len = Math.sqrt(dx * dx + dy * dy);
        lengths.push(len);
        total += len;
    }
    if (0 === total) return {
        x: points[0].x,
        y: points[0].y
    };
    const target = total / 2;
    let acc = 0;
    for(let i = 0; i < lengths.length; i++){
        const segLen = lengths[i];
        if (acc + segLen >= target) {
            const t = (target - acc) / segLen;
            const start = points[i];
            const end = points[i + 1];
            return {
                x: start.x + t * (end.x - start.x),
                y: start.y + t * (end.y - start.y)
            };
        }
        acc += segLen;
    }
    const last = points[points.length - 1];
    return {
        x: last.x,
        y: last.y
    };
}
export { buildPathVertices, calculateAutoWaypoints, consolidateWaypoints, createRoundedPath, extractSegments, getPathArcMidpoint, getSegmentLength, getSegmentMidpoint, getSegmentOrientation, isHorizontalPosition, isSegmentInteractable, isSegmentPerpendicular, routeAnchorToPoint, snapPointToGrid };
