package cview

import (
	
	

	
)

// gridItem represents one primitive and its possible position on a grid.
type gridItem struct {
	Item                        Primitive // The item to be positioned. May be nil for an empty item.
	Row, Column                 int       // The top-left grid cell where the item is placed.
	Width, Height               int       // The number of rows and columns the item occupies.
	MinGridWidth, MinGridHeight int       // The minimum grid width/height for which this item is visible.
	Focus                       bool      // Whether or not this item attracts the layout's focus.

	visible    bool // Whether or not this item was visible the last time the grid was drawn.
	x, y, w, h int  // The last position of the item relative to the top-left corner of the grid. Undefined if visible is false.
}

// Grid is an implementation of a grid-based layout. It works by defining the
// size of the rows and columns, then placing primitives into the grid.
//
// Some settings can lead to the grid exceeding its available space. SetOffset()
// can then be used to scroll in steps of rows and columns. These offset values
// can also be controlled with the arrow keys (or the "g","G", "j", "k", "h",
// and "l" keys) while the grid has focus and none of its contained primitives
// do.
type Grid struct {
	*Box

	// The items to be positioned.
	items []*gridItem

	// The definition of the rows and columns of the grid. See
	// SetRows()/SetColumns() for details.
	rows, columns []int

	// The minimum sizes for rows and columns.
	minWidth, minHeight int

	// The size of the gaps between neighboring primitives. This is automatically
	// set to 1 if borders is true.
	gapRows, gapColumns int

	// The number of rows and columns skipped before drawing the top-left corner
	// of the grid.
	rowOffset, columnOffset int

	// Whether or not borders are drawn around grid items. If this is set to true,
	// a gap size of 1 is automatically assumed (which is filled with the border
	// graphics).
	borders bool

	// The color of the borders around grid items.
	bordersColor tcell.Color

	sync.RWMutex
}

// NewGrid returns a new grid-based layout container with no initial primitives.
//
// Note that Grid will have a transparent background by default so that any
// areas not covered by any primitives will show primitives behind the Grid.
// To disable this transparency:
//
//	grid.SetBackgroundTransparent(false)
func () *Grid {
	 := &Grid{
		Box:          NewBox(),
		bordersColor: Styles.GraphicsColor,
	}
	.SetBackgroundTransparent(true)
	.focus = 
	return 
}

// SetColumns defines how the columns of the grid are distributed. Each value
// defines the size of one column, starting with the leftmost column. Values
// greater 0 represent absolute column widths (gaps not included). Values less
// or equal 0 represent proportional column widths or fractions of the remaining
// free space, where 0 is treated the same as -1. That is, a column with a value
// of -3 will have three times the width of a column with a value of -1 (or 0).
// The minimum width set with SetMinSize() is always observed.
//
// Primitives may extend beyond the columns defined explicitly with this
// function. A value of 0 is assumed for any undefined column. In fact, if you
// never call this function, all columns occupied by primitives will have the
// same width. On the other hand, unoccupied columns defined with this function
// will always take their place.
//
// Assuming a total width of the grid of 100 cells and a minimum width of 0, the
// following call will result in columns with widths of 30, 10, 15, 15, and 30
// cells:
//
//	grid.SetColumns(30, 10, -1, -1, -2)
//
// If a primitive were then placed in the 6th and 7th column, the resulting
// widths would be: 30, 10, 10, 10, 20, 10, and 10 cells.
//
// If you then called SetMinSize() as follows:
//
//	grid.SetMinSize(15, 20)
//
// The resulting widths would be: 30, 15, 15, 15, 20, 15, and 15 cells, a total
// of 125 cells, 25 cells wider than the available grid width.
func ( *Grid) ( ...int) {
	.Lock()
	defer .Unlock()

	.columns = 
}

// SetRows defines how the rows of the grid are distributed. These values behave
// the same as the column values provided with SetColumns(), see there for a
// definition and examples.
//
// The provided values correspond to row heights, the first value defining
// the height of the topmost row.
func ( *Grid) ( ...int) {
	.Lock()
	defer .Unlock()

	.rows = 
}

