package tui

import (
	"slices"
	"strings"

	"charm.land/bubbles/v2/textinput"
	tea "charm.land/bubbletea/v2"
	"github.com/charmbracelet/x/ansi"
)

// form is a column of text fields with one focused; the MCP and webhook
// forms both build on it.
type form struct {
	Inputs map[string]*textinput.Model
	Focus  int
}

// newField is a text input that follows a label instead of a prompt.
func newField() textinput.Model {
	input := newTextInput()
	input.Prompt = ""
	return input
}

// ask re-arms input for the next question, masking a secret, and reports
// the blink command that shows its cursor.
func ask(input *textinput.Model, value string, secret bool) tea.Cmd {
	input.Reset()
	input.EchoMode = echo(secret)
	input.SetValue(value)
	input.Focus()
	return textinput.Blink
}

func echo(secret bool) textinput.EchoMode {
	if secret {
		return textinput.EchoPassword
	}
	return textinput.EchoNormal
}

// newForm makes a field for each key, the masked ones echoing dots, with a
// length limit no pasted secret reaches.
func newForm(keys []string, masked ...string) form {
	f := form{Inputs: map[string]*textinput.Model{}}
	for _, key := range keys {
		input := newField()
		input.CharLimit, input.EchoMode = 4096, echo(slices.Contains(masked, key))
		f.Inputs[key] = &input
	}
	return f
}

// focus focuses the field named current and blurs the rest.
func (f *form) focus(current string) {
	for key, input := range f.Inputs {
		if key == current {
			input.Focus()
		} else {
			input.Blur()
		}
	}
}

// key applies the movement keys every form shares, cycling through fields
// from first on; submit reports that the form should be saved.
func (f *form) key(msg tea.Msg, fields []string, first int) (submit, handled bool) {
	key, ok := msg.(tea.KeyPressMsg)
	if !ok {
		return false, false
	}
	delta := 0
	switch key.String() {
	case "tab", "down":
		delta = 1
	case "shift+tab", "up":
		delta = -1
	case "ctrl+s":
		return true, true
	case "enter":
		if f.Focus == len(fields)-1 {
			return true, true
		}
		delta = 1
	default:
		return false, false
	}
	n := len(fields) - first
	f.Focus = first + (f.Focus-first+delta+n)%n
	f.focus(fields[f.Focus])
	return false, true
}

// value is a field's text without surrounding space.
func (f form) value(key string) string { return strings.TrimSpace(f.Inputs[key].Value()) }

// fit sizes the fields to the columns their rows leave for a value; a
// textinput without a width shows only the first rune of its placeholder.
func (f form) fit(width int) {
	for _, input := range f.Inputs {
		input.SetWidth(max(1, width))
	}
}

// formRow is one labelled field, marked when focused.
func formRow(focused bool, label string, pad int, value string, width int) string {
	lead := "  "
	if focused {
		lead = promptLead()
	}
	return ansi.Truncate(lead+padRight(label, pad)+value, width, "…")
}

// formFooter explains the focused field, then offers the keys every form
// shares after any of its own.
func formFooter(note string, width int, own ...hint) string {
	line := keyHints(append(own, hint{"tab/↑↓", "move"}, hint{"enter", "next"}, hint{"ctrl+s", "save"}, hint{"esc", "cancel"})...)
	if note != "" {
		line = DefaultStyles.Faint.Render(note) + DefaultStyles.Decor.Render(" · ") + line
	}
	return ansi.Truncate(line, width, "…")
}

func padRight(s string, n int) string {
	if len(s) >= n {
		return s + " "
	}
	return s + strings.Repeat(" ", n-len(s))
}
