/*
 * Copyright 2026, Jaime Terrats.
 *
 * 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.
 */
/**
 * Determine which side of a rectangle a connection should attach to,
 * based on the relative position of the other node's center.
 */
export function determineSide(nodeRect, otherCenter) {
    const cx = nodeRect.x + nodeRect.width / 2;
    const cy = nodeRect.y + nodeRect.height / 2;
    const dx = otherCenter.x - cx;
    const dy = otherCenter.y - cy;
    // Use aspect ratio to determine whether horizontal or vertical side is preferred
    if (Math.abs(dx) * nodeRect.height > Math.abs(dy) * nodeRect.width) {
        return dx > 0 ? 'right' : 'left';
    }
    return dy > 0 ? 'bottom' : 'top';
}
/**
 * Get the default port point for a given side of a rectangle (center of the side).
 */
export function sideCenter(rect, side) {
    switch (side) {
        case 'top':
            return { x: rect.x + rect.width / 2, y: rect.y };
        case 'bottom':
            return { x: rect.x + rect.width / 2, y: rect.y + rect.height };
        case 'left':
            return { x: rect.x, y: rect.y + rect.height / 2 };
        case 'right':
            return { x: rect.x + rect.width, y: rect.y + rect.height / 2 };
    }
}
/**
 * Distribute multiple connection points evenly along a border side of a node.
 * Returns the computed port points ordered by the angular position of their targets.
 */
export function distributeConnectionPoints(nodeRect, connectedEdges) {
    // Group edges by side
    const bySide = new Map();
    for (let i = 0; i < connectedEdges.length; i++) {
        const { side, targetCenter } = connectedEdges[i];
        const list = bySide.get(side) ?? [];
        list.push({ targetCenter, index: i });
        bySide.set(side, list);
    }
    const result = Array.from({ length: connectedEdges.length });
    for (const [side, entries] of bySide) {
        // Sort entries by their target's position along the perpendicular axis
        const sorted = [...entries].sort((a, b) => {
            if (side === 'top' || side === 'bottom') {
                return a.targetCenter.x - b.targetCenter.x;
            }
            return a.targetCenter.y - b.targetCenter.y;
        });
        // Distribute evenly along the border
        const count = sorted.length;
        for (let i = 0; i < count; i++) {
            const fraction = (i + 1) / (count + 1);
            let point;
            switch (side) {
                case 'top':
                    point = { x: nodeRect.x + nodeRect.width * fraction, y: nodeRect.y };
                    break;
                case 'bottom':
                    point = { x: nodeRect.x + nodeRect.width * fraction, y: nodeRect.y + nodeRect.height };
                    break;
                case 'left':
                    point = { x: nodeRect.x, y: nodeRect.y + nodeRect.height * fraction };
                    break;
                case 'right':
                    point = { x: nodeRect.x + nodeRect.width, y: nodeRect.y + nodeRect.height * fraction };
                    break;
            }
            result[sorted[i].index] = point;
        }
    }
    return result;
}
/**
 * Check whether a horizontal or vertical segment intersects a rectangle (with padding).
 */
function segmentIntersectsRect(a, b, rect, padding) {
    const rx = rect.x - padding;
    const ry = rect.y - padding;
    const rw = rect.width + padding * 2;
    const rh = rect.height + padding * 2;
    // Horizontal segment
    if (a.y === b.y) {
        const minX = Math.min(a.x, b.x);
        const maxX = Math.max(a.x, b.x);
        return a.y >= ry && a.y <= ry + rh && maxX >= rx && minX <= rx + rw;
    }
    // Vertical segment
    if (a.x === b.x) {
        const minY = Math.min(a.y, b.y);
        const maxY = Math.max(a.y, b.y);
        return a.x >= rx && a.x <= rx + rw && maxY >= ry && minY <= ry + rh;
    }
    return false;
}
/**
 * Route an edge between two rectangles using orthogonal (Manhattan) routing.
 * Produces L-shaped (2 segments) or Z-shaped (3 segments) paths.
 * Avoids obstacles by adding detour segments when needed.
 */
