package proximity

import (
	
	
	
	

	
	
	
)

// AssignHerds chooses a shared container side for siblings whose external
// cousins should remain mutually accessible during placement.
func ( context.Context,  *layoutgraph.Graph,  *layoutgraph.Node,  []*layoutgraph.EdgeAbduction) error {
	if  := .Err();  != nil {
		return fmt.Errorf("AssignHerds: %w", )
	}
	, ,  := GroupSheep(, , , )
	if  != nil {
		return 
	}
	var  []*layoutgraph.Node
	for ,  := range  {
		if len() <= 1 {
			delete(, )
			delete(, )
		} else {
			 = append(, )
		}
	}
	slices.SortFunc(, func(,  *layoutgraph.Node) int {
		return cmp.Compare(.ID, .ID)
	})

	,  := connectedHerds(, , )
	if  != nil {
		return 
	}
	 := 0
	for ,  := range  {
		// Overlapping groups describe a single equality constraint: every member
		// must use the same side. Choose that side only after intersecting all
		// placed cousins' available sides, so a later uncle cannot change one
		// member and invalidate an earlier group.
		 := []geo.Orientation{geo.Top, geo.Right, geo.Bottom, geo.Left}
		 := geo.NONE
		for ,  := range .uncles {
			if  := .Err();  != nil {
				return fmt.Errorf("AssignHerds: %w", )
			}
			 := .Containers[]
			if len() == 0 {
				return invariant.Errorf("herding uncle %s has no children", .DebugID())
			}
			if [0].TopLeft == nil {
				continue
			}
			for ,  := range [] {
				for ,  := range [][] {
					 := .HerdAssignment
					if  == nil {
						continue
					}
					 := .Orientation
					if !.IsHorizontal() && !.IsVertical() {
						return invariant.Errorf("cousin %s has an invalid herd orientation", .DebugID())
					}
					 := CanUseBothSides(, )
					 = slices.DeleteFunc(, func( geo.Orientation) bool {
						return  != .GetOpposite() && !( &&  == )
					})
					if  == geo.NONE {
						 = .GetOpposite()
						if  && .SameSidePairCount() < .OppositeSidePairCount() {
							 = 
						}
					}
				}
			}
		}
		if len() == 0 {
			// Herding is a placement preference. Cousins on incompatible sides
			// can make that preference impossible for a valid graph; in that
			// case leave the entire connected herd free to place normally.
			for ,  := range .nodes {
				.HerdAssignment = nil
			}
			continue
		}
		if  == geo.NONE {
			 = [%len()]
			++
		} else if !slices.Contains(, ) {
			 = [0]
		}
		for ,  := range .nodes {
			.HerdAssignment = layoutgraph.NewHerdAssignment()
			.HerdAssignment.Orientation = 
		}
		// Pair counts influence later herds, so record only the final choice.
		for ,  := range .uncles {
			if .Containers[][0].TopLeft == nil {
				continue
			}
			for ,  := range [] {
				for ,  := range [][] {
					if .HerdAssignment == nil {
						continue
					}
					if  == .HerdAssignment.Orientation {
						.HerdAssignment.PairSameSide()
						.HerdAssignment.PairSameSide()
					} else {
						.HerdAssignment.PairOppositeSide()
						.HerdAssignment.PairOppositeSide()
					}
				}
			}
		}
	}
	if  := ApplyVirally(, , );  != nil {
		return 
	}

	for ,  := range  {
		for ,  := range [] {
			if .HerdAssignment != nil && .IsClusterVessel() {
				 := .Clusters[]
				if (.HerdAssignment.Orientation == geo.Top || .HerdAssignment.Orientation == geo.Bottom) && .Arrangement == layoutgraph.Column {
					.Arrangement = layoutgraph.Row
				} else if (.HerdAssignment.Orientation == geo.Left || .HerdAssignment.Orientation == geo.Right) && .Arrangement == layoutgraph.Row {
					.Arrangement = layoutgraph.Column
				}
			}
		}
	}
	return nil
}

type herdComponent struct {
	nodes  []*layoutgraph.Node
	uncles []*layoutgraph.Node
}

// connectedHerds joins groups that share a node, retaining deterministic order.
func connectedHerds( context.Context,  []*layoutgraph.Node,  map[*layoutgraph.Node][]*layoutgraph.Node) ([]herdComponent, error) {
	 := make(map[*layoutgraph.Node][]*layoutgraph.Node)
	for ,  := range  {
		for ,  := range [] {
			[] = append([], )
		}
	}
	 := make(map[*layoutgraph.Node]bool)
	 := make(map[*layoutgraph.Node]bool)
	var  []herdComponent
	for ,  := range  {
		if [] {
			continue
		}
		 := herdComponent{uncles: []*layoutgraph.Node{}}
		[] = true
		for  := 0;  < len(.uncles); ++ {
			if  := .Err();  != nil {
				return nil, fmt.Errorf("AssignHerds: %w", )
			}
			for ,  := range [.uncles[]] {
				if [] {
					continue
				}
				[] = true
				.nodes = append(.nodes, )
				for ,  := range [] {
					if ![] {
						[] = true
						.uncles = append(.uncles, )
					}
				}
			}
		}
		 = append(, )
	}
	return , nil
}

