package d2grid

import (
	
	
	
	

	
	
	
	
	
)

const (
	CONTAINER_PADDING = 60
	DEFAULT_GAP       = 40
)

// Layout runs the grid layout on containers with rows/columns
// Note: children are not allowed edges or descendants
// 1. Run grid layout on the graph root
// 2. Set the resulting dimensions to the graph root
func ( context.Context,  *d2graph.Graph) error {
	 := .Root

	,  := layoutGrid(, )
	if  != nil {
		return 
	}

	if .HasLabel() && .LabelPosition == nil {
		.LabelPosition = go2.Pointer(label.InsideTopCenter.String())
	}
	if .Icon != nil && .IconPosition == nil {
		.IconPosition = go2.Pointer(label.InsideTopLeft.String())
	}

	if .Box != nil {
		// CONTAINER_PADDING is default, but use gap value if set
		,  := CONTAINER_PADDING, CONTAINER_PADDING
		if .GridGap != nil || .HorizontalGap != nil {
			 = .horizontalGap
		}
		if .GridGap != nil || .VerticalGap != nil {
			 = .verticalGap
		}

		,  := .width, .height

		var ,  label.Position
		if .LabelPosition != nil {
			 = label.FromString(*.LabelPosition)
		}
		if .IconPosition != nil {
			 = label.FromString(*.IconPosition)
		}

		// compute how much space the label and icon occupy
		,  := .Spacing()

		var ,  float64
		if .LabelDimensions.Width > 0 {
			 = float64(.LabelDimensions.Width) + 2*label.PADDING
		}
		if .LabelDimensions.Height > 0 {
			 = float64(.LabelDimensions.Height) + 2*label.PADDING
		}

		if  > 0 {
			switch  {
			case label.OutsideTopLeft, label.OutsideTopCenter, label.OutsideTopRight,
				label.InsideTopLeft, label.InsideTopCenter, label.InsideTopRight,
				label.InsideBottomLeft, label.InsideBottomCenter, label.InsideBottomRight,
				label.OutsideBottomLeft, label.OutsideBottomCenter, label.OutsideBottomRight:
				 :=  - 
				if  > 0 {
					.Left +=  / 2
					.Right +=  / 2
				}
			}
		}
		if  > 0 {
			switch  {
			case label.OutsideLeftTop, label.OutsideLeftMiddle, label.OutsideLeftBottom,
				label.InsideMiddleLeft, label.InsideMiddleCenter, label.InsideMiddleRight,
				label.OutsideRightTop, label.OutsideRightMiddle, label.OutsideRightBottom:
				 :=  - 
				if  > 0 {
					.Top +=  / 2
					.Bottom +=  / 2
				}
			}
		}

		// configure spacing for default label+icon
		if  == label.InsideTopLeft &&  == label.InsideTopCenter {
			// . ├────┤───────├────┤
			// .  icon  label  icon
			// with an icon in top left we need 2x the space to fit the label in the center
			 := float64(d2target.MAX_ICON_SIZE) + 2*label.PADDING
			.Left = math.Max(.Left, )
			.Right = math.Max(.Right, )
			 := 2* + float64(.LabelDimensions.Width) + 2*label.PADDING
			 :=  - 
			if  > 0 {
				.Left = math.Max(.Left, /2)
				.Right = math.Max(.Right, /2)
			}
		}

		.Top = math.Max(.Top, float64())
		.Bottom = math.Max(.Bottom, float64())
		.Left = math.Max(.Left, float64())
		.Right = math.Max(.Right, float64())

		 := .Left +  + .Right
		 := .Top +  + .Bottom
		.SizeToContent(, , 0, 0)

		// compute where the grid should be placed inside shape
		 := .ToShape()
		 := .GetInsidePlacement(, , 0, 0)
		// depending on the shape innerBox may be larger than totalWidth, totalHeight
		// if this is the case, we want to center the cells within the larger innerBox
		 := .GetInnerBox()
		var ,  float64
		if .Width >  {
			 = (.Width - ) / 2
		}
		if .Height >  {
			 = (.Height - ) / 2
		}

		// move from horizontalPadding,verticalPadding to innerTL.X+padding.Left, innerTL.Y+padding.Top
		// and if innerBox is larger than content dimensions, adjust to center within innerBox
		 := -float64() + .X + .Left + 
		 := -float64() + .Y + .Top + 
		if  != 0 ||  != 0 {
			.shift(, )
		}
	}

	// simple straight line edge routing between grid objects
	for ,  := range .Edges {
		if !.Src.Parent.IsDescendantOf() && !.Dst.Parent.IsDescendantOf() {
			continue
		}
		// if edge is within grid, remove it from outer layout
		.edges = append(.edges, )

		if .Src.Parent !=  || .Dst.Parent !=  {
			continue
		}
		// if edge is grid child, use simple routing
		.Route = []*geo.Point{.Src.Center(), .Dst.Center()}
		.TraceToShape(.Route, 0, 1)
		if .Label.Value != "" {
			.LabelPosition = go2.Pointer(label.InsideMiddleCenter.String())
		}
	}

	if .Root.IsGridDiagram() && len(.Root.ChildrenArray) != 0 {
		.Root.TopLeft = geo.NewPoint(0, 0)
	}

	if .RootLevel > 0 {
		,  := CONTAINER_PADDING, CONTAINER_PADDING
		if .GridGap != nil || .HorizontalGap != nil {
			 = .horizontalGap
		}
		if .GridGap != nil || .VerticalGap != nil {
			 = .verticalGap
		}

		// shift the grid from (0, 0)
		.shift(
			.TopLeft.X+float64(),
			.TopLeft.Y+float64(),
		)
	}
	return nil
}

