package routing

import (
	
	
	
	
	
	
	
	

	
	
	
	

	
)

type GenerateRouteResponse struct {
	Routes   []*Route
	Distance float64
	Err      error
	Flavor   RouteGenerationFlavor
	work     *routeSearchWorkGuard
}

// routeWorkerPanic records a panic from a route-flavor worker for its
// coordinator. It intentionally does not carry a stack: invariant failures
// are reported as typed errors, while stack capture belongs at process or API
// boundaries that can keep it out of user-facing errors.
type routeWorkerPanic struct {
	flavor RouteGenerationFlavor
}

func ( *routeWorkerPanic) () error {
	return invariant.Errorf("edge routing flavor %s violated an invariant", .flavor)
}

type routeFlavorWorker func(*ovgEdgeRouter, context.Context, bool) GenerateRouteResponse

type routeFlavorResult struct {
	index       int
	response    GenerateRouteResponse
	workerPanic *routeWorkerPanic
}

func runRouteFlavorWorker(
	 context.Context,
	 int,
	 *ovgEdgeRouter,
	 bool,
	 routeFlavorWorker,
) ( routeFlavorResult) {
	var  RouteGenerationFlavor
	if  != nil {
		 = .flavor
	}
	.index = 
	.response.Flavor = 
	defer func() {
		if  := recover();  != nil {
			 = routeFlavorResult{
				index:       ,
				response:    GenerateRouteResponse{Flavor: },
				workerPanic: &routeWorkerPanic{flavor: },
			}
		}
	}()

	.response = (, , )
	// The coordinator owns flavor identity and ordering. Do not trust worker
	// output for either, even though the production worker currently sets both.
	.response.Flavor = 
	return 
}

func generateRouteFlavorResponses( context.Context,  []*ovgEdgeRouter,  bool) ([]GenerateRouteResponse, error) {
	return generateRouteFlavorResponsesWith(, , , (*ovgEdgeRouter).generateRoutes)
}

func generateRouteFlavorResponsesWith(
	 context.Context,
	 []*ovgEdgeRouter,
	 bool,
	 routeFlavorWorker,
) ([]GenerateRouteResponse, error) {
	if len() > maxRouteSearchFlavors {
		return nil, fmt.Errorf(
			"%w: edge routing declared %d flavors; limit %d",
			errRouteSearchWorkLimit,
			len(),
			maxRouteSearchFlavors,
		)
	}
	,  := context.WithCancel()
	defer ()

	 := make(chan routeFlavorResult, len())
	var  sync.WaitGroup
	for ,  := range  {
		.Go(func() {
			 <- runRouteFlavorWorker(, , , , )
		})
	}
	go func() {
		.Wait()
		close()
	}()
	return collectRouteFlavorResponses(, , )
}

func collectRouteFlavorResponses( context.Context,  <-chan routeFlavorResult,  context.CancelFunc) ([]GenerateRouteResponse, error) {
	 := make([]routeFlavorResult, 0, cap())
	 := .Done()
	var  error
	 := false
	 := func() {
		if ! {
			 = true
			()
		}
	}

	for {
		select {
		case <-:
			 = .Err()
			()
			// Disable this select arm after observing cancellation so the result
			// channel can be drained without a permanently-ready competitor.
			 = nil
		case ,  := <-:
			if ! {
				// Worker completion order is scheduler-dependent. Restore the declared
				// router order before selecting either failures or successes.
				slices.SortStableFunc(, func(,  routeFlavorResult) int {
					return cmp.Compare(.index, .index)
				})
				for ,  := range  {
					if .workerPanic != nil {
						return nil, .workerPanic.asError()
					}
				}
				if  == nil {
					 = .Err()
				}
				if  != nil {
					return nil, fmt.Errorf("EdgeRouting: %w", )
				}
				 := make([]GenerateRouteResponse, len())
				for ,  := range  {
					[] = .response
				}
				return , nil
			}
			 = append(, )
			if .workerPanic != nil {
				// A panic invalidates the whole routing attempt. Cancel sibling work,
				// but keep draining until every worker has joined.
				()
			}
		}
	}
}

func successfulRouteFlavorResponses( []GenerateRouteResponse) (map[RouteGenerationFlavor]GenerateRouteResponse, error) {
	 := make(map[RouteGenerationFlavor]GenerateRouteResponse, len())
	var  error
	for ,  := range  {
		if .Err != nil {
			// An ordinary flavor can fail while another valid flavor succeeds. The
			// whole-stage aggregate is different: once exhausted, accepting a flavor
			// that happened to finish first would make the resource bound depend on
			// goroutine scheduling and let total routing work exceed its contract.
			if errors.Is(.Err, errRouteStageWorkLimit) {
				return nil, .Err
			}
			if  == nil {
				 = .Err
			}
			continue
		}
		[.Flavor] = 
	}

	if len() > 0 {
		return , nil
	}
	if  != nil {
		return nil, 
	}
	return nil, invariant.New("edge routing produced no flavor responses")
}

type selectedRouteUpdate struct {
	edge   *layoutgraph.Edge
	points []*geo.Point
}

// finalizeSelectedRoutes accounts route grouping, sorting, reversal, and point
// materialization against the winning flavor's remaining budget. All fallible
// work completes before graph edges are changed, preserving route atomicity.
func finalizeSelectedRoutes(
	 context.Context,
	 *layoutgraph.Graph,
	 []*Route,
	 map[*layoutgraph.Edge]struct{},
	 *routeSearchWorkGuard,
	 bool,
) ( error) {
	if  == nil {
		return invariant.New("selected edge-routing flavor has no work guard")
	}
	if  == nil {
		return invariant.New("selected edge-routing flavor has no graph")
	}
	if  := .bind();  != nil {
		return 
	}
	 := false
	defer func() {
		if ! {
			_ = .finish()
		}
	}()
	if  != nil {
		 := make([]*Route, 0, len())
		 := make(map[*layoutgraph.Edge]struct{}, len())
		for ,  := range  {
			if  := .step();  != nil {
				return 
			}
			if  == nil {
				return invariant.New("selected edge-routing flavor produced a nil route")
			}
			if ,  := [.GEdge]; ! {
				continue
			}
			if ,  := [.GEdge];  {
				return invariant.New("selected edge-routing flavor produced duplicate routes for an edge")
			}
			[.GEdge] = struct{}{}
			 = append(, )
		}
		if len() != len() {
			return invariant.New("selected edge-routing flavor omitted a requested edge")
		}
		 = 
	}

	for ,  := range  {
		if  := .step();  != nil {
			return 
		}
		if  == nil || .GEdge == nil || .GEdge.From == nil || .GEdge.To == nil || len(.OVGNodes) < 2 {
			return invariant.New("selected edge-routing flavor produced an invalid route")
		}
		for ,  := range .OVGNodes {
			if  := .step();  != nil {
				return 
			}
			if  == nil || .Point == nil {
				return invariant.New("selected edge-routing flavor produced an invalid route node")
			}
		}
	}

	if  {
		if  := reorderSelectedRoutes(, , );  != nil {
			return 
		}
	}

	if  := .add(uint64(len()));  != nil {
		return 
	}
	 := make([]selectedRouteUpdate, 0, len())
	for ,  := range  {
		if  := .step();  != nil {
			return 
		}
		if  := .add(uint64(len(.OVGNodes)));  != nil {
			return 
		}
		 = append(, selectedRouteUpdate{
			edge:   .GEdge,
			points: .createSegmentEndpoints(),
		})
	}
	if  := .finish();  != nil {
		return 
	}

	// The commit contains no fallible work. A route-search error or cancellation
	// above therefore leaves every graph edge byte-for-byte unchanged.
	for ,  := range  {
		.edge.Points = .points
	}
	 = true
	return nil
}

