import { snapToGrid } from "../../../utils/NodeUtils.js";
import { EDGE_CONSTANTS } from "./constants.js";
import { consolidateWaypoints, isHorizontalPosition, snapPointToGrid } from "./geometry.js";
function materializePathWaypoints(vertices, stored) {
    return vertices.slice(1, -1).map((vertex)=>{
        const existing = vertex.waypointIndex >= 0 ? stored[vertex.waypointIndex] : void 0;
        return existing ?? {
            id: generateWaypointId(),
            x: vertex.x,
            y: vertex.y
        };
    });
}
function generateWaypointId() {
    return `wp-${crypto.randomUUID()}`;
}
function waypointsEqual(a, b) {
    if (a === b) return true;
    if (a.length !== b.length) return false;
    for(let i = 0; i < a.length; i++){
        const wa = a[i];
        const wb = b[i];
        if (wa.x !== wb.x || wa.y !== wb.y || wa.id !== wb.id) return false;
    }
    return true;
}
function waypointsPositionallyEqual(a, b) {
    if (a === b) return true;
    if (a.length !== b.length) return false;
    for(let i = 0; i < a.length; i++)if (a[i].x !== b[i].x || a[i].y !== b[i].y) return false;
    return true;
}
function insertWaypoint(vertices, stored, segmentIndex, position) {
    const newWaypoint = {
        id: generateWaypointId(),
        ...snapPointToGrid(position)
    };
    const materialized = materializePathWaypoints(vertices, stored);
    materialized.splice(Math.max(0, segmentIndex), 0, newWaypoint);
    return materialized;
}
function removeWaypoint(waypoints, index) {
    return waypoints.filter((_, i)=>i !== index);
}
const REBALANCE_EPSILON = 0.5;
function rebalanceWaypoints(waypoints, source, target) {
    if (0 === waypoints.length) return waypoints;
    const sourceDelta = waypoints_delta(source);
    const targetDelta = waypoints_delta(target);
    if (isMoved(sourceDelta) && deltasEqual(sourceDelta, targetDelta)) return waypoints.map((w)=>({
            id: w.id,
            x: w.x + sourceDelta.x,
            y: w.y + sourceDelta.y
        }));
    const next = waypoints.map((w)=>({
            ...w
        }));
    if (1 === next.length) {
        const sourceMoved = isMoved(sourceDelta);
        const targetMoved = isMoved(targetDelta);
        if (sourceMoved && !targetMoved) shiftAdjacentWaypoint(next[0], source);
        else if (targetMoved && !sourceMoved) shiftAdjacentWaypoint(next[0], target);
        return next;
    }
    shiftAdjacentWaypoint(next[0], source);
    shiftAdjacentWaypoint(next[next.length - 1], target);
    return next;
}
function waypoints_delta(endpoint) {
    return {
        x: endpoint.to.x - endpoint.from.x,
        y: endpoint.to.y - endpoint.from.y
    };
}
function isMoved(d) {
    return Math.abs(d.x) >= REBALANCE_EPSILON || Math.abs(d.y) >= REBALANCE_EPSILON;
}
function deltasEqual(a, b) {
    return Math.abs(a.x - b.x) < REBALANCE_EPSILON && Math.abs(a.y - b.y) < REBALANCE_EPSILON;
}
function shiftAdjacentWaypoint(waypoint, endpoint) {
    if (isHorizontalPosition(endpoint.position)) waypoint.y += endpoint.to.y - endpoint.from.y;
    else waypoint.x += endpoint.to.x - endpoint.from.x;
}
function moveWaypoint(waypoints, index, newPosition) {
    const snapped = snapPointToGrid(newPosition);
    return waypoints.map((wp, i)=>i === index ? {
            ...wp,
            ...snapped
        } : wp);
}
function moveSegmentByDelta(initialWaypoints, initialSegment, delta, initialPathPoints) {
    const moveAxis = 'horizontal' === initialSegment.orientation ? 'y' : 'x';
    const moveValue = 'y' === moveAxis ? delta.y : delta.x;
    const working = initialWaypoints.map((wp)=>({
            ...wp
        }));
    const isConnectedToSource = initialSegment.waypointIndexBefore < 0;
    const isConnectedToTarget = initialSegment.waypointIndexAfter >= working.length;
    let insertedAtStart = false;
    const stubOffset = EDGE_CONSTANTS.STUB_OFFSET;
    const orientation = initialSegment.orientation;
    const segmentStart = initialSegment.start;
    const segmentEnd = initialSegment.end;
    const pathSource = initialPathPoints[0];
    const pathTarget = initialPathPoints[initialPathPoints.length - 1];
    const movedCoordinate = 'horizontal' === orientation ? snapToGrid(segmentStart.y + moveValue) : snapToGrid(segmentStart.x + moveValue);
    if (isConnectedToSource) {
        const stubCoord = snapToGrid(('horizontal' === orientation ? segmentStart.x : segmentStart.y) + stubOffset);
        const [anchorStub, movedStub] = makeStubPair(segmentStart, orientation, stubCoord, movedCoordinate);
        working.unshift(anchorStub, movedStub);
        insertedAtStart = true;
    }
    if (isConnectedToTarget) {
        const stubCoord = snapToGrid(('horizontal' === orientation ? segmentEnd.x : segmentEnd.y) - stubOffset);
        const [anchorStub, movedStub] = makeStubPair(segmentEnd, orientation, stubCoord, movedCoordinate);
        working.push(movedStub, anchorStub);
    }
    const indexOffset = insertedAtStart ? 2 : 0;
    if (!isConnectedToSource && initialSegment.waypointIndexBefore >= 0) {
        const idx = initialSegment.waypointIndexBefore + indexOffset;
        const wp = working[idx];
        if (wp) wp[moveAxis] = movedCoordinate;
    }
    if (!isConnectedToTarget && initialSegment.waypointIndexAfter < initialWaypoints.length) {
        const idx = initialSegment.waypointIndexAfter + indexOffset;
        const wp = working[idx];
        if (wp) wp[moveAxis] = movedCoordinate;
    }
    return consolidateWaypoints(working, pathSource, pathTarget);
}
function makeStubPair(anchor, orientation, stubCoord, movedCoordinate) {
    if ('horizontal' === orientation) return [
        {
            id: generateWaypointId(),
            x: stubCoord,
            y: anchor.y
        },
        {
            id: generateWaypointId(),
            x: stubCoord,
            y: movedCoordinate
        }
    ];
    return [
        {
            id: generateWaypointId(),
            x: anchor.x,
            y: stubCoord
        },
        {
            id: generateWaypointId(),
            x: movedCoordinate,
            y: stubCoord
        }
    ];
}
export { generateWaypointId, insertWaypoint, materializePathWaypoints, moveSegmentByDelta, moveWaypoint, rebalanceWaypoints, removeWaypoint, waypointsEqual, waypointsPositionallyEqual };