func layoutGrid( *d2graph.Graph,  *d2graph.Object) (*gridDiagram, error) {
	 := newGridDiagram()

	// position labels and icons
	for ,  := range .objects {
		 := false
		if .Icon != nil && .IconPosition == nil {
			if len(.ChildrenArray) > 0 {
				.IconPosition = go2.Pointer(label.OutsideTopLeft.String())
				// don't overwrite position if nested graph layout positioned label/icon
				if .LabelPosition == nil {
					.LabelPosition = go2.Pointer(label.OutsideTopRight.String())
					 = true
				}
			} else {
				.IconPosition = go2.Pointer(label.InsideMiddleCenter.String())
			}
		}
		if ! && .HasLabel() && .LabelPosition == nil {
			if len(.ChildrenArray) > 0 {
				.LabelPosition = go2.Pointer(label.OutsideTopCenter.String())
			} else if .HasOutsideBottomLabel() {
				.LabelPosition = go2.Pointer(label.OutsideBottomCenter.String())
			} else if .Icon != nil {
				.LabelPosition = go2.Pointer(label.InsideTopCenter.String())
			} else {
				.LabelPosition = go2.Pointer(label.InsideMiddleCenter.String())
			}
		}
	}

	// to handle objects with outside labels, we adjust their dimensions before layout and
	// after layout, we remove the label adjustment and reposition TopLeft if needed
	 := .sizeForOutsideLabels()

	if .rows != 0 && .columns != 0 {
		.layoutEvenly(, )
	} else {
		.layoutDynamic(, )
	}

	()

	return , nil
}

func ( *gridDiagram) ( *d2graph.Graph,  *d2graph.Object) {
	// layout objects in a grid with these 2 properties:
	// all objects in the same row should have the same height
	// all objects in the same column should have the same width

	 := func(,  int) *d2graph.Object {
		var  int
		if .rowDirected {
			 = *.columns + 
		} else {
			 = *.rows + 
		}
		if  < len(.objects) {
			return .objects[]
		}
		return nil
	}

	 := make([]float64, 0, .rows)
	 := make([]float64, 0, .columns)
	for  := 0;  < .rows; ++ {
		 := 0.
		for  := 0;  < .columns; ++ {
			 := (, )
			if  == nil {
				break
			}
			 = math.Max(, .Height)
		}
		 = append(, )
	}
	for  := 0;  < .columns; ++ {
		 := 0.
		for  := 0;  < .rows; ++ {
			 := (, )
			if  == nil {
				break
			}
			 = math.Max(, .Width)
		}
		 = append(, )
	}

	 := float64(.horizontalGap)
	 := float64(.verticalGap)

	 := geo.NewPoint(0, 0)
	if .rowDirected {
		for  := 0;  < .rows; ++ {
			for  := 0;  < .columns; ++ {
				 := (, )
				if  == nil {
					break
				}
				.Width = []
				.Height = []
				.MoveWithDescendantsTo(.X, .Y)
				.X += .Width + 
			}
			.X = 0
			.Y += [] + 
		}
	} else {
		for  := 0;  < .columns; ++ {
			for  := 0;  < .rows; ++ {
				 := (, )
				if  == nil {
					break
				}
				.Width = []
				.Height = []
				.MoveWithDescendantsTo(.X, .Y)
				.Y += .Height + 
			}
			.X += [] + 
			.Y = 0
		}
	}

	var ,  float64
	for ,  := range  {
		 +=  + 
	}
	for ,  := range  {
		 +=  + 
	}
	 -= 
	 -= 
	.width = 
	.height = 
}

