package packing

import (
	
	
	

	
	
	
	

	
)

const (
	subgraphPadding        = 20.0
	subgraphSquareDampener = 0.5
)

// Pack recursively arranges disconnected subgraphs into compact containers.
// Containers may shrink but never grow, and packing may interleave subgraph
// bounding boxes while preserving edge lengths. Hierarchy subgraphs remain
// separate to avoid obstructing routes.
func ( context.Context,  *layoutgraph.Graph,  *layoutgraph.Node) error {
	,  := newWorkGuard(, limits.MaxBinPackWorkUnits)
	if  != nil {
		return 
	}
	return packAtomic(, , , )
}

func packAtomic( context.Context,  *layoutgraph.Graph,  *layoutgraph.Node,  *limits.WorkGuard) ( error) {
	if  == nil {
		return fmt.Errorf("TALA BinPack requires a work guard")
	}
	 = layoutgraph.ContextWithTransactionWorkGuard(, )
	 := layoutgraph.NewGraphStateSnapshot(layoutgraph.GraphStateSnapshotOptions{
		CaptureEdgeRoutes: true,
	})
	if  := .UpdateWithWorkGuard(, );  != nil {
		return 
	}
	 := &layoutgraph.Transaction{Graph: , PriorGraphState: }
	 := false
	defer func() {
		if  := recover();  != nil {
			.Rollback()
			panic()
		}
		if ! {
			.Rollback()
		}
	}()
	if  := packGuarded(, , , );  != nil {
		return 
	}
	if  := .Finish();  != nil {
		return 
	}
	 = true
	return nil
}