// SetSize is a shortcut for SetRows() and SetColumns() where all row and column
// values are set to the given size values. See SetColumns() for details on sizes.
func ( *Grid) (, , ,  int) {
	.Lock()
	defer .Unlock()

	.rows = make([]int, )
	for  := range .rows {
		.rows[] = 
	}
	.columns = make([]int, )
	for  := range .columns {
		.columns[] = 
	}
}

// SetMinSize sets an absolute minimum width for rows and an absolute minimum
// height for columns. Panics if negative values are provided.
func ( *Grid) (,  int) {
	.Lock()
	defer .Unlock()

	if  < 0 ||  < 0 {
		panic("Invalid minimum row/column size")
	}
	.minHeight, .minWidth = , 
}

// SetGap sets the size of the gaps between neighboring primitives on the grid.
// If borders are drawn (see SetBorders()), these values are ignored and a gap
// of 1 is assumed. Panics if negative values are provided.
func ( *Grid) (,  int) {
	.Lock()
	defer .Unlock()

	if  < 0 ||  < 0 {
		panic("Invalid gap size")
	}
	.gapRows, .gapColumns = , 
}

// SetBorders sets whether or not borders are drawn around grid items. Setting
// this value to true will cause the gap values (see SetGap()) to be ignored and
// automatically assumed to be 1 where the border graphics are drawn.
func ( *Grid) ( bool) {
	.Lock()
	defer .Unlock()

	.borders = 
}

// SetBordersColor sets the color of the item borders.
func ( *Grid) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.bordersColor = 
}

// AddItem adds a primitive and its position to the grid. The top-left corner
// of the primitive will be located in the top-left corner of the grid cell at
// the given row and column and will span "rowSpan" rows and "colSpan" columns.
// For example, for a primitive to occupy rows 2, 3, and 4 and columns 5 and 6:
//
//	grid.AddItem(p, 2, 5, 3, 2, 0, 0, true)
//
// If rowSpan or colSpan is 0, the primitive will not be drawn.
//
// You can add the same primitive multiple times with different grid positions.
// The minGridWidth and minGridHeight values will then determine which of those
// positions will be used. This is similar to CSS media queries. These minimum
// values refer to the overall size of the grid. If multiple items for the same
// primitive apply, the one that has at least one highest minimum value will be
// used, or the primitive added last if those values are the same. Example:
//
//	grid.AddItem(p, 0, 0, 0, 0, 0, 0, true). // Hide in small grids.
//	  AddItem(p, 0, 0, 1, 2, 100, 0, true).  // One-column layout for medium grids.
//	  AddItem(p, 1, 1, 3, 2, 300, 0, true)   // Multi-column layout for large grids.
//
// To use the same grid layout for all sizes, simply set minGridWidth and
// minGridHeight to 0.
//
// If the item's focus is set to true, it will receive focus when the grid
// receives focus. If there are multiple items with a true focus flag, the last
// visible one that was added will receive focus.
func ( *Grid) ( Primitive, , , , , ,  int,  bool) {
	.Lock()
	defer .Unlock()

	.items = append(.items, &gridItem{
		Item:          ,
		Row:           ,
		Column:        ,
		Height:        ,
		Width:         ,
		MinGridHeight: ,
		MinGridWidth:  ,
		Focus:         ,
	})
}

func ( *Grid) ( Primitive) bool {
	for ,  := range .items {
		if .Item ==  {
			return true
		}
	}

	return false
}

func ( *Grid) ( Primitive, , , , , ,  int,  bool) {
	.Lock()
	defer .Unlock()

	for ,  := range .items {
		if .Item !=  {
			continue
		}

		.Row = 
		.Column = 
		.Height = 
		.Width = 
		.MinGridHeight = 
		.MinGridWidth = 
		.Focus = 
		break
	}
}

