package cview

import (
	
	

	
	
)

// DropDownOption is one option that can be selected in a drop-down primitive.
type DropDownOption struct {
	text      string                                  // The text to be displayed in the drop-down.
	selected  func(index int, option *DropDownOption) // The (optional) callback for when this option was selected.
	reference interface{}                             // An optional reference object.

	sync.RWMutex
}

// NewDropDownOption returns a new option for a dropdown.
func ( string) *DropDownOption {
	return &DropDownOption{text: }
}

// GetText returns the text of this dropdown option.
func ( *DropDownOption) () string {
	.RLock()
	defer .RUnlock()

	return .text
}

// SetText returns the text of this dropdown option.
func ( *DropDownOption) ( string) {
	.text = 
}

// SetSelectedFunc sets the handler to be called when this option is selected.
func ( *DropDownOption) ( func( int,  *DropDownOption)) {
	.selected = 
}

// GetReference returns the reference object of this dropdown option.
func ( *DropDownOption) () interface{} {
	.RLock()
	defer .RUnlock()

	return .reference
}

// SetReference allows you to store a reference of any type in this option.
func ( *DropDownOption) ( interface{}) {
	.reference = 
}

// DropDown implements a selection widget whose options become visible in a
// drop-down list when activated.
type DropDown struct {
	*Box

	// The options from which the user can choose.
	options []*DropDownOption

	// Strings to be placed before and after each drop-down option.
	optionPrefix, optionSuffix string

	// The index of the currently selected option. Negative if no option is
	// currently selected.
	currentOption int

	// Strings to be placed before and after the current option.
	currentOptionPrefix, currentOptionSuffix string

	// The text to be displayed when no option has yet been selected.
	noSelection string

	// Set to true if the options are visible and selectable.
	open bool

	// The runes typed so far to directly access one of the list items.
	prefix string

	// The list element for the options.
	list *List

	// The text to be displayed before the input area.
	label string

	// The label color.
	labelColor tcell.Color

	// The label color when focused.
	labelColorFocused tcell.Color

	// The background color of the input area.
	fieldBackgroundColor tcell.Color

	// The background color of the input area when focused.
	fieldBackgroundColorFocused tcell.Color

	// The text color of the input area.
	fieldTextColor tcell.Color

	// The text color of the input area when focused.
	fieldTextColorFocused tcell.Color

	// The color for prefixes.
	prefixTextColor tcell.Color

	// The screen width of the label area. A value of 0 means use the width of
	// the label text.
	labelWidth int

	// The screen width of the input area. A value of 0 means extend as much as
	// possible.
	fieldWidth int

	// An optional function which is called when the user indicated that they
	// are done selecting options. The key which was pressed is provided (tab,
	// shift-tab, or escape).
	done func(tcell.Key)

	// A callback function set by the Form class and called when the user leaves
	// this form item.
	finished func(tcell.Key)

	// A callback function which is called when the user changes the drop-down's
	// selection.
	selected func(index int, option *DropDownOption)

	// Set to true when mouse dragging is in progress.
	dragging bool

	// The chars to show when the option's text gets shortened.
	abbreviationChars string

	// The symbol to draw at the end of the field when closed.
	dropDownSymbol rune

	// The symbol to draw at the end of the field when opened.
	dropDownOpenSymbol rune

	// The symbol used to draw the selected item when opened.
	dropDownSelectedSymbol rune

	// A flag that determines whether the drop down symbol is always drawn.
	alwaysDrawDropDownSymbol bool

	sync.RWMutex
}

// NewDropDown returns a new drop-down.
func () *DropDown {
	 := NewList()
	.ShowSecondaryText(false)
	.SetMainTextColor(Styles.SecondaryTextColor)
	.SetSelectedTextColor(Styles.PrimitiveBackgroundColor)
	.SetSelectedBackgroundColor(Styles.PrimaryTextColor)
	.SetHighlightFullLine(true)
	.SetBackgroundColor(Styles.ContrastBackgroundColor)

	 := &DropDown{
		Box:                         NewBox(),
		currentOption:               -1,
		list:                        ,
		labelColor:                  Styles.SecondaryTextColor,
		fieldBackgroundColor:        Styles.MoreContrastBackgroundColor,
		fieldTextColor:              Styles.PrimaryTextColor,
		prefixTextColor:             Styles.ContrastSecondaryTextColor,
		dropDownSymbol:              Styles.DropDownSymbol,
		dropDownOpenSymbol:          Styles.DropDownOpenSymbol,
		dropDownSelectedSymbol:      Styles.DropDownSelectedSymbol,
		abbreviationChars:           Styles.DropDownAbbreviationChars,
		labelColorFocused:           ColorUnset,
		fieldBackgroundColorFocused: ColorUnset,
		fieldTextColorFocused:       ColorUnset,
	}

	if  := .dropDownSelectedSymbol;  != 0 {
		.SetIndicators(" "+string()+" ", "", "   ", "")
	}
	.focus = 

	return 
}