func packGuarded( context.Context,  *layoutgraph.Graph,  *layoutgraph.Node,  *limits.WorkGuard) error {
	 := func() error {
		return .Step()
	}
	if  := ();  != nil {
		return 
	}
	, ,  := allEdgesHaveCompleteRoutesGuarded(, , )
	if  != nil {
		return 
	}

	// Pack more nested before less nested
	for ,  := range .Containers[] {
		if  := ();  != nil {
			return 
		}
		if .IsContainer() {
			if  := (, , , );  != nil {
				return 
			}
		}
	}
	// For this container level, split subgraphs into 3 categories:
	// 0. Fixed nodes and their subgraphs cannot move
	// 1. Cross-container subgraphs (have edges to another container) cannot move
	// 2. Contained subgraphs can move
	var  []layoutgraph.Nodes
	var  []layoutgraph.Nodes
	 := make(map[*layoutgraph.Node]struct{})

	// we create the fixedNodesSubgraph first to ensure no subgraph with a fixed node is packed
	// for simplicity we add all nodes to the fixedNodesSubgraph list instead of creating one for each fixed subgraph
	var  layoutgraph.Nodes
	for ,  := range .Containers[] {
		if  := ();  != nil {
			return 
		}
		if .FixedTopLeft == nil {
			continue
		}

		,  := .AllReachableNodesContext(true, true, false, map[*layoutgraph.Node]struct{}{: {}}, )
		if  != nil {
			return 
		}
		for ,  := range  {
			if  := .Step();  != nil {
				return 
			}
			if ,  := []; ! {
				 = append(, )
			}
			[] = struct{}{}
		}
	}

	var  error
	 := func( *layoutgraph.Node) bool { return true }
	if  != nil {
		 = func( *layoutgraph.Node) bool {
			if  != nil {
				return false
			}
			var  bool
			,  = binPackIsDescendantOf(, , )
			return 
		}
	}

	// 1. get subgraphs contained in root
	// 2. get subgraphs not contained in root
	// 3. filter out subgraphs contained in root from subgraphs not contained in root
	 := make(map[*layoutgraph.Node]struct{})
	for ,  := range .Containers[] {
		if  := ();  != nil {
			return 
		}
		if ,  := [];  {
			continue
		}

		,  := .ReachableNodesContext(, true, true, false, map[*layoutgraph.Node]struct{}{: {}}, )
		if  != nil {
			return 
		}
		if  != nil {
			return 
		}
		 := false
		for ,  := range  {
			if  := .Step();  != nil {
				return 
			}
			if .Hierarchy != nil {
				 = true
				break
			}
			,  := binPackHasExternalConnection(, , , false, , )
			if  != nil {
				return 
			}
			if  {
				 = true
				break
			}
			,  := binPackHasExternalConnection(, , , true, false, )
			if  != nil {
				return 
			}
			if  {
				 = true
				break
			}
		}
		if ! {
			for ,  := range  {
				if  := .Step();  != nil {
					return 
				}
				[] = struct{}{}
				[] = struct{}{}
			}
			 = append(, layoutgraph.Nodes())
		}
	}
	for ,  := range .Containers[] {
		if  := ();  != nil {
			return 
		}
		if ,  := [];  {
			continue
		}
		var  []*layoutgraph.Node
		,  := .AllReachableNodesContext(true, false, false, map[*layoutgraph.Node]struct{}{: {}}, )
		if  != nil {
			return 
		}
		for ,  := range  {
			if  := .Step();  != nil {
				return 
			}
			if ,  := [];  {
				continue
			}
			 = append(, )
		}

		for ,  := range  {
			if  := .Step();  != nil {
				return 
			}
			[] = struct{}{}
		}
		 = append(, layoutgraph.Nodes())
	}

	if len() == 0 {
		if  := ();  != nil {
			return 
		}
		return nil
	}

	 := 
	 := 

	if len() > 0 {
		 = append(, )
	}

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

	,  := binPackScoreGuarded(layoutgraph.Nodes(.Containers[]), , )
	if  != nil {
		return 
	}
	 := make(map[*layoutgraph.Node]pointerSnapshot[geo.Point])
	var  []*layoutgraph.Node
	 := make(map[*layoutgraph.Edge][]*geo.Point)
	var  []*layoutgraph.Edge
	 := func() error {
		return ()
	}

	// Move out of the way
	for ,  := range  {
		if  := .Step();  != nil {
			return 
		}
		for ,  := range  {
			if  := .Step();  != nil {
				return 
			}
			if ,  := []; ! {
				[] = snapshotPointer(.TopLeft)
				 = append(, )
			}
			.Translate(100000, 100000)
			if  {
				for ,  := range .Edges {
					if  := .Step();  != nil {
						return 
					}
					if ,  := [];  {
						continue
					}
					 := []*geo.Point{}
					for ,  := range .Points {
						if  := .Step();  != nil {
							return 
						}
						 = append(, .Copy())
					}
					[] = 
					 = append(, )
				}
			}
		}
	}

	if len() == 0 {
		, ,  := graphbounds.BoundingBox([0], )
		if  != nil {
			return 
		}
		 := .X - .X
		 := .Y - .Y
		for ,  := range [0] {
			if  := .Step();  != nil {
				return 
			}
			.Translate(, )
		}
		if  {
			 := make(map[*layoutgraph.Edge]struct{})
			for ,  := range [0] {
				if  := .Step();  != nil {
					return 
				}
				for ,  := range .Edges {
					if  := .Step();  != nil {
						return 
					}
					if ,  := [];  {
						continue
					}
					[] = struct{}{}
					for ,  := range .Points {
						if  := .Step();  != nil {
							return 
						}
						.X += (.TopLeft.X - [].value.X)
						.Y += (.TopLeft.Y - [].value.Y)
					}
				}
			}
		}
		 = []layoutgraph.Nodes{[0]}
		 = [1:]
	}
	if  := ();  != nil {
		return 
	}

	var  layoutgraph.Nodes
	var  []*geo.Segment
	// If edges are already placed, use their actual routes
	// Otherwise consider multiple possible good routes
	if  {
		var  []*layoutgraph.Edge
		 := make(map[*layoutgraph.Edge]struct{})
		for ,  := range  {
			if  := .Step();  != nil {
				return 
			}
			for ,  := range  {
				if  := .Step();  != nil {
					return 
				}
				if  ==  {
					continue
				}
				,  := binPackIsDescendantOf(, , )
				if  != nil {
					return 
				}
				if ! {
					continue
				}
				for ,  := range .Edges {
					if  := .Step();  != nil {
						return 
					}
					if ,  := []; ! {
						[] = struct{}{}
						 = append(, )
					}
				}

			}
		}
		,  := routedEdgeSegments(layoutgraph.Edges(), true, )
		if  != nil {
			return 
		}
		,  := routedEdgeSegments(layoutgraph.Edges(), false, )
		if  != nil {
			return 
		}
		for ,  := range  {
			if  := .Step();  != nil {
				return 
			}
			 = append(, &.Segment)
		}
		for ,  := range  {
			if  := .Step();  != nil {
				return 
			}
			 = append(, &.Segment)
		}
		for ,  := range  {
			if  := .Step();  != nil {
				return 
			}
			for ,  := range  {
				if  := .Step();  != nil {
					return 
				}
				if .Container ==  {
					 = append(, )
				}
			}
		}
	} else {
		 = make([]*geo.Segment, 0, len([0])*10)
		for ,  := range  {
			if  := .Step();  != nil {
				return 
			}
			for ,  := range  {
				if  := .Step();  != nil {
					return 
				}
				if .Container ==  {
					 = append(, )
				}
			}
			, ,  := binPackSmallestDeltas(, )
			if  != nil {
				return 
			}

			// We only care about segments of packed nodes that are descendants of root
			// There's no chance of collision of a subgraph with a more nested node or less nested node
			var  layoutgraph.Nodes
			for ,  := range  {
				if  := .Step();  != nil {
					return 
				}
				if  ==  {
					continue
				}
				,  := binPackIsDescendantOf(, , )
				if  != nil {
					return 
				}
				if  {
					 = append(, )
				}
			}
			,  := estimateRouteSegments(, , , , )
			if  != nil {
				return 
			}
			for ,  := range  {
				if  := .Step();  != nil {
					return 
				}
				 = append(, )
			}
		}
	}

	 := make(map[geo.Point]struct{})

	 := true
	 := true
	 := true
	 := true

	if  &&  != nil {
		for ,  := range .Edges {
			if  := .Step();  != nil {
				return 
			}
			var  *geo.Segment
			if .From ==  {
				 = geo.NewSegment(.Points[0], .Points[1])
			} else {
				 = geo.NewSegment(.Points[len(.Points)-1], .Points[len(.Points)-2])
			}
			if .Start.Y == .End.Y {
				if .Start.X < .End.X {
					 = false
				}
				if .Start.X > .End.X {
					 = false
				}
			} else if .Start.X == .End.X {
				if .Start.Y < .End.Y {
					 = false
				}
				if .Start.Y > .End.Y {
					 = false
				}
			} else {
				// It's a diagonal line, can't risk any movement
				 = false
				 = false
				 = false
				 = false
				break
			}
		}
	}

	 := false
	// place greedily
	// TODO perhaps there's better algorithms here
	// Maybe we can multithread race some random orders and get the best scored one
	for len() > 0 {
		if  := ();  != nil {
			return 
		}
		var  []*geo.Point
		 := make(map[geo.Point]struct{})
		,  := binPackHierarchyBoxes(, )
		if  != nil {
			return 
		}

		 := geo.NewPoint(.X, .Y)
		if ,  := [*]; ! {
			 = append(, )
			[*] = struct{}{}
		}

		 := math.Inf(1)
		 := math.Inf(1)
		for ,  := range  {
			if  := .Step();  != nil {
				return 
			}
			, ,  := graphbounds.BoundingBox(, )
			if  != nil {
				return 
			}
			 = math.Min(, .X)
			 = math.Min(, .Y)
			 := [0].Hierarchy == nil
			,  := placementCandidatesGuarded(, , , , )
			if  != nil {
				return 
			}
			for ,  := range  {
				if  := .Step();  != nil {
					return 
				}
				if ,  := [*];  {
					continue
				}
				,  := binPackPointInHierarchy(, , )
				if  != nil {
					return 
				}
				if  {
					continue
				}
				if ,  := [*]; ! {
					 = append(, )
					[*] = struct{}{}
				}
			}
		}

		 := [0]
		 = [1:]
		, ,  := graphbounds.FixedBoundingBox(, )
		if  != nil {
			return 
		}
		 := .X - .X
		 := .Y - .Y

		// TODO ideally the placement candidates should also take into account which direction it can move
		if  {
			 = append(, geo.NewPoint(, --subgraphPadding))
		}
		if  {
			 = append(, geo.NewPoint(--subgraphPadding, ))
		}
		if  &&  {
			 = append(, geo.NewPoint(--subgraphPadding, --subgraphPadding))
		}

		,  := .NewRequestTransaction(, layoutgraph.TransactionOptions{
			IgnoreContainerEscape: true,
			AffectEdgeRoutes:      ,
		})
		if  != nil {
			return 
		}

		, ,  := graphbounds.BoundingBox(, )
		if  != nil {
			return 
		}
		 := make(layoutgraph.Nodes, 0, len()+len())
		for ,  := range  {
			if  := .Step();  != nil {
				return 
			}
			 = append(, )
		}
		for ,  := range  {
			if  := .Step();  != nil {
				return 
			}
			 = append(, )
		}
		 := math.Inf(1)

		for ,  := range  {
			if  := .Step();  != nil {
				return 
			}
			 := [].value
			 := .Copy()
			if ,  := [*]; ! {
				 = append(, )
				[*] = struct{}{}
			}
		}
		, ,  := binPackSmallestDeltas(, )
		if  != nil {
			return 
		}

		var  *geo.Point
		for ,  := range  {
			if  := ();  != nil {
				return 
			}
			.Clear()
			.AddOp(func() error {
				 := .X - .X
				 := .Y - .Y
				for ,  := range  {
					if  := .Step();  != nil {
						return 
					}
					.Translate(, )
				}
				return nil
			})
			if  := .Commit();  != nil {
				if layoutgraph.IsCandidateRejection() {
					// TODO handle candidate overlap with the root before committing.
					continue
				}
				if  := ();  != nil {
					return 
				}
				return 
			}
			// Not allowed to expand out of containers
			if  != nil {
				var  layoutgraph.Nodes
				for ,  := range  {
					if  := .Step();  != nil {
						.Rollback()
						return 
					}
					if .Container ==  {
						 = append(, )
					}
				}
				, ,  := graphbounds.BoundingBox(, )
				if  != nil {
					.Rollback()
					return 
				}
				,  := .X-.X, .Y-.Y
				 := .InnerBox()
				if  > .Width ||  > .Height {
					.Rollback()
					continue
				}
			}
			if ! {
				// Not allowed for any node to go into the bounding box of a placed hierarchy
				 := false
				for ,  := range  {
					, ,  := graphbounds.NodeBoundingBox(, nil, )
					if  != nil {
						.Rollback()
						return 
					}
					,  := binPackPointInHierarchy(, , )
					if  != nil {
						.Rollback()
						return 
					}
					,  := binPackPointInHierarchy(, , )
					if  != nil {
						.Rollback()
						return 
					}
					if  ||  {
						 = true
						break
					}
				}
				if  {
					.Rollback()
					continue
				}
			}
			,  := blocksRoutesWithDeltasGuarded(
				, , , , , , ,
			)
			if  != nil {
				.Rollback()
				return 
			}
			if  {
				.Rollback()
				continue
			}
			,  := binPackScoreGuarded(, , )
			if  != nil {
				.Rollback()
				return 
			}
			if  <  {
				 = 
				 = 
			}
			.Rollback()
		}
		if  := ();  != nil {
			return 
		}

		if  == nil {
			 = true
			break
		}

		.Clear()
		.AddOp(func() error {
			 := .X - .X
			 := .Y - .Y
			for ,  := range  {
				if  := .Step();  != nil {
					return 
				}
				.Translate(, )
			}
			if  {
				 := make(map[*layoutgraph.Edge]struct{})
				for ,  := range  {
					if  := .Step();  != nil {
						return 
					}
					for ,  := range .Edges {
						if  := .Step();  != nil {
							return 
						}
						if ,  := [];  {
							continue
						}
						[] = struct{}{}
						for ,  := range .Points {
							if  := .Step();  != nil {
								return 
							}
							.X += (.TopLeft.X - [].value.X)
							.Y += (.TopLeft.Y - [].value.Y)
						}
					}
				}
			}
			return nil
		})
		if  := .Commit();  != nil {
			if  := ();  != nil {
				return 
			}
			return 
		}
		[*] = struct{}{}

		for ,  := range  {
			if  := .Step();  != nil {
				return 
			}
			 = append(, )
		}
		 = append(, )
		, ,  := binPackSmallestDeltas(, )
		if  != nil {
			return 
		}

		if  {
			var  []*layoutgraph.Edge
			 := make(map[*layoutgraph.Edge]struct{})
			for ,  := range  {
				if  := .Step();  != nil {
					return 
				}
				if  ==  {
					continue
				}
				,  := binPackIsDescendantOf(, , )
				if  != nil {
					return 
				}
				if ! {
					continue
				}
				for ,  := range .Edges {
					if  := .Step();  != nil {
						return 
					}
					if ,  := []; ! {
						[] = struct{}{}
						 = append(, )
					}
				}
			}
			,  := routedEdgeSegments(layoutgraph.Edges(), true, )
			if  != nil {
				return 
			}
			,  := routedEdgeSegments(layoutgraph.Edges(), false, )
			if  != nil {
				return 
			}
			for ,  := range  {
				if  := .Step();  != nil {
					return 
				}
				 = append(, &.Segment)
			}
			for ,  := range  {
				if  := .Step();  != nil {
					return 
				}
				 = append(, &.Segment)
			}
		} else {
			var  layoutgraph.Nodes
			for ,  := range  {
				if  := .Step();  != nil {
					return 
				}
				if  ==  {
					continue
				}
				,  := binPackIsDescendantOf(, , )
				if  != nil {
					return 
				}
				if  {
					 = append(, )
				}
			}

			,  := estimateRouteSegments(, , , , )
			if  != nil {
				return 
			}
			for ,  := range  {
				if  := .Step();  != nil {
					return 
				}
				 = append(, )
			}
		}
	}
	if  := ();  != nil {
		return 
	}

	var  float64
	if  {
		 = math.Inf(1)
	} else {
		,  = binPackScoreGuarded(layoutgraph.Nodes(.Containers[]), , )
		if  != nil {
			return 
		}
	}
	if  <  {
		for ,  := range  {
			if  := .Step();  != nil {
				return 
			}
			.TopLeft = [].restore()
		}
		if  := binPackSyncClusters(, );  != nil {
			return 
		}
		if  := binPackSyncSequences(, );  != nil {
			return 
		}
		 = true
	} else if  != nil && .Cluster == nil {
		// record state to revert to if fitting to the children's packed position causes a bad state
		// we need to save positions of packed children not in toPack
		,  := .AllDescendantNodesWithWorkGuard(, true, )
		if  != nil {
			return 
		}
		for ,  := range  {
			if  := .Step();  != nil {
				return 
			}
			if ,  := []; ! {
				[] = snapshotPointer(.TopLeft)
				 = append(, )
			}
		}

		 := .TopLeft.X
		 := .TopLeft.Y
		,  := .Width, .Height
		 := geo.Box{
			TopLeft: geo.NewPoint(, ),
			Width:   ,
			Height:  ,
		}

		if  := binPackWrapChildren(, );  != nil {
			return 
		}
		 := false
		if  {
			,  := binPackCanUseRoutedContainerBox(
				, , &, , ,
			)
			if  != nil {
				return 
			}
			switch  {
			case routedContainerDeferToSideConstraints:
			case routedContainerUseProposedBox:
				 = true
			case routedContainerKeepOriginalBox:
				 = true
				// Unsupported shapes, detached endpoints, and route geometry cut
				// by the proposed shrink retain the exact routed box.
				.TopLeft.X = 
				.TopLeft.Y = 
				.Width = 
				.Height = 
			default:
				return fmt.Errorf("TALA BinPack received an invalid routed-container decision")
			}
		}
		if ! {
			if ! {
				.TopLeft.X = 
			}
			if ! {
				.TopLeft.Y = 
			}
			if ! {
				.Width = 
			}
			if ! {
				.Height = 
			}
			// wrapChildren preserves the aspect ratio of circles and other square
			// shapes. Restoring one route-constrained side independently can break
			// that invariant, so retain the larger constrained dimension on both
			// axes. The child-fit and bad-state checks below still reject an unsafe
			// expansion and restore the complete original box.
			if .AspectRatio1() {
				 := math.Max(.Width, .Height)
				.Width = 
				.Height = 
			}
		}

		 := true
		 := .InnerBox()
		for ,  := range .Containers[] {
			if  := .Step();  != nil {
				return 
			}
			if !layoutgraph.Covers(, &.Box) {
				 = false
				break
			}
		}
		,  := .IsBadStateWithWorkGuard(, nil, false, )
		if  != nil {
			return 
		}
		if ! ||  {
			for ,  := range  {
				if  := .Step();  != nil {
					return 
				}
				.TopLeft = [].restore()
			}
			.TopLeft.X = 
			.TopLeft.Y = 
			.Width = 
			.Height = 
			 = true
		}
	}
	// Transactions don't deal with edge routes, so rollback manually
	if  {
		for ,  := range  {
			if  := .Step();  != nil {
				return 
			}
			 := []
			for ,  := range  {
				if  := .Step();  != nil {
					return 
				}
				.Points[].X = .X
				.Points[].Y = .Y
			}
		}
	}
	return .Finish()
}