func ( *gridDiagram) ( *d2graph.Graph,  *d2graph.Object) {
	// assume we have the following objects to layout:
	// . ┌A──────────────┐  ┌B──┐  ┌C─────────┐  ┌D────────┐  ┌E────────────────┐
	// . └───────────────┘  │   │  │          │  │         │  │                 │
	// .                    │   │  └──────────┘  │         │  │                 │
	// .                    │   │                │         │  └─────────────────┘
	// .                    └───┘                │         │
	// .                                         └─────────┘
	// Note: if the grid is row dominant, all objects should be the same height (same width if column dominant)
	// . ┌A─────────────┐  ┌B──┐  ┌C─────────┐  ┌D────────┐  ┌E────────────────┐
	// . ├ ─ ─ ─ ─ ─ ─ ─┤  │   │  │          │  │         │  │                 │
	// . │              │  │   │  ├ ─ ─ ─ ─ ─┤  │         │  │                 │
	// . │              │  │   │  │          │  │         │  ├ ─ ─ ─ ─ ─ ─ ─ ─ ┤
	// . │              │  ├ ─ ┤  │          │  │         │  │                 │
	// . └──────────────┘  └───┘  └──────────┘  └─────────┘  └─────────────────┘

	 := float64(.horizontalGap)
	 := float64(.verticalGap)

	// we want to split up the total width across the N rows or columns as evenly as possible
	var ,  float64
	for ,  := range .objects {
		 += .Width
		 += .Height
	}
	 +=  * float64(len(.objects)-.rows)
	 +=  * float64(len(.objects)-.columns)

	var  [][]*d2graph.Object
	if .rowDirected {
		 :=  / float64(.rows)
		 = .getBestLayout(, false)
	} else {
		 :=  / float64(.columns)
		 = .getBestLayout(, true)
	}

	 := geo.NewPoint(0, 0)
	var ,  float64
	if .rowDirected {
		// measure row widths
		 := []float64{}
		for ,  := range  {
			 := 0.
			for ,  := range  {
				 += .Width + 
			}
			 :=  - 
			 = append(, )
			 = math.Max(, )
		}

		// TODO if object is a nested grid, consider growing descendants according to the inner grid layout

		// then expand thinnest objects to make each row the same width
		// . ┌A─────────────┐  ┌B──┐  ┌C─────────┐ ┬ maxHeight(A,B,C)
		// . │              │  │   │  │          │ │
		// . │              │  │   │  │          │ │
		// . │              │  │   │  │          │ │
		// . └──────────────┘  └───┘  └──────────┘ ┴
		// . ┌D────────┬────┐  ┌E────────────────┐ ┬ maxHeight(D,E)
		// . │              │  │                 │ │
		// . │         │    │  │                 │ │
		// . │              │  │                 │ │
		// . │         │    │  │                 │ │
		// . └─────────┴────┘  └─────────────────┘ ┴
		for ,  := range  {
			 := []
			if  ==  {
				continue
			}
			 :=  - 
			var  float64
			for ,  := range  {
				 = math.Max(, .Width)
			}
			 := make([]float64, len())
			 := 0.
			for ,  := range  {
				[] =  - .Width
				 += []
			}
			if  > 0 {
				// expand smaller nodes up to the size of the larger ones with delta
				// percentage diff
				for  := range  {
					[] /= 
				}
				 := math.Min(, )
				// expand smaller objects to fill remaining space
				for ,  := range  {
					.Width += [] * 
				}
			}
			if  >  {
				 := ( - ) / float64(len())
				for ,  := range  {
					.Width += 
				}
			}
		}

		// if we have 2 rows, then each row's objects should have the same height
		// . ┌A─────────────┐  ┌B──┐  ┌C─────────┐ ┬ maxHeight(A,B,C)
		// . ├ ─ ─ ─ ─ ─ ─ ─┤  │   │  │          │ │
		// . │              │  │   │  ├ ─ ─ ─ ─ ─┤ │
		// . │              │  │   │  │          │ │
		// . └──────────────┘  └───┘  └──────────┘ ┴
		// . ┌D────────┐  ┌E────────────────┐ ┬ maxHeight(D,E)
		// . │         │  │                 │ │
		// . │         │  │                 │ │
		// . │         │  ├ ─ ─ ─ ─ ─ ─ ─ ─ ┤ │
		// . │         │  │                 │ │
		// . └─────────┘  └─────────────────┘ ┴
		for ,  := range  {
			 := 0.
			for ,  := range  {
				.MoveWithDescendantsTo(.X, .Y)
				.X += .Width + 
				 = math.Max(, .Height)
			}

			// set all objects in row to the same height
			for ,  := range  {
				.Height = 
			}

			// new row
			.X = 0
			.Y +=  + 
		}
		 = .Y - 
	} else {
		// measure column heights
		 := []float64{}
		for ,  := range  {
			 := 0.
			for ,  := range  {
				 += .Height + 
			}
			 :=  - 
			 = append(, )
			 = math.Max(, )
		}

		// then expand shortest objects to make each column the same height
		// . ├maxWidth(A,B)─┤  ├maxW(C,D)─┤  ├maxWidth(E)──────┤
		// . ┌A─────────────┐  ┌C─────────┐  ┌E────────────────┐
		// . ├ ─ ─ ─ ─ ─ ─  ┤  │          │  │                 │
		// . │              │  └──────────┘  │                 │
		// . └──────────────┘  ┌D─────────┐  ├ ─ ─ ─ ─ ─ ─ ─ ─ ┤
		// . ┌B─────────────┐  │          │  │                 │
		// . │              │  │          │  │                 │
		// . │              │  │          │  │                 │
		// . │              │  │          │  │                 │
		// . └──────────────┘  └──────────┘  └─────────────────┘
		for ,  := range  {
			 := []
			if  ==  {
				continue
			}
			 :=  - 
			var  float64
			for ,  := range  {
				 = math.Max(, .Height)
			}
			 := make([]float64, len())
			 := 0.
			for ,  := range  {
				[] =  - .Height
				 += []
			}
			if  > 0 {
				// expand smaller nodes up to the size of the larger ones with delta
				// percentage diff
				for  := range  {
					[] /= 
				}
				 := math.Min(, )
				// expand smaller objects to fill remaining space
				for ,  := range  {
					.Height += [] * 
				}
			}
			if  >  {
				 := ( - ) / float64(len())
				for ,  := range  {
					.Height += 
				}
			}
		}
		// if we have 3 columns, then each column's objects should have the same width
		// . ├maxWidth(A,B)─┤  ├maxW(C,D)─┤  ├maxWidth(E)──────┤
		// . ┌A─────────────┐  ┌C─────────┐  ┌E────────────────┐
		// . └──────────────┘  │          │  │                 │
		// . ┌B──┬──────────┐  └──────────┘  │                 │
		// . │              │  ┌D────────┬┐  └─────────────────┘
		// . │   │          │  │          │
		// . │              │  │         ││
		// . └───┴──────────┘  │          │
		// .                   │         ││
		// .                   └─────────┴┘
		for ,  := range  {
			 := 0.
			for ,  := range  {
				.MoveWithDescendantsTo(.X, .Y)
				.Y += .Height + 
				 = math.Max(, .Width)
			}
			// set all objects in column to the same width
			for ,  := range  {
				.Width = 
			}

			// new column
			.Y = 0
			.X +=  + 
		}
		 = .X - 
	}
	.width = 
	.height = 
}

