package routing

import (
	
	
	

	
	
	
	
	
)

const (
	maxShortcutInput = 256
	maxShortcutRoute = 32
	shortcutEpsilon  = 1e-6
)

// ShortcutEdgeRoutes is a bounded bend-reduction postpass with fixed boxes and
// ports. WueOrtho's routing step chooses bend-minimal shortest paths
// (https://arxiv.org/html/2309.01671v2#S2), motivating this local adaptation:
// replace a subchain by either rectilinear L, retaining only strict bend wins.
// It is not a shortest-path solver and does not change the routing grid.
func ( context.Context,  *layoutgraph.Graph) error {
	 := shortcutRoutesWithLimit(, , maxRouteStageWorkUnits)
	if errors.Is(, errRouteStageWorkLimit) {
		return nil // The atomic stage has already restored the entire drawing.
	}
	return 
}

func shortcutInputTooLarge( *layoutgraph.Graph) bool {
	if  == nil {
		return false
	}
	if len(.Nodes) > maxShortcutInput || len(.Edges) > maxShortcutInput {
		return true
	}
	 := maxShortcutInput
	for ,  := range .Edges {
		if  != nil {
			if len(.Points) >  {
				return true
			}
			 -= len(.Points)
		}
	}
	return false
}

func shortcutRoutesWithLimit( context.Context,  *layoutgraph.Graph,  uint64) error {
	if  != nil &&  != nil {
		if  := .Err();  != nil {
			return 
		}
		if shortcutInputTooLarge() {
			// Oversized inputs are optional no-ops, before validation/snapshot
			// allocation. Malformed admitted inputs retain stage validation.
			return .Err()
		}
	}
	return runAtomicRouteStage(, "ShortcutEdgeRoutes", , nil, , func( *routeWorkGuard) error {
		return shortcutRoutesGuarded(, )
	})
}

func shortcutRoutesGuarded( *layoutgraph.Graph,  *routeWorkGuard) error {
	var  []geo.Box
	 := false
	 := false
	for ,  := range .Edges {
		if  := .step();  != nil {
			return 
		}
		,  := .NodeToTree[.From]
		,  := .NodeToTree[.To]
		if .IsInvisible || .IsCurve || .IsLoop() ||  ||  ||
			.Label != nil || .SourceArrowheadLabel != nil || .TargetArrowheadLabel != nil ||
			len(.Points) < 5 || len(.Points) > maxShortcutRoute || !shortcutOrthogonal(.Points) {
			continue
		}
		// A crossing or contact with a curved route cannot be certified from
		// its control polygon. Leave these drawings to the existing router.
		 := false
		for ,  := range .Edges {
			if  := .step();  != nil {
				return 
			}
			 =  || .IsCurve || !shortcutOrthogonal(.Points)
		}
		if  {
			return nil
		}
		if ! {
			var  error
			,  = shortcutLabelBoxes(, )
			if  != nil {
				return 
			}
			 = true
		}
		 := .Points
		 := shortcutLength()
		,  := shortcutBends(), shortcutBends()
		for  := 0; +3 < len(.Points); ++ {
			for  :=  + 3;  < len(.Points); ++ {
				for ,  := range []bool{true, false} {
					if  := .add(uint64(len(.Points)));  != nil {
						return 
					}
					 := shortcutCandidate(.Points, , , )
					 := shortcutLength()
					 := shortcutBends()
					if  >=  ||  >  ||
						 > shortcutLength(.Points)+shortcutEpsilon ||
						( ==  &&  >= -shortcutEpsilon) {
						continue
					}
					,  := shortcutCandidateSafe(, , , , )
					if  != nil {
						return 
					}
					if  {
						, ,  = , , 
					}
				}
			}
		}
		if  ==  {
			continue
		}
		if ! {
			// Direct graph slices define the modeled obstacles. Route-stage
			// ownership may reach other nodes/edges, so conservatively decline
			// these inputs before committing any uninspected geometry.
			,  := captureRouteMutations(, nil, )
			if  != nil {
				return 
			}
			,  := channelInventoryIsClosed(, , )
			if  != nil {
				return 
			}
			if ! {
				return nil
			}
			 = true
		}
		// The local guards are per node/label/edge pair; inspection is a final
		// aggregate backstop, paid only for a geometrically viable shortcut.
		,  := quality.Inspect(.ctx, )
		if  != nil {
			return 
		}
		 := .Points
		.Points = 
		,  := quality.Inspect(.ctx, )
		if  != nil {
			return 
		}
		if .RouteObstructions > .RouteObstructions || .Crossings > .Crossings ||
			.TextOcclusions > .TextOcclusions || .RouteLength > .RouteLength+shortcutEpsilon {
			.Points = 
		}
	}
	return nil
}