// placementCandidates returns the candidate points in the graph that are suitable to pack more nodes/subgraphs at
// if considerWithin is true, candidate points include points inside the bounding box of the graph
// Currently (Sept 24, 2022), that is false during node placement stage and true for the more global packing stage.

// E.g., the "@" are always included
// the "*" is included iff considerWithin is true
// ┌─────────────┬───┬─*─────┐ @
// │             │   │       │
// │             └───┘ *     │
// ├───┐ *       *           │
// │   │                 ┌───┤ *
// ├───┘ *               │   │
// *                     └───┤ *
// │      ┌───┐ *        *   │
// │      │   │              │
// └──────┴───┴─*────────────┘ @
// @      *
func placementCandidatesGuarded( layoutgraph.Nodes,
	 *layoutgraph.Node,
	,  bool,
	 *limits.WorkGuard,
) ([]*geo.Point, error) {
	var  []*geo.Point
	var  layoutgraph.Nodes
	for ,  := range  {
		if  := .Step();  != nil {
			return nil, 
		}
		// Avoid placement points that'd move into different container
		if .Container !=  {
			continue
		}
		 = append(, )
		if ! {
			continue
		}
		 := subgraphPadding
		if .IsTable() {
			 = layoutgraph.TableNodeGap
		}
		if  {
			 = append(, geo.NewPoint(.TopLeft.X+.Width+, .TopLeft.Y))
			 = append(, geo.NewPoint(.TopLeft.X, .TopLeft.Y+.Height+subgraphPadding))
			 = append(, geo.NewPoint(.TopLeft.X+.Width+, .TopLeft.Y+.Height))
		} else {
			// Avoid placement points that are likely to cause obstructions
			 := false
			 := false
			 := false
			for ,  := range .Edges {
				if  := .Step();  != nil {
					return nil, 
				}
				 := .Adjacent()
				switch .Orientation() {
				case geo.Left:
					 = true
				case geo.Top:
					 = true
				case geo.TopLeft:
					 = true
				}
				if  &&  &&  {
					break
				}
			}

			if ! {
				 = append(, geo.NewPoint(.TopLeft.X+.Width+, .TopLeft.Y))
			}
			if ! {
				 = append(, geo.NewPoint(.TopLeft.X, .TopLeft.Y+.Height+subgraphPadding))
			}
			if ! {
				 = append(, geo.NewPoint(.TopLeft.X+.Width+, .TopLeft.Y+.Height))
			}
		}
	}

	, ,  := graphbounds.FixedBoundingBox(, )
	if  != nil {
		return nil, 
	}

	 = append(, geo.NewPoint(.X, .Y),
		geo.NewPoint(.X, .Y),
		geo.NewPoint(.X, .Y),
		geo.NewPoint(.X+subgraphPadding, .Y),
		geo.NewPoint(.X+subgraphPadding, .Y),
		geo.NewPoint(.X, .Y+subgraphPadding),
	)
	return , .Finish()
}