// SetDropDownSymbolRune sets the rune to be drawn at the end of the dropdown field
// to indicate that this field is a dropdown.
func ( *DropDown) ( rune) {
	.Lock()
	defer .Unlock()
	.dropDownSymbol = 
}

// SetDropDownOpenSymbolRune sets the rune to be drawn at the end of the
// dropdown field to indicate that the a dropdown is open.
func ( *DropDown) ( rune) {
	.Lock()
	defer .Unlock()
	.dropDownOpenSymbol = 

	if  != 0 {
		.list.SetIndicators(" "+string()+" ", "", "   ", "")
	} else {
		.list.SetIndicators("", "", "", "")
	}

}

// SetDropDownSelectedSymbolRune sets the rune to be drawn at the start of the
// selected list item.
func ( *DropDown) ( rune) {
	.Lock()
	defer .Unlock()
	.dropDownSelectedSymbol = 
}

// SetAlwaysDrawDropDownSymbol sets a flad that determines whether the drop
// down symbol is always drawn. The symbol is normally only drawn when focused.
func ( *DropDown) ( bool) {
	.Lock()
	defer .Unlock()
	.alwaysDrawDropDownSymbol = 
}

// SetCurrentOption sets the index of the currently selected option. This may
// be a negative value to indicate that no option is currently selected. Calling
// this function will also trigger the "selected" callback (if there is one).
func ( *DropDown) ( int) {
	.Lock()
	if  >= 0 &&  < len(.options) {
		.currentOption = 
		.list.SetCurrentItem()
		if .selected != nil {
			.Unlock()
			.selected(, .options[])
			.Lock()
		}
		if .options[].selected != nil {
			.Unlock()
			.options[].selected(, .options[])
			.Lock()
		}
	} else {
		.currentOption = -1
		.list.SetCurrentItem(0) // Set to 0 because -1 means "last item".
		if .selected != nil {
			.Unlock()
			.selected(-1, nil)
			.Lock()
		}
	}
	.Unlock()
}

// GetCurrentOption returns the index of the currently selected option as well
// as the option itself. If no option was selected, -1 and nil is returned.
func ( *DropDown) () (int, *DropDownOption) {
	.RLock()
	defer .RUnlock()

	var  *DropDownOption
	if .currentOption >= 0 && .currentOption < len(.options) {
		 = .options[.currentOption]
	}
	return .currentOption, 
}

// SetTextOptions sets the text to be placed before and after each drop-down
// option (prefix/suffix), the text placed before and after the currently
// selected option (currentPrefix/currentSuffix) as well as the text to be
// displayed when no option is currently selected. Per default, all of these
// strings are empty.
func ( *DropDown) (, , , ,  string) {
	.Lock()
	defer .Unlock()

	.currentOptionPrefix = 
	.currentOptionSuffix = 
	.noSelection = 
	.optionPrefix = 
	.optionSuffix = 
	for  := 0;  < .list.GetItemCount(); ++ {
		.list.SetItemText(, +.options[].text+, "")
	}
}

// SetLabel sets the text to be displayed before the input area.
func ( *DropDown) ( string) {
	.Lock()
	defer .Unlock()

	.label = 
}

// GetLabel returns the text to be displayed before the input area.
func ( *DropDown) () string {
	.RLock()
	defer .RUnlock()

	return .label
}

// SetLabelWidth sets the screen width of the label. A value of 0 will cause the
// primitive to use the width of the label string.
func ( *DropDown) ( int) {
	.Lock()
	defer .Unlock()

	.labelWidth = 
}

// SetLabelColor sets the color of the label.
func ( *DropDown) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.labelColor = 
}

// SetLabelColorFocused sets the color of the label when focused.
func ( *DropDown) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.labelColorFocused = 
}

// SetFieldBackgroundColor sets the background color of the options area.
func ( *DropDown) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.fieldBackgroundColor = 
}

// SetFieldBackgroundColorFocused sets the background color of the options area when focused.
func ( *DropDown) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.fieldBackgroundColorFocused = 
}

