package graphbounds

import (
	

	
	

	
	
)

// WorkStepper is the aggregate accounting contract shared by callers. Bounds
// never create or reset a budget: every scan is charged to the caller's guard.
type WorkStepper interface {
	// Step charges one unit of bounds work.
	Step() error
	// Finish observes cancellation after the final bounds operation.
	Finish() error
}

func isExtreme(
	 layoutgraph.Nodes,
	 *layoutgraph.Node,
	 func(*layoutgraph.Node) bool,
	 WorkStepper,
) (bool, error) {
	for ,  := range  {
		if  := .Step();  != nil {
			return false, 
		}
		if  ==  || .TopLeft == nil {
			continue
		}
		if () {
			return false, nil
		}
	}
	return true, nil
}

// NodeBoundingBox computes the packing-compatible bounds for one placed node. When
// allNodes is non-nil, outside labels and icons reserve their exact boundary
// space and outside-label extremity scans consume the shared work budget.
func (
	 *layoutgraph.Node,
	 layoutgraph.Nodes,
	 WorkStepper,
) (*geo.Point, *geo.Point, error) {
	if  := .Step();  != nil {
		return nil, nil, 
	}
	if  == nil || .TopLeft == nil {
		return nil, nil, invariant.New("BinPack bounding box contains an unplaced node")
	}
	 := .TopLeft.Copy()
	 := geo.NewPoint(math.Round(.X+.Width), math.Round(.Y+.Height))
	if ,  := .ModifierElementAdjustments();  != 0 ||  != 0 {
		.Y -= 
		.X += 
	}
	.X -= .LoopOffsets[geo.Left]
	.Y -= .LoopOffsets[geo.Top]
	.X += .LoopOffsets[geo.Right]
	.Y += .LoopOffsets[geo.Bottom]

	if .Label != nil && .Label.Position.IsOutside() &&  != nil {
		 := .LabelTopLeft(.Label.Position, .Label.Width, .Label.Height)
		 := float64(label.PADDING)
		 := 2. * label.PADDING
		if .X < .X {
			,  := isExtreme(, , func( *layoutgraph.Node) bool {
				return .TopLeft.X < .TopLeft.X
			}, )
			if  != nil {
				return nil, nil, 
			}
			 := 
			if  {
				 = 
			}
			.X = math.Floor(.X - )
		}
		if .Y < .Y {
			,  := isExtreme(, , func( *layoutgraph.Node) bool {
				return .TopLeft.Y < .TopLeft.Y
			}, )
			if  != nil {
				return nil, nil, 
			}
			 := 
			if  {
				 = 
			}
			.Y = math.Floor(.Y - )
		}
		if .X > .X {
			,  := isExtreme(, , func( *layoutgraph.Node) bool {
				return .TopLeft.X+.Width > .TopLeft.X+.Width
			}, )
			if  != nil {
				return nil, nil, 
			}
			 := 
			if  {
				 = 
			}
			.X = math.Ceil(.X + .Label.Width + )
		}
		if .Y > .Y {
			,  := isExtreme(, , func( *layoutgraph.Node) bool {
				return .TopLeft.Y+.Height > .TopLeft.Y+.Height
			}, )
			if  != nil {
				return nil, nil, 
			}
			 := 
			if  {
				 = 
			}
			.Y = math.Ceil(.Y + .Label.Height + )
		}
	}
	if .Icon != nil && !.IsImage() && .Icon.Position.IsOutside() &&  != nil {
		 := float64(layoutgraph.MaxIconSize)
		 := .LabelTopLeft(.Icon.Position, , )
		 := 2. * label.PADDING
		.X = math.Min(.X, math.Floor(.X-))
		.Y = math.Min(.Y, math.Floor(.Y-))
		.X = math.Max(.X, math.Ceil(.X++))
		.Y = math.Max(.Y, math.Ceil(.Y++))
	}
	return , , nil
}

// BoundingBox computes the packing-compatible bounds for a node set.
func ( layoutgraph.Nodes,  WorkStepper) (*geo.Point, *geo.Point, error) {
	if len() == 0 {
		return geo.NewPoint(math.Inf(-1), math.Inf(-1)), geo.NewPoint(math.Inf(1), math.Inf(1)), .Finish()
	}
	,  := math.Inf(1), math.Inf(1)
	,  := math.Inf(-1), math.Inf(-1)
	for ,  := range  {
		, ,  := NodeBoundingBox(, , )
		if  != nil {
			return nil, nil, 
		}
		 = math.Min(, .X)
		 = math.Min(, .Y)
		 = math.Max(, .X)
		 = math.Max(, .Y)
	}
	if  := .Finish();  != nil {
		return nil, nil, 
	}
	return geo.NewPoint(, ), geo.NewPoint(, ), nil
}

func containerLevel( *layoutgraph.Node,  WorkStepper) (int, error) {
	 := 0
	 := make(map[*layoutgraph.Node]struct{})
	for  := ;  != nil;  = .OwningContainer() {
		if  := .Step();  != nil {
			return 0, 
		}
		if ,  := [];  {
			return 0, invariant.New("BinPack found a cycle in container ancestry")
		}
		[] = struct{}{}
		++
	}
	return , nil
}

func fixedOrigin( layoutgraph.Nodes,  WorkStepper) (*geo.Point, error) {
	var  *layoutgraph.Node
	 := math.MaxInt
	for ,  := range  {
		if  := .Step();  != nil {
			return nil, 
		}
		 := .OwningContainer()
		if  == nil {
			 = nil
			break
		}
		,  := containerLevel(, )
		if  != nil {
			return nil, 
		}
		if  <  {
			 = 
			 = 
		}
	}
	for ,  := range  {
		if  := .Step();  != nil {
			return nil, 
		}
		if .OwningContainer() !=  {
			continue
		}
		if  := .FixedOrigin();  != nil {
			return , nil
		}
	}
	return nil, .Finish()
}

// FixedBoundingBox computes node-set bounds and substitutes the inherited fixed origin
// used by packing when one of the relevant children is pinned.
func ( layoutgraph.Nodes,  WorkStepper) (*geo.Point, *geo.Point, error) {
	, ,  := BoundingBox(, )
	if  != nil {
		return nil, nil, 
	}
	,  := fixedOrigin(, )
	if  != nil {
		return nil, nil, 
	}
	if  != nil {
		 = 
	}
	return , , .Finish()
}