package hierarchy

import (
	
	
	
	
	
	

	

	
)

const (
	crossingSpacing     = 50.0
	parentSpacing       = 300.0
	siblingDummySpacing = 50.0
	siblingSpacing      = 60.0
)

type placementNode struct {
	graphNode *layoutgraph.Node
	level     int
	// this is the level index considering the order left to right as if it was a flatten array, no container depth considered
	rank      int
	aboves    map[*placementNode]struct{}
	belows    map[*placementNode]struct{}
	container *placementNode
	children  []*placementNode
	// flags whether or not we should recurse to optimize the children's position
	// currently only for SQL Tables as we need to consider their columns as `children`
	// but we don't want to change their position
	// also, tables are simple nodes and we create children as dummy nodes so that we can optimize
	// the tables position based on the columns (as routing from column to column can change crossings)
	optimizeChildrenCrossings bool

	// a flag for intermediate nodes used to break long connections
	// a connection from level 1 to 3 is broken with a dummy node at level 2
	// (so that all `aboves`/`belows` are 1 level apart)
	// this flags marks the node at level 2 as being part of the chain
	isChainningConnection bool

	// isContainer quickly checks if a node is a container or not
	// Table nodes have children, but aren't containers
	isContainer bool

	isDummy bool
}

func newPlacementNode( int,  *layoutgraph.Node) *placementNode {
	return &placementNode{
		graphNode:                 ,
		level:                     ,
		rank:                      0,
		aboves:                    make(map[*placementNode]struct{}),
		belows:                    make(map[*placementNode]struct{}),
		container:                 nil,
		children:                  nil,
		optimizeChildrenCrossings: true,
		isChainningConnection:     false,
		isContainer:               false,
		isDummy:                   false,
	}
}

func ( *placementNode) () int {
	if .isDummy {
		// dummy nodes have only 2 edges
		return 2
	}
	return countHierarchyStructuralEdges(.graphNode)
}

func ( *placementNode) ( bool) *placementNode {
	 := func( *placementNode,  bool) *placementNode {
		 := .aboves
		if ! {
			 = .belows
		}
		for  := range  {
			return 
		}
		return nil
	}
	 := 
	for .isDummy && .isChainningConnection {
		 = (, )
	}
	return 
}

func ( *placementNode) ( *placementNode) {
	if .level == .level {
		return
	}
	if .level > .level {
		,  = , 
	}
	.aboves[] = struct{}{}
	.belows[] = struct{}{}
}

func ( context.Context,  *layoutgraph.Graph,  *layoutgraph.Node,  *rand.Rand) ( error) {
	if  := layoutgraph.Validate(, "PlaceHierarchies", );  != nil {
		return 
	}
	if len(.Containers[]) == 0 {
		return nil
	}
	, ,  := layoutgraph.EnsureTransactionWorkGuard(, "PlaceHierarchiesTransactions")
	if  != nil {
		return 
	}
	 := layoutgraph.NewGraphStateSnapshot(layoutgraph.GraphStateSnapshotOptions{
		CaptureTopology:   true,
		CaptureEdgeRoutes: true,
	})
	if  := .UpdateWithWorkGuard(, );  != nil {
		return 
	}
	 := &layoutgraph.Transaction{Graph: , PriorGraphState: }
	 := false
	defer func() {
		if ! {
			.Rollback()
		}
	}()

	if  := place(, , , );  != nil {
		return 
	}
	if  := .Err();  != nil {
		return fmt.Errorf("PlaceHierarchies: %w", )
	}
	 = true
	return nil
}