func reorderSelectedRoutes( *layoutgraph.Graph,  []*Route,  *routeSearchWorkGuard) error {
	if  := .add(uint64(len()));  != nil {
		return 
	}
	 := make([][]*Route, 0)
	 := make(map[*Route]struct{}, len())
	for ,  := range  {
		if  := .step();  != nil {
			return 
		}
		if ,  := [];  {
			continue
		}
		 := []*Route{}
		[] = struct{}{}
		for ,  := range [+1:] {
			if  := .step();  != nil {
				return 
			}
			if ,  := [];  {
				continue
			}
			,  := .canSwapEdgesGuarded(, , )
			if  != nil {
				return 
			}
			if  {
				 = append(, )
				[] = struct{}{}
			}
		}
		if len() > 1 {
			 = append(, )
		}
	}

	if  := .add(uint64(len(.Edges)));  != nil {
		return 
	}
	 := make(map[*layoutgraph.Edge]int, len(.Edges))
	for ,  := range .Edges {
		if  := .step();  != nil {
			return 
		}
		[] = 
	}

	for ,  := range  {
		if  := .step();  != nil {
			return 
		}
		if  := .reserveSort(len());  != nil {
			return 
		}
		// Sort bucket from left-right or top-bottom.
		sort.Slice(, func(,  int) bool {
			 := []
			 := []
			 := .GEdge.From
			 := .GEdge.To
			if .Orientation().IsVertical() {
				if .GEdge.From == .GEdge.From {
					return .FromPort.X < .FromPort.X
				}
				return .FromPort.X < .ToPort.X
			}
			if .GEdge.From == .GEdge.From {
				return .FromPort.Y < .FromPort.Y
			}
			return .FromPort.Y < .ToPort.Y
		})

		if  := .add(uint64(len()));  != nil {
			return 
		}
		 := make([]*layoutgraph.Edge, len())
		for ,  := range  {
			if  := .step();  != nil {
				return 
			}
			[] = .GEdge
		}
		if  := .reserveSort(len());  != nil {
			return 
		}
		sort.Slice(, func(,  int) bool {
			,  := [[]]
			if ! {
				 = -1
			}
			,  := [[]]
			if ! {
				 = -1
			}
			return  < 
		})

		for ,  := range  {
			if  := .step();  != nil {
				return 
			}
			if .GEdge.From != [].From {
				if  := .add(uint64(len(.OVGNodes)));  != nil {
					return 
				}
				.FromPort, .ToPort = .ToPort, .FromPort
				slices.Reverse(.OVGNodes)
			}
			.GEdge = []
		}
	}
	return .check()
}

func routeEdgesWithResourceGuards(
	 context.Context,
	 *layoutgraph.Graph,
	 []*layoutgraph.Node,
	 *ovgBuildGuard,
	 uint64,
) (*OVG, error) {
	,  := buildOVGFromGraphWithGuard(, , )
	if  != nil {
		return , 
	}

	// Route all tree edges first, then route the remaining edges
	 := make([]*Route, 0)
	 := .TreeEdgeMap()
	.AddIsolatedTreeEdges()

	 := make(map[*OVGNode]map[layoutgraph.Arrowhead]struct{})
	 := map[*layoutgraph.Edge]struct{}{}
	for ,  := range .Nodes {
		// we route each tree edge by iterating over the tree nodes, and routing with its sentinel edge
		// this way we know the edge parent/child relation (tree.Node is the child), and that the node is in nodeToTree (not a rootSentinel)
		if ,  := .NodeToTree[];  {
			// we don't route the edge from tree root to rootSentinel here since the root may move relative to the rootSentinel
			if ,  := [.SentinelEdge]; ! {
				continue
			}

			,  := routeSentinelEdge(, .Ports, .Centers, )
			if  != nil {
				continue
			}

			 = append(, )
			 := .OVGNodes[1]
			 := .OVGNodes[len(.OVGNodes)-2]
			if ,  := []; ! {
				[] = make(map[layoutgraph.Arrowhead]struct{})
			}
			if ,  := []; ! {
				[] = make(map[layoutgraph.Arrowhead]struct{})
			}
			[][.SentinelEdge.SourceArrowhead] = struct{}{}
			[][.SentinelEdge.TargetArrowhead] = struct{}{}
			[.SentinelEdge] = struct{}{}
		} else {
			for ,  := range loops.Route() {
				 = append(, makeLoopRoute(, ))
				[] = struct{}{}
			}
		}
	}
	 := make([]*layoutgraph.Edge, 0, len(.Edges))
	for ,  := range .Edges {
		if ,  := []; ! {
			 = append(, )
		}
	}

	// cache these ahead of time
	.CrossingCost()
	.TurnCost()
	.NonCenterPortCost()

	 := []RouteGenerationFlavor{ShortestToLongest, LongestToShortest, Default}
	if .Nodes[0].Hierarchy != nil {
		 = []RouteGenerationFlavor{TopDownLeftRight}
	}
	 := make([]*ovgEdgeRouter, 0, len())
	for ,  := range  {
		,  := newOVGEdgeRouterWithWorkLimit(, , , , , , )
		if  != nil {
			return , 
		}
		 = append(, )
	}
	,  := generateRouteFlavorResponses(, , true)
	if  != nil {
		return , 
	}

	var  []*Route
	var  *routeSearchWorkGuard
	 := math.Inf(1)

	,  := successfulRouteFlavorResponses()
	if  != nil {
		return , 
	}

	// compare responses in fixed order
	for ,  := range  {
		if ,  := [];  {
			if geo.PrecisionCompare(.Distance, , geo.PRECISION) < 0 {
				 = .Distance
				 = .Routes
				 = .work
			}
		}
	}

	if  := finalizeSelectedRoutes(, , , nil, , true);  != nil {
		return , 
	}

	return , nil
}