// ApplyVirally propagates each known herd orientation through its ordered groups.
func ( context.Context,  []*layoutgraph.Node,  map[*layoutgraph.Node][]*layoutgraph.Node) error {
	for {
		if  := .Err();  != nil {
			return fmt.Errorf("AssignHerds: %w", )
		}
		 := true
		for ,  := range  {
			if  := .Err();  != nil {
				return fmt.Errorf("AssignHerds: %w", )
			}
			 := []
			var  *layoutgraph.HerdAssignment
			for ,  := range  {
				if .HerdAssignment != nil && .HerdAssignment.Orientation != geo.NONE {
					 = .HerdAssignment
					break
				}
			}
			if  != nil {
				for ,  := range  {
					if .HerdAssignment == nil {
						 = false
						.HerdAssignment = .Copy()
					} else if .HerdAssignment.Orientation != geo.NONE && .HerdAssignment.Orientation != .Orientation {
						return invariant.Errorf(
							"node %s has herd orientation %s; expected %s",
							.DebugID(), .HerdAssignment.Orientation.ToString(), .Orientation.ToString(),
						)
					}
				}
			}
		}
		if  {
			return nil
		}
	}
}

// CanUseBothSides reports whether node is long enough perpendicular to
// orientation to host herd members on both parallel sides.
func ( *layoutgraph.Node,  geo.Orientation) bool {
	 := .Width >= 2*.Height
	 := .Height >= 2*.Width
	return ( == geo.Top ||  == geo.Bottom) &&  ||
		( == geo.Left ||  == geo.Right) && 
}

// GroupSheep groups root's children by their external uncle and records the
// cousin connections that define each group.
func (
	 context.Context,
	 *layoutgraph.Graph,
	 *layoutgraph.Node,
	 []*layoutgraph.EdgeAbduction,
) (map[*layoutgraph.Node][]*layoutgraph.Node, map[*layoutgraph.Node]map[*layoutgraph.Node][]*layoutgraph.Node, error) {
	 := make(map[*layoutgraph.Node][]*layoutgraph.Node)
	 := make(map[*layoutgraph.Node]map[*layoutgraph.Node][]*layoutgraph.Node)
	 := make([]bool, len())
	for ,  := range .Containers[] {
		if  := .Err();  != nil {
			return nil, nil, fmt.Errorf("AssignHerds: %w", )
		}
		for ,  := range  {
			if  := .Err();  != nil {
				return nil, nil, fmt.Errorf("AssignHerds: %w", )
			}
			if [] {
				continue
			}
			if  == nil {
				return nil, nil, invariant.New("herding has a nil edge abduction")
			}
			 := groupVessel(.OriginallyFrom)
			 := groupVessel(.OriginallyTo)
			var ,  *layoutgraph.Node
			if .OriginallyTo != nil && ( ==  || descendantOf(, )) {
				if .CurrentTo != nil && !.CurrentTo.IsContainer() {
					continue
				}
				if  == nil || .OwningContainer() == nil {
					continue
				}
				,  = , .CurrentTo
			} else if .OriginallyFrom != nil && ( ==  || descendantOf(, )) {
				if .CurrentFrom != nil && !.CurrentFrom.IsContainer() {
					continue
				}
				if  == nil || .OwningContainer() == nil {
					continue
				}
				,  = , .CurrentFrom
			}
			if  == nil {
				continue
			}
			[] = true
			for .OwningContainer() !=  {
				if .Cluster != nil {
					 = .Cluster.Vessel
				} else if .Sequence != nil {
					 = .Sequence.Vessel
				} else {
					 = .OwningContainer()
				}
			}
			 := .OwningContainer()
			if  == nil || !.IsContainer() {
				continue
			}
			if [] == nil {
				[] = make(map[*layoutgraph.Node][]*layoutgraph.Node)
			}
			if ,  := [][]; ! {
				[] = append([], )
			}
			[][] = append([][], )
		}
	}
	return , , nil
}

func descendantOf(,  *layoutgraph.Node) bool {
	for  != nil {
		if  ==  {
			return true
		}
		switch {
		case .Container != nil:
			 = .Container
		case .Cluster != nil:
			 = .Cluster.Vessel
		case .Sequence != nil:
			 = .Sequence.Vessel
		default:
			 = nil
		}
	}
	return  == nil
}

// SyncHerdFences updates the coordinate constraint for every assigned herd to
// the current graph boundary.
func ( *layoutgraph.Graph) {
	,  := .BoundingBox()
	for ,  := range .Nodes {
		if .HerdAssignment == nil || .FixedTopLeft != nil {
			continue
		}
		switch .HerdAssignment.Orientation {
		case geo.Top:
			.HerdAssignment.Val = .Y
		case geo.Bottom:
			.HerdAssignment.Val = .Y
		case geo.Left:
			.HerdAssignment.Val = .X
		case geo.Right:
			.HerdAssignment.Val = .X
		}
	}
}