package tk

import (
	
	

	
	
)

// ListBox is a list for displaying and selecting from a list of items.
type ListBox interface {
	Widget
	// CopyState returns a copy of the state.
	CopyState() ListBoxState
	// Reset resets the state of the widget with the given items and index of
	// the selected item. It triggers the OnSelect callback if the index is
	// valid.
	Reset(it Items, selected int)
	// Select changes the selection by calling f with the current state, and
	// using the return value as the new selection index. It triggers the
	// OnSelect callback if the selected index has changed and is valid.
	Select(f func(ListBoxState) int)
	// Accept accepts the currently selected item.
	Accept()
}

// ListBoxSpec specifies the configuration and initial state for ListBox.
type ListBoxSpec struct {
	// Key bindings.
	Bindings Bindings
	// A placeholder to show when there are no items.
	Placeholder ui.Text
	// A function to call when the selected item has changed.
	OnSelect func(it Items, i int)
	// A function called on the accept event.
	OnAccept func(it Items, i int)
	// Whether the listbox should be rendered in a horizontal  Note that
	// in the horizontal layout, items must have only one line.
	Horizontal bool
	// The minimal amount of space to reserve for left and right sides of each
	// entry.
	Padding int
	// If true, the left padding of each item will be styled the same as the
	// first segment of the item, and the right spacing and padding will be
	// styled the same as the last segment of the item.
	ExtendStyle bool

	// State. When used in New, this field specifies the initial state.
	State ListBoxState
}

type listBox struct {
	// Mutex for synchronizing access to the state.
	StateMutex sync.RWMutex
	// Configuration and state.
	ListBoxSpec
}

// NewListBox creates a new ListBox from the given spec.
func ( ListBoxSpec) ListBox {
	if .Bindings == nil {
		.Bindings = DummyBindings{}
	}
	if .OnAccept == nil {
		.OnAccept = func(Items, int) {}
	}
	if .OnSelect == nil {
		.OnSelect = func(Items, int) {}
	} else {
		 := .State
		if .Items != nil && 0 <= .Selected && .Selected < .Items.Len() {
			.OnSelect(.Items, .Selected)
		}
	}
	return &listBox{ListBoxSpec: }
}

var stylingForSelected = ui.Inverse

func ( *listBox) (,  int) *term.Buffer {
	if .Horizontal {
		return .renderHorizontal(, )
	}
	return .renderVertical(, )
}

const listBoxColGap = 2

func ( *listBox) (,  int) *term.Buffer {
	var  ListBoxState
	.mutate(func( *ListBoxState) {
		if .Items == nil || .Items.Len() == 0 {
			.First = 0
		} else {
			.First, .Height = getHorizontalWindow(*, .Padding, , )
			// Override height to the height required; we don't need the
			// original height later.
			 = .Height
		}
		 = *
	})

	if .Items == nil || .Items.Len() == 0 {
		return Label{Content: .Placeholder}.Render(, )
	}

	, ,  := .Items, .Selected, .First
	 := .Len()

	 := term.NewBuffer(0)
	 := 
	 := false
	 := 
	for  := ;  < ;  +=  {
		 := -1
		// Render the column starting from i.
		 := make([]ui.Text, 0, )
		for  := ;  < + &&  < ; ++ {
			 = 
			 := .Show()
			if  ==  {
				 =  - 
			}
			 = append(, )
		}

		 := maxWidth(, .Padding, , +)
		if  >  {
			 = 
			 = true
		}

		 := croppedLines{
			lines: , padding: .Padding,
			selectFrom: , selectTo:  + 1,
			extendStyle: .ExtendStyle}.Render(, )
		.ExtendRight()

		 -= 
		if  <= listBoxColGap {
			break
		}
		 -= listBoxColGap
		.Width += listBoxColGap
	}
	// We may not have used all the width required; force buffer width.
	.Width = 
	if  != 0 ||  != -1 ||  {
		 := HScrollbar{Total: , Low: , High:  + 1}
		.Extend(.Render(, 1), false)
	}
	return 
}

func ( *listBox) (,  int) *term.Buffer {
	var  ListBoxState
	var  int
	.mutate(func( *ListBoxState) {
		if .Items == nil || .Items.Len() == 0 {
			.First = 0
		} else {
			.First,  = getVerticalWindow(*, )
		}
		.Height = 
		 = *
	})

	if .Items == nil || .Items.Len() == 0 {
		return Label{Content: .Placeholder}.Render(, )
	}

	, ,  := .Items, .Selected, .First
	 := .Len()
	 := []ui.Text{}
	 :=  > 0

	var , ,  int
	for  = ;  <  && len() < ; ++ {
		 := .Show()
		 := .SplitByRune('\n')
		if  ==  {
			 = [:]
		}
		if  ==  {
			,  = len(), len()+len()
		}
		// TODO: Optionally, add underlines to the last line as a visual
		// separator between adjacent entries.

		if len()+len() >  {
			 = [:len()+len()-]
			 = true
		}
		 = append(, ...)
	}

	var  Renderer = croppedLines{
		lines: , padding: .Padding,
		selectFrom: , selectTo: , extendStyle: .ExtendStyle}
	if  > 0 ||  <  ||  {
		 = VScrollbarContainer{
			Content:   ,
			Scrollbar: VScrollbar{Total: , Low: , High: },
		}
	}
	return .Render(, )
}