func routeAdditionalEdgesWithLimits( context.Context,  *layoutgraph.Graph,  []*layoutgraph.Edge,  ovgBuildLimits) (*OVG, error) {
	return routeAdditionalEdgesWithResourceLimits(, , , , maxRouteSearchWorkUnits)
}

func routeAdditionalEdgesWithResourceLimits(
	 context.Context,
	 *layoutgraph.Graph,
	 []*layoutgraph.Edge,
	 ovgBuildLimits,
	 uint64,
) (*OVG, error) {
	 := make(map[*layoutgraph.Edge]struct{})
	for ,  := range  {
		[] = struct{}{}
	}

	,  := buildOVGFromGraphWithLimits(, , nil, )
	if  != nil {
		return , 
	}
	 := .buildGuard

	 := make([]*Route, 0)
	for ,  := range .Edges {
		if  := .step();  != nil {
			return , 
		}
		if ,  := [];  {
			continue
		}

		var  []*OVGNode
		var  *geo.Point

		 := func( *geo.Point) error {
			if  := .step();  != nil {
				return 
			}
			// Skip if this point is the same as the last one
			if  != nil && * == * {
				return nil
			}
			 = 

			var  *OVGNode
			var  bool
			if ,  = .OccupiedPoints[*];  {
				 = append(, )
			} else {
				 = NewOVGNode()
				// The base OVG was indexed before authoritative routes were added.
				.Index = len(.Nodes)
				 = append(, )
				if  := .addNodeUnchecked(, );  != nil {
					return 
				}
			}

			// Only connect if we have at least 2 different points
			if len() > 1 {
				if ,  := .connect(, [len()-2], [len()-1]);  != nil {
					return 
				}
			}
			return nil
		}

		if  := (.From.Center());  != nil {
			return , 
		}
		for ,  := range .Points {
			if  := ();  != nil {
				return , 
			}
		}
		if  := (.To.Center());  != nil {
			return , 
		}

		// Only create route if we have at least 2 different points
		if len() > 1 {
			 := &Route{
				GEdge:    ,
				OVGNodes: ,
				FromPort: *.Points[0],
				ToPort:   *.Points[len(.Points)-1],
			}
			 = append(, )
		}
	}

	// cache these ahead of time
	.CrossingCost()
	.TurnCost()
	.NonCenterPortCost()

	 := []RouteGenerationFlavor{ShortestToLongest, LongestToShortest, Default}
	if .Nodes[0].Hierarchy != nil {
		 = []RouteGenerationFlavor{TopDownLeftRight}
	}
	 := make([]*ovgEdgeRouter, 0, len())
	for ,  := range  {
		,  := newOVGEdgeRouterWithWorkLimit(, , , , , , )
		if  != nil {
			return , 
		}
		.considerNodeLabels = true
		 = append(, )
	}
	,  := generateRouteFlavorResponses(, , true)
	if  != nil {
		return , 
	}

	var  []*Route
	var  *routeSearchWorkGuard
	 := math.Inf(1)

	,  := successfulRouteFlavorResponses()
	if  != nil {
		return , 
	}

	// compare responses in fixed order
	for ,  := range  {
		if ,  := [];  {
			if geo.PrecisionCompare(.Distance, , geo.PRECISION) < 0 {
				 = .Distance
				 = .Routes
				 = .work
			}
		}
	}

	if  := finalizeSelectedRoutes(, , , , , false);  != nil {
		return , 
	}

	return , nil
}

// For Trees, ideal route between parent & child is S shaped bend or straight line
// .  e.g    ┌───┐        ┌───┐
// .         │   │        │   │
// .         └─▲─┘        └─▲─┘
// .       ┌───┴───┐        │
// .     ┌─┴─┐   ┌─┴─┐    ┌─┴─┐
// .     │   │   │   │    │   │
// .     └─▲─┘   └───┘    └─▲─┘
// .   ┌───┴───┐            │
// . ┌─┴─┐   ┌─┴─┐        ┌─┴─┐
// . │   │   │   │        │   │
// . └───┘   └───┘        └───┘
func routeSentinelEdge( *layoutgraph.Tree,
	 map[*layoutgraph.Node][]*OVGNode,
	 map[*layoutgraph.Node]*OVGNode,
	 map[*OVGNode]map[layoutgraph.Arrowhead]struct{},
) (*Route, error) {
	// Note: we are the child node since we are considering the sentinel edge
	 := .SentinelEdge
	 := treeEdgePath(, , )

	,  := routeInSShape()
	if  != nil {
		return nil, 
	}

	// validate route
	for ,  := range  {
		if .isPort() {
			 := false
			for  := range .portOwners() {
				if .From.IsDescendantOf() || .To.IsDescendantOf() {
					 = true
					break
				}
			}
			if ! {
				return nil, fmt.Errorf("routing through other node's port")
			}
		}
	}

	 := make([]*OVGNode, 0)
	 = append(, [.From])
	 = append(, ...)
	 = append(, [.To])

	return &Route{
		GEdge:    ,
		OVGNodes: ,
		FromPort: *.SourcePortNode.Point,
		ToPort:   *.TargetPortNode.Point,
	}, nil
}