// SetFieldTextColor sets the text color of the options area.
func ( *DropDown) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.fieldTextColor = 
}

// SetFieldTextColorFocused sets the text color of the options area when focused.
func ( *DropDown) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.fieldTextColorFocused = 
}

// SetDropDownTextColor sets text color of the drop-down list.
func ( *DropDown) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.list.SetMainTextColor()
}

// SetDropDownBackgroundColor sets the background color of the drop-down list.
func ( *DropDown) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.list.SetBackgroundColor()
}

// SetDropDownSelectedTextColor sets the text color of the selected option in
// the drop-down list.
func ( *DropDown) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.list.SetSelectedTextColor()
}

// SetDropDownSelectedBackgroundColor sets the background color of the selected
// option in the drop-down list.
func ( *DropDown) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.list.SetSelectedBackgroundColor()
}

// SetPrefixTextColor sets the color of the prefix string. The prefix string is
// shown when the user starts typing text, which directly selects the first
// option that starts with the typed string.
func ( *DropDown) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.prefixTextColor = 
}

// SetFieldWidth sets the screen width of the options area. A value of 0 means
// extend to as long as the longest option text.
func ( *DropDown) ( int) {
	.Lock()
	defer .Unlock()

	.fieldWidth = 
}

// GetFieldHeight returns the height of the field.
func ( *DropDown) () int {
	return 1
}

// GetFieldWidth returns this primitive's field screen width.
func ( *DropDown) () int {
	.RLock()
	defer .RUnlock()
	return .getFieldWidth()
}

func ( *DropDown) () int {
	if .fieldWidth > 0 {
		return .fieldWidth
	}
	 := 0
	for ,  := range .options {
		 := TaggedStringWidth(.text)
		if  >  {
			 = 
		}
	}
	 += len(.optionPrefix) + len(.optionSuffix)
	 += len(.currentOptionPrefix) + len(.currentOptionSuffix)
	// space <text> space + dropDownSymbol + space
	 += 4
	return 
}

// AddOptionsSimple adds new selectable options to this drop-down.
func ( *DropDown) ( ...string) {
	 := make([]*DropDownOption, len())
	for ,  := range  {
		[] = NewDropDownOption()
	}
	.AddOptions(...)
}

// AddOptions adds new selectable options to this drop-down.
func ( *DropDown) ( ...*DropDownOption) {
	.Lock()
	defer .Unlock()
	.addOptions(...)
}

func ( *DropDown) ( ...*DropDownOption) {
	.options = append(.options, ...)
	for ,  := range  {
		.list.AddItem(NewListItem(.optionPrefix + .text + .optionSuffix))
	}
}

// SetOptionsSimple replaces all current options with the ones provided and installs
// one callback function which is called when one of the options is selected.
// It will be called with the option's index and the option itself
// The "selected" parameter may be nil.
func ( *DropDown) ( func( int,  *DropDownOption),  ...string) {
	 := make([]*DropDownOption, len())
	for ,  := range  {
		[] = NewDropDownOption()
	}
	.SetOptions(, ...)
}

// SetOptions replaces all current options with the ones provided and installs
// one callback function which is called when one of the options is selected.
// It will be called with the option's index and the option itself.
// The "selected" parameter may be nil.
func ( *DropDown) ( func( int,  *DropDownOption),  ...*DropDownOption) {
	.Lock()
	defer .Unlock()

	.list.Clear()
	.options = nil
	.addOptions(...)
	.selected = 
}
func ( *DropDown) () {
	.Lock()
	defer .Unlock()

	.list.Clear()
	.options = nil
}

// SetChangedFunc sets a handler which is called when the user changes the
// focused drop-down option. The handler is provided with the selected option's
// index and the option itself. If "no option" was selected, these values are
// -1 and nil.
func ( *DropDown) ( func( int,  *DropDownOption)) {
	.list.SetChangedFunc(func( int,  *ListItem) {
		(, .options[])
	})
}

// SetSelectedFunc sets a handler which is called when the user selects a
// drop-down's option. This handler will be called in addition and prior to
// an option's optional individual handler. The handler is provided with the
// selected option's index and the option itself. If "no option" was selected, these values
// are -1 and nil.
func ( *DropDown) ( func( int,  *DropDownOption)) {
	.Lock()
	defer .Unlock()

	.selected = 
}

