package placement

import (
	
	
	

	
	
	
	
)

type directionCounts struct {
	left, right, top, bottom int
}

func ( *directionCounts) ( directionCounts) {
	.left += .left
	.right += .right
	.top += .top
	.bottom += .bottom
}

func edgeDirectionCounts( *layoutgraph.Edge) directionCounts {
	var  directionCounts
	, ,  := .DirectedEndpoints()
	if ! {
		return 
	}
	switch .Orientation().GetOpposite() {
	case geo.Left:
		.left++
	case geo.TopLeft:
		.top++
		.left++
	case geo.BottomLeft:
		.bottom++
		.left++
	case geo.Right:
		.right++
	case geo.TopRight:
		.top++
		.right++
	case geo.BottomRight:
		.bottom++
		.right++
	case geo.Top:
		.top++
	case geo.Bottom:
		.bottom++
	}
	return 
}

type directionTransforms struct{ mirrorX, mirrorY bool }

type directionCount struct {
	direction geo.Orientation
	count     int
}

func compareDirectionCounts(,  directionCount,  geo.Orientation) int {
	if  := cmp.Compare(.count, .count);  != 0 {
		return 
	}
	 := .direction == 
	 := .direction == 
	switch {
	case  && !:
		return -1
	case ! && :
		return 1
	default:
		return 0
	}
}

func ( directionCounts) ( geo.Orientation) directionTransforms {
	 := []directionCount{
		{geo.Right, .right},
		{geo.Bottom, .bottom},
		{geo.Left, .left},
		{geo.Top, .top},
	}
	slices.SortStableFunc(, func(,  directionCount) int {
		return compareDirectionCounts(, , )
	})
	,  := [0], [1]
	if .direction == .direction.GetOpposite() {
		 = [2]
	}
	if  == geo.NONE {
		if .direction.IsHorizontal() {
			 = geo.Right
		} else {
			 = geo.Bottom
		}
	}
	,  := geo.Right, geo.Bottom
	if .IsHorizontal() {
		 = 
	} else {
		 = 
	}
	var  directionTransforms
	if .direction.IsHorizontal() {
		.mirrorX = .direction != 
	} else {
		.mirrorY = .direction != 
	}
	if .count > [3].count {
		if .direction.IsHorizontal() {
			.mirrorX = .direction != 
		} else {
			.mirrorY = .direction != 
		}
	}
	return 
}

func containerEdgeDirections( *layoutgraph.Graph,  *layoutgraph.Node) directionCounts {
	 := make(map[*layoutgraph.Edge]struct{})
	var  directionCounts
	for ,  := range .Containers[] {
		if .Graph !=  {
			continue
		}
		for ,  := range .Edges {
			if ,  := [];  {
				continue
			}
			[] = struct{}{}
			.add(edgeDirectionCounts())
		}
	}
	return 
}

func hasFixedDescendant( *layoutgraph.Node) bool {
	if .FixedTopLeft != nil {
		return true
	}
	if .IsContainer() {
		for ,  := range .Graph.Containers[] {
			if () {
				return true
			}
		}
	}
	if .IsClusterVessel() {
		for ,  := range .Graph.Clusters[].Nodes {
			if () {
				return true
			}
		}
	} else if  := .Graph.Sequences[];  != nil {
		for ,  := range .Nodes {
			if () {
				return true
			}
		}
	}
	return false
}

func mirrorAxes( context.Context,  *layoutgraph.Graph, ,  bool) error {
	,  := limits.NewWorkGuard(, "MirrorAxesReachability", limits.MaxEngineWorkUnits)
	if  != nil {
		return 
	}
	 := make(map[*layoutgraph.Node]struct{})
	 := make(map[*layoutgraph.Node]struct{})
	for ,  := range .Nodes {
		if ,  := [];  {
			continue
		}
		,  := .AllReachableNodesContext(true, true, false, nil, )
		if  != nil {
			return 
		}
		for ,  := range  {
			[] = struct{}{}
			[.Container] = struct{}{}
		}
	}
	if  := .Finish();  != nil {
		return 
	}

	,  := snapshotNodePositionsContext(, "MirrorAxes", .Nodes)
	if  != nil {
		return 
	}
	 := make(map[*layoutgraph.Tree]geo.Orientation)
	for ,  := range  {
		if  := .NodeToTree[.node];  != nil {
			[] = .Orientation
		}
	}
	 := false
	defer func() {
		if  {
			return
		}
		restoreNodePositions()
		for ,  := range  {
			.Orientation = 
		}
	}()

	 := make(map[*layoutgraph.Node]struct{})
	var  error
	 := func( *layoutgraph.Node) {
		if  != nil {
			return
		}
		if  := .Step();  != nil {
			 = 
			return
		}
		if ,  := [];  {
			return
		}
		if ,  := [.EffectiveContainer()];  {
			.Mirror(, )
			[] = struct{}{}
			if  := .Finish();  != nil {
				 = 
				return
			}
			if  := .NodeToTree[];  != nil &&
				( && .Orientation.IsHorizontal() ||  && .Orientation.IsVertical()) {
				.Orientation = .Orientation.GetOpposite()
				if  := .Finish();  != nil {
					 = 
					return
				}
			}
		}
		.PositionContainerChildren(true)
		if  := .Finish();  != nil {
			 = 
		}
	}
	for ,  := range .Nodes {
		if  != nil {
			break
		}
		if ,  := []; ! {
			.WalkRDFS()
		}
	}
	if  != nil {
		return 
	}
	if  := .Finish();  != nil {
		return 
	}
	 = true
	return nil
}

// direct mirrors a placed subgraph toward its dominant edge direction when
// doing so preserves or improves the placement cost.
type directOptions struct {
	checkEdgeLength bool
}

func direct( context.Context,  *layoutgraph.Graph,  layoutgraph.Nodes,  *layoutgraph.Node,  directOptions) error {
	if  := .Err();  != nil {
		return 
	}
	if len() == 0 || [0].Hierarchy != nil {
		return nil
	}
	for ,  := range  {
		if hasFixedDescendant() {
			return nil
		}
		if  := .Sequences[];  != nil && len(.EdgeAbductions) > 0 {
			return nil
		}
		if .Sequence != nil && len(.Sequence.EdgeAbductions) > 0 {
			return nil
		}
	}
	 := containerEdgeDirections(, ).transformsTo(.Direction())
	if !.mirrorX && !.mirrorY {
		return nil
	}
	if !.checkEdgeLength {
		return mirrorAxes(, , .mirrorX, .mirrorY)
	}
	,  := placementcost.EdgeLength(, , placementcost.EdgeLengthOptions{EdgeAbductions: nil, IncludeNodeSizes: true, EnforceMinimumGap: false, PenalizeDirection: true})
	if  != nil {
		return 
	}
	,  := .NewRequestTransaction(, layoutgraph.TransactionOptions{AffectContainers: true})
	if  != nil {
		return 
	}
	.AddOp(func() error { return mirrorAxes(, , .mirrorX, .mirrorY) })
	if  := .Commit();  != nil {
		if layoutgraph.IsCandidateRejection() {
			return nil
		}
		return 
	}
	,  := placementcost.EdgeLength(, , placementcost.EdgeLengthOptions{EdgeAbductions: nil, IncludeNodeSizes: true, EnforceMinimumGap: false, PenalizeDirection: true})
	if  != nil {
		.Rollback()
		return 
	}
	if geo.PrecisionCompare(, , geo.PRECISION) > 0 {
		.Rollback()
	}
	return nil
}