package commands

import (
	"cmp"

	"charm.land/bubbles/v2/help"
	"charm.land/bubbles/v2/key"
	"charm.land/bubbles/v2/textinput"
	tea "charm.land/bubbletea/v2"
	"charm.land/lipgloss/v2"
	"git.secluded.site/crush/internal/tui/components/dialogs"
	"git.secluded.site/crush/internal/tui/styles"
	"git.secluded.site/crush/internal/tui/util"
)

const (
	argumentsDialogID dialogs.DialogID = "arguments"
)

// ShowArgumentsDialogMsg is a message that is sent to show the arguments dialog.
type ShowArgumentsDialogMsg struct {
	CommandID   string
	Description string
	ArgNames    []string
	OnSubmit    func(args map[string]string) tea.Cmd
}

// CloseArgumentsDialogMsg is a message that is sent when the arguments dialog is closed.
type CloseArgumentsDialogMsg struct {
	Submit    bool
	CommandID string
	Content   string
	Args      map[string]string
}

// CommandArgumentsDialog represents the commands dialog.
type CommandArgumentsDialog interface {
	dialogs.DialogModel
}

type commandArgumentsDialogCmp struct {
	wWidth, wHeight int
	width, height   int

	inputs    []textinput.Model
	focused   int
	keys      ArgumentsDialogKeyMap
	arguments []Argument
	help      help.Model

	id          string
	title       string
	name        string
	description string

	onSubmit func(args map[string]string) tea.Cmd
}

type Argument struct {
	Name, Title, Description string
	Required                 bool
}

func NewCommandArgumentsDialog(
	id, title, name, description string,
	arguments []Argument,
	onSubmit func(args map[string]string) tea.Cmd,
) CommandArgumentsDialog {
	t := styles.CurrentTheme()
	inputs := make([]textinput.Model, len(arguments))

	for i, arg := range arguments {
		ti := textinput.New()
		ti.Placeholder = cmp.Or(arg.Description, "Enter value for "+arg.Title)
		ti.SetWidth(40)
		ti.SetVirtualCursor(false)
		ti.Prompt = ""

		ti.SetStyles(t.S().TextInput)
		// Only focus the first input initially
		if i == 0 {
			ti.Focus()
		} else {
			ti.Blur()
		}

		inputs[i] = ti
	}

	return &commandArgumentsDialogCmp{
		inputs:      inputs,
		keys:        DefaultArgumentsDialogKeyMap(),
		id:          id,
		name:        name,
		title:       title,
		description: description,
		arguments:   arguments,
		width:       60,
		help:        help.New(),
		onSubmit:    onSubmit,
	}
}

// Init implements CommandArgumentsDialog.
func (c *commandArgumentsDialogCmp) Init() tea.Cmd {
	return nil
}

// Update implements CommandArgumentsDialog.
func (c *commandArgumentsDialogCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.WindowSizeMsg:
		c.wWidth = msg.Width
		c.wHeight = msg.Height
		c.width = min(90, c.wWidth)
		c.height = min(15, c.wHeight)
		for i := range c.inputs {
			c.inputs[i].SetWidth(c.width - (paddingHorizontal * 2))
		}
	case tea.KeyPressMsg:
		switch {
		case key.Matches(msg, c.keys.Close):
			return c, util.CmdHandler(dialogs.CloseDialogMsg{})
		case key.Matches(msg, c.keys.Confirm):
			if c.focused == len(c.inputs)-1 {
				args := make(map[string]string)
				for i, arg := range c.arguments {
					value := c.inputs[i].Value()
					args[arg.Name] = value
				}
				return c, tea.Sequence(
					util.CmdHandler(dialogs.CloseDialogMsg{}),
					c.onSubmit(args),
				)
			}
			// Otherwise, move to the next input
			c.inputs[c.focused].Blur()
			c.focused++
			c.inputs[c.focused].Focus()
		case key.Matches(msg, c.keys.Next):
			// Move to the next input
			c.inputs[c.focused].Blur()
			c.focused = (c.focused + 1) % len(c.inputs)
			c.inputs[c.focused].Focus()
		case key.Matches(msg, c.keys.Previous):
			// Move to the previous input
			c.inputs[c.focused].Blur()
			c.focused = (c.focused - 1 + len(c.inputs)) % len(c.inputs)
			c.inputs[c.focused].Focus()
		case key.Matches(msg, c.keys.Close):
			return c, util.CmdHandler(dialogs.CloseDialogMsg{})
		default:
			var cmd tea.Cmd
			c.inputs[c.focused], cmd = c.inputs[c.focused].Update(msg)
			return c, cmd
		}
	case tea.PasteMsg:
		var cmd tea.Cmd
		c.inputs[c.focused], cmd = c.inputs[c.focused].Update(msg)
		return c, cmd
	}
	return c, nil
}

// View implements CommandArgumentsDialog.
func (c *commandArgumentsDialogCmp) View() string {
	t := styles.CurrentTheme()
	baseStyle := t.S().Base

	title := lipgloss.NewStyle().
		Foreground(t.Primary).
		Bold(true).
		Padding(0, 1).
		Render(cmp.Or(c.title, c.name))

	promptName := t.S().Text.
		Padding(0, 1).
		Render(c.description)

	inputFields := make([]string, len(c.inputs))
	for i, input := range c.inputs {
		labelStyle := baseStyle.Padding(1, 1, 0, 1)

		if i == c.focused {
			labelStyle = labelStyle.Foreground(t.FgBase).Bold(true)
		} else {
			labelStyle = labelStyle.Foreground(t.FgMuted)
		}

		arg := c.arguments[i]
		argName := cmp.Or(arg.Title, arg.Name)
		if arg.Required {
			argName += "*"
		}
		label := labelStyle.Render(argName + ":")

		field := t.S().Text.
			Padding(0, 1).
			Render(input.View())

		inputFields[i] = lipgloss.JoinVertical(lipgloss.Left, label, field)
	}

	elements := []string{title, promptName}
	elements = append(elements, inputFields...)

	c.help.ShowAll = false
	helpText := baseStyle.Padding(0, 1).Render(c.help.View(c.keys))
	elements = append(elements, "", helpText)

	content := lipgloss.JoinVertical(lipgloss.Left, elements...)

	return baseStyle.Padding(1, 1, 0, 1).
		Border(lipgloss.RoundedBorder()).
		BorderForeground(t.BorderFocus).
		Width(c.width).
		Render(content)
}

func (c *commandArgumentsDialogCmp) Cursor() *tea.Cursor {
	if len(c.inputs) == 0 {
		return nil
	}
	cursor := c.inputs[c.focused].Cursor()
	if cursor != nil {
		cursor = c.moveCursor(cursor)
	}
	return cursor
}

const (
	headerHeight      = 3
	itemHeight        = 3
	paddingHorizontal = 3
)

func (c *commandArgumentsDialogCmp) moveCursor(cursor *tea.Cursor) *tea.Cursor {
	row, col := c.Position()
	offset := row + headerHeight + (1+c.focused)*itemHeight
	cursor.Y += offset
	cursor.X = cursor.X + col + paddingHorizontal
	return cursor
}

func (c *commandArgumentsDialogCmp) Position() (int, int) {
	row := (c.wHeight / 2) - (c.height / 2)
	col := (c.wWidth / 2) - (c.width / 2)
	return row, col
}

// ID implements CommandArgumentsDialog.
func (c *commandArgumentsDialogCmp) ID() dialogs.DialogID {
	return argumentsDialogID
}