// create the straight line or S shaped route between source and target ports
// return the ovg nodes along the way (including source and target port)
func routeInSShape( TreeEdgePath) ([]*OVGNode, error) {
	 := .SourceOrientationToTarget
	// Create the route in 3 steps
	// 1. Route from source port to source midpoint
	// 2. Route from source midpoint to target midpoint
	// 3. Route from target midpoint to target port
	//                  ┌───┐
	//                  │ T │
	//                  └─┬─┘
	//                    3
	//       ┌──────2─────┘
	//       1
	//     ┌─┴─┐
	//     │ S │
	//     └───┘
	 := .SourcePortNode
	 := func( geo.Orientation) *OVGNode {
		var  *OVGNode
		for ,  := range .Edges {
			 := .adjacent()
			switch  {
			case geo.Left:
				// if current is Left of adjacent, we want the closest y-aligned adjacent node to the Right of current
				if .Point.Y == .Point.Y && .Point.X > .Point.X {
					if  == nil || .Point.X < .Point.X {
						 = 
					}
				}
			case geo.Right:
				if .Point.Y == .Point.Y && .Point.X < .Point.X {
					if  == nil || .Point.X > .Point.X {
						 = 
					}
				}
			case geo.Top:
				if .Point.X == .Point.X && .Point.Y > .Point.Y {
					if  == nil || .Point.Y < .Point.Y {
						 = 
					}
				}
			default:
				if .Point.X == .Point.X && .Point.Y < .Point.Y {
					if  == nil || .Point.Y > .Point.Y {
						 = 
					}
				}
			}
		}
		return 
	}

	 := make([]*OVGNode, 0)
	 = append(, .SourcePortNode)

	// Step 1. source port to source midpoint
	for !nonNilEquals(.Point, .SourceMidpoint) {
		 = ()
		if  == nil {
			return nil, invariant.New("couldn't find s shaped route (source port to source midpoint)")
		}
		 = append(, )
	}

	// Step 2. source midpoint to target midpoint
	var  geo.Orientation
	if  == geo.Left ||  == geo.Right {
		if .SourcePortNode.Point.Y < .TargetPortNode.Point.Y {
			 = geo.Top
		} else {
			 = geo.Bottom
		}
	} else {
		if .SourcePortNode.Point.X < .TargetPortNode.Point.X {
			 = geo.Left
		} else {
			 = geo.Right
		}
	}

	for !nonNilEquals(.Point, .TargetMidpoint) {
		 = ()
		if  == nil {
			return nil, invariant.New("couldn't find s shaped route (source midpoint to target midpoint)")
		}
		 = append(, )
	}

	// Step 3. target midpoint to target port
	for !nonNilEquals(.Point, .TargetPortNode.Point) {
		 = ()
		if  == nil {
			return nil, invariant.New("couldn't find s shaped route (target midpoint to target port)")
		}
		 = append(, )
	}

	return , nil
}

type RouteGenerationFlavor string

const (
	ShortestToLongest RouteGenerationFlavor = "ShortestToLongest"
	LongestToShortest RouteGenerationFlavor = "LongestToShortest"
	Default           RouteGenerationFlavor = "Default"
	TopDownLeftRight  RouteGenerationFlavor = "TopDownLeftRight"
)

func routeLine( context.Context,  *layoutgraph.Graph,  *layoutgraph.Edge,  []*layoutgraph.Edge,  []*Route) (*geo.Point, *geo.Point, float64, error) {
	return routeLineChecked(, , , , func() error {
		return checkEdgeRoutingCanceled()
	}, nil)

}

func routeLineGuarded( *layoutgraph.Graph,  *layoutgraph.Edge,  []*layoutgraph.Edge,  []*Route,  *routeWorkGuard) (*geo.Point, *geo.Point, float64, error) {
	return routeLineChecked(, , , , .step, )
}

