package d2grid

import (
	
	

	
	
)

type gridDiagram struct {
	objects []*d2graph.Object
	edges   []*d2graph.Edge
	rows    int
	columns int

	// if true, place objects left to right along rows
	// if false, place objects top to bottom along columns
	rowDirected bool

	width  float64
	height float64

	verticalGap   int
	horizontalGap int
}

func newGridDiagram( *d2graph.Object) (*gridDiagram, error) {
	 := gridDiagram{
		objects:       .ChildrenArray,
		verticalGap:   DEFAULT_GAP,
		horizontalGap: DEFAULT_GAP,
	}

	if .GridRows != nil {
		var  error
		.rows,  = strconv.Atoi(.GridRows.Value)
		if  != nil || .rows <= 0 {
			return nil, fmt.Errorf("invalid grid-rows %q", .GridRows.Value)
		}
		if .rows > d2graph.MaxGridDimension {
			return nil, fmt.Errorf("grid-rows %d exceeds the maximum of %d", .rows, d2graph.MaxGridDimension)
		}
	}
	if .GridColumns != nil {
		var  error
		.columns,  = strconv.Atoi(.GridColumns.Value)
		if  != nil || .columns <= 0 {
			return nil, fmt.Errorf("invalid grid-columns %q", .GridColumns.Value)
		}
		if .columns > d2graph.MaxGridDimension {
			return nil, fmt.Errorf("grid-columns %d exceeds the maximum of %d", .columns, d2graph.MaxGridDimension)
		}
	}
	if len(.objects) > d2graph.MaxGridCells {
		return nil, fmt.Errorf("grid object count %d exceeds the limit of %d cells", len(.objects), d2graph.MaxGridCells)
	}

	if .rows != 0 && .columns != 0 {
		,  := d2graph.GridCapacity(.rows, .columns)
		if  != nil {
			return nil, 
		}
		// . row-directed  column-directed
		// .  ┌───────┐    ┌───────┐
		// .  │ a b c │    │ a d g │
		// .  │ d e f │    │ b e h │
		// .  │ g h i │    │ c f i │
		// .  └───────┘    └───────┘
		// if keyword rows is first, make it row-directed, if columns is first it is column-directed
		if .GridRows.MapKey == nil || .GridColumns.MapKey == nil || .GridRows.MapKey.Range.Before(.GridColumns.MapKey.Range) {
			.rowDirected = true
		}

		// rows and columns specified, but we want to continue naturally if user enters more objects
		// e.g. 2 rows, 3 columns specified + g added:      │ with 3 columns, 2 rows:
		// . original  add row   add column                 │ original  add row   add column
		// . ┌───────┐ ┌───────┐ ┌─────────┐                │ ┌───────┐ ┌───────┐ ┌─────────┐
		// . │ a b c │ │ a b c │ │ a b c d │                │ │ a c e │ │ a d g │ │ a c e g │
		// . │ d e f │ │ d e f │ │ e f g   │                │ │ b d f │ │ b e   │ │ b d f   │
		// . └───────┘ │ g     │ └─────────┘                │ └───────┘ │ c f   │ └─────────┘
		// .           └───────┘ ▲                          │           └───────┘ ▲
		// .           ▲         └─existing objects modified│           ▲         └─existing columns preserved
		// .           └─existing rows preserved            │           └─existing objects modified
		if  < len(.objects) {
			if .rowDirected {
				.rows = divideRoundUp(len(.objects), .columns)
			} else {
				.columns = divideRoundUp(len(.objects), .rows)
			}
			if ,  := d2graph.GridCapacity(.rows, .columns);  != nil {
				return nil, fmt.Errorf("expanded grid: %w", )
			}
		}
	} else if .columns == 0 {
		.rowDirected = true
		// we can only make N rows with N objects
		if len(.objects) < .rows {
			.rows = len(.objects)
		}
	} else {
		if len(.objects) < .columns {
			.columns = len(.objects)
		}
	}

	// grid gap sets both, but can be overridden
	if .GridGap != nil {
		.verticalGap, _ = strconv.Atoi(.GridGap.Value)
		.horizontalGap = .verticalGap
	}
	if .VerticalGap != nil {
		.verticalGap, _ = strconv.Atoi(.VerticalGap.Value)
	}
	if .HorizontalGap != nil {
		.horizontalGap, _ = strconv.Atoi(.HorizontalGap.Value)
	}

	for ,  := range .objects {
		.TopLeft = geo.NewPoint(0, 0)
	}

	return &, nil
}

func divideRoundUp(,  int) int {
	 :=  / 
	if % != 0 {
		++
	}
	return 
}

func ( *gridDiagram) (,  float64) {
	for ,  := range .objects {
		.MoveWithDescendants(, )
	}
	for ,  := range .edges {
		.Move(, )
	}
}