package cview

import (
	
	
	
	
	

	
	
)

// InputField is a one-line box (three lines if there is a title) where the
// user can enter text. Use SetAcceptanceFunc() to accept or reject input,
// SetChangedFunc() to listen for changes, and SetMaskCharacter() to hide input
// from onlookers (e.g. for password input).
//
// The following keys can be used for navigation and editing:
//
//   - Left arrow: Move left by one character.
//   - Right arrow: Move right by one character.
//   - Home, Ctrl-A, Alt-a: Move to the beginning of the line.
//   - End, Ctrl-E, Alt-e: Move to the end of the line.
//   - Alt-left, Alt-b: Move left by one word.
//   - Alt-right, Alt-f: Move right by one word.
//   - Backspace: Delete the character before the cursor.
//   - Delete: Delete the character after the cursor.
//   - Ctrl-K: Delete from the cursor to the end of the line.
//   - Ctrl-W: Delete the last word before the cursor.
//   - Ctrl-U: Delete the entire line.
type InputField struct {
	*Box

	// The text that was entered.
	text []byte

	// The text to be displayed before the input area.
	label []byte

	// The text to be displayed in the input area when "text" is empty.
	placeholder []byte

	// 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 text color of the placeholder.
	placeholderTextColor tcell.Color

	// The text color of the placeholder when focused.
	placeholderTextColorFocused tcell.Color

	// The text color of the list items.
	autocompleteListTextColor tcell.Color

	// The background color of the autocomplete list.
	autocompleteListBackgroundColor tcell.Color

	// The text color of the selected ListItem.
	autocompleteListSelectedTextColor tcell.Color

	// The background color of the selected ListItem.
	autocompleteListSelectedBackgroundColor tcell.Color

	// The text color of the suggestion.
	autocompleteSuggestionTextColor tcell.Color

	// The text color of the note below the input field.
	fieldNoteTextColor tcell.Color

	// The note to show below the input field.
	fieldNote []byte

	// 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

	// A character to mask entered text (useful for password fields). A value of 0
	// disables masking.
	maskCharacter rune

	// The cursor position as a byte index into the text string.
	cursorPos int

	// An optional autocomplete function which receives the current text of the
	// input field and returns a slice of ListItems to be displayed in a drop-down
	// selection. Items' main text is displayed in the autocomplete list. When
	// set, items' secondary text is used as the selection value. Otherwise,
	// the main text is used.
	autocomplete func(text string) []*ListItem

	// The List object which shows the selectable autocomplete entries. If not
	// nil, the list's main texts represent the current autocomplete entries.
	autocompleteList *List

	// The suggested completion of the current autocomplete ListItem.
	autocompleteListSuggestion []byte

	// An optional function which may reject the last character that was entered.
	accept func(text string, ch rune) bool

	// An optional function which is called when the input has changed.
	changed func(text string)

	// An optional function which is called when the user indicated that they
	// are done entering text. The key which was pressed is provided (tab,
	// shift-tab, enter, 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)

	// The x-coordinate of the input field as determined during the last call to Draw().
	fieldX int

	// The number of bytes of the text string skipped ahead while drawing.
	offset int

	sync.RWMutex
}

// NewInputField returns a new input field.
func () *InputField {
	return &InputField{
		Box:                                     NewBox(),
		labelColor:                              Styles.SecondaryTextColor,
		fieldBackgroundColor:                    Styles.MoreContrastBackgroundColor,
		fieldBackgroundColorFocused:             Styles.ContrastBackgroundColor,
		fieldTextColor:                          Styles.PrimaryTextColor,
		fieldTextColorFocused:                   Styles.PrimaryTextColor,
		placeholderTextColor:                    Styles.ContrastSecondaryTextColor,
		autocompleteListTextColor:               Styles.PrimitiveBackgroundColor,
		autocompleteListBackgroundColor:         Styles.MoreContrastBackgroundColor,
		autocompleteListSelectedTextColor:       Styles.PrimitiveBackgroundColor,
		autocompleteListSelectedBackgroundColor: Styles.PrimaryTextColor,
		autocompleteSuggestionTextColor:         Styles.ContrastSecondaryTextColor,
		fieldNoteTextColor:                      Styles.SecondaryTextColor,
		labelColorFocused:                       ColorUnset,
		placeholderTextColorFocused:             ColorUnset,
	}
}