// SetDoneFunc sets a handler which is called when the user is done selecting
// options. The callback function is provided with the key that was pressed,
// which is one of the following:
//
//   - KeyEscape: Abort selection.
//   - KeyTab: Move to the next field.
//   - KeyBacktab: Move to the previous field.
func ( *DropDown) ( func( tcell.Key)) {
	.Lock()
	defer .Unlock()

	.done = 
}

// SetFinishedFunc sets a callback invoked when the user leaves this form item.
func ( *DropDown) ( func( tcell.Key)) {
	.Lock()
	defer .Unlock()

	.finished = 
}

// Draw draws this primitive onto the screen.
func ( *DropDown) ( tcell.Screen) {
	.Box.Draw()
	 := .GetFocusable().HasFocus()

	.Lock()
	defer .Unlock()

	// Select colors
	 := .labelColor
	 := .fieldBackgroundColor
	 := .fieldTextColor
	if  {
		if .labelColorFocused != ColorUnset {
			 = .labelColorFocused
		}
		if .fieldBackgroundColorFocused != ColorUnset {
			 = .fieldBackgroundColorFocused
		}
		if .fieldTextColorFocused != ColorUnset {
			 = .fieldTextColorFocused
		}
	}

	// Prepare.
	, , ,  := .GetInnerRect()
	 :=  + 
	if  < 1 ||  <=  {
		return
	}

	// Draw label.
	if .labelWidth > 0 {
		 := .labelWidth
		if  > - {
			 =  - 
		}
		Print(, []byte(.label), , , , AlignLeft, )
		 += 
	} else {
		,  := Print(, []byte(.label), , , -, AlignLeft, )
		 += 
	}

	// What's the longest option text?
	 := 0
	 := TaggedStringWidth(.optionPrefix + .optionSuffix)
	for ,  := range .options {
		 := TaggedStringWidth(.text) + 
		if  >  {
			 = 
		}
	}

	// Draw selection area.
	 := .getFieldWidth()
	if  == 0 {
		 = 
		if .currentOption < 0 {
			 := TaggedStringWidth(.noSelection)
			if  >  {
				 = 
			}
		} else if .currentOption < len(.options) {
			 := TaggedStringWidth(.currentOptionPrefix + .options[.currentOption].text + .currentOptionSuffix)
			if  >  {
				 = 
			}
		}
	}
	if - <  {
		 =  - 
	}
	 := tcell.StyleDefault.Background()
	for  := 0;  < ; ++ {
		.SetContent(+, , ' ', nil, )
	}

	// Draw selected text.
	if .open && len(.prefix) > 0 {
		// Show the prefix.
		 := TaggedStringWidth(.currentOptionPrefix)
		 := runewidth.StringWidth(.prefix)
		 := .options[.list.GetCurrentItemIndex()].text
		Print(, []byte(.currentOptionPrefix), , , , AlignLeft, )
		Print(, []byte(.prefix), +, , -, AlignLeft, .prefixTextColor)
		if len(.prefix) < len() {
			Print(, []byte([len(.prefix):]+.currentOptionSuffix), ++, , --, AlignLeft, )
		}
	} else {
		 := 
		 := .noSelection
		if .currentOption >= 0 && .currentOption < len(.options) {
			 = .currentOptionPrefix + .options[.currentOption].text + .currentOptionSuffix
		}
		// Abbreviate text when not fitting
		if  > len(.abbreviationChars)+3 && len() >  {
			 = [0:-3-len(.abbreviationChars)] + .abbreviationChars
		}

		// Just show the current selection.
		Print(, []byte(), , , , AlignLeft, )
	}

	// Draw drop-down symbol
	if .alwaysDrawDropDownSymbol || ._hasFocus() {
		 := .dropDownSymbol
		if .open {
			 = .dropDownOpenSymbol
		}
		.SetContent(+-2, , , nil, new(tcell.Style).Foreground().Background())
	}

	// Draw options list.
	if  && .open {
		// We prefer to drop-down but if there is no space, maybe drop up?
		 := 
		 :=  + 1
		 := len(.options)
		,  := .Size()
		if + >=  && -2 > - {
			 =  - 
			if  < 0 {
				 = 0
			}
		}
		if + >=  {
			 =  - 
		}
		 := 
		if .list.scrollBarVisibility == ScrollBarAlways || (.list.scrollBarVisibility == ScrollBarAuto && len(.options) > ) {
			++ // Add space for scroll bar
		}
		if  <  {
			 = 
		}
		.list.SetRect(, , , )
		.list.Draw()
	}
}