export function routeEdge(sourceRect, targetRect, sourcePort, targetPort, obstacles) {
    const PADDING = 20;
    // Determine exit/entry direction based on port position relative to rect center
    const sourceSide = determineSide(sourceRect, targetPort);
    const targetSide = determineSide(targetRect, sourcePort);
    // Simple L-shaped route: source → bend → target
    // Works when source exits horizontally and target enters vertically (or vice versa)
    const canDoLShape = (sourceSide === 'left' || sourceSide === 'right') && (targetSide === 'top' || targetSide === 'bottom');
    if (canDoLShape) {
        // L-shape: horizontal from source, then vertical to target
        const bend = { x: targetPort.x, y: sourcePort.y };
        const segments = [sourcePort, bend, targetPort];
        // Check if the bend path crosses any obstacle
        const blocked = obstacles.some((obs) => segmentIntersectsRect(sourcePort, bend, obs, PADDING) ||
            segmentIntersectsRect(bend, targetPort, obs, PADDING));
        if (!blocked) {
            return { points: segments };
        }
    }
    // Z-shaped route: source → horizontal → vertical → horizontal → target
    // Compute a midpoint for the vertical segment
    const midX = findClearMidX(sourcePort, targetPort, obstacles, sourceRect, targetRect, PADDING);
    const points = [sourcePort, { x: midX, y: sourcePort.y }, { x: midX, y: targetPort.y }, targetPort];
    // Verify the Z-path isn't blocked; if it is, try a U-shaped detour
    const zBlocked = obstacles.some((obs) => segmentIntersectsRect(points[0], points[1], obs, PADDING) ||
        segmentIntersectsRect(points[1], points[2], obs, PADDING) ||
        segmentIntersectsRect(points[2], points[3], obs, PADDING));
    if (!zBlocked) {
        return { points };
    }
    // Fallback U-shaped detour: go wide around obstacles
    const midY = findClearMidY(sourcePort, targetPort, obstacles, sourceRect, targetRect, PADDING);
    const uPoints = [
        sourcePort,
        { x: sourcePort.x, y: midY },
        { x: targetPort.x, y: midY },
        targetPort,
    ];
    return { points: uPoints };
}
/**
 * Find a clear vertical X coordinate for the middle segment of a Z-shaped route.
 */
function findClearMidX(source, target, obstacles, sourceRect, targetRect, padding) {
    const idealMidX = (source.x + target.x) / 2;
    // Check if idealMidX is clear of all obstacles
    const isBlocked = (x) => obstacles.some((obs) => {
        // Skip source and target rects from obstacle check (they are the endpoints)
        if (obs === sourceRect || obs === targetRect)
            return false;
        return x > obs.x - padding && x < obs.x + obs.width + padding;
    });
    if (!isBlocked(idealMidX)) {
        return idealMidX;
    }
    // Search outward from ideal
    for (let offset = 1; offset < 1500; offset += 15) {
        if (!isBlocked(idealMidX - offset))
            return idealMidX - offset;
        if (!isBlocked(idealMidX + offset))
            return idealMidX + offset;
    }
    return idealMidX;
}
/**
 * Find a clear horizontal Y coordinate for a U-shaped detour.
 */
function findClearMidY(source, target, obstacles, sourceRect, targetRect, padding) {
    // Try going above or below all obstacles
    const allRects = [sourceRect, targetRect, ...obstacles];
    const minY = Math.min(...allRects.map((r) => r.y));
    const maxY = Math.max(...allRects.map((r) => r.y + r.height));
    const aboveCandidate = minY - padding * 3;
    const belowCandidate = maxY + padding * 3;
    // Prefer whichever is closer to the midpoint of source/target
    const mid = (source.y + target.y) / 2;
    if (Math.abs(aboveCandidate - mid) < Math.abs(belowCandidate - mid)) {
        return aboveCandidate;
    }
    return belowCandidate;
}
//# sourceMappingURL=edge-router.js.map