func routeLineChecked(
	 *layoutgraph.Graph,
	 *layoutgraph.Edge,
	 []*layoutgraph.Edge,
	 []*Route,
	 func() error,
	 workBudget,
) (*geo.Point, *geo.Point, float64, error) {
	if  := ();  != nil {
		return nil, nil, 0, 
	}
	 := .From.Orientation(.To)
	if  == geo.NONE {
		return nil, nil, 0.0, layoutgraph.ErrInvalidCandidate
	}
	 := .From.PortsByOrientation(.GetOpposite())

	 := .To.Orientation(.From)
	if  == geo.NONE {
		return nil, nil, 0.0, layoutgraph.ErrInvalidCandidate
	}
	 := .To.PortsByOrientation(.GetOpposite())

	var ,  map[geo.Point][]*layoutgraph.Edge
	if  == nil {
		 = layoutgraph.Edges().PortEdges(.From)
		 = layoutgraph.Edges().PortEdges(.To)
	} else {
		var  error
		,  = portEdgesGuarded(layoutgraph.Edges(), .From, )
		if  != nil {
			return nil, nil, 0, 
		}
		,  = portEdgesGuarded(layoutgraph.Edges(), .To, )
		if  != nil {
			return nil, nil, 0, 
		}
	}
	// we filter out the current edge here to keep the logic for arrowhead matching simpler
	 := func( []*layoutgraph.Edge) ([]*layoutgraph.Edge, error) {
		 := make([]*layoutgraph.Edge, 0)
		for ,  := range  {
			if  := ();  != nil {
				return nil, 
			}
			if  !=  {
				 = append(, )
			}
		}
		return , nil
	}
	for ,  := range  {
		,  := ()
		if  != nil {
			return nil, nil, 0, 
		}
		[] = 
	}
	for ,  := range  {
		,  := ()
		if  != nil {
			return nil, nil, 0, 
		}
		[] = 
	}

	 := make(map[geo.Point]bool)
	for ,  := range .From.CenterPorts() {
		[] = true
	}
	 := make(map[geo.Point]bool)
	for ,  := range .To.CenterPorts() {
		[] = true
	}
	var ,  map[*layoutgraph.Node]bool
	if  == nil {
		,  = sourceAndTargetClusterNodes(, .From, .To)
	} else {
		var  error
		, ,  = sourceAndTargetClusterNodesGuarded(, .From, .To, )
		if  != nil {
			return nil, nil, 0, 
		}
	}

	var  layoutgraph.Nodes
	if .SourceArrowheadLabel != nil || .TargetArrowheadLabel != nil {
		if  == nil {
			 = filterEdgeAncestors(, .Nodes)
		} else {
			var  error
			,  = filterEdgeAncestorsGuarded(, .Nodes, )
			if  != nil {
				return nil, nil, 0, 
			}
		}
	}

	var  []labeling.PositionedArrowheadLabel
	for ,  := range  {
		if  := ();  != nil {
			return nil, nil, 0, 
		}
		if  ==  {
			continue
		}
		var  []*geo.Point
		if  != nil {
			 = [].createSegmentEndpoints()
		} else {
			 = .Points
		}
		if len() == 0 {
			continue
		}
		if  != nil {
			if  := .add(uint64(len()));  != nil {
				return nil, nil, 0, 
			}
		}
		if  := labeling.PositionArrowheadLabel(, false, );  != nil {
			 = append(, *)
		}
		if  := labeling.PositionArrowheadLabel(, true, );  != nil {
			 = append(, *)
		}
	}

	 := math.Inf(1)
	var ,  *geo.Point

	// for each pair, place an edge, get the cost, but multiply it by some penalty factor for being non-orthogonal
	for ,  := range  {
		if  := ();  != nil {
			return nil, nil, 0, 
		}
		 := 0.0
		// Note: remove fromPortEdges could be empty if there are no edges after filtering
		if ,  := [];  && len() > 0 {
			// we don't consider ports used by a duplicate edge
			var  bool
			if  == nil {
				 = .HasDuplicateIn()
			} else {
				var  error
				,  = edgeHasDuplicateInGuarded(, , )
				if  != nil {
					return nil, nil, 0, 
				}
			}
			if  {
				// Edge case option: consider ports used by a duplicate edge, as long
				// as the pair of source and target ports are not both the same (e.g. 1/2 shared ok)
				continue
			}

			// if the port is to be shared, all the edges must match arrowheads
			 := true
			for ,  := range  {
				if  := ();  != nil {
					return nil, nil, 0, 
				}
				if .From == .From {
					// other edge source arrow must match
					if .SourceArrowhead != .SourceArrowhead {
						 = false
						break
					}
				} else {
					// other edge target arrow must match
					if .SourceArrowhead != .TargetArrowhead {
						 = false
						break
					}
				}
			}
			if ! {
				continue
			}

			// sharing a port with matching arrowheads is not ideal so other source ports should be preferred
			if .HasSourceArrow() {
				 = maxSideLength(.From, ) / 2.0
			}
		}

		for ,  := range  {
			if  := ();  != nil {
				return nil, nil, 0, 
			}
			 := 0.0
			if ,  := [];  && len() > 0 {
				var  bool
				if  == nil {
					 = .HasDuplicateIn()
				} else {
					var  error
					,  = edgeHasDuplicateInGuarded(, , )
					if  != nil {
						return nil, nil, 0, 
					}
				}
				if  {
					continue
				}

				 := true
				for ,  := range  {
					if  := ();  != nil {
						return nil, nil, 0, 
					}
					if .To == .To {
						if .TargetArrowhead != .TargetArrowhead {
							 = false
							break
						}
					} else {
						if .TargetArrowhead != .SourceArrowhead {
							 = false
							break
						}
					}
				}
				if ! {
					continue
				}

				 = maxSideLength(.To, ) / 2.0
			}

			 := layoutgraph.NewEdge(.From, .To)
			.Points = []*geo.Point{&([]), &([])}

			// don't consider option if edge crosses over another node
			var  bool
			if  == nil {
				 = layoutgraph.Nodes(.Nodes).IntersectsNode()
			} else {
				var  error
				,  = routeIntersectsNodeGuarded(layoutgraph.Nodes(.Nodes), , )
				if  != nil {
					return nil, nil, 0, 
				}
			}
			if  {
				continue
			}

			,  := ()
			if  != nil {
				return nil, nil, 0, 
			}

			var  []*layoutgraph.Edge
			if  == nil {
				 = findOverlappingEdges(&[], &[], )
			} else {
				var  error
				,  = overlappingEdgesGuarded(&[], &[], , )
				if  != nil {
					return nil, nil, 0, 
				}
			}
			if len() > 0 {
				 := false
				if  == nil {
					 = edgeCanOverlapEdges(, , , )
				} else {
					var  error
					,  = edgeCanOverlapEdgesGuarded(, , , , )
					if  != nil {
						return nil, nil, 0, 
					}
				}
				if ! {
					continue
				}
			}

			var  float64
			if  == nil {
				 = estimateRouteCost(layoutgraph.Edges(), )
			} else {
				var  error
				,  = estimateRouteCostGuarded(layoutgraph.Edges(), , )
				if  != nil {
					return nil, nil, 0, 
				}
			}
			 :=  +  + 

			if .X != .X && .Y != .Y {
				 := false
				// Reward axis-aligned lines
				if geo.PrecisionCompare(.X, .X, layoutgraph.AxisAlignmentTolerance) == 0 {
					 = true
				}
				if geo.PrecisionCompare(.Y, .Y, layoutgraph.AxisAlignmentTolerance) == 0 {
					 = true
				}
				if ! {
					 *= nonOrthogonalFactor
				}
			}

			// skip straight edges with sharp angles with source or target node
			if hasSharpAngleToBorder(&[], &[], ) {
				continue
			}
			if hasSharpAngleToBorder(&[], &[], ) {
				continue
			}

			// Reward lines that go through center
			if ,  := []; ! {
				 += .NonCenterPortCost()
			}
			if ,  := []; ! {
				 += .NonCenterPortCost()
			}

			if  := .SourceArrowheadLabel;  != nil {
				 := []*geo.Point{&([]), &([])}
				 := labeling.PositionArrowheadLabel(, false, )
				if  == nil {
					 += positionedArrowheadLabelCost(*, , , nil, )
				} else {
					,  := positionedArrowheadLabelCostGuarded(*, , , nil, , )
					if  != nil {
						return nil, nil, 0, 
					}
					 += 
				}
			}
			if  := .TargetArrowheadLabel;  != nil {
				 := []*geo.Point{&([]), &([])}
				 := labeling.PositionArrowheadLabel(, true, )
				if  == nil {
					 += positionedArrowheadLabelCost(*, , , nil, )
				} else {
					,  := positionedArrowheadLabelCostGuarded(*, , , nil, , )
					if  != nil {
						return nil, nil, 0, 
					}
					 += 
				}
			}

			// get the best cost of these pairs
			if  <  {
				,  = &([]), &([])
				 = 
			}
		}
	}

	if  == math.Inf(1) {
		return nil, nil, 0.0, layoutgraph.ErrInvalidCandidate
	}

	return , , , nil
}

// return true if gEdge can overlap with the other edges provided
func edgeCanOverlapEdges( *layoutgraph.Edge,  []*layoutgraph.Edge, ,  map[*layoutgraph.Node]bool) bool {
	if len() == 0 {
		return true
	}
	if .TargetArrowheadLabel != nil || .SourceArrowheadLabel != nil {
		return false
	}
	for ,  := range  {
		if .TargetArrowheadLabel != nil || .SourceArrowheadLabel != nil {
			return false
		}
		if !.EquivalentStyles() {
			return false
		}
	}

	 := false
	if len() > 0 {
		 := false
		for ,  := range  {
			,  := [.From]
			,  := [.To]
			if ! && ! {
				 = true
				break
			}
		}
		if ! {
			 = true
		}
	}
	if ! && len() > 0 {
		 := false
		for ,  := range  {
			,  := [.From]
			,  := [.To]
			if ! && ! {
				 = true
				break
			}
		}
		if ! {
			 = true
		}
	}

	// TODO find a better solution for edge routing with labels. Shared edges with
	// labels require special handling unless they belong to clusters.

	// if directed edges share a source or target, they can share an edge
	 := .IsDirected()
	if  {
		for ,  := range  {
			if !.IsDirected() {
				 = false
				break
			}
			if !(.To == .To || .From == .From) {
				 = false
				break
			}
			if .TargetArrowhead != .TargetArrowhead || .SourceArrowhead != .SourceArrowhead {
				 = false
				break
			}
		}
	}
	if  {
		return true
	}

	// bidirectional / undirected edges can overlap as long as source/target arrowheads match and
	// if they are part of the same cluster or if all edges share a node
	// TODO support overlapping bidirectional edges with different source/target arrowheads.
	var ,  bool
	 = .IsBidirectional() && .OwnArrowheadsMatch()
	if  {
		for ,  := range  {
			if !.IsBidirectional() || !.MatchingArrowheads() {
				return false
			}
		}
	} else {
		 = .IsUndirected()
		if  {
			for ,  := range  {
				if !.IsUndirected() {
					return false
				}
			}
		}
	}
	if ! && ! {
		return false
	}

	// Note: ovg_edge_router treats edges between tables columns as clusters so those are also included here
	// if edges are of the same cluster, bidirectional / undirected edges can overlap
	if  {
		return true
	}

	// Note: assuming no self-loops here (node appears once per edge)
	 := make(map[*layoutgraph.Node]int)
	[.From]++
	[.To]++
	for ,  := range  {
		[.From]++
		[.To]++
	}
	// if all edges are connected to a shared node, then they can overlap without it becoming unclear which nodes are connected
	 := false
	 := len() + 1
	for ,  := range  {
		if  ==  {
			 = true
			break
		}
	}
	return 
}