func place( context.Context,  *layoutgraph.Graph,  *layoutgraph.Node,  *rand.Rand) error {

	 := layoutgraph.NewGraph()
	.CopyEntitiesFrom()
	for ,  := range .Containers[] {
		.AddNodeUnchecked()
	}

	 := .AbductEdges(, )
	 := false
	var  layoutgraph.NodeGraphOwnershipJournal
	defer func() {
		if ! {
			.RestoreEdgeAbductions()
		}
		// The split follows Nears outside hGraph.Nodes and temporarily redirects
		// their owners too. Restore every exact pre-split owner before retaining the
		// hierarchy stage's existing successful owner state for nodes in g.
		.Restore()
		layoutgraph.Nodes(.Nodes).SetGraphReference()
	}()

	for ,  := range .Containers[] {
		if .Hierarchy == nil && .IsContainer() {
			if  := (, , , );  != nil {
				return 
			}
		}
	}

	, ,  := .SplitSubgraphsTracked(, layoutgraph.SplitOptions{IncludeNears: true}, nil)
	if  != nil {
		return 
	}
	for ,  := range  {
		if .Nodes[0].Hierarchy == nil {
			continue
		}
		.ComputeCellSize()
		layoutgraph.Nodes(.Nodes).SetGraphReference()
		 = .RestoreEdgeAbductions()
		if  := placeNodesInHierarchy(, , );  != nil {
			return 
		}
	}

	.RestoreEdgeAbductions()
	 = true
	return nil
}

func placeNodesInHierarchy( context.Context,  *layoutgraph.Graph,  *rand.Rand) ( error) {
	// A hierarchy may already be attached by an earlier stage. Never let derived
	// state override an absolute constraint on a member, including a nested
	// container member.
	if  := .Nodes[0].Hierarchy;  != nil {
		for  := range .Levels() {
			if  := .Err();  != nil {
				return fmt.Errorf("PlaceHierarchies: %w", )
			}
			if .FixedTopLeft != nil {
				return nil
			}
		}
	}
	, ,  := layoutgraph.EnsureTransactionWorkGuard(, "PlaceHierarchiesTransactions")
	if  != nil {
		return 
	}
	 := layoutgraph.NewGraphStateSnapshot(layoutgraph.GraphStateSnapshotOptions{
		CaptureEdgeRoutes: true,
	})
	if  := .UpdateWithWorkGuard(, );  != nil {
		return 
	}
	 := &layoutgraph.Transaction{Graph: , PriorGraphState: }
	 := false
	defer func() {
		if ! {
			.Rollback()
		}
	}()

	 := createPlacementNodes(, .Nodes, )
	connectPlacementNodes(, )
	 := groupPlacementNodesByLevel()
	initializeRanks()
	breakLongConnections(, )
	minimizeHierarchyCrossings()
	if  := globalSifting();  != nil {
		return 
	}
	 := IsHorizontal(.Nodes)
	if  {
		transpose()
	}
	placeNodesByLevel(, , )
	for  := 0;  < len(); ++ {
		[] = removeTableColumnNodes([])
		computeLevelRanks([])
	}
	if  := alignHierarchy(, , );  != nil {
		return 
	}
	if  {
		transpose()
	}
	switch .Nodes[0].ContainerDirection() {
	case geo.Left:
		mirrorX()
	case geo.Top:
		mirrorY()
	}
	 = true
	return nil
}

// create placement nodes recursively and set the container whenever required
func createPlacementNodes( *layoutgraph.Graph,  []*layoutgraph.Node,  *rand.Rand) []*placementNode {
	var  []*placementNode
	for ,  := range  {
		 := newPlacementNode(.HierarchyLevel(), )
		 = append(, )
		if .IsTable() {
			.children = make([]*placementNode, .NumColumns())
			.optimizeChildrenCrossings = false
			.isContainer = false
			for  := 0;  < .NumColumns(); ++ {
				.children[] = newPlacementNode(.level, nil)
				.children[].container = 
				.children[].isDummy = true
			}
		} else {
			.optimizeChildrenCrossings = true
			.children = make([]*placementNode, 0, len(.Containers[]))
			for ,  := range (, .Containers[], ) {
				if .container == nil {
					.container = 
					.children = append(.children, )
				}
			}
			.isContainer = len(.children) > 0
		}
	}
	if  != nil {
		.Shuffle(len(), func(,  int) {
			[], [] = [], []
		})
	}
	return 
}