type croppedLines struct {
	lines       []ui.Text
	padding     int
	selectFrom  int
	selectTo    int
	extendStyle bool
}

func ( croppedLines) (,  int) *term.Buffer {
	 := term.NewBufferBuilder()
	 := ui.T(strings.Repeat(" ", .padding))
	 := ui.T(strings.Repeat(" ", -.padding))
	for ,  := range .lines {
		if  > 0 {
			.Newline()
		}

		 := .selectFrom <=  &&  < .selectTo
		 := .extendStyle && len() > 0

		 := .Clone()
		if  {
			[0].Style = [0].Style
		}
		 := ui.Concat(, .TrimWcwidth(-2*.padding))
		if  ||  {
			 := .Clone()
			if  {
				[0].Style = [len()-1].Style
			}
			 = ui.Concat(, ).TrimWcwidth()
		}
		if  {
			 = ui.StyleText(, stylingForSelected)
		}

		.WriteStyled()
	}
	return .Buffer()
}

func ( *listBox) ( term.Event) bool {
	if .Bindings.Handle(, ) {
		return true
	}

	switch  {
	case term.K(ui.Up):
		.Select(Prev)
		return true
	case term.K(ui.Down):
		.Select(Next)
		return true
	case term.K(ui.Enter):
		.Accept()
		return true
	}
	return false
}

func ( *listBox) () ListBoxState {
	.StateMutex.RLock()
	defer .StateMutex.RUnlock()
	return .State
}

func ( *listBox) ( Items,  int) {
	.mutate(func( *ListBoxState) { * = ListBoxState{Items: , Selected: } })
	if 0 <=  &&  < .Len() {
		.OnSelect(, )
	}
}

func ( *listBox) ( func(ListBoxState) int) {
	var  Items
	var ,  int
	.mutate(func( *ListBoxState) {
		,  = .Selected, .Items
		 = (*)
		.Selected = 
	})
	if  !=  && 0 <=  &&  < .Len() {
		.OnSelect(, )
	}
}

// Prev moves the selection to the previous item, or does nothing if the
// first item is currently selected. It is a suitable as an argument to
// Widget.Select.
func ( ListBoxState) int {
	return fixIndex(.Selected-1, .Items.Len())
}

// PrevPage moves the selection to the item one page before. It is only
// meaningful in vertical layout and suitable as an argument to Widget.Select.
//
// TODO(xiaq): This does not correctly with multi-line items.
func ( ListBoxState) int {
	return fixIndex(.Selected-.Height, .Items.Len())
}

// Next moves the selection to the previous item, or does nothing if the
// last item is currently selected. It is a suitable as an argument to
// Widget.Select.
func ( ListBoxState) int {
	return fixIndex(.Selected+1, .Items.Len())
}

// NextPage moves the selection to the item one page after. It is only
// meaningful in vertical layout and suitable as an argument to Widget.Select.
//
// TODO(xiaq): This does not correctly with multi-line items.
func ( ListBoxState) int {
	return fixIndex(.Selected+.Height, .Items.Len())
}

// PrevWrap moves the selection to the previous item, or to the last item if
// the first item is currently selected. It is a suitable as an argument to
// Widget.Select.
func ( ListBoxState) int {
	,  := .Selected, .Items.Len()
	switch {
	case  >= :
		return  - 1
	case  <= 0:
		return  - 1
	default:
		return  - 1
	}
}

// NextWrap moves the selection to the previous item, or to the first item
// if the last item is currently selected. It is a suitable as an argument to
// Widget.Select.
func ( ListBoxState) int {
	,  := .Selected, .Items.Len()
	switch {
	case  >= -1:
		return 0
	case  < 0:
		return 0
	default:
		return  + 1
	}
}

// Left moves the selection to the item to the left. It is only meaningful in
// horizontal layout and suitable as an argument to Widget.Select.
func ( ListBoxState) int {
	return horizontal(.Selected, .Items.Len(), -.Height)
}

// Right moves the selection to the item to the right. It is only meaningful in
// horizontal layout and suitable as an argument to Widget.Select.
func ( ListBoxState) int {
	return horizontal(.Selected, .Items.Len(), .Height)
}

func (, ,  int) int {
	 = fixIndex(, )
	 :=  + 
	if  < 0 ||  >=  {
		return 
	}
	return 
}

func (,  int) int {
	switch {
	case  < 0:
		return 0
	case  >= :
		return  - 1
	default:
		return 
	}
}

func ( *listBox) () {
	 := .CopyState()
	if 0 <= .Selected && .Selected < .Items.Len() {
		.OnAccept(.Items, .Selected)
	}
}

func ( *listBox) ( func( *ListBoxState)) {
	.StateMutex.Lock()
	defer .StateMutex.Unlock()
	(&.State)
}