func isVerticalOrHorizontalOverlap(, , ,  *geo.Point) bool {
	 := .X == .X && .X == .X
	 := .Y == .Y && .Y == .Y
	if  ||  {
		if intersects(, , , ) {
			return true
		}
	}
	return false
}

func findOverlappingEdges(,  *geo.Point,  []*layoutgraph.Edge) []*layoutgraph.Edge {
	 := make([]*layoutgraph.Edge, 0)
	for ,  := range  {
		for  := 0;  < len(.Points)-1; ++ {
			if isVerticalOrHorizontalOverlap(, , .Points[], .Points[+1]) {
				 = append(, )
				break
			}
		}
	}
	return 
}

func sourceAndTargetClusterNodes( *layoutgraph.Graph, ,  *layoutgraph.Node) (map[*layoutgraph.Node]bool, map[*layoutgraph.Node]bool) {
	 := make(map[*layoutgraph.Node]bool)
	 := make(map[*layoutgraph.Node]bool)

	for ,  := range .ClusterOrder() {
		 := .Clusters[]
		 := false
		 := false
		for ,  := range .Nodes {
			if  ==  {
				 = true
			}
			if  ==  {
				 = true
			}
		}

		if  {
			for ,  := range .Nodes {
				if  ==  {
					continue
				}
				[] = true
			}
		}

		if  {
			for ,  := range .Nodes {
				if  ==  {
					continue
				}
				[] = true
			}
		}
	}
	return , 
}

// return the length of the side of the node
// if orientation is on a corner return whichever side is longer
func maxSideLength( *layoutgraph.Node,  geo.Orientation) float64 {
	switch {
	case  == geo.Top ||  == geo.Bottom:
		return .Width
	case  == geo.Left ||  == geo.Right:
		return .Height
	default:
		return math.Max(.Width, .Height)
	}
}

// | Consider straight edge alternatives for each edge.
// | For each edge we consider each pair of ports between the source and target nodes.
// | By default we don't consider an alternative if it uses an occupied port, but a port can be shared if:
// |   (1) it is not used by a duplicate edge, and (2) the edge and all edges at the port have matching arrowheads
//
// | Edge case: if there are more duplicate edges than ports, we end up running out of alternatives.
// |   we could allow duplicate edges to share ports as long as they're not the same pair
// |      (in which case we wouldn't run out of options until there are more duplicates than port pairs)
type straightEdgeFallbackChange struct {
	edge   *layoutgraph.Edge
	points []*geo.Point
}

type straightEdgeFallbackRollback struct {
	graph         *layoutgraph.Graph
	costs         layoutgraph.RoutingCostState
	costsCaptured bool
	firstChange   straightEdgeFallbackChange
	hasChange     bool
	moreChanges   []straightEdgeFallbackChange
}

func ( *straightEdgeFallbackRollback) ( *layoutgraph.Graph) {
	if .costsCaptured {
		return
	}
	.graph = 
	.costs = .RoutingCosts()
	.costsCaptured = true
}

func ( *straightEdgeFallbackRollback) ( *layoutgraph.Edge,  []*geo.Point) {
	 := straightEdgeFallbackChange{edge: , points: }
	if !.hasChange {
		.firstChange = 
		.hasChange = true
		return
	}
	.moreChanges = append(.moreChanges, )
}

func ( *straightEdgeFallbackRollback) () {
	for  := len(.moreChanges) - 1;  >= 0; -- {
		 := .moreChanges[]
		.edge.Points = .points
	}
	if .hasChange {
		.firstChange.edge.Points = .firstChange.points
	}
	if .costsCaptured {
		.graph.RestoreRoutingCosts(.costs)
	}
}

func isStraightEdgeFallbackCandidate( *layoutgraph.Edge,  map[*layoutgraph.Node]bool,  map[*layoutgraph.Edge]bool) bool {
	if ,  := [.From];  {
		return false
	}
	if ,  := [.To];  {
		return false
	}
	if ,  := [];  {
		return false
	}
	if .From.Hierarchy != nil {
		return false
	}
	return !.HasTableColumn()
}

func ( context.Context,  *layoutgraph.Graph) error {
	if  := checkEdgeRoutingCanceled();  != nil {
		return 
	}

	 := make(map[*layoutgraph.Node]bool)
	for ,  := range .Clusters {
		if  := checkEdgeRoutingCanceled();  != nil {
			return 
		}
		for ,  := range .Nodes {
			if  := checkEdgeRoutingCanceled();  != nil {
				return 
			}
			[] = true
		}
	}
	 := .TreeEdgeMap()
	.AddIsolatedTreeEdges()
	if  := checkEdgeRoutingCanceled();  != nil {
		return 
	}
	for ,  := range .Edges {
		if  := checkEdgeRoutingCanceled();  != nil {
			return 
		}
		if !isStraightEdgeFallbackCandidate(, , ) {
			continue
		}
		return straightEdgesFallbackFrom(, , , , )
	}
	return nil
}

func straightEdgesFallbackFrom(
	 context.Context,
	 *layoutgraph.Graph,
	 map[*layoutgraph.Node]bool,
	 map[*layoutgraph.Edge]bool,
	 int,
) error {
	var  straightEdgeFallbackRollback
	.captureCosts()
	 := false
	defer func() {
		if ! {
			.restore()
		}
	}()

	for ,  := range .Edges[:] {
		if  != 0 {
			if  := checkEdgeRoutingCanceled();  != nil {
				return 
			}
			if !isStraightEdgeFallbackCandidate(, , ) {
				continue
			}
		}
		 := .Points
		,  := tryStraightEdgeFallback(, , )
		if  {
			.record(, )
		}
		if  != nil {
			return 
		}
	}
	if  := checkEdgeRoutingCanceled();  != nil {
		return 
	}
	 = true
	return nil
}