// SetText sets the current text of the input field.
func ( *InputField) ( string) {
	.Lock()

	.text = []byte()
	.cursorPos = len()
	if .changed != nil {
		.Unlock()
		.changed()
	} else {
		.Unlock()
	}
}

// GetText returns the current text of the input field.
func ( *InputField) () string {
	.RLock()
	defer .RUnlock()

	return string(.text)
}

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

	.label = []byte()
}

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

	return string(.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 ( *InputField) ( int) {
	.Lock()
	defer .Unlock()

	.labelWidth = 
}

// SetPlaceholder sets the text to be displayed when the input text is empty.
func ( *InputField) ( string) {
	.Lock()
	defer .Unlock()

	.placeholder = []byte()
}

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

	.labelColor = 
}

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

	.labelColorFocused = 
}

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

	.fieldBackgroundColor = 
}

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

	.fieldBackgroundColorFocused = 
}

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

	.fieldTextColor = 
}

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

	.fieldTextColorFocused = 
}

// SetPlaceholderTextColor sets the text color of placeholder text.
func ( *InputField) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.placeholderTextColor = 
}

// SetPlaceholderTextColorFocused sets the text color of placeholder text when
// focused.
func ( *InputField) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.placeholderTextColorFocused = 
}

// SetAutocompleteListTextColor sets the text color of the ListItems.
func ( *InputField) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.autocompleteListTextColor = 
}

// SetAutocompleteListBackgroundColor sets the background color of the
// autocomplete list.
func ( *InputField) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.autocompleteListBackgroundColor = 
}

// SetAutocompleteListSelectedTextColor sets the text color of the selected
// ListItem.
func ( *InputField) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.autocompleteListSelectedTextColor = 
}

// SetAutocompleteListSelectedBackgroundColor sets the background of the
// selected ListItem.
func ( *InputField) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.autocompleteListSelectedBackgroundColor = 
}

// SetAutocompleteSuggestionTextColor sets the text color of the autocomplete
// suggestion in the input field.
func ( *InputField) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.autocompleteSuggestionTextColor = 
}

// SetFieldNoteTextColor sets the text color of the note.
func ( *InputField) ( tcell.Color) {
	.Lock()
	defer .Unlock()

	.fieldNoteTextColor = 
}

// SetFieldNote sets the text to show below the input field, e.g. when the
// input is invalid.
func ( *InputField) ( string) {
	.Lock()
	defer .Unlock()

	.fieldNote = []byte()
}

// ResetFieldNote sets the note to an empty string.
func ( *InputField) () {
	.Lock()
	defer .Unlock()

	.fieldNote = nil
}

// SetFieldWidth sets the screen width of the input area. A value of 0 means
// extend as much as possible.
func ( *InputField) ( int) {
	.Lock()
	defer .Unlock()

	.fieldWidth = 
}

// GetFieldWidth returns this primitive's field width.
func ( *InputField) () int {
	.RLock()
	defer .RUnlock()

	return .fieldWidth
}

// GetFieldHeight returns the height of the field.
func ( *InputField) () int {
	.RLock()
	defer .RUnlock()
	if len(.fieldNote) == 0 {
		return 1
	}
	return 2
}

// GetCursorPosition returns the cursor position.
func ( *InputField) () int {
	.RLock()
	defer .RUnlock()

	return .cursorPos
}

// SetCursorPosition sets the cursor position.
func ( *InputField) ( int) {
	.Lock()
	defer .Unlock()

	.cursorPos = 
}

// SetMaskCharacter sets a character that masks user input on a screen. A value
// of 0 disables masking.
func ( *InputField) ( rune) {
	.Lock()
	defer .Unlock()

	.maskCharacter = 
}