// generate the best layout of objects aiming for each row to be the targetSize width
// if columns is true, each column aims to have the targetSize height
func ( *gridDiagram) ( float64,  bool) [][]*d2graph.Object {
	 := false
	var  int
	if  {
		 = .columns - 1
	} else {
		 = .rows - 1
	}
	if  == 0 {
		return GenLayout(.objects, nil)
	}

	var  [][]*d2graph.Object
	 := math.MaxFloat64
	 := false
	// try fast layout algorithm as a baseline
	if  := .fastLayout(, , );  != nil {
		 := getDistToTarget(, , float64(.horizontalGap), float64(.verticalGap), )
		if  {
			fmt.Printf("fast dist %v dist per row %v\n", , /(float64()+1))
		}
		if  == 0 {
			return 
		}
		 = 
		 = 
		 = true
	}

	var  float64
	if  {
		 = float64(.verticalGap)
	} else {
		 = float64(.horizontalGap)
	}
	 := func( *d2graph.Object) float64 {
		if  {
			return .Height
		} else {
			return .Width
		}
	}

	 := []float64{}
	for ,  := range .objects {
		 := ()
		 = append(, )
	}
	 := stddev()
	if  {
		fmt.Printf("sizes (%d): %v\n", len(), )
		fmt.Printf("std dev: %v; targetSize %v\n", , )
	}

	 := 0
	 := 0
	// quickly eliminate bad row groupings
	 := make(map[int]bool)
	// Note: we want a low threshold to explore good options within attemptLimit,
	// but the best option may require a few rows that are far from the target size.
	 := STARTING_THRESHOLD
	 := func( []*d2graph.Object,  bool) ( bool) {
		if  {
			// we can cache results from starting positions since they repeat and don't change
			// with starting=true it will always be the 1st N objects based on len(row)
			if ,  := [len()];  {
				return 
			}
			defer func() {
				// cache result before returning
				[len()] = 
			}()
		}

		 := 0.
		for ,  := range  {
			 += ()
		}
		if len() > 1 {
			 +=  * float64(len()-1)
			// if multiple nodes are too big, it isn't ok. but a single node can't shrink so only check here
			if  > * {
				++
				// there may even be too many to skip
				return  >= SKIP_LIMIT
			}
		}
		// row is too small to be good overall
		if  < / {
			++
			return  >= SKIP_LIMIT
		}
		return true
	}

	// get all options for where to place these cuts, preferring later cuts over earlier cuts
	// with 5 objects and 2 cuts we have these options:
	// .       A   B   C │ D │ E     <- these cuts would produce: ┌A─┐ ┌B─┐ ┌C─┐
	// .       A   B │ C   D │ E                                  └──┘ └──┘ └──┘
	// .       A │ B   C   D │ E                                  ┌D───────────┐
	// .       A   B │ C │ D   E                                  └────────────┘
	// .       A │ B   C │ D   E                                  ┌E───────────┐
	// .       A │ B │ C   D   E                                  └────────────┘
	// of these divisions, find the layout with rows closest to the targetSize
	 := func( []int) bool {
		 := GenLayout(.objects, )
		 := getDistToTarget(, , float64(.horizontalGap), float64(.verticalGap), )
		if  <  {
			 = 
			 = 
			 = false
		} else if  &&  ==  {
			// prefer ordered search solution to fast layout solution
			 = 
			 = false
		}
		++
		// with few objects we can try all options to get best result but this won't scale, so only try up to 100k options
		return  >= ATTEMPT_LIMIT ||  >= SKIP_LIMIT
	}

	// try number of different okThresholds depending on std deviation of sizes
	 := int(math.Ceil())
	if  < MIN_THRESHOLD_ATTEMPTS {
		 = MIN_THRESHOLD_ATTEMPTS
	} else if  > MAX_THRESHOLD_ATTEMPTS {
		 = MAX_THRESHOLD_ATTEMPTS
	}
	for  := 0;  <  ||  == nil; ++ {
		 = 0.
		 = 0.
		iterDivisions(.objects, , , )
		 += THRESHOLD_STEP_SIZE
		if  {
			fmt.Printf("count %d, skip count %d, bestDist %v increasing ok threshold to %v\n", , , , )
		}
		 = make(map[int]bool)
		if  == 0 {
			// threshold isn't skipping anything so increasing it won't help
			break
		}
		// okThreshold isn't high enough yet, we skipped every option so don't count it
		if  == 0 &&  < MAX_THRESHOLD_ATTEMPTS {
			++
		}
	}

	if  {
		fmt.Printf("best layout: %v\n", layoutString(, ))
	}
	return 
}