// RemoveItem removes all items for the given primitive from the grid, keeping
// the order of the remaining items intact.
func ( *Grid) ( Primitive) {
	.Lock()
	defer .Unlock()

	for  := len(.items) - 1;  >= 0; -- {
		if .items[].Item ==  {
			.items = append(.items[:], .items[+1:]...)
		}
	}
}

// Clear removes all items from the grid.
func ( *Grid) () {
	.Lock()
	defer .Unlock()

	.items = nil
}

// SetOffset sets the number of rows and columns which are skipped before
// drawing the first grid cell in the top-left corner. As the grid will never
// completely move off the screen, these values may be adjusted the next time
// the grid is drawn. The actual position of the grid may also be adjusted such
// that contained primitives that have focus remain visible.
func ( *Grid) (,  int) {
	.Lock()
	defer .Unlock()

	.rowOffset, .columnOffset = , 
}

// GetOffset returns the current row and column offset (see SetOffset() for
// details).
func ( *Grid) () (,  int) {
	.RLock()
	defer .RUnlock()

	return .rowOffset, .columnOffset
}

// Focus is called when this primitive receives focus.
func ( *Grid) ( func( Primitive)) {
	.Lock()
	 := .items
	.Unlock()

	for ,  := range  {
		if .Focus {
			(.Item)
			return
		}
	}

	.Lock()
	.hasFocus = true
	.Unlock()
}

// Blur is called when this primitive loses focus.
func ( *Grid) () {
	.Lock()
	defer .Unlock()

	.hasFocus = false
}

// HasFocus returns whether or not this primitive has focus.
func ( *Grid) () bool {
	.RLock()
	defer .RUnlock()

	for ,  := range .items {
		if .visible && .Item.GetFocusable().HasFocus() {
			return true
		}
	}
	return .hasFocus
}

// InputHandler returns the handler for this primitive.
func ( *Grid) () func( *tcell.EventKey,  func( Primitive)) {
	return .WrapInputHandler(func( *tcell.EventKey,  func( Primitive)) {
		.Lock()
		defer .Unlock()

		if HitShortcut(, Keys.MoveFirst, Keys.MoveFirst2) {
			.rowOffset, .columnOffset = 0, 0
		} else if HitShortcut(, Keys.MoveLast, Keys.MoveLast2) {
			.rowOffset = math.MaxInt32
		} else if HitShortcut(, Keys.MoveUp, Keys.MoveUp2, Keys.MovePreviousField) {
			.rowOffset--
		} else if HitShortcut(, Keys.MoveDown, Keys.MoveDown2, Keys.MoveNextField) {
			.rowOffset++
		} else if HitShortcut(, Keys.MoveLeft, Keys.MoveLeft2) {
			.columnOffset--
		} else if HitShortcut(, Keys.MoveRight, Keys.MoveRight2) {
			.columnOffset++
		}
	})
}