func shortcutOrthogonal( []*geo.Point) bool {
	for ,  := range  {
		if  == nil || math.IsNaN(.X) || math.IsNaN(.Y) || math.IsInf(.X, 0) || math.IsInf(.Y, 0) {
			return false
		}
		if  > 0 && (* == *[-1] || (.X != [-1].X && .Y != [-1].Y)) {
			return false
		}
		if  > 1 && !sameRouteDirection([-2], [-1], [-1], ) &&
			([-2].X == .X || [-2].Y == .Y) {
			return false // A U-turn is not a harmless collinear vertex.
		}
	}
	return len() >= 2
}

func shortcutLength( []*geo.Point) float64 {
	 := 0.0
	for  := 1;  < len(); ++ {
		 += math.Abs([].X-[-1].X) + math.Abs([].Y-[-1].Y)
	}
	return 
}

func shortcutBends( []*geo.Point) int {
	 := 0
	for  := 2;  < len(); ++ {
		, ,  := [-2], [-1], []
		if (.X-.X)*(.Y-.Y) != (.Y-.Y)*(.X-.X) {
			++
		}
	}
	return 
}

func shortcutCandidate( []*geo.Point, ,  int,  bool) []*geo.Point {
	 := geo.NewPoint([].X, [].Y)
	if ! {
		 = geo.NewPoint([].X, [].Y)
	}
	 := append([]*geo.Point(nil), [:+1]...)
	 = append(, )
	 = append(, [:]...)
	 := make([]*geo.Point, 0, len())
	for ,  := range  {
		if len() > 0 && *[len()-1] == * {
			continue
		}
		for len() >= 2 && sameRouteDirection([len()-2], [len()-1], [len()-1], ) {
			 = [:len()-1]
		}
		 = append(, )
	}
	return 
}

func shortcutCandidateSafe( *layoutgraph.Graph,  *layoutgraph.Edge,  []*geo.Point,  []geo.Box,  *routeWorkGuard) (bool, error) {
	if !shortcutOrthogonal() || shortcutBends() >= shortcutBends(.Points) || shortcutLength() > shortcutLength(.Points)+shortcutEpsilon {
		return false, nil
	}
	 := *
	.Points = 
	,  := changedRouteIsClear(, &, .Points, true, )
	if  != nil || ! {
		return , 
	}
	for  := 1;  < len(); ++ {
		,  := [-1], []
		 := math.Abs(.X-.X) + math.Abs(.Y-.Y)
		 := segmentSpacingBuffer
		 := false
		for  := 1;  < len(.Points); ++ {
			if  := .step();  != nil {
				return false, 
			}
			 =  || (* == *.Points[-1] && * == *.Points[])
		}
		if  == 1 {
			 = math.Min(, shortcutLength(.Points[:2]))
		}
		if  == len()-1 {
			 = math.Min(, shortcutLength(.Points[len(.Points)-2:]))
		}
		if ! && +shortcutEpsilon <  {
			return false, nil
		}
		for  :=  + 2;  < len(); ++ {
			if  := .step();  != nil {
				return false, 
			}
			if shortcutSegmentsMeet(, , [-1], []) {
				return false, nil
			}
		}
		if  {
			continue
		}
		for ,  := range  {
			if  := .step();  != nil {
				return false, 
			}
			if orthogonalSegmentEntersNode(&layoutgraph.Node{Box: }, , ) {
				return false, nil
			}
		}
		,  := shortcutWallClearance(, .Points, , , )
		if  != nil || ! {
			return , 
		}
		,  = shortcutParallelClearance(, , , , )
		if  != nil || ! {
			return , 
		}
	}
	for ,  := range .Edges {
		if  ==  {
			continue
		}
		,  := shortcutContactsPreserved(.Points, , .Points, )
		if  != nil || ! {
			return , 
		}
	}
	return true, nil
}