func connectPlacementNodes( *layoutgraph.Graph,  []*placementNode) {
	 := map[*layoutgraph.Node]*placementNode{}
	 := make([]*placementNode, 0, len())
	 = append(, ...)
	for len() > 0 {
		 := [0]
		 = [1:]
		[.graphNode] = 
		 = append(, .children...)
	}

	for ,  := range .Edges {
		 := [.From]
		 := [.To]
		.connect()

		if .IsBetweenTableColumns() {
			// connect the rows
			 = .children[*.FromTableColumnIndex]
			 = .children[*.ToTableColumnIndex]
			.connect()
		}
	}
}

// breakLongConnections creates chains of dummy nodes when two nodes are connected but their level distance is greater than 1
// . ┌──────────┐           ┌──────────┐
// . │          │           │          │
// . └────┬─────┘           └────┬─┬───┘
// .      │                      │ │
// . ┌────▼─────┐                │ │
// . │          │                │ │
// . │          ◄────────────────┘ │
// . │          │              #########
// . │          │              # dummy #
// . │          │              #########
// . └─────┬────┘                  │
// .       │                       │
// .  ┌────▼─────┐         ┌───────▼──┐
// .  │          │         │          │
// .  └──────────┘         └──────────┘
func breakLongConnections( []*placementNode,  map[int][]*placementNode) []*placementNode {
	var  []*placementNode
	for ,  := range  {
		 := make([]*placementNode, 0, len(.belows))
		for  := range .belows {
			if .level-.level > 1 {
				 = append(, )
			}
		}
		slices.SortStableFunc(, func(,  *placementNode) int {
			if  := cmp.Compare(.rank, .rank);  != 0 {
				return 
			}
			return cmp.Compare(.level, .level)
		})
		for ,  := range  {
			delete(.belows, )
			delete(.aboves, )

			 := 
			for  := .level + 1;  < .level; ++ {
				 := -layoutgraph.EntityID(len() + 1)
				 := newPlacementNode(, layoutgraph.NewNode(, 1, 1))
				.isChainningConnection = true
				.isDummy = true
				.rank = len([])
				.connect()
				 = append(, )
				[] = append([], )
				 = 
			}
			.connect()
		}
		 = append(, (.children, )...)
	}
	return 
}

func groupPlacementNodesByLevel( []*placementNode) map[int][]*placementNode {
	 := map[int][]*placementNode{}
	for ,  := range  {
		[.level] = append([.level], )
	}
	return 
}

// removeTableColumnNodes removes nodes where `graphNode == nil` and restores edge references
func removeTableColumnNodes( []*placementNode) []*placementNode {
	 := make([]*placementNode, 0, len())
	for ,  := range  {
		if .isDummy {
			if .graphNode == nil {
				// table column
				continue
			} else if .isChainningConnection {
				 := .findNonDummyNode(true)
				 := .findNonDummyNode(false)
				if .graphNode == nil || .graphNode == nil {
					// dummy node chaining a long connection between two table columns
					continue
				}
			}
		}
		 = append(, )
		.children = (.children)
	}
	return 
}

func placeNodesByLevel( *layoutgraph.Graph,  map[int][]*placementNode,  bool) {
	 := 0.0
	for  := 0;  < len(); ++ {
		var  []*layoutgraph.Node
		 := 0.0
		 := 0.0
		for ,  := range [] {
			 = append(, .graphNode)
			.graphNode.TopLeft = geo.NewPoint(, )
			if !.isDummy {
				var  *layoutgraph.Node
				if .container != nil {
					 = .container.graphNode
				}
				 := .ContainerPadding(, true)
				placeDescendants(, , .graphNode.InsidePlacement(1, 1, ))
				 = math.Max(, .graphNode.Height)
				 += math.Ceil(.graphNode.Width) + siblingSpacing
			} else {
				 += siblingSpacing
			}
		}
		 := minimumDistanceToNextLevel([], )
		 += math.Ceil( +  + 2*layoutgraph.MinPortClearance)

		// move to center
		 := layoutgraph.Nodes().Center()
		.Y = math.Ceil(.Y)
		for ,  := range  {
			 := math.Ceil(.Y - .Center().Y)
			.Translate(0, )
		}
	}
}