func tryStraightEdgeFallback( context.Context,  *layoutgraph.Graph,  *layoutgraph.Edge) (bool, error) {
	if  := checkEdgeRoutingCanceled();  != nil {
		return false, 
	}

	 := estimateRouteCost(layoutgraph.Edges(.Edges), )
	, , ,  := routeLine(, , , .Edges, nil)
	if  != nil {
		if layoutgraph.IsCandidateRejection() {
			// A straight replacement is optional. Keep the original route when
			// no valid line exists.
			return false, nil
		}
		return false, 
	}

	// If dejitter fails but the segment is really short, we would prefer a straight line (all else equivalent)
	if len(.Points) == 4 {
		if geo.EuclideanDistance(
			.Points[1].X,
			.Points[1].Y,
			.Points[2].X,
			.Points[2].Y,
		) <= lowerJitterThreshold {
			// remove the straight line penalty from this line
			 /= nonOrthogonalFactor
		}
	}

	if  := checkEdgeRoutingCanceled();  != nil {
		return false, 
	}
	if  <  {
		.Points = []*geo.Point{, }
		return true, nil
	}
	return false, nil
}

// true if angle between line and border (based on orientation) is less than min angle
func hasSharpAngleToBorder(,  *geo.Point,  geo.Orientation) bool {
	switch {
	case  == geo.Top ||  == geo.Bottom:
		// border is to bottom
		//    \       /
		//     \ ok  /
		//      \   /
		//  min˚ \ / min˚
		// =====border====
		//  min˚ / \ min˚
		//      /   \
		//     / ok  \
		// border is to top
		 := .X - .X
		 := .Y - .Y
		 := math.Abs(180 * math.Atan2(, ) / math.Pi)
		if  > 90 {
			 = 180 - 
		}
		return  < minStraightEdgeAngle
	case  == geo.Left ||  == geo.Right:
		//    border
		//   \  ||  /
		//    \˚||˚/
		//right\||/ left
		// ok  /||\  ok
		//    / || \
		//   / ˚||˚ \
		 := .X - .X
		 := .Y - .Y
		 := math.Abs(180 * math.Atan2(, ) / math.Pi)
		if  > 90 {
			 = 180 - 
		}
		return  < minStraightEdgeAngle
	}
	return false
}

func ( context.Context,  *layoutgraph.Graph) error {
	return reorderDuplicatesInEdges(, .Edges)
}

func reorderDuplicatesInEdges( context.Context,  []*layoutgraph.Edge) error {
	 := func() error {
		if  := .Err();  != nil {
			return fmt.Errorf("ReorderDuplicates: %w", )
		}
		return nil
	}
	if  := ();  != nil {
		return 
	}
	type  struct {
		   *layoutgraph.Edge
		 []*geo.Point
		 []*geo.Point
	}
	 := make([], 0, len())
	 := make(map[*layoutgraph.Edge]struct{}, len())
	for ,  := range  {
		if ,  := [];  {
			continue
		}
		[] = struct{}{}
		 = append(, {
			:   ,
			: .Points,
			: append([]*geo.Point(nil), .Points...),
		})
	}
	 := false
	defer func() {
		if  {
			return
		}
		for ,  := range  {
			copy(., .)
			..Points = .
		}
	}()

	// look for labeled straight edges that have duplicates
	for ,  := range  {
		if  := ();  != nil {
			return 
		}
		if .HasTableColumn() {
			continue
		}
		if .Label == nil || !.IsStraight() {
			continue
		}

		 := .HasOverlappingEnd()

		 := []int{}
		for ,  := range  {
			if  := ();  != nil {
				return 
			}
			if  ==  {
				continue
			}
			if !.IsDuplicateOf() || !.IsStraight() {
				continue
			}
			// Note: only need to check for matching arrowheads if one of the edges overlaps another edge
			if  || .HasOverlappingEnd() {
				 := .SourceArrowhead
				 := .TargetArrowhead
				if .From == .To {
					// Note: since other is in opposite direction, we reverse points when swapping below
					,  = , 
				}
				if .TargetArrowhead ==  && .SourceArrowhead ==  {
					 = append(, )
				}
			} else {
				 = append(, )
			}
		}

		if len() > 1 {
			 := .Points[0]
			 := .Points[len(.Points)-1]

			 := .X - .X
			 := .Y - .Y

			 := make([]*layoutgraph.Edge, 0, len()+1)
			 := make([]*layoutgraph.Edge, 0, len()+1)

			 = append(, )
			 = append(, )

			for ,  := range  {
				 := []
				 = append(, )
				 = append(, )
			}

			if  >  {
				// . ┌───────┐
				// . │       │
				// . └─┬─┬─┬─┘
				// .   0 1 2 source order
				// .   │ │ │
				// .   0 1 2 target order
				// . ┌─▼─▼─▼─┐
				// . │       │
				// . └───────┘
				sort.Slice(, func(,  int) bool {
					return [].Points[0].X < [].Points[0].X
				})
				sort.Slice(, func(,  int) bool {
					,  := [], []
					return .Points[len(.Points)-1].X < .Points[len(.Points)-1].X
				})
			} else {
				sort.Slice(, func(,  int) bool {
					return [].Points[0].Y < [].Points[0].Y
				})
				sort.Slice(, func(,  int) bool {
					,  := [], []
					return .Points[len(.Points)-1].Y < .Points[len(.Points)-1].Y
				})
			}
			if  := ();  != nil {
				return 
			}

			 := true
			for  := range  {
				if [] != [] {
					 = false
					break
				}
			}
			if ! {
				continue
			}
			 := -1
			for  := range  {
				if [] ==  {
					 = 
					break
				}
			}
			if  == 0 ||  == len()-1 {
				continue
			}
			 := [0]
			 := [len()-1]
			if .Label != nil && .Label != nil {
				continue
			}
			if  := ();  != nil {
				return 
			}

			if .Label == nil {
				.Points, .Points = .Points, .Points
				if .From == .To {
					// need to reverse order of points
					for ,  := 0, len(.Points)-1;  < ; ,  = +1, -1 {
						.Points[], .Points[] = .Points[], .Points[]
					}
					for ,  := 0, len(.Points)-1;  < ; ,  = +1, -1 {
						.Points[], .Points[] = .Points[], .Points[]
					}
				}
			} else if .Label == nil {
				.Points, .Points = .Points, .Points
				if .From == .To {
					for ,  := 0, len(.Points)-1;  < ; ,  = +1, -1 {
						.Points[], .Points[] = .Points[], .Points[]
					}
					for ,  := 0, len(.Points)-1;  < ; ,  = +1, -1 {
						.Points[], .Points[] = .Points[], .Points[]
					}
				}
			}
		}
	}
	if  := ();  != nil {
		return 
	}
	 = true
	return nil
}