// SetAutocompleteFunc sets an autocomplete callback function which may return
// ListItems to be selected from a drop-down based on the current text of the
// input field. The drop-down appears only if len(entries) > 0. The callback is
// invoked in this function and whenever the current text changes or when
// Autocomplete() is called. Entries are cleared when the user selects an entry
// or presses Escape.
func ( *InputField) ( func( string) ( []*ListItem)) {
	.Lock()
	.autocomplete = 
	.Unlock()

	.Autocomplete()
}

// Autocomplete invokes the autocomplete callback (if there is one). If the
// length of the returned autocomplete entries slice is greater than 0, the
// input field will present the user with a corresponding drop-down list the
// next time the input field is drawn.
//
// It is safe to call this function from any goroutine. Note that the input
// field is not redrawn automatically unless called from the main goroutine
// (e.g. in response to events).
func ( *InputField) () {
	.Lock()
	if .autocomplete == nil {
		.Unlock()
		return
	}
	.Unlock()

	// Do we have any autocomplete entries?
	 := .autocomplete(string(.text))
	if len() == 0 {
		// No entries, no list.
		.Lock()
		.autocompleteList = nil
		.autocompleteListSuggestion = nil
		.Unlock()
		return
	}

	.Lock()

	// Make a list if we have none.
	if .autocompleteList == nil {
		 := NewList()
		.SetChangedFunc(.autocompleteChanged)
		.ShowSecondaryText(false)
		.SetMainTextColor(.autocompleteListTextColor)
		.SetSelectedTextColor(.autocompleteListSelectedTextColor)
		.SetSelectedBackgroundColor(.autocompleteListSelectedBackgroundColor)
		.SetHighlightFullLine(true)
		.SetBackgroundColor(.autocompleteListBackgroundColor)

		.autocompleteList = 
	}

	// Fill it with the entries.
	 := -1
	.autocompleteList.Clear()
	for ,  := range  {
		.autocompleteList.AddItem()
		if  < 0 && .GetMainText() == string(.text) {
			 = 
		}
	}

	// Set the selection if we have one.
	if  >= 0 {
		.autocompleteList.SetCurrentItem()
	}

	.Unlock()
}

// autocompleteChanged gets called when another item in the
// autocomplete list has been selected.
func ( *InputField) ( int,  *ListItem) {
	 := .GetMainBytes()
	 := .GetSecondaryBytes()
	if len(.text) < len() {
		.autocompleteListSuggestion = [len(.text):]
	} else if len(.text) < len() {
		.autocompleteListSuggestion = [len(.text):]
	} else {
		.autocompleteListSuggestion = nil
	}
}

// SetAcceptanceFunc sets a handler which may reject the last character that was
// entered (by returning false).
//
// This package defines a number of variables prefixed with InputField which may
// be used for common input (e.g. numbers, maximum text length).
func ( *InputField) ( func( string,  rune) bool) {
	.Lock()
	defer .Unlock()

	.accept = 
}