// blocksRoutesWithDeltasGuarded checks if the given `packing` placement overlaps with some desired routes in `packed`
// or if `packed` nodes overlap with routes in `packing`.
// Desired routes are roughly estimated as L-shaped and S-shaped routes between connected nodes
// For example, we want to avoid bin packing to the positions shown as `#` below
// . ┌─────────┐
// . │         │      #####
// . │         ├──────#####
// . └────┬────┘      #####
// .      │               │
// .      │          ┌────┴────┐
// .      ####       │         │
// .      ####───────┤         │
// .      ####       └─────────┘
// TODO: if top-right and bottom-left are available, it can overlap with one of them as it would be able
// to generate a good route using the remaining one
//
// The function checks one candidate using the gaps for the immutable packed
// set. BinPack evaluates many translations of the same
// packing against that set, so its caller computes these deltas once per set
// instead of rescanning every packed node for every candidate.
func blocksRoutesWithDeltasGuarded(
	 *layoutgraph.Graph,
	 layoutgraph.Nodes,
	 []layoutgraph.Nodes,
	 []*geo.Segment,
	,  float64,
	 *limits.WorkGuard,
) (bool, error) {
	if  == nil {
		return false, fmt.Errorf("TALA BinPack route check requires a work guard")
	}
	,  := estimateRouteSegments(, , , , )
	if  != nil {
		return false, 
	}

	// check if `packed` nodes could block routes routes in `packing`
	for ,  := range  {
		if  := .Step();  != nil {
			return false, 
		}
		for ,  := range  {
			if  := .Step();  != nil {
				return false, 
			}
			for ,  := range  {
				if  := .Step();  != nil {
					return false, 
				}
				if .OverlapsLine(.Start, .End, layoutgraph.NodeGap) {
					if  := .Finish();  != nil {
						return false, 
					}
					return true, nil
				}
			}
		}
	}
	// check if `packing` nodes could block routes in `packed`
	for ,  := range  {
		if  := .Step();  != nil {
			return false, 
		}
		for ,  := range  {
			if  := .Step();  != nil {
				return false, 
			}
			if .OverlapsLine(.Start, .End, layoutgraph.NodeGap) {
				if  := .Finish();  != nil {
					return false, 
				}
				return true, nil
			}
		}
	}

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

// estimateRouteSegments generates all L-shaped and S-shaped estimates between
// connected nodes, sharing the complete BinPack operation's guard.
func estimateRouteSegments(
	 layoutgraph.Nodes,
	 *layoutgraph.Graph,
	,  float64,
	 *limits.WorkGuard,
) ([]*geo.Segment, error) {
	// just an estimate: 1 edge per node
	 := make([]*geo.Segment, 0, len()*6)
	 := make(map[*layoutgraph.Edge]struct{})
	for ,  := range  {
		if  := .Step();  != nil {
			return nil, 
		}
		for ,  := range .Edges {
			if  := .Step();  != nil {
				return nil, 
			}
			if ,  := [];  {
				continue
			}
			[] = struct{}{}
			,  := estimateEdgeSegments(, , , , )
			if  != nil {
				return nil, 
			}
			 = append(, ...)
		}
	}
	return , .Finish()
}

func routedEdgeSegments( layoutgraph.Edges,  bool,  *limits.WorkGuard) ([]*layoutgraph.EdgeSegment, error) {
	 := make([]*layoutgraph.EdgeSegment, 0)
	for ,  := range  {
		if  := .Step();  != nil {
			return nil, 
		}
		if  == nil {
			return nil, invariant.New("BinPack routed segment scan encountered a nil edge")
		}
		for  := 0;  < len(.Points)-1; ++ {
			if  := .Step();  != nil {
				return nil, 
			}
			,  := .Points[], .Points[+1]
			if  == nil ||  == nil {
				return nil, invariant.New("BinPack routed segment scan encountered a nil point")
			}
			var ,  *geo.Point
			if  {
				if .Y == .Y {
					if .X < .X {
						,  = , 
					} else {
						,  = , 
					}
				}
			} else if .X == .X {
				if .Y < .Y {
					,  = , 
				} else {
					,  = , 
				}
			}
			if  != nil &&  != nil {
				 = append(, layoutgraph.NewEdgeSegment(, , ))
			}
		}
	}
	return , .Finish()
}

// estimateEdgeSegments generates horizontal and vertical segments for the given edge.
// It only generates the segments for diagonal edges, as these are the ones that can have S/L-shaped routes.
// There's an exception for cluster vessels, in which is is allowed to be vertical or horizontal.
// . ┌──────────┐
// . │          ├───────────1──────────────┐
// . │          │                          │
// . │          ├─────8───┐                │
// . └───┬───┬──┘         │                2
// .     │   5            │                │
// .     3   │            9                │
// .     │   └─────6──────┼───────────┐    │
// .     │                │           7    │
// .     │                │        ┌──┴────┴────┐
// .     │                │        │            │
// .     │                └──10────┤            │
// .     └───4─────────────────────┤            │
// .                               └────────────┘
// Didn't want to revise the ascii art, but we don't need 8, 10, 5, and 7, they overlap with others
func estimateEdgeSegments( *layoutgraph.Edge,  *layoutgraph.Graph, ,  float64,  *limits.WorkGuard) ([]*geo.Segment, error) {
	if  := .Step();  != nil {
		return nil, 
	}
	if  == nil || .From == nil || .To == nil || .From.TopLeft == nil || .To.TopLeft == nil {
		return nil, invariant.New("BinPack segment estimate encountered an incomplete edge")
	}
	 := .From.Orientation(.To)

	// Straight lines only matter if they're long (can potentially fit other subgraphs)
	if !.IsDiagonal() && !.From.IsClusterVessel() && !.To.IsClusterVessel() {
		 := .From.Center()
		 := .To.Center()

		if .IsHorizontal() {
			if math.Abs(.X-.X) >  {
				return []*geo.Segment{geo.NewSegment(, )}, nil
			}
		}

		if .IsVertical() {
			if math.Abs(.Y-.Y) >  {
				return []*geo.Segment{geo.NewSegment(, )}, nil
			}
		}

		return nil, nil
	}

	// for clusters, as they can be properly aligned vertically or horizontally
	// generate the segments using the endpoints depending on the arrangement
	//               ┌───────────┐                     ┌───────────┐
	//               │           │                     │endpoint 1 │
	//               │           │                     └─────┬─────┘
	//               │           │                           │
	//               │           │                           │
	//               │           │                           │
	// ┌───────┐     │           │       ┌───────┐           │
	// │       │     │           │       │       ├───────────┘
	// │  n    ├─────┤ cluster   │       │   n   │
	// │       │     │           │       │       ├───────────┐
	// └───────┘     │           │       └───────┘           │
	//               │           │                           │
	//               │           │                           │
	//               │           │                           │
	//               │           │                           │
	//               │           │                     ┌─────┴─────┐
	//               │           │                     │endpoint 2 │
	//               └───────────┘                     └───────────┘
	 := func( *layoutgraph.Cluster) []*geo.Point {
		 := .Vessel.Center()
		,  := .Vessel.BoundingBox(nil)
		if .Arrangement == layoutgraph.Row {
			return []*geo.Point{geo.NewPoint(.X, .Y), geo.NewPoint(.X, .Y)}
		}
		if .Arrangement == layoutgraph.Column {
			return []*geo.Point{geo.NewPoint(.X, .Y), geo.NewPoint(.X, .Y)}
		}

		return nil
	}
	// for non-cluster nodes, it generates 10 segments
	 := make([]*geo.Segment, 0, 6)
	var  []*geo.Point
	var  []*geo.Point
	if .From.IsClusterVessel() {
		 = (.Clusters[.From])
	} else {
		 = []*geo.Point{.From.Center()}
	}
	if .To.IsClusterVessel() {
		 = (.Clusters[.To])
	} else {
		 = []*geo.Point{.To.Center()}
	}

	for ,  := range  {
		for ,  := range  {
			if  := .Step();  != nil {
				return nil, 
			}
			// L-shaped estimate (segments 1-4 in the method doc above)
			 = append(, geo.NewSegment(, geo.NewPoint(.X, .Y)))
			 = append(, geo.NewSegment(geo.NewPoint(.X, .Y), ))
			 = append(, geo.NewSegment(, geo.NewPoint(.X, .Y)))
			 = append(, geo.NewSegment(geo.NewPoint(.X, .Y), ))
			// S-shaped estimate (segments 5-10 in the method doc above)
			 := math.Round((.X + .X) / 2.)
			 = append(, geo.NewSegment(geo.NewPoint(, .Y), geo.NewPoint(, .Y)))
			 := math.Round((.Y + .Y) / 2.)
			 = append(, geo.NewSegment(geo.NewPoint(.X, ), geo.NewPoint(.X, )))
		}
	}

	return , .Finish()
}