// Draw draws this primitive onto the screen.
func ( *Grid) ( tcell.Screen) {
	if !.GetVisible() {
		return
	}

	.Box.Draw()

	.Lock()
	defer .Unlock()

	, , ,  := .GetInnerRect()
	,  := .Size()

	// Make a list of items which apply.
	 := make(map[Primitive]*gridItem)
	for ,  := range .items {
		.visible = false
		if .Width <= 0 || .Height <= 0 ||  < .MinGridWidth ||  < .MinGridHeight {
			continue
		}
		,  := [.Item]
		if  && .MinGridWidth < .MinGridWidth && .MinGridHeight < .MinGridHeight {
			continue
		}
		[.Item] = 
	}

	// How many rows and columns do we have?
	 := len(.rows)
	 := len(.columns)
	for ,  := range  {
		 := .Row + .Height
		if  >  {
			 = 
		}
		 := .Column + .Width
		if  >  {
			 = 
		}
	}
	if  == 0 ||  == 0 {
		return // No content.
	}

	// Where are they located?
	 := make([]int, )
	 := make([]int, )
	 := make([]int, )
	 := make([]int, )

	// How much space do we distribute?
	 := 
	 := 
	 := 0
	 := 0
	for ,  := range .rows {
		if  > 0 {
			if  < .minHeight {
				 = .minHeight
			}
			 -= 
			[] = 
		} else if  == 0 {
			++
		} else {
			 += -
		}
	}
	for ,  := range .columns {
		if  > 0 {
			if  < .minWidth {
				 = .minWidth
			}
			 -= 
			[] = 
		} else if  == 0 {
			++
		} else {
			 += -
		}
	}
	if .borders {
		 -=  + 1
		 -=  + 1
	} else {
		 -= ( - 1) * .gapRows
		 -= ( - 1) * .gapColumns
	}
	if  > len(.rows) {
		 +=  - len(.rows)
	}
	if  > len(.columns) {
		 +=  - len(.columns)
	}

	// Distribute proportional rows/columns.
	for  := 0;  < ; ++ {
		 := 0
		if  < len(.rows) {
			 = .rows[]
		}
		if  > 0 {
			if  < .minHeight {
				 = .minHeight
			}
			continue // Not proportional. We already know the width.
		} else if  == 0 {
			 = 1
		} else {
			 = -
		}
		 :=  *  / 
		 -= 
		 -= 
		if  < .minHeight {
			 = .minHeight
		}
		[] = 
	}
	for  := 0;  < ; ++ {
		 := 0
		if  < len(.columns) {
			 = .columns[]
		}
		if  > 0 {
			if  < .minWidth {
				 = .minWidth
			}
			continue // Not proportional. We already know the height.
		} else if  == 0 {
			 = 1
		} else {
			 = -
		}
		 :=  *  / 
		 -= 
		 -= 
		if  < .minWidth {
			 = .minWidth
		}
		[] = 
	}

	// Calculate row/column positions.
	var ,  int
	if .borders {
		++
		++
	}
	for ,  := range  {
		[] = 
		 := .gapRows
		if .borders {
			 = 1
		}
		 +=  + 
	}
	for ,  := range  {
		[] = 
		 := .gapColumns
		if .borders {
			 = 1
		}
		 +=  + 
	}

	// Calculate primitive positions.
	var  *gridItem // The item which has focus.
	for ,  := range  {
		 := [.Column]
		 := [.Row]
		var ,  int
		for  := 0;  < .Height; ++ {
			 += [.Row+]
		}
		for  := 0;  < .Width; ++ {
			 += [.Column+]
		}
		if .borders {
			 += .Width - 1
			 += .Height - 1
		} else {
			 += (.Width - 1) * .gapColumns
			 += (.Height - 1) * .gapRows
		}
		.x, .y, .w, .h = , , , 
		.visible = true
		if .GetFocusable().HasFocus() {
			 = 
		}
	}

	// Calculate screen offsets.
	var ,  int
	 := 1
	if !.borders {
		 = .gapRows
	}
	for ,  := range  {
		if  >= .rowOffset {
			break
		}
		 +=  + 
	}
	if !.borders {
		 = .gapColumns
	}
	for ,  := range  {
		if  >= .columnOffset {
			break
		}
		 +=  + 
	}

	// Line up the last row/column with the end of the available area.
	var  int
	if .borders {
		 = 1
	}
	 := len() - 1
	if []+[]+- <  {
		 = [] -  + [] + 
	}
	 = len() - 1
	if []+[]+- <  {
		 = [] -  + [] + 
	}

	// The focused item must be within the visible area.
	if  != nil {
		if .y+.h- >=  {
			 = .y -  + .h
		}
		if .y- < 0 {
			 = .y
		}
		if .x+.w- >=  {
			 = .x -  + .w
		}
		if .x- < 0 {
			 = .x
		}
	}

	// Adjust row/column offsets based on this value.
	var ,  int
	for ,  := range  {
		if - < 0 {
			 =  + 1
		}
		if - <  {
			 = 
		}
	}
	if .rowOffset <  {
		.rowOffset = 
	}
	if .rowOffset >  {
		.rowOffset = 
	}
	,  = 0, 0
	for ,  := range  {
		if - < 0 {
			 =  + 1
		}
		if - <  {
			 = 
		}
	}
	if .columnOffset <  {
		.columnOffset = 
	}
	if .columnOffset >  {
		.columnOffset = 
	}

	// Draw primitives and borders.
	for ,  := range  {
		// Final primitive position.
		if !.visible {
			continue
		}
		.x -= 
		.y -= 
		if .x >=  || .x+.w <= 0 || .y >=  || .y+.h <= 0 {
			.visible = false
			continue
		}
		if .x+.w >  {
			.w =  - .x
		}
		if .y+.h >  {
			.h =  - .y
		}
		if .x < 0 {
			.w += .x
			.x = 0
		}
		if .y < 0 {
			.h += .y
			.y = 0
		}
		if .w <= 0 || .h <= 0 {
			.visible = false
			continue
		}
		.x += 
		.y += 
		.SetRect(.x, .y, .w, .h)

		// Draw primitive.
		if  ==  {
			defer .Draw()
		} else {
			.Draw()
		}

		// Draw border around primitive.
		if .borders {
			for  := .x;  < .x+.w; ++ { // Top/bottom lines.
				if  < 0 ||  >=  {
					continue
				}
				 := .y - 1
				if  >= 0 &&  <  {
					PrintJoinedSemigraphics(, , , Borders.Horizontal, .bordersColor)
				}
				 = .y + .h
				if  >= 0 &&  <  {
					PrintJoinedSemigraphics(, , , Borders.Horizontal, .bordersColor)
				}
			}
			for  := .y;  < .y+.h; ++ { // Left/right lines.
				if  < 0 ||  >=  {
					continue
				}
				 := .x - 1
				if  >= 0 &&  <  {
					PrintJoinedSemigraphics(, , , Borders.Vertical, .bordersColor)
				}
				 = .x + .w
				if  >= 0 &&  <  {
					PrintJoinedSemigraphics(, , , Borders.Vertical, .bordersColor)
				}
			}
			,  := .x-1, .y-1 // Top-left corner.
			if  >= 0 &&  <  &&  >= 0 &&  <  {
				PrintJoinedSemigraphics(, , , Borders.TopLeft, .bordersColor)
			}
			,  = .x+.w, .y-1 // Top-right corner.
			if  >= 0 &&  <  &&  >= 0 &&  <  {
				PrintJoinedSemigraphics(, , , Borders.TopRight, .bordersColor)
			}
			,  = .x-1, .y+.h // Bottom-left corner.
			if  >= 0 &&  <  &&  >= 0 &&  <  {
				PrintJoinedSemigraphics(, , , Borders.BottomLeft, .bordersColor)
			}
			,  = .x+.w, .y+.h // Bottom-right corner.
			if  >= 0 &&  <  &&  >= 0 &&  <  {
				PrintJoinedSemigraphics(, , , Borders.BottomRight, .bordersColor)
			}
		}
	}
}

// MouseHandler returns the mouse handler for this primitive.
func ( *Grid) () func( MouseAction,  *tcell.EventMouse,  func( Primitive)) ( bool,  Primitive) {
	return .WrapMouseHandler(func( MouseAction,  *tcell.EventMouse,  func( Primitive)) ( bool,  Primitive) {
		if !.InRect(.Position()) {
			return false, nil
		}

		// Pass mouse events along to the first child item under the mouse that consumes it.
		,  := .Position()
		for ,  := range .items {
			, , ,  := .Item.GetRect()
			 :=  >=  &&  < + &&  >=  &&  < +
			if ! {
				continue
			}

			,  = .Item.MouseHandler()(, , )
			if  {
				return
			}
		}

		return
	})
}