func ( context.Context,  *layoutgraph.Graph) error {
	return crosshatchWithWorkLimit(, , maxRouteStageWorkUnits)
}

func crosshatchWithWorkLimit( context.Context,  *layoutgraph.Graph,  uint64) error {
	return runAtomicRouteStage(, "Crosshatch", , nil, , func( *routeWorkGuard) error {
		 := make([]*layoutgraph.Node, 0, len(.Clusters))
		for  := range .Clusters {
			if  := .step();  != nil {
				return 
			}
			 = append(, )
		}
		layoutgraph.SortNodesByID()
		for ,  := range  {
			if  := .step();  != nil {
				return 
			}
			 := .Clusters[]
			if  == nil {
				return invariant.New("nil cluster while crosshatching routes")
			}
			,  := externalEdgesForClusterGuarded(, )
			if  != nil {
				return 
			}
			if len() < 2 {
				continue
			}

			,  := groupEdgesByClusterPortGuarded(, , )
			if  != nil {
				return 
			}
			 := make([]geo.Point, 0, len())
			for  := range  {
				if  := .step();  != nil {
					return 
				}
				 = append(, )
			}
			sort.Slice(, func(,  int) bool {
				if [].X != [].X {
					return [].X < [].X
				}
				return [].Y < [].Y
			})
			for ,  := range  {
				 := []
				if len() < 2 {
					continue
				}
				for ,  := range  {
					if  := convertToStraightLineGuarded(, , );  != nil {
						return 
					}
				}
			}
		}
		return nil
	})
}

func externalEdgesForClusterGuarded( *layoutgraph.Cluster,  *routeWorkGuard) ([]*layoutgraph.Edge, error) {
	var  []*layoutgraph.Edge
	for ,  := range .EdgeAbductions {
		if  := .step();  != nil {
			return nil, 
		}
		if  == nil || .Edge == nil {
			return nil, invariant.New("nil cluster edge abduction while crosshatching routes")
		}
		 = append(, .Edge)
	}
	return , nil
}

func groupEdgesByClusterPortGuarded( *layoutgraph.Cluster,  []*layoutgraph.Edge,  *routeWorkGuard) (map[geo.Point][]*layoutgraph.Edge, error) {
	 := make(map[geo.Point][]*layoutgraph.Edge)

	for ,  := range  {
		if  := .step();  != nil {
			return nil, 
		}
		if  == nil {
			return nil, invariant.New("nil edge while grouping cluster ports")
		}
		// Find which cluster node this edge connects to and get the port
		var  *geo.Point
		for ,  := range .Nodes {
			if  := .step();  != nil {
				return nil, 
			}
			if .From ==  {
				 = .SourcePort()
				break
			} else if .To ==  {
				 = .TargetPort()
				break
			}
		}

		if  != nil {
			[*] = append([*], )
		}
	}

	return , nil
}

func convertToStraightLineGuarded( *layoutgraph.Graph,  *layoutgraph.Edge,  *routeWorkGuard) error {
	if  := .step();  != nil {
		return 
	}
	if  == nil || .From == nil || .To == nil || .From.TopLeft == nil || .To.TopLeft == nil {
		return invariant.New("invalid edge while crosshatching routes")
	}
	 := &geo.Point{
		X: .From.TopLeft.X + .From.Width/2,
		Y: .From.TopLeft.Y + .From.Height/2,
	}
	 := &geo.Point{
		X: .To.TopLeft.X + .To.Width/2,
		Y: .To.TopLeft.Y + .To.Height/2,
	}

	 := findLineBorderIntersection(, , .From)
	 := findLineBorderIntersection(, , .To)

	if  != nil &&  != nil {
		,  := straightLineIntersectsNonAncestorNodesGuarded(, , , , )
		if  != nil {
			return 
		}
		if  {
			return nil
		}
		if  := .finish();  != nil {
			return 
		}
		.Points = []*geo.Point{, }
		if  := .finish();  != nil {
			return 
		}
	}
	return nil
}

// findLineBorderIntersection finds where a line from nodeCenter towards targetPoint intersects the node's border
func findLineBorderIntersection(,  *geo.Point,  *layoutgraph.Node) *geo.Point {
	// Calculate direction vector
	 := .X - .X
	 := .Y - .Y

	if  == 0 &&  == 0 {
		return 
	}

	// Node boundaries
	 := .TopLeft.X
	 := .TopLeft.X + .Width
	 := .TopLeft.Y
	 := .TopLeft.Y + .Height

	var  float64 = math.Inf(1)

	// Check intersection with each edge of the rectangle
	if  > 0 {
		// Right edge intersection
		 = math.Min(, (-.X)/)
	} else if  < 0 {
		// Left edge intersection
		 = math.Min(, (-.X)/)
	}

	if  > 0 {
		// Bottom edge intersection
		 = math.Min(, (-.Y)/)
	} else if  < 0 {
		// Top edge intersection
		 = math.Min(, (-.Y)/)
	}

	if  == math.Inf(1) ||  <= 0 {
		return 
	}

	return &geo.Point{
		X: .X + *,
		Y: .Y + *,
	}
}

// straightLineIntersectsNonAncestorNodes checks if a straight line between two points
// intersects any nodes that are not ancestors of the edge's endpoints
func straightLineIntersectsNonAncestorNodesGuarded( *layoutgraph.Graph,  *layoutgraph.Edge, ,  *geo.Point,  *routeWorkGuard) (bool, error) {
	// Check intersection with all nodes in the graph
	for ,  := range .Nodes {
		if  := .step();  != nil {
			return false, 
		}
		// Skip if this node is an ancestor of either endpoint
		,  := isDescendantOfWithRouteGuard(.From, , )
		if  != nil {
			return false, 
		}
		,  := isDescendantOfWithRouteGuard(.To, , )
		if  != nil {
			return false, 
		}
		if  ||  {
			continue
		}

		// Skip if this node is one of the endpoints
		if  == .From ||  == .To {
			continue
		}

		// Check if the straight line passes through this node
		if .PassesThrough(, ) {
			return true, nil
		}
	}

	return false, nil
}