package completion

import (
	
	
	
	
	

	
	
)

// group is used to structure different types of completions with different
// display types, autosuffix removal matchers, under their tag heading.
type group struct {
	tag               string        // Printed on top of the group's completions
	rows              [][]Candidate // Values are grouped by aliases/rows, with computed paddings.
	noSpace           SuffixMatcher // Suffixes to remove if a space or non-nil character is entered after the completion.
	columnsWidth      []int         // Computed width for each column of completions, when aliases
	descriptionsWidth []int         // Computed width for each column of completions, when aliases
	listSeparator     string        // This is used to separate completion candidates from their descriptions.
	list              bool          // Force completions to be listed instead of grided
	noSort            bool          // Don't sort completions
	aliased           bool          // Are their aliased completions
	preserveEscapes   bool          // Preserve escape sequences in the completion inserted values.
	isCurrent         bool          // Currently cycling through this group, for highlighting choice
	longestValue      int           // Used when display is map/list, for determining message width
	longestDesc       int           // Used to know how much descriptions can use when there are aliases.
	maxDescAllowed    int           // Maximum ALLOWED description width.
	termWidth         int           // Term size queried at beginning of computes by the engine.

	// Selectors (position/bounds) management
	posX int
	posY int
	maxX int
	maxY int
}

// newCompletionGroup initializes a group of completions to be displayed in the same area/header.
func ( *Engine) ( Values,  string,  RawValues,  []string) {
	 := &group{
		tag:          ,
		noSpace:      .NoSpace,
		posX:         -1,
		posY:         -1,
		columnsWidth: []int{0},
		termWidth:    term.GetWidth(),
		longestDesc:  longest(, true),
	}

	// Initialize all options for the group.
	.initOptions(, &, , )

	// Global actions to take on all values.
	if !.noSort {
		sort.Stable()
	}

	// Initial processing of our assigned values:
	// Compute color/no-color sizes, some max/min, etc.
	.prepareValues()

	// Generate the full grid of completions.
	// Special processing is needed when some values
	// share a common description, they are "aliased".
	if completionsAreAliases() {
		.initCompletionAliased()
	} else {
		.initCompletionsGrid()
	}

	.groups = append(.groups, )
}

// initOptions checks for global or group-specific options (display, behavior, grouping, etc).
func ( *group) ( *Engine,  *Values,  string,  RawValues) {
	// Override grid/list displays
	_, .list = .ListLong[]
	if ,  := .ListLong["*"];  && len(.ListLong) == 1 {
		.list = true
	}

	// Description list separator
	,  := strconv.Unquote(.config.GetString("completion-list-separator"))
	if  != nil {
		.listSeparator = "--"
	} else {
		.listSeparator = 
	}

	// Strip escaped characters in the value component.
	.preserveEscapes = .Escapes[.tag]
	if !.preserveEscapes {
		.preserveEscapes = .Escapes["*"]
	}

	// Always list long commands when they have descriptions.
	if strings.HasSuffix(.tag, "commands") && len() > 0 && [0].Description != "" {
		.list = true
	}

	// Description list separator
	,  := .ListSep[]
	if ! {
		if ,  := .ListSep["*"];  {
			.listSeparator = 
		}
	} else {
		.listSeparator = 
	}

	// Override sorting or sort if needed
	.noSort = .NoSort[]
	if ,  := .NoSort["*"];  &&  && len(.NoSort) == 1 {
		.noSort = true
	}
}

// initCompletionsGrid arranges completions when there are no aliases.
func ( *group) ( RawValues) {
	if len() == 0 {
		return
	}

	 := .longestValueDescribed()
	 = min(.termWidth, )

	 := .termWidth / 
	if .list ||  < 0 {
		 = 1
	}

	 := int(math.Ceil(float64(len()) / (float64())))

	.rows = createGrid(, , )
	.calculateMaxColumnWidths(.rows)
}

// initCompletionsGrid arranges completions when some of them share the same description.
func ( *group) ( []Candidate) {
	.aliased = true

	// Filter out all duplicates: group aliased completions together.
	,  := .createDescribedRows()
	.calculateMaxColumnWidths()
	.wrapExcessAliases()

	.maxY = len(.rows)
	.maxX = len(.columnsWidth)
}

// This createDescribedRows function takes a list of values, a list of descriptions, and the
// terminal width as input, and returns a list of rows based on the provided requirements:.
func ( *group) ( []Candidate) ([][]Candidate, []string) {
	 := make(map[string][]Candidate)
	 := make([]string, 0)
	 := make([][]Candidate, 0)

	// Separate duplicates and store them.
	for ,  := range  {
		if slices.Contains(, .Description) {
			[.Description] = append([.Description], [])
		} else {
			 = append(, .Description)
			[.Description] = []Candidate{[]}
		}
	}

	// Sorting helps with easier grids.
	for ,  := range  {
		 := []
		 = append(, )
	}

	return , 
}

// Wraps all rows for which there are too many aliases to be displayed on a single one.
func ( *group) ( [][]Candidate) {
	 := 0
	 := len(.columnsWidth)

	for ,  := range .columnsWidth {
		if ( +  + 1) > .termWidth/2 {
			 = 
			break
		}

		 +=  + 1
	}

	var  [][]Candidate

	for  := range  {
		 := []

		for len() >  {
			 = append(, [:])
			 = [:]
		}

		 = append(, )
	}

	.rows = 
	.columnsWidth = .columnsWidth[:]
}

// prepareValues ensures all of them have a display, and starts
// gathering information on longest/shortest values, etc.
func ( *group) ( RawValues) RawValues {
	for ,  := range  {
		if .Display == "" {
			.Display = .Value
		}

		// Only pass for colors regex should be here.
		.displayLen = len(color.Strip(.Display))
		.descLen = len(color.Strip(.Description))

		if .displayLen > .longestValue {
			.longestValue = .displayLen
		}

		if .descLen > .longestDesc {
			.longestDesc = .descLen
		}

		[] = 
	}

	return 
}

func ( *group) ( int) int {
	// Get the length of the longest description in the same column.
	 := .descriptionsWidth[]
	 := sum(.columnsWidth) + len(.columnsWidth) + len(.listSep())

	if + > .termWidth {
		 = .termWidth - 
	} else if + < .termWidth {
		 = .termWidth - 
	}

	return 
}

// calculateMaxColumnWidths is in charge of optimizing the sizes of rows/columns.
func ( *group) ( [][]Candidate) {
	var  int

	// Get the row with the greatest number of columns.
	for ,  := range  {
		if len() >  {
			 = len()
		}
	}

	// First, all columns are as wide as the longest of their elements,
	// regardless of if this longest element is longer than terminal
	 := make([]int, )
	 := make([]int, )

	for ,  := range  {
		for ,  := range  {
			if .displayLen+1 > [] {
				[] = .displayLen + 1
			}

			if .descLen+1 > [] {
				[] = .descLen + 1
			}
		}
	}

	// If we have only one row, it means that the number of columns
	// multiplied by the size on the longest one will fit into the
	// terminal, so we can just
	if len() == 1 && len([0]) <=  && sum() == 0 {
		for  := range  {
			[] = .longestValue
		}
	}

	// Last time adjustment: try to reallocate any space modulo to each column.
	 := len() > 1 &&  > 1 && sum() == 0
	 := ( * 2)
	 := sum() + sum() + 
	 := .termWidth - 

	if  && !.aliased &&  >=  {
		 :=  / 

		for  := range  {
			[] += 
		}
	}

	// The group is mostly ready to print and select its values for completion.
	.maxY = len(.rows)
	.maxX = len()
	.columnsWidth = 
	.descriptionsWidth = 
}

func ( *group) ( []Candidate) int {
	var ,  int

	// Equivalent to `<completion> -- <Description>`,
	// asuuming no trailing spaces in the completion
	// nor leading spaces in the description.
	 := 1 + len(.listSeparator) + 1

	// Get the length of the longest value
	// and the length of the longest description.
	for ,  := range  {
		if .displayLen >  {
			 = .displayLen
		}

		if .descLen >  {
			 = .descLen
		}

		if .descLen >  {
			 = .descLen
		}
	}

	if  > 0 {
		 += 
	}

	if  > 0 {
		 += 
	}

	// Always add one: there is at least one space between each column.
	return  +  + 2
}

func ( *group) ( Candidate, ,  int) (,  string) {
	 := .Display

	// No display value means padding.
	if  == "" {
		return "", padSpace()
	}

	// Get the allowed length for this column.
	// The display can never be longer than terminal width.
	 := .columnsWidth[] + 1
	 = min(.termWidth, )

	 = sanitizer.Replace()

	if .displayLen >  {
		 = color.Trim(, -trailingValueLen)
		 += "..." // 3 dots + 1 safety space = -3

		return , " "
	}

	return , padSpace()
}

func ( *group) ( Candidate,  int) (,  string) {
	 = .Description
	if  == "" {
		return , padSpace()
	}

	// We don't compare against the terminal width:
	// the correct padding should have been computed
	// based on the space taken by all candidates
	// described by our current string.
	if  > .maxDescAllowed {
		 = .maxDescAllowed - .descLen
	}

	 = sanitizer.Replace()

	// Trim the description accounting for escapes.
	if .descLen > .maxDescAllowed && .maxDescAllowed > 0 {
		 = color.Trim(, .maxDescAllowed-trailingDescLen)
		 += "..." // 3 dots =  -3

		return .listSep() + , ""
	}

	if .descLen+ > .maxDescAllowed {
		 = .maxDescAllowed - .descLen
	}

	return .listSep() + , padSpace()
}

func ( *group) ( Candidate,  int,  bool) int {
	 := .columnsWidth
	 := .displayLen - 1

	if  {
		 = .descriptionsWidth
		 = .descLen
	}

	// Ensure we never longer or even fully equal
	// to the terminal size: we are not really sure
	// of where offsets might be set in the code...
	 := []
	 = min(.termWidth-1, )
	 :=  - 

	if  < 0 {
		return 0
	}

	return 
}

func ( *group) () string {
	return .listSeparator + " "
}

//
// Usage-time functions (selecting/writing) -----------------------------------------------------------------
//

// updateIsearch - When searching through all completion groups (whether it be command history or not),
// we ask each of them to filter its own items and return the results to the shell for aggregating them.
// The rx parameter is passed, as the shell already checked that the search pattern is valid.
func ( *group) ( *Engine) {
	if .IsearchRegex == nil {
		return
	}

	 := make([]Candidate, 0)

	for  := range .rows {
		 := .rows[]

		for ,  := range  {
			if .IsearchRegex.MatchString(.Value) {
				 = append(, )
			} else if .Description != "" && .IsearchRegex.MatchString(.Description) {
				 = append(, )
			}
		}
	}

	// Reset the group parameters
	.rows = make([][]Candidate, 0)
	.posX = -1
	.posY = -1

	// Initial processing of our assigned values:
	// Compute color/no-color sizes, some max/min, etc.
	 = .prepareValues()

	// Generate the full grid of completions.
	// Special processing is needed when some values
	// share a common description, they are "aliased".
	if completionsAreAliases() {
		.initCompletionAliased()
	} else {
		.initCompletionsGrid()
	}
}

func ( *group) () ( Candidate) {
	defer func() {
		if !.preserveEscapes {
			.Value = color.Strip(.Value)
		}
	}()

	if .posY == -1 || .posX == -1 {
		return .rows[0][0]
	}

	return .rows[.posY][.posX]
}

func ( *group) (,  int) (,  bool) {
	// When the group has not yet been used, adjust
	if .posX == -1 && .posY == -1 {
		if  != 0 {
			.posY++
		} else {
			.posX++
		}
	}

	.posX += 
	.posY += 
	 := ( < 0 ||  < 0)

	// 1) Ensure columns is minimum one, if not, either
	// go to previous row, or go to previous group.
	if .posX < 0 {
		if .posY == 0 &&  {
			.posX = 0
			.posY = 0

			return true, false
		}

		.posY--
		.posX = len(.rows[.posY]) - 1
	}

	// 2) If we are reverse-cycling and currently on the first candidate,
	// we are done with this group. Stay on those coordinates still.
	if .posY < 0 {
		if .posX == 0 {
			.posX = 0
			.posY = 0

			return true, false
		}

		.posY = len(.rows) - 1
		.posX--
	}

	// 3) If we are on the last row, we might have to move to
	// the next column, if there is another one.
	if .posY > .maxY-1 {
		.posY = 0
		if .posX < .maxX-1 {
			.posX++
		} else {
			return true, true
		}
	}

	// 4) If we are on the last column, go to next row or next group
	if .posX > len(.rows[.posY])-1 {
		if .aliased {
			return .findFirstCandidate(, )
		}

		.posX = 0

		if .posY < .maxY-1 {
			.posY++
		} else {
			return true, true
		}
	}

	// By default, come back to this group for next item.
	return false, false
}

// Check that there is indeed a completion in the column for a given row,
// otherwise loop in the direction wished until one is found, or go next/
// previous column, and so on.
func ( *group) (,  int) (,  bool) {
	for .posX > len(.rows[.posY])-1 {
		.posY += 
		.posY += 

		// Previous column or group
		if .posY < 0 {
			if .posX == 0 {
				.posX = 0
				.posY = 0

				return true, false
			}

			.posY = len(.rows) - 1
			.posX--
		}

		// Next column or group
		if .posY > .maxY-1 {
			.posY = 0
			if .posX < len(.columnsWidth)-1 {
				.posX++
			} else {
				return true, true
			}
		}
	}

	return
}

func ( *group) () {
	.posX = 0
	.posY = 0
}

func ( *group) () {
	.posY = len(.rows) - 1
	.posX = len(.columnsWidth) - 1

	if .aliased {
		.findFirstCandidate(0, -1)
	} else {
		.posX = len(.rows[.posY]) - 1
	}
}

func completionsAreAliases( []Candidate) bool {
	 := make(map[string]bool)

	for ,  := range  {
		if .Description == "" {
			continue
		}

		if ,  := [.Description];  {
			return true
		}

		[.Description] = true
	}

	return false
}

func createGrid( []Candidate, ,  int) [][]Candidate {
	if  < 0 {
		 = 0
	}

	 := make([][]Candidate, )

	for  := range  {
		[] = createRow(, , )
	}

	return 
}

func createRow( []Candidate, ,  int) []Candidate {
	 :=  * 
	 := ( + 1) * 
	 = min(len(), )

	return [:]
}

func padSpace( int) string {
	if  > 0 {
		return strings.Repeat(" ", )
	}

	return ""
}