// recursively place all descendants of a given container
func placeDescendants( *layoutgraph.Graph,  *placementNode,  geo.Point) {
	if !.isContainer {
		return
	}
	 := .ContainerPadding(.graphNode, true)
	 := .X
	for ,  := range .children {
		.graphNode.TopLeft = geo.NewPoint(, .Y)
		(, , .graphNode.InsidePlacement(1, 1, ))
		 += math.Ceil(.graphNode.Width) + siblingSpacing
	}
	,  := layoutgraph.Nodes(.Containers[.graphNode]).BoundingBox()
	.graphNode.FitToBoundingBox(, , )
}

// minimumDistanceToNextLevel computes the distance between two levels, accounting for crossings between them.
// and the label sizes
func minimumDistanceToNextLevel( []*placementNode,  bool) float64 {
	var  crossingScratch
	 := .crossLevelSegments(, false, true)
	 := countCrossings()
	 := float64() * crossingSpacing

	 := 0.
	for ,  := range  {
		if .isDummy {
			continue
		}
		for ,  := range .graphNode.Edges {
			 := .graphNode.Adjacent()
			 := .graphNode
			 := 
			if .HierarchyLevel() < .HierarchyLevel() {
				,  = , 
			}
			if .HierarchyLevel()-.HierarchyLevel() != 1 {
				continue
			}
			if .Label != nil {
				if  {
					 = math.Max(.Label.Width, )
				} else {
					 = math.Max(.Label.Height, )
				}
			}
		}
	}
	 := math.Max(float64(), +crossingSpacing)
	// ensure there'll be some space if there are no crossings
	 = math.Max(crossingSpacing, )
	// crossings distance is a kind of overshot (as routes might be shared), so clipping here to avoid large gaps between levels
	 = math.Min(, parentSpacing)
	return math.Round()
}

func isSource( *layoutgraph.Node) bool {
	for ,  := range .Edges {
		if !isHierarchyStructuralEdge() {
			continue
		}
		if .HasSourceArrow() == .HasTargetArrow() || (.HasSourceArrow() && .From == ) || (.HasTargetArrow() && .To == ) {
			return false
		}
	}
	return true
}

func isSink( *layoutgraph.Node) bool {
	for ,  := range .Edges {
		if !isHierarchyStructuralEdge() {
			continue
		}
		if .HasSourceArrow() == .HasTargetArrow() || (.HasTargetArrow() && .From == ) || (.HasSourceArrow() && .To == ) {
			return false
		}
	}
	return true
}

func iterContainersBFS( *layoutgraph.Graph,  func( *layoutgraph.Node)) {
	 := make([]*layoutgraph.Node, 0, len(.Nodes))
	 = append(, .Nodes...)
	for len() > 0 {
		 := [0]
		 = [1:]
		if .IsContainer() {
			 = append(, .Containers[]...)
		}
		()
	}
}

func transpose( *layoutgraph.Graph) {
	iterContainersBFS(, func( *layoutgraph.Node) {
		.Transpose()
	})
}

func mirrorX( *layoutgraph.Graph) {
	iterContainersBFS(, func( *layoutgraph.Node) {
		.Mirror(true, false)
	})
}

func mirrorY( *layoutgraph.Graph) {
	iterContainersBFS(, func( *layoutgraph.Node) {
		.Mirror(false, true)
	})
}

func ( *placementNode) () string {
	return fmt.Sprintf("%s[%d, %d]", .graphNode.DebugID(), .level, .rank)
}