package trees

import (
	
	
	
	

	
	
	
)

func extractTreesInContainer( *layoutgraph.Graph,  *layoutgraph.Node,  *limits.WorkGuard) (map[*layoutgraph.Node][]*layoutgraph.Tree, error) {
	 := .Containers[]
	 := make(map[*layoutgraph.Node]*layoutgraph.Tree)
	for ,  := range  {
		if  := .Step();  != nil {
			return nil, 
		}
		if  == nil {
			return nil, treePreprocessBadState("tree container contains a nil node")
		}
		if .IsTable() {
			return nil, nil
		}
		[] = layoutgraph.NewTree()
	}
	// TODO: Compare this adjacency matrix with repeated ConnectionTo lookups.
	,  := buildTreeAdjacencyMatrix(, )
	if  != nil {
		return nil, 
	}

	// Terminal node: tree never reaches here, but roots can connect to graph here
	// Sentinel node: the node that a tree root is connected to
	 := make(map[*layoutgraph.Node]bool)
	 := make(map[*layoutgraph.Node][]*layoutgraph.Tree)
	 := make(map[*layoutgraph.Node]map[layoutgraph.Arrowhead]struct{})

	// mark all containers as terminal so they aren't in trees
	for  := range .Containers {
		if  := .Step();  != nil {
			return nil, 
		}
		[] = true
	}
	for  := range .Sequences {
		if  := .Step();  != nil {
			return nil, 
		}
		[] = true
	}
	// if node has Nears, don't put it in a tree since placement doesn't work with both Nears and Trees
	// TODO: place trees early enough that they are restored before Near placement.
	for ,  := range .Nodes {
		if  := .Step();  != nil {
			return nil, 
		}
		if len(.Nears) > 0 || .FixedTopLeft != nil {
			[] = true
		}
	}

	for {
		 := make([]*layoutgraph.Node, 0)
		,  := treeFringeNodes(, )
		if  != nil {
			return nil, 
		}
		for ,  := range  {
			if  := .Step();  != nil {
				return nil, 
			}
			if ,  := []; ! {
				 = append(, )
			}
		}
		if len() == 0 {
			break
		}

		for ,  := range  {
			if  := .Step();  != nil {
				return nil, 
			}
			// Note: fn can be marked terminal if it is the sentinel of another fringe node
			// in this set which has its incoming edge directions not matching the sentinel edge
			if ,  := [];  {
				continue
			}

			if len(.Edges) != 1 || .Edges[0] == nil {
				return nil, treePreprocessBadState("fringe node %d has invalid degree", .ID)
			}
			 := .Adjacent(.Edges[0])
			if  == nil {
				return nil, treePreprocessBadState("fringe node %d has no sentinel", .ID)
			}
			// if there are a pair of fringe nodes remaining, mark the first one as terminal and continue
			// otherwise both will have their tree nodes as children of each other
			if len(.Edges) == 1 && .Adjacent(.Edges[0]) ==  {
				if ,  := []; ! {
					[] = true
					continue
				}
			}
			 := []
			if  == nil {
				return nil, treePreprocessBadState("fringe node %d has no tree record", .ID)
			}
			.SentinelEdge = (, )
			if .SentinelEdge == nil {
				return nil, treePreprocessBadState("fringe node %d has no graph sentinel edge", .ID)
			}

			if ,  := []; ! {
				[] = make([]*layoutgraph.Tree, 0)
				[] = make(map[layoutgraph.Arrowhead]struct{})
			}

			 := .SentinelEdge.ArrowheadTo()

			[] = append([], )
			[][] = struct{}{}

			if ,  := [];  {
				// Note: treeRoots may contain a terminal node if a root was marked from another direction leading to it
				,  = filterTerminalTrees(, , )
				if  != nil {
					return nil, 
				}
				if len() == 0 {
					continue
				}
				,  := countTreeDirections(, )
				if  != nil {
					return nil, 
				}
				// the fringe node is terminal if it has incoming edges with more than one direction
				// or multiple different arrowheads
				if len() > 1 || len([]) > 1 {
					[] = true
				} else {
					// merge the roots with the fringe node tree
					for ,  := range  {
						if  := addTreeChild(, , );  != nil {
							return nil, 
						}
					}

					,  := [Undirected]
					 := treeEdgeDirection(, .SentinelEdge)
					,  := []
					// sentinel node is terminal if the fringe node's incoming edges don't match the edge to the sentinel node
					// a directed edge can merge with Undirected edges and an Undirected edge can merge with matching directed edges
					 := !( ||  == Undirected) && !

					if  {
						[] = true
					}
				}
			}
		}

		for ,  := range  {
			if  := .Step();  != nil {
				return nil, 
			}
			if ,  := []; ! {
				if  := removeNodeFromGraph(, , );  != nil {
					return nil, 
				}
				if  := disconnectTreeEdge(, [].SentinelEdge, );  != nil {
					return nil, 
				}
				// remove node from container to children map
				if  := removeNodeFromContainer(, , , );  != nil {
					return nil, 
				}
			}
		}
	}

	 := make(map[*layoutgraph.Node][]*layoutgraph.Tree)
	// Note: sentinelToTreeRoots will only have nodes in this container, but can contain nodes in the tree.
	// by iterating over g.Nodes, we only consider the nodes remaining in the graph, and by checking
	// sentinelToTreeRoots, we get the tree roots connected to the nodes remaining in the graph
	for ,  := range .Nodes {
		if  := .Step();  != nil {
			return nil, 
		}
		if ,  := [];  {
			,  := filterTerminalTrees(, , )
			if  != nil {
				return nil, 
			}
			if len() == 0 {
				continue
			}
			// if the sentinel is a sequence, we must keep it as is since it can't be inside the tree
			if ,  := .Sequences[];  {
				[] = 
			} else {
				, ,  := handleTreeSubgraph(, , , , )
				if  != nil {
					return nil, 
				}
				[] = 
			}
		}
	}

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

func extractTrees( *layoutgraph.Graph,  *limits.WorkGuard) (map[*layoutgraph.Node][]*layoutgraph.Tree, error) {
	// extract trees in the base graph (nil container) and in all containers
	 := make(map[*layoutgraph.Node][]*layoutgraph.Tree)
	// TODO: investigate why this has to be in RDFSOrder
	,  := treeContainerRDFSOrder(, nil, )
	if  != nil {
		return nil, 
	}
	 = append(, nil)
	for ,  := range  {
		if  := .Step();  != nil {
			return nil, 
		}
		,  := extractTreesInContainer(, , )
		if  != nil {
			return nil, 
		}
		for ,  := range  {
			if  := .Step();  != nil {
				return nil, 
			}
			[] = 
		}
	}

	return , nil
}

// handleTreeSubgraph merges tree parts that share an internal root sentinel.
// Entire tree subgraphs can produce multiple parts because extraction assumes
// every tree is attached to a non-tree core.
func handleTreeSubgraph( *layoutgraph.Graph, ,  *layoutgraph.Node,  []*layoutgraph.Tree,  *limits.WorkGuard) (*layoutgraph.Node, []*layoutgraph.Tree, error) {
	// if rootSentinel has connections after extract trees it isn't a subgraph,
	//  and if there isn't more than 1 tree root there aren't any parts to merge
	if !(len(.Edges) == 0 && len() > 1) {
		return , , nil
	}
	if .IsContainer() {
		return , , nil
	}

	var  *layoutgraph.Node
	var  *layoutgraph.Tree
	// Of the roots connecting to rootSentinel, each can only possibly lead to the root of a merged tree if
	//   the directions are valid for a tree.
	// e.g. with 1 root via an outward edge and 2 roots via inward edges,
	//   only the outward edge root could possibly lead to the root of a single merged tree.
	// Example     ┌───┐
	//             │ 1 ├...?
	//             └─┬─┘
	//      ...?     │
	//     ┌─┴─┐   ┌─▼─┐   ┌───┐
	//     │ 2 ◄───┤gN ◄───┤ 3 ├...?
	//     └───┘   └───┘   └───┘
	// Here only the root 2 could possibly be the root of the merged tree (due to edge directions)
	,  := candidateTreeRoots(, )
	if  != nil {
		return nil, nil, 
	}
	for ,  := range  {
		if  := .Step();  != nil {
			return nil, nil, 
		}
		// if the root tree is a line, then it can be the merged tree root
		,  := treeEndOfLine(, )
		if  != nil {
			return nil, nil, 
		}
		if  != nil {
			// We just need to join the other roots as children of a new Tree for rootSentinel,
			 := layoutgraph.NewTree()
			for ,  := range  {
				if  := .Step();  != nil {
					return nil, nil, 
				}
				if  !=  {
					if  := addTreeChild(, , );  != nil {
						return nil, nil, 
					}
				}
			}

			// Note: the root's Sentinel Edge is overwritten by reversing the chain, so we have to record it beforehand
			 := .SentinelEdge
			//   flip the tree nodes along the root,
			if  := reverseTreeChain(, );  != nil {
				return nil, nil, 
			}
			//   and connect the node and the flipped chain together.
			if  := addTreeChild(, , );  != nil {
				return nil, nil, 
			}
			.SentinelEdge = 

			 = .Node
			 = .Children[0]
			.Parent = nil
			break
		}
	}

	if  != nil &&  != nil {
		if  := removeNodeFromGraph(, , );  != nil {
			return nil, nil, 
		}
		.AddNodeUnchecked()
		if  := .Step();  != nil {
			return nil, nil, 
		}

		 := make([]*layoutgraph.Node, 0)
		for ,  := range .Containers[] {
			if  := .Step();  != nil {
				return nil, nil, 
			}
			if  ==  {
				continue
			}
			 = append(, )
		}
		 = append(, )
		.Containers[] = 

		return , []*layoutgraph.Tree{}, nil
	}
	return , , nil
}

// prepare tree for operation defined to Bottom, when we want it to work for the provided orientation
// Note: must also call after the operation to cancel out the effects of this function
func invertOrientationToBottom( *layoutgraph.Tree,  geo.Orientation,  *limits.WorkGuard) error {
	switch  {
	case geo.Top:
		return flip(, )
	case geo.Right:
		return swapDimensions(, )
	case geo.Left:
		if  := swapDimensions(, );  != nil {
			return 
		}
		return flip(, )
	}
	return .Check()
}

func constructToOrientation( *layoutgraph.Tree,  geo.Orientation,  *limits.WorkGuard) error {
	if  := setOrientation(, , );  != nil {
		return 
	}
	if  := invertOrientationToBottom(, , );  != nil {
		return 
	}
	if  := layoutTree(, );  != nil {
		return 
	}
	if  := centerAlignChildren(, );  != nil {
		return 
	}
	return invertOrientationToBottom(, , )
}

func treePlacementFixedOrigin( *layoutgraph.Graph,  *layoutgraph.Node,  *limits.WorkGuard) (*geo.Point, error) {
	for ,  := range .Nodes {
		if  := .Step();  != nil {
			return nil, 
		}
		if  == nil {
			return nil, treePreprocessBadState("tree placement encountered a nil graph node")
		}
		if .EffectiveContainer() !=  {
			continue
		}
		if  := .FixedOrigin();  != nil {
			return , nil
		}
	}
	return nil, nil
}

func placeAtOrientationGuarded( context.Context,  *layoutgraph.Graph,  *layoutgraph.Tree,  geo.Orientation,  *limits.WorkGuard) (float64, error) {
	 = layoutgraph.ContextWithTransactionWorkGuard(, )
	if  == nil || .Node == nil || .Node.TopLeft == nil {
		return 0, treePreprocessBadState("tree placement encountered an incomplete placement tree")
	}
	if  := .Finish();  != nil {
		return 0, 
	}
	 := func( *layoutgraph.Node) float64 {
		switch  {
		case geo.Left:
			return .TopLeft.X
		case geo.Right:
			return .TopLeft.X + .Width
		case geo.Top:
			return .TopLeft.Y
		default:
			return .TopLeft.Y + .Height
		}
	}
	 := func(,  float64) error {
		 := 0.0
		 := 0.0
		switch {
		case  == geo.Left ||  == geo.Right:
			 = math.Round( - )
		case  == geo.Top ||  == geo.Bottom:
			 = math.Round( - )
		}
		for ,  := range .Children {
			if  := .Step();  != nil {
				return 
			}
			if  := offsetSubtree(, , , );  != nil {
				return 
			}
		}
		return .Finish()
	}
	 := func(,  float64) bool {
		if  == geo.Left ||  == geo.Top {
			return  < 
		} else {
			return  > 
		}
	}

	// 0. Find the root position and border position
	 := (.Node)
	 := 
	,  := treeDescendants(, )
	if  != nil {
		return 0, 
	}
	 := make(map[*layoutgraph.Node]struct{}, len()+1)
	[.Node] = struct{}{}
	for ,  := range  {
		if  := .Step();  != nil {
			return 0, 
		}
		[.Node] = struct{}{}
	}
	for ,  := range .Nodes {
		if  := .Step();  != nil {
			return 0, 
		}
		if  == nil || .TopLeft == nil {
			return 0, treePreprocessBadState("tree placement encountered an incomplete graph node")
		}
		if ,  := []; ! {
			 := ()
			if (, ) {
				 = 
			}
		}
	}

	// 1. Move the tree to the starting border position
	//   we could include the obstacle(s) at the border to find this state, but we also want to
	//   start the transaction attempts from here since there will not be any existing overlaps
	 := 
	 := math.Abs( - )

	if  := (0, );  != nil {
		return 0, 
	}
	// if there are fixed nodes in the container, there shouldn't be any negative coordinates in the initial placement
	,  := treePlacementFixedOrigin(, .Node.EffectiveContainer(), )
	if  != nil {
		return 0, 
	}
	if  != nil {
		 := false
		 := append([]*layoutgraph.Tree{}, ...)
		for ,  := range  {
			if  := .Step();  != nil {
				return 0, 
			}
			if .Node.TopLeft.X < .X || .Node.TopLeft.Y < .Y {
				 = true
				break
			}
		}
		if  {
			 = math.Inf(1)
		}
	}

	// 2a. find nodes between the root and the border to find placement options closer to the root node
	//     we will try to place the tree next to each of these to find the best option
	 := make([]float64, 0)
	for ,  := range .Nodes {
		if  := .Step();  != nil {
			return 0, 
		}
		if ,  := []; ! {
			 := ()
			if (, ) && (, ) {
				 = append(, )
			}
		}
	}
	// 2b. We add the root position as an option and we start at the border for the edge cases, other obstacles are in-between
	 = append(, )

	,  := .NewRequestTransaction(, layoutgraph.TransactionOptions{})
	if  != nil {
		return 0, 
	}
	for ,  := range  {
		if  := .Step();  != nil {
			return 0, 
		}
		// 2c. Try moving the tree next to each obstacle
		.AddOp(func() error {
			return (, )
		})
		// 2d. Undo if overlapping or too close to other nodes
		if  := .Commit();  != nil {
			.Clear()
			if layoutgraph.IsCandidateRejection() {
				continue
			}
			return 0, 
		}
		if  := .Finish();  != nil {
			.Clear()
			return 0, 
		}

		// 2e. Keep this position if it is closer to the root, otherwise undo the movement
		 := math.Abs( - )
		if  <  {
			 = 
			 = 
			if  := .UpdateState();  != nil {
				.Clear()
				return 0, 
			}
		} else {
			.Rollback()
		}
		.Clear()
	}

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

// trees look nicer due to their internal relative placement and consistent edges, but they
// have the drawback of fixed placement options on the sides of the rootSentinel its connected to
// when the tree doesn't branch at all, we'd prefer to have node placement since there's little benefit to tree placement
func putBackNonBranchingTrees( *layoutgraph.Graph,  *limits.WorkGuard) error {
	,  := treeContainerRDFSOrder(, nil, )
	if  != nil {
		return 
	}
	 = append(, nil)
	for ,  := range  {
		if  := .Step();  != nil {
			return 
		}
		 := .Containers[]
		for ,  := range  {
			if  := .Step();  != nil {
				return 
			}
			,  := .Trees[]
			if ! {
				continue
			}
			if len() == 0 {
				return treePreprocessBadState("tree sentinel %d has no roots", .ID)
			}
			,  := isIsolatedTreeGuarded(, , )
			if  != nil {
				return 
			}
			 := false
			if  && len() == 1 {
				,  = treeBranches([0], )
				if  != nil {
					return 
				}
			}
			if  && (len() > 1 || ) {
				// if it is a branching isolated tree, don't put back its roots
				,  := .Sequences[]
				// for a sequence vessel sentinel: put non-branching roots back since tree placement
				// isn't tailored for sequences so it's only worth keeping branching roots
				if ! {
					continue
				}
			}
			 := make([]*layoutgraph.Tree, 0, len())
			for ,  := range  {
				if  := .Step();  != nil {
					return 
				}
				,  := treeBranches(, )
				if  != nil {
					return 
				}
				if ! {
					 := layoutgraph.NewTree()
					if  := addTreeChild(, , );  != nil {
						return 
					}
					if  := reconnectTreeGuarded(, , , );  != nil {
						return 
					}
				} else {
					 = append(, )
				}
			}
			if len() > 0 {
				.Trees[] = 
			} else {
				delete(.Trees, )
			}
		}
	}
	return .Check()
}

func preprocessTreesWithWorkLimit( context.Context,  *layoutgraph.Graph,  int64) ( error) {
	if  := layoutgraph.Validate(, treePreprocessLocation, );  != nil {
		return 
	}
	,  := newWorkGuard(, treePreprocessLocation)
	if  != nil {
		return 
	}
	.SetLimit()

	 := layoutgraph.NewGraphStateSnapshot(layoutgraph.GraphStateSnapshotOptions{
		CaptureTopology:   true,
		CaptureEdgeRoutes: true,
	})
	if  := .UpdateWithWorkGuard(, );  != nil {
		return 
	}
	 := &layoutgraph.Transaction{Graph: , PriorGraphState: }
	 := false
	defer func() {
		if ! {
			.Rollback()
		}
	}()

	,  := extractTrees(, )
	if  != nil {
		return 
	}
	.Trees = 
	if  := .Step();  != nil {
		return 
	}
	if  := .Check();  != nil {
		return 
	}
	if  := putBackNonBranchingTrees(, );  != nil {
		return 
	}
	if  := buildNodeToTreeGuarded(, );  != nil {
		return 
	}
	if  := .Finish();  != nil {
		return 
	}
	 = true
	return nil
}

func ( context.Context,  *layoutgraph.Graph) error {
	return preprocessTreesWithWorkLimit(, , limits.MaxEngineWorkUnits)
}

// if width >= height this is the aspect ratio, otherwise it is 1/the aspect ratio
// this way the value is always >= 1, and 1:10 has the same value as 10:1
func normalizedAspectRatio( *layoutgraph.Graph,  *limits.WorkGuard) (float64, error) {
	// BoundingBox has no guard parameter and may inspect every peer node while
	// resolving outside labels. Charge its node-pair and route-point traversal
	// here so cancellation and the aggregate work limit cover the calculation.
	for ,  := range .Nodes {
		if  := .Step();  != nil {
			return 0, 
		}
		if  == nil || .TopLeft == nil {
			return 0, treePreprocessBadState("tree placement encountered an incomplete graph node")
		}
		for range .Nodes {
			if  := .Step();  != nil {
				return 0, 
			}
		}
	}
	for ,  := range .Edges {
		if  := .Step();  != nil {
			return 0, 
		}
		if  == nil {
			return 0, treePreprocessBadState("tree placement encountered a nil graph edge")
		}
		for ,  := range .Points {
			if  := .Step();  != nil {
				return 0, 
			}
			if  == nil {
				return 0, treePreprocessBadState("tree placement encountered a nil route point")
			}
		}
	}
	,  := .BoundingBox()
	if  == nil ||  == nil {
		return 0, treePreprocessBadState("tree placement could not compute a graph bounding box")
	}
	if  := .Finish();  != nil {
		return 0, 
	}
	 := .X - .X
	 := .Y - .Y
	if  >=  {
		return  / , nil
	}
	return  / , nil
}

// reconnect tree to the graph with the best placement we can find
func placeTree( context.Context,  *layoutgraph.Graph,  *layoutgraph.Tree,  *layoutgraph.Node,  map[geo.Orientation]bool,  *limits.WorkGuard) (geo.Orientation, error) {
	// 0. initialize TopLeft to non-nil for placement
	,  := treeDescendants(, )
	if  != nil {
		return 0, 
	}
	for ,  := range  {
		if  := .Step();  != nil {
			return 0, 
		}
		.Node.TopLeft = new(geo.Point)
		if  := .Finish();  != nil {
			return 0, 
		}
	}

	// 1. restore the tree to the graph
	if  := reconnectTreeGuarded(, , , );  != nil {
		return 0, 
	}

	// 2. find the best orientation to place the tree close to the root node
	var  geo.Orientation
	 := math.Inf(1)
	 := math.Inf(1)

	 := []geo.Orientation{geo.Bottom, geo.Top, geo.Left, geo.Right}
	 := []geo.Orientation{}

	,  := isIsolatedTreeGuarded(, .Node, )
	if  != nil {
		return , 
	}
	if len() == 1 {
		for ,  := range  {
			if ,  := [];  {
				 = .GetOpposite()
				break
			}
		}
	} else {
		// for isolated trees, don't place on the same side as an already placed tree
		if  {
			for ,  := range  {
				if ,  := [];  {
					continue
				}
				 = append(, )
			}
			if len() == 0 {
				// we ran out of unused sides
				 = 
			}
		} else {
			 = 
		}

		 := .Direction()
		for ,  := range  {
			if  := .Step();  != nil {
				return , 
			}
			if  := constructToOrientation(, , );  != nil {
				return , 
			}
			,  := placeAtOrientationGuarded(, , , , )
			if  != nil {
				return , 
			}
			,  := normalizedAspectRatio(, )
			if  != nil {
				return , 
			}

			// slightly favor the container direction
			if  !=  {
				 += directionPenalty
			}

			if  <  {
				 = 
				 = 
				 = 
			} else if  ==  &&  <  {
				// break ties with width & height ratio closest to 1 (most square)
				 = 
				 = 
			}
		}
	}

	// 3. place using the best orientation
	if  := .Step();  != nil {
		return , 
	}
	if  := constructToOrientation(, , );  != nil {
		return , 
	}
	if ,  := placeAtOrientationGuarded(, , , , );  != nil {
		return , 
	}

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

func positionTreeEdgeLabels( *layoutgraph.Tree,  bool,  *limits.WorkGuard) ( error) {
	 := .Orientation
	if  := invertOrientationToBottom(, , );  != nil {
		return 
	}
	defer func() {
		 := invertOrientationToBottom(, , )
		if  == nil {
			 = 
		}
	}()

	var  []*layoutgraph.Tree
	for ,  := range .Children {
		if  := .Step();  != nil {
			return 
		}
		// if it is an isolated tree, we also position the root's sentinel edge label (because we route it in an s-shape)
		if  {
			 = append(, )
		} else {
			for ,  := range .Children {
				if  := .Step();  != nil {
					return 
				}
				 = append(, )
			}
		}
	}
	for ,  := range  {
		if  := .Step();  != nil {
			return 
		}
		if  := validateBottomOrientation(, );  != nil {
			return 
		}
	}
	for ,  := range  {
		if  := .Step();  != nil {
			return 
		}
		if  := positionEdgeLabels(, );  != nil {
			return 
		}
	}
	return .Finish()
}

func placeTrees( context.Context,  *layoutgraph.Graph,  *layoutgraph.Node,  *limits.WorkGuard) error {
	,  := buildPlacementTrees(, )
	if  != nil {
		return 
	}
	 := make(map[*layoutgraph.Tree]int, len())
	for ,  := range  {
		,  := treeSize(, )
		if  != nil {
			return 
		}
		[] = 
	}
	// Sorting cannot return an error from its comparator. Charge a stable
	// n*ceil(log2(n)) upper bound first, one step at a time, so sorting remains
	// covered by the same overflow-safe aggregate budget.
	for  := 1;  < len(); {
		for range  {
			if  := .Step();  != nil {
				return 
			}
		}
		if  > len()/2 {
			break
		}
		 *= 2
	}
	// Note: we sort placementTrees so larger trees are placed first and to have a deterministic order
	slices.SortFunc(, func(,  *layoutgraph.Tree) int {
		if  := cmp.Compare([], []);  != 0 {
			return 
		}
		// We compare each placement tree's first child's ID, since placement tree roots can be the same rootSentinel (if a rootSentinel has multiple roots).
		return cmp.Compare(.Children[0].Node.ID, .Children[0].Node.ID)
	})
	// record placement sides to prevent repeats in isolated trees
	 := make(map[*layoutgraph.Node]map[geo.Orientation]bool)
	for ,  := range  {
		if  := .Step();  != nil {
			return 
		}
		if ,  := [.Node]; ! {
			[.Node] = make(map[geo.Orientation]bool)
		}
		,  := placeTree(, , , , [.Node], )
		if  != nil {
			return 
		}
		[.Node][] = true
		if  := .Finish();  != nil {
			return 
		}
	}
	// disconnect from placement tree
	for ,  := range .Nodes {
		if  := .Step();  != nil {
			return 
		}
		for ,  := range .Trees[] {
			if  := .Step();  != nil {
				return 
			}
			.Parent = nil
			if  := .Finish();  != nil {
				return 
			}
		}
	}
	return .Check()
}

func placeTreesWithWorkLimit( context.Context,  *layoutgraph.Graph,  *layoutgraph.Node,  int64) error {
	if  := layoutgraph.Validate(, "PlaceTrees", );  != nil {
		return 
	}
	,  := newWorkGuard(, "PlaceTrees")
	if  != nil {
		return 
	}
	.SetLimit()

	 := layoutgraph.NewGraphStateSnapshot(layoutgraph.GraphStateSnapshotOptions{
		CaptureTopology:   true,
		CaptureEdgeRoutes: true,
	})
	if  := .UpdateWithWorkGuard(, );  != nil {
		return 
	}
	if  != nil {
		if !.TracksNode() {
			return treePreprocessBadState("cannot place trees into an unknown container")
		}
	}
	 := &layoutgraph.Transaction{Graph: , PriorGraphState: }
	 := false
	defer func() {
		if ! {
			.Rollback()
		}
	}()

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

func ( context.Context,  *layoutgraph.Graph,  *layoutgraph.Node) error {
	return placeTreesWithWorkLimit(, , , limits.MaxPlaceTreesWorkUnits)
}

func buildPlacementTrees( *layoutgraph.Graph,  *limits.WorkGuard) ([]*layoutgraph.Tree, error) {
	var  []*layoutgraph.Tree
	for ,  := range .Nodes {
		if  := .Step();  != nil {
			return nil, 
		}
		 := len()
		,  := .Trees[]
		if ! {
			// not actually a rootSentinel
			continue
		}

		,  := isIsolatedTreeGuarded(, , )
		if  != nil {
			return nil, 
		}
		if  {
			// for an isolated tree and we should try to place all the matching roots together
			,  := rootsByTreeDirection(, )
			if  != nil {
				return nil, 
			}
			 := [Undirected]
			delete(, Undirected)
			for ,  := range  {
				 := make(map[layoutgraph.Arrowhead]*layoutgraph.Tree)

				for ,  := range  {
					if  := .Step();  != nil {
						return nil, 
					}
					if  == nil || .SentinelEdge == nil {
						return nil, treePreprocessBadState("tree placement encountered an incomplete root")
					}
					 := .SentinelEdge.ArrowheadTo()
					if ,  := []; ! {
						[] = layoutgraph.NewTree()
						 = append(, [])
					}
					if  := addTreeChild([], , );  != nil {
						return nil, 
					}
				}
			}
			if len() > 0 {
				// merges undirected trees to the largest one that has no arrowhead in the sentinel edge
				var  *layoutgraph.Tree
				for ,  := range [:] {
					if  := .Step();  != nil {
						return nil, 
					}
					if  != nil && len(.Children) <= len(.Children) {
						continue
					}
					// all children should have compatible edges, so we just need to check the first one
					if len(.Children) > 0 && !.Children[0].SentinelEdge.HasArrowTo() {
						 = 
					}
				}
				if  == nil {
					// if it could not fit with any of the existing trees, we need to create a new for undirected trees
					 = layoutgraph.NewTree()
					 = append(, )
				}
				for ,  := range  {
					if  := addTreeChild(, , );  != nil {
						return nil, 
					}
				}
			}
		} else {
			for ,  := range  {
				if  := .Step();  != nil {
					return nil, 
				}
				// For placement, create a tree node with the rootSentinel connected to the tree root
				 := layoutgraph.NewTree()
				if  := addTreeChild(, , );  != nil {
					return nil, 
				}
				 = append(, )
			}
		}
	}
	return , .Check()
}

// dejitter performs micro-movements on nodes to remove the jittery connections that can be straightened out with slight adjustments in one direction
// E.g. jittery line:
// .             +--------->
// . +-----------+