// SetChangedFunc sets a handler which is called whenever the text of the input
// field has changed. It receives the current text (after the change).
func ( *InputField) ( func( string)) {
	.Lock()
	defer .Unlock()

	.changed = 
}

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

	.done = 
}

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

	.finished = 
}

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

	.Box.Draw()

	.Lock()
	defer .Unlock()

	// Select colors
	 := .labelColor
	 := .fieldBackgroundColor
	 := .fieldTextColor
	if .GetFocusable().HasFocus() {
		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(, .label, , , , AlignLeft, )
		 += 
	} else {
		,  := Print(, .label, , , -, AlignLeft, )
		 += 
	}

	// Draw input area.
	.fieldX = 
	 := .fieldWidth
	if  == 0 {
		 = math.MaxInt32
	}
	if - <  {
		 =  - 
	}
	 := tcell.StyleDefault.Background()
	for  := 0;  < ; ++ {
		.SetContent(+, , ' ', nil, )
	}

	// Text.
	var  int
	 := .text
	if len() == 0 && len(.placeholder) > 0 {
		// Draw placeholder text.
		 := .placeholderTextColor
		if .GetFocusable().HasFocus() && .placeholderTextColorFocused != ColorUnset {
			 = .placeholderTextColorFocused
		}
		Print(, EscapeBytes(.placeholder), , , , AlignLeft, )
		.offset = 0
	} else {
		// Draw entered text.
		if .maskCharacter > 0 {
			 = bytes.Repeat([]byte(string(.maskCharacter)), utf8.RuneCount(.text))
		}
		var  []byte
		if  > runewidth.StringWidth(string()) {
			// We have enough space for the full text.
			 = EscapeBytes()
			Print(, , , , , AlignLeft, )
			.offset = 0
			iterateString(string(), func( rune,  []rune, , , ,  int) bool {
				if  >= .cursorPos {
					return true
				}
				 += 
				return false
			})
		} else {
			// The text doesn't fit. Where is the cursor?
			if .cursorPos < 0 {
				.cursorPos = 0
			} else if .cursorPos > len() {
				.cursorPos = len()
			}
			// Shift the text so the cursor is inside the field.
			var  int
			if .offset > .cursorPos {
				.offset = .cursorPos
			} else if  := runewidth.StringWidth(string([.offset:.cursorPos]));  > -1 {
				 =  -  + 1
			}
			 := .offset
			iterateString(string(), func( rune,  []rune, , , ,  int) bool {
				if  >=  {
					if  > 0 {
						.offset =  + 
						 -= 
					} else {
						if + > .cursorPos {
							return true
						}
						 += 
					}
				}
				return false
			})
			 = EscapeBytes([.offset:])
			Print(, , , , , AlignLeft, )
		}
		// Draw suggestion
		if .maskCharacter == 0 && len(.autocompleteListSuggestion) > 0 {
			Print(, .autocompleteListSuggestion, +runewidth.StringWidth(string()), , -runewidth.StringWidth(string()), AlignLeft, .autocompleteSuggestionTextColor)
		}
	}

	// Draw field note
	if len(.fieldNote) > 0 {
		Print(, .fieldNote, , +1, , AlignLeft, .fieldNoteTextColor)
	}

	// Draw autocomplete list.
	if .autocompleteList != nil {
		// How much space do we need?
		 := .autocompleteList.GetItemCount()
		 := 0
		for  := 0;  < ; ++ {
			,  := .autocompleteList.GetItemText()
			 := TaggedStringWidth()
			if  >  {
				 = 
			}
		}

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

	// Set cursor.
	if .focus.HasFocus() {
		.ShowCursor(+, )
	}
}

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

		// Trigger changed events.
		 := .text
		defer func() {
			.Lock()
			 := .text
			.Unlock()

			if !bytes.Equal(, ) {
				.Autocomplete()
				if .changed != nil {
					.changed(string(.text))
				}
			}
		}()

		// Movement functions.
		 := func() { .cursorPos = 0 }
		 := func() { .cursorPos = len(.text) }
		 := func() {
			iterateStringReverse(string(.text[:.cursorPos]), func( rune,  []rune, , , ,  int) bool {
				.cursorPos -= 
				return true
			})
		}
		 := func() {
			iterateString(string(.text[.cursorPos:]), func( rune,  []rune, , , ,  int) bool {
				.cursorPos += 
				return true
			})
		}
		 := func() {
			.cursorPos = len(regexRightWord.ReplaceAll(.text[:.cursorPos], nil))
		}
		 := func() {
			.cursorPos = len(.text) - len(regexLeftWord.ReplaceAll(.text[.cursorPos:], nil))
		}

		// Add character function. Returns whether or not the rune character is
		// accepted.
		 := func( rune) bool {
			 := append(append(.text[:.cursorPos], []byte(string())...), .text[.cursorPos:]...)
			if .accept != nil && !.accept(string(), ) {
				return false
			}
			.text = 
			.cursorPos += len(string())
			return true
		}

		// Finish up.
		 := func( tcell.Key) {
			if .done != nil {
				.done()
			}
			if .finished != nil {
				.finished()
			}
		}

		// Process key event.
		switch  := .Key();  {
		case tcell.KeyRune: // Regular character.
			if .Modifiers()&tcell.ModAlt > 0 {
				// We accept some Alt- key combinations.
				switch .Rune() {
				case 'a': // Home.
					()
				case 'e': // End.
					()
				case 'b': // Move word left.
					()
				case 'f': // Move word right.
					()
				default:
					if !(.Rune()) {
						.Unlock()
						return
					}
				}
			} else {
				// Other keys are simply accepted as regular characters.
				if !(.Rune()) {
					.Unlock()
					return
				}
			}
		case tcell.KeyCtrlU: // Delete all.
			.text = nil
			.cursorPos = 0
		case tcell.KeyCtrlK: // Delete until the end of the line.
			.text = .text[:.cursorPos]
		case tcell.KeyCtrlW: // Delete last word.
			 := append(regexRightWord.ReplaceAll(.text[:.cursorPos], nil), .text[.cursorPos:]...)
			.cursorPos -= len(.text) - len()
			.text = 
		case tcell.KeyBackspace, tcell.KeyBackspace2: // Delete character before the cursor.
			iterateStringReverse(string(.text[:.cursorPos]), func( rune,  []rune, , , ,  int) bool {
				.text = append(.text[:], .text[+:]...)
				.cursorPos -= 
				return true
			})
			if .offset >= .cursorPos {
				.offset = 0
			}
		case tcell.KeyDelete: // Delete character after the cursor.
			iterateString(string(.text[.cursorPos:]), func( rune,  []rune, , , ,  int) bool {
				.text = append(.text[:.cursorPos], .text[.cursorPos+:]...)
				return true
			})
		case tcell.KeyLeft:
			if .Modifiers()&tcell.ModAlt > 0 {
				()
			} else {
				()
			}
		case tcell.KeyRight:
			if .Modifiers()&tcell.ModAlt > 0 {
				()
			} else {
				()
			}
		case tcell.KeyHome, tcell.KeyCtrlA:
			()
		case tcell.KeyEnd, tcell.KeyCtrlE:
			()
		case tcell.KeyEnter: // We might be done.
			if .autocompleteList != nil {
				 := .autocompleteList.GetCurrentItem()
				 := .GetMainText()
				if .GetSecondaryText() != "" {
					 = .GetSecondaryText()
				}
				.Unlock()
				.SetText()
				.Lock()
				.autocompleteList = nil
				.autocompleteListSuggestion = nil
				.Unlock()
			} else {
				.Unlock()
				()
			}
			return
		case tcell.KeyEscape:
			if .autocompleteList != nil {
				.autocompleteList = nil
				.autocompleteListSuggestion = nil
				.Unlock()
			} else {
				.Unlock()
				()
			}
			return
		case tcell.KeyDown, tcell.KeyTab: // Autocomplete selection.
			if .autocompleteList != nil {
				 := .autocompleteList.GetItemCount()
				 := .autocompleteList.GetCurrentItemIndex() + 1
				if  >=  {
					 = 0
				}
				.autocompleteList.SetCurrentItem()
				.Unlock()
			} else {
				.Unlock()
				()
			}
			return
		case tcell.KeyUp, tcell.KeyBacktab: // Autocomplete selection.
			if .autocompleteList != nil {
				 := .autocompleteList.GetCurrentItemIndex() - 1
				if  < 0 {
					 = .autocompleteList.GetItemCount() - 1
				}
				.autocompleteList.SetCurrentItem()
				.Unlock()
			} else {
				.Unlock()
				()
			}
			return
		}

		.Unlock()
	})
}

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

		// Process mouse event.
		if  == MouseLeftClick &&  ==  {
			// Determine where to place the cursor.
			if  >= .fieldX {
				if !iterateString(string(.text), func( rune,  []rune,  int,  int,  int,  int) bool {
					if -.fieldX < + {
						.cursorPos = 
						return true
					}
					return false
				}) {
					.cursorPos = len(.text)
				}
			}
			()
			 = true
		}

		return
	})
}

var (
	regexRightWord = regexp.MustCompile(`(\w*|\W)$`)
	regexLeftWord  = regexp.MustCompile(`^(\W|\w*)`)
)