// Compare actual intersection geometry rather than segment indices: reducing
// bends renumbers segments. New point contacts must already lie on the old
// route; each new shared interval must be covered by old collinear geometry.
func shortcutContactsPreserved(, ,  []*geo.Point,  *routeWorkGuard) (bool, error) {
	for  := 1;  < len(); ++ {
		for  := 1;  < len(); ++ {
			if  := .step();  != nil {
				return false, 
			}
			, , ,  := [-1], [], [-1], []
			if !shortcutSegmentsMeet(, , , ) {
				continue
			}
			if (.X == .X) != (.X == .X) {
				 := geo.NewPoint(.X, .Y)
				if .Y == .Y {
					 = geo.NewPoint(.X, .Y)
				}
				 := false
				for  := 1;  < len(); ++ {
					if  := .step();  != nil {
						return false, 
					}
					 =  || shortcutPointOnSegment(, [-1], [])
				}
				, ,  := shortcutThroughRays(, , )
				if  != nil {
					return false, 
				}
				, ,  := shortcutThroughRays(, , )
				if  != nil {
					return false, 
				}
				, ,  := shortcutThroughRays(, , )
				if  != nil {
					return false, 
				}
				 := ( && ) || ( && )
				 := ( && ) || ( && )
				if ! || ( && !) {
					return false, nil
				}
				continue
			}
			 := .Y == .Y
			 := .X
			if  {
				 = .Y
			}
			 := math.Max(math.Min(channelCoordinate(, ), channelCoordinate(, )), math.Min(channelCoordinate(, ), channelCoordinate(, )))
			 := math.Min(math.Max(channelCoordinate(, ), channelCoordinate(, )), math.Max(channelCoordinate(, ), channelCoordinate(, )))
			// Extend covered prefix without sorting or altering the old route.
			 := 
			for {
				,  := , false
				for  := 1;  < len(); ++ {
					if  := .step();  != nil {
						return false, 
					}
					,  := [-1], []
					if ( && (.Y !=  || .Y != )) || (! && (.X !=  || .X != )) {
						continue
					}
					,  := math.Min(channelCoordinate(, ), channelCoordinate(, )), math.Max(channelCoordinate(, ), channelCoordinate(, ))
					if  <= +shortcutEpsilon &&  >= -shortcutEpsilon {
						 = true
						 = math.Max(, )
					}
				}
				if  &&  >= -shortcutEpsilon {
					break
				}
				if  <= +shortcutEpsilon {
					return false, nil
				}
				 = 
			}
		}
	}
	return true, nil
}

func shortcutPointOnSegment(, ,  *geo.Point) bool {
	return ((.X == .X && .X == .X) || (.Y == .Y && .Y == .Y)) &&
		.X >= math.Min(.X, .X)-shortcutEpsilon && .X <= math.Max(.X, .X)+shortcutEpsilon &&
		.Y >= math.Min(.Y, .Y)-shortcutEpsilon && .Y <= math.Max(.Y, .Y)+shortcutEpsilon
}

// For orthogonal segments, overlapping closed bounding boxes mean contact.
// Unlike line intersection, this also includes positive collinear overlaps.
func shortcutSegmentsMeet(, , ,  *geo.Point) bool {
	return math.Max(math.Min(.X, .X), math.Min(.X, .X)) <= math.Min(math.Max(.X, .X), math.Max(.X, .X)) &&
		math.Max(math.Min(.Y, .Y), math.Min(.Y, .Y)) <= math.Min(math.Max(.Y, .Y), math.Max(.Y, .Y))
}

// Classify the whole route at a contact. Joining the two rays is equivalent
// to removing harmless collinear subdivisions, without touching source slices.
// Segment-local endpoint tests miss a through-crossing at such a subdivision.
func shortcutThroughRays( []*geo.Point,  *geo.Point,  *routeWorkGuard) (bool, bool, error) {
	, , ,  := false, false, false, false
	for  := 1;  < len(); ++ {
		if  := .step();  != nil {
			return false, false, 
		}
		,  := [-1], []
		if !shortcutPointOnSegment(, , ) {
			continue
		}
		if .Y == .Y {
			 =  || math.Min(.X, .X) < .X
			 =  || math.Max(.X, .X) > .X
		} else {
			 =  || math.Min(.Y, .Y) < .Y
			 =  || math.Max(.Y, .Y) > .Y
		}
	}
	return  && ,  && , nil
}

// A new parallel leg must retain min(previous gap,40) to every projected node
// wall, including container walls. With no old parallel leg on that side of
// the wall, require40. Existing short port approaches are handled separately.
func shortcutWallClearance( *layoutgraph.Graph,  []*geo.Point, ,  *geo.Point,  *routeWorkGuard) (bool, error) {
	 := .Y == .Y
	 := channelCoordinate(, !)
	,  := math.Min(channelCoordinate(, ), channelCoordinate(, )), math.Max(channelCoordinate(, ), channelCoordinate(, ))
	for ,  := range .Nodes {
		if  := .step();  != nil {
			return false, 
		}
		 := channelCoordinate(.TopLeft, )
		,  := +.Height, .TopLeft.X
		 := [2]float64{,  + .Width}
		if  {
			,  = +.Width, .TopLeft.Y
			 = [2]float64{,  + .Height}
		}
		if math.Min(, )-math.Max(, ) <= shortcutEpsilon {
			continue
		}
		for ,  := range  {
			 := math.Abs( - )
			if  >= segmentSpacingBuffer-shortcutEpsilon {
				continue
			}
			 := segmentSpacingBuffer
			for  := 1;  < len(); ++ {
				if  := .step();  != nil {
					return false, 
				}
				,  := [-1], []
				 := channelCoordinate(, !)
				if  != channelCoordinate(, !) || (-)*(-) < 0 {
					continue
				}
				,  := math.Min(channelCoordinate(, ), channelCoordinate(, )), math.Max(channelCoordinate(, ), channelCoordinate(, ))
				if math.Min(, )-math.Max(, ) > shortcutEpsilon {
					 = math.Min(, math.Abs(-))
				}
			}
			if +shortcutEpsilon <  {
				return false, nil
			}
		}
	}
	return true, nil
}

// Preserve the channel pass's readable spacing. For each projected parallel
// segment of another route, retain min(previous positive gap,40); if there was
// no positive parallel gap, require40. Exact shared geometry is certified by
// shortcutContactsPreserved and is not used to justify a new narrow corridor.
func shortcutParallelClearance( *layoutgraph.Graph,  *layoutgraph.Edge, ,  *geo.Point,  *routeWorkGuard) (bool, error) {
	 := .Y == .Y
	 := channelCoordinate(, !)
	,  := math.Min(channelCoordinate(, ), channelCoordinate(, )), math.Max(channelCoordinate(, ), channelCoordinate(, ))
	for ,  := range .Edges {
		if  ==  {
			continue
		}
		for  := 1;  < len(.Points); ++ {
			if  := .step();  != nil {
				return false, 
			}
			,  := .Points[-1], .Points[]
			 := channelCoordinate(, !)
			if  != channelCoordinate(, !) {
				continue
			}
			,  := math.Min(channelCoordinate(, ), channelCoordinate(, )), math.Max(channelCoordinate(, ), channelCoordinate(, ))
			if math.Min(, )-math.Max(, ) <= shortcutEpsilon {
				continue
			}
			 := math.Abs( - )
			if  <= shortcutEpsilon ||  >= segmentSpacingBuffer-shortcutEpsilon {
				continue
			}
			 := segmentSpacingBuffer
			for  := 1;  < len(.Points); ++ {
				if  := .step();  != nil {
					return false, 
				}
				,  := .Points[-1], .Points[]
				 := channelCoordinate(, !)
				if  != channelCoordinate(, !) {
					continue
				}
				,  := math.Min(channelCoordinate(, ), channelCoordinate(, )), math.Max(channelCoordinate(, ), channelCoordinate(, ))
				 := math.Abs( - )
				if  > shortcutEpsilon && math.Min(, )-math.Max(, ) > shortcutEpsilon {
					 = math.Min(, )
				}
			}
			if +shortcutEpsilon <  {
				return false, nil
			}
		}
	}
	return true, nil
}

func shortcutLabelBoxes( *layoutgraph.Graph,  *routeWorkGuard) ([]geo.Box, error) {
	var  []geo.Box
	for ,  := range .Nodes {
		if  := .add(uint64(1 + 4*len(.Nodes)));  != nil {
			return nil, 
		}
		if .IsInvisible {
			continue
		}
		if .Label != nil && .Label.Position != label.Unset {
			 = append(, geo.Box{TopLeft: .LabelTopLeft(.Label.Position, .Label.Width, .Label.Height), Width: .Label.Width, Height: .Label.Height})
		}
		if .Icon != nil && !.IsImage() {
			 := .IconSize(.Icon.Position)
			 = append(, geo.Box{TopLeft: .LabelTopLeft(.Icon.Position, , ), Width: , Height: })
		}
	}
	for ,  := range .Edges {
		if  := .add(uint64(1 + 3*len(.Points)));  != nil {
			return nil, 
		}
		if .IsInvisible || len(.Points) < 2 {
			continue
		}
		if .Label != nil && .Label.Position != label.Unset {
			 = append(, geo.Box{TopLeft: .LabelTopLeft(.Label.Position, .Label.Width, .Label.Height), Width: .Label.Width, Height: .Label.Height})
		}
		if .SourceArrowheadLabel != nil {
			 = append(, labeling.PositionArrowheadLabel(, false, .Points).Box)
		}
		if .TargetArrowheadLabel != nil {
			 = append(, labeling.PositionArrowheadLabel(, true, .Points).Box)
		}
	}
	return , nil
}