// InputHandler returns the handler for this primitive.
func ( *DropDown) () func( *tcell.EventKey,  func( Primitive)) {
	return .WrapInputHandler(func( *tcell.EventKey,  func( Primitive)) {
		// Process key event.
		switch  := .Key();  {
		case tcell.KeyEnter, tcell.KeyRune, tcell.KeyDown:
			.Lock()
			defer .Unlock()

			.prefix = ""

			// If the first key was a letter already, it becomes part of the prefix.
			if  := .Rune();  == tcell.KeyRune &&  != ' ' {
				.prefix += string()
				.evalPrefix()
			}

			.openList()
		case tcell.KeyEscape, tcell.KeyTab, tcell.KeyBacktab:
			if .done != nil {
				.done()
			}
			if .finished != nil {
				.finished()
			}
		}
	})
}

// evalPrefix selects an item in the drop-down list based on the current prefix.
func ( *DropDown) () {
	if len(.prefix) > 0 {
		for ,  := range .options {
			if strings.HasPrefix(strings.ToLower(.text), .prefix) {
				.list.SetCurrentItem()
				return
			}
		}

		// Prefix does not match any item. Remove last rune.
		 := []rune(.prefix)
		.prefix = string([:len()-1])
	}
}

// openList hands control over to the embedded List primitive.
func ( *DropDown) ( func(Primitive)) {
	.open = true
	 := .currentOption

	.list.SetSelectedFunc(func( int,  *ListItem) {
		if .dragging {
			return // If we're dragging the mouse, we don't want to trigger any events.
		}

		// An option was selected. Close the list again.
		.currentOption = 
		.closeList()

		// Trigger "selected" event.
		if .selected != nil {
			.selected(.currentOption, .options[.currentOption])
		}
		if .options[.currentOption].selected != nil {
			.options[.currentOption].selected(.currentOption, .options[.currentOption])
		}
	})
	.list.SetInputCapture(func( *tcell.EventKey) *tcell.EventKey {
		if .Key() == tcell.KeyRune {
			.prefix += string(.Rune())
			.evalPrefix()
		} else if .Key() == tcell.KeyBackspace || .Key() == tcell.KeyBackspace2 {
			if len(.prefix) > 0 {
				 := []rune(.prefix)
				.prefix = string([:len()-1])
			}
			.evalPrefix()
		} else if .Key() == tcell.KeyEscape {
			.currentOption = 
			.list.SetCurrentItem(.currentOption)
			.closeList()
			if .selected != nil {
				if .currentOption > -1 {
					.selected(.currentOption, .options[.currentOption])
				}
			}
		} else {
			.prefix = ""
		}

		return 
	})

	(.list)
}

// closeList closes the embedded List element by hiding it and removing focus
// from it.
func ( *DropDown) ( func(Primitive)) {
	.open = false
	if .list.HasFocus() {
		()
	}
}

// Focus is called by the application when the primitive receives focus.
func ( *DropDown) ( func( Primitive)) {
	.Box.Focus()
	if .open {
		(.list)
	}
}

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

	return ._hasFocus()
}

func ( *DropDown) () bool {
	if .open {
		return .list.HasFocus()
	}
	return .hasFocus
}

// MouseHandler returns the mouse handler for this primitive.
func ( *DropDown) () func( MouseAction,  *tcell.EventMouse,  func( Primitive)) ( bool,  Primitive) {
	return .WrapMouseHandler(func( MouseAction,  *tcell.EventMouse,  func( Primitive)) ( bool,  Primitive) {
		// Was the mouse event in the drop-down box itself (or on its label)?
		,  := .Position()
		, , ,  := .GetInnerRect()
		 :=  == 
		if !.open && ! {
			return .InRect(, ), nil // No, and it's not expanded either. Ignore.
		}

		// Handle dragging. Clicks are implicitly handled by this logic.
		switch  {
		case MouseLeftDown:
			 = .open || 
			 = 
			if !.open {
				.openList()
				.dragging = true
			} else if ,  := .list.MouseHandler()(MouseLeftClick, , ); ! {
				.closeList() // Close drop-down if clicked outside of it.
			}
		case MouseMove:
			if .dragging {
				// We pretend it's a left click so we can see the selection during
				// dragging. Because we don't act upon it, it's not a problem.
				.list.MouseHandler()(MouseLeftClick, , )
				 = true
				 = 
			}
		case MouseLeftUp:
			if .dragging {
				.dragging = false
				.list.MouseHandler()(MouseLeftClick, , )
				 = true
			}
		}

		return
	})
}