func sum( []float64) float64 {
	 := 0.
	for ,  := range  {
		 += 
	}
	return 
}

func avg( []float64) float64 {
	return sum() / float64(len())
}

func variance( []float64) float64 {
	 := avg()
	 := 0.
	for ,  := range  {
		 :=  - 
		 +=  * 
	}
	return  / float64(len())
}

func stddev( []float64) float64 {
	return math.Sqrt(variance())
}

func ( *gridDiagram) ( float64,  int,  bool) ( [][]*d2graph.Object) {
	var  float64
	if  {
		 = float64(.verticalGap)
	} else {
		 = float64(.horizontalGap)
	}

	 := 0.
	 := make([]int, 0, )
	 := 0.
	for  := 0;  < len(.objects); ++ {
		 := .objects[]
		var  float64
		if  {
			 = .Height
		} else {
			 = .Width
		}
		if  == 0 {
			// if a single object meets the target size, end the row here
			if  > - {
				// cut row with just this object
				 = append(, )
				// we build up a debt of distance past the target size across rows
				 :=  - 
				 += 
			} else {
				 += 
			}
			continue
		}
		// debt is paid by decreasing threshold to start new row and ending below targetSize
		if ++()/2. > - {
			// start a new row before this object since it is mostly past the target size
			// .              size
			// ├...row─┼gap┼───┼───┤
			// ├──targetSize──┤ (debt=0)
			 = append(, -1)
			 :=  - 
			 += 
			 = 
		} else {
			 +=  + 
		}
	}
	if len() ==  {
		 = GenLayout(.objects, )
	}

	return 
}

func layoutString( [][]*d2graph.Object,  []float64) string {
	 := &bytes.Buffer{}
	 := 0
	fmt.Fprintf(, "[\n")
	for ,  := range  {
		 := [ : +len()]
		fmt.Fprintf(, "%v:\t%v\n", sum(), )
		 += len()
	}
	fmt.Fprintf(, "]\n")
	return .String()
}

// process current division, return true to stop iterating
type iterDivision func(division []int) (done bool)
type checkCut func(objects []*d2graph.Object, starting bool) (ok bool)

// get all possible divisions of objects by the number of cuts
func iterDivisions( []*d2graph.Object,  int,  iterDivision,  checkCut) {
	if len() < 2 ||  == 0 {
		return
	}
	 := false
	// we go in this order to prefer extra objects in starting rows rather than later ones
	 := len() - 1
	// with objects=[A, B, C, D, E]; nCuts=2
	// d:depth; i:index; n:nCuts;
	// ┌────┬───┬───┬─────────────────────┬────────────┐
	// │ d  │ i │ n │ objects             │ cuts       │
	// ├────┼───┼───┼─────────────────────┼────────────┤
	// │ 0  │ 4 │ 2 │ [A   B   C   D | E] │            │
	// ├────┼───┼───┼─────────────────────┼────────────┤
	// │ └1 │ 3 │ 1 │ [A   B   C | D]     │ + | E]     │
	// ├────┼───┼───┼─────────────────────┼────────────┤
	// │ └1 │ 2 │ 1 │ [A   B | C   D]     │ + | E]     │
	// ├────┼───┼───┼─────────────────────┼────────────┤
	// │ └1 │ 1 │ 1 │ [A | B   C   D]     │ + | E]     │
	// ├────┼───┼───┼─────────────────────┼────────────┤
	// │ 0  │ 3 │ 2 │ [A   B   C | D   E] │            │
	// ├────┼───┼───┼─────────────────────┼────────────┤
	// │ └1 │ 2 │ 1 │ [A   B | C]         │ + | D E]   │
	// ├────┼───┼───┼─────────────────────┼────────────┤
	// │ └1 │ 1 │ 1 │ [A | B   C]         │ + | D E]   │
	// ├────┼───┼───┼─────────────────────┼────────────┤
	// │ 0  │ 2 │ 2 │ [A   B | C   D   E] │            │
	// ├────┼───┼───┼─────────────────────┼────────────┤
	// │ └1 │ 1 │ 1 │ [A | B]             │ + | C D E] │
	// └────┴───┴───┴─────────────────────┴────────────┘
	for  := ;  >= ; -- {
		if !([:], false) {
			// optimization: if current cut gives a bad grouping, don't recurse
			continue
		}
		if  > 1 {
			([:], -1, func( []int) bool {
				 = (append(, -1))
				return 
			}, )
		} else {
			if !([:], true) {
				// e.g. [A   B   C | D] if [A,B,C] is bad, skip it
				continue
			}
			 = ([]int{ - 1})
		}
		if  {
			return
		}
	}
}

// generate a grid of objects from the given cut indices
// each cut index applies after the object at that index
// e.g. [0 1 2 3 4 5 6 7] with cutIndices [0, 2, 6] => [[0], [1, 2], [3,4,5,6], [7]]
func ( []*d2graph.Object,  []int) [][]*d2graph.Object {
	 := make([][]*d2graph.Object, len()+1)
	 := 0
	for  := 0;  <= len(); ++ {
		var  int
		if  < len() {
			 = []
		} else {
			 = len() - 1
		}
		if  >=  {
			[] = make([]*d2graph.Object, 0, -+1)
		}
		for ;  <= ; ++ {
			[] = append([], [])
		}
	}
	return 
}

func getDistToTarget( [][]*d2graph.Object,  float64, ,  float64,  bool) float64 {
	 := 0.
	for ,  := range  {
		 := 0.
		for ,  := range  {
			if  {
				 += .Height + 
			} else {
				 += .Width + 
			}
		}
		if len() > 0 {
			if  {
				 -= 
			} else {
				 -= 
			}
		}
		 += math.Abs( - )
	}
	return 
}

func ( *gridDiagram) () ( func()) {
	 := make(map[*d2graph.Object]geo.Spacing)

	for ,  := range .objects {
		 := .GetMargin()
		[] = 

		.Height += .Top + .Bottom
		.Width += .Left + .Right
	}

	// Example: a single column with 3 shapes and
	// `x.label: long label {near: outside-bottom-left}`
	// `y.label: outsider {near: outside-right-center}`
	// . ┌───────────────────┐
	// . │ widest shape here │
	// . └───────────────────┘
	// . ┌───┐
	// . │ x │
	// . └───┘
	// . long label
	// . ├─────────┤ x's new width
	// .     ├─mr──┤ margin.right added to width during layout
	// . ┌───┐
	// . │ y │ outsider
	// . └───┘
	// . ├─────────────┤ y's new width
	// .     ├───mr────┤ margin.right added to width during layout

	// BEFORE LAYOUT
	// . ┌───────────────────┐
	// . │ widest shape here │
	// . └───────────────────┘
	// . ┌─────────┐
	// . │ x       │
	// . └─────────┘
	// . ┌─────────────┐
	// . │ y           │
	// . └─────────────┘

	// AFTER LAYOUT
	// . ┌───────────────────┐
	// . │ widest shape here │
	// . └───────────────────┘
	// . ┌───────────────────┐
	// . │ x                 │
	// . └───────────────────┘
	// . ┌───────────────────┐
	// . │ y                 │
	// . └───────────────────┘

	// CLEANUP 1/2
	// . ┌───────────────────┐
	// . │ widest shape here │
	// . └───────────────────┘
	// . ┌─────────────┐
	// . │ x           │
	// . └─────────────┘
	// . long label    ├─mr──┤ remove margin we added
	// . ┌─────────┐
	// . │ y       │ outsider
	// . └─────────┘
	// .           ├───mr────┤ remove margin we added
	// CLEANUP 2/2
	// . ┌───────────────────┐
	// . │ widest shape here │
	// . └───────────────────┘
	// . ┌───────────────────┐
	// . │ x                 │
	// . └───────────────────┘
	// . long label    ├─mr──┤ we removed too much so add back margin we subtracted, then subtract new margin
	// . ┌─────────┐
	// . │ y       │ outsider
	// . └─────────┘
	// .           ├───mr────┤ margin.right is still needed

	return func() {
		for ,  := range .objects {
			,  := []
			if ! {
				continue
			}
			 := .Top + .Bottom
			 := .Left + .Right
			.Height -= 
			.Width -= 

			// less margin may be needed if layout grew the object
			// compute the new margin after removing the old margin we added
			 := .GetMargin()
			 := .Left + .Right
			 := .Top + .Bottom
			if  <  {
				// layout grew width and now we need less of a margin (but we subtracted too much)
				// add back dx and subtract the new amount
				.Width +=  - 
			}
			if  <  {
				.Height +=  - 
			}

			if .Left > 0 || .Top > 0 {
				.MoveWithDescendants(.Left, .Top)
			}
		}
	}
}