// Package screens provides Screen implementations that wrap keld's
// interactive UI components for use with the unified session.
package screens

import (
	"fmt"
	"strings"
	"unicode"

	"charm.land/bubbles/v2/key"
	tea "charm.land/bubbletea/v2"
	"charm.land/lipgloss/v2"

	"git.secluded.site/keld/internal/theme"
	"git.secluded.site/keld/internal/ui"
)

// MenuItem represents a single entry in the command menu.
type MenuItem struct {
	// Label is the display text (e.g. "backup").
	Label string
	// Hotkey is the single character that instantly selects this item.
	Hotkey rune
	// Value is the string returned by Selection() when chosen. If
	// empty, Label is used.
	Value string
}

// itemValue returns the effective value for an item.
func (mi MenuItem) itemValue() string {
	if mi.Value != "" {
		return mi.Value
	}
	return mi.Label
}

// menuKeys defines the key bindings for the menu screen.
var menuKeys = struct {
	Up    key.Binding
	Down  key.Binding
	Enter key.Binding
	Esc   key.Binding
}{
	Up:    key.NewBinding(key.WithKeys("up", "k"), key.WithHelp("↑/↓", "navigate")),
	Down:  key.NewBinding(key.WithKeys("down", "j"), key.WithHelp("↑/↓", "navigate")),
	Enter: key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "select")),
	Esc:   key.NewBinding(key.WithKeys("esc")),
}

// Menu is a Screen adapter that presents a selectable list of
// commands. It replaces the standalone menu.Model with a version
// that integrates into the unified session.
type Menu struct {
	items     []MenuItem
	cursor    int
	selection string
	styles    *theme.Styles
}

// NewMenu creates a menu screen for the given items. The styles
// pointer should come from Session.Styles() so theme updates
// propagate automatically.
func NewMenu(items []MenuItem, styles *theme.Styles) *Menu {
	owned := make([]MenuItem, len(items))
	copy(owned, items)
	return &Menu{
		items:  owned,
		styles: styles,
	}
}

// Init is a no-op for the menu — it has no async startup work.
func (m *Menu) Init() tea.Cmd { return nil }

// Update handles key presses for navigation, selection, and hotkeys.
func (m *Menu) Update(msg tea.Msg) (ui.Screen, tea.Cmd) {
	kp, ok := msg.(tea.KeyPressMsg)
	if !ok {
		return m, nil
	}

	switch {
	case key.Matches(kp, menuKeys.Esc):
		return m, ui.BackCmd

	case key.Matches(kp, menuKeys.Up):
		if m.cursor > 0 {
			m.cursor--
		}
		return m, nil

	case key.Matches(kp, menuKeys.Down):
		if m.cursor < len(m.items)-1 {
			m.cursor++
		}
		return m, nil

	case key.Matches(kp, menuKeys.Enter):
		m.selection = m.items[m.cursor].itemValue()
		return m, ui.DoneCmd
	}

	// Check for hotkey match. Use rune decoding so multi-byte
	// UTF-8 characters (e.g. 'ñ') are handled correctly.
	runes := []rune(kp.Text)
	if len(runes) == 1 {
		for i, item := range m.items {
			if item.Hotkey == runes[0] {
				m.cursor = i
				m.selection = item.itemValue()
				return m, ui.DoneCmd
			}
		}
	}

	return m, nil
}

// View renders the menu as a vertical list with a cursor indicator.
func (m *Menu) View() string {
	accent := m.styles.Accent
	hotStyle := lipgloss.NewStyle().Bold(true).Foreground(accent)
	labelStyle := lipgloss.NewStyle()
	cursorStyle := lipgloss.NewStyle().Foreground(accent).Bold(true)

	var b strings.Builder
	for i, item := range m.items {
		cursor := "  "
		if i == m.cursor {
			cursor = cursorStyle.Render(theme.Cursor)
		}

		line := renderMenuItem(item, hotStyle, labelStyle)
		fmt.Fprintf(&b, "%s%s\n", cursor, line)
	}

	return b.String()
}

// Title returns the menu's display title.
func (m *Menu) Title() string { return "Select a command" }

// KeyBindings returns the key bindings for the help bar.
func (m *Menu) KeyBindings() []key.Binding {
	return []key.Binding{menuKeys.Up, menuKeys.Enter}
}

// Selection returns the chosen command name, or "" if nothing has
// been selected yet.
func (m *Menu) Selection() string { return m.selection }

// renderMenuItem formats a menu item with the hotkey highlighted.
// For example, with hotkey 'b' and label "backup", it renders
// "[b]ackup" where [b] is in the accent style.
//
// The search is rune-based so multi-byte characters are handled
// correctly.
func renderMenuItem(item MenuItem, hotStyle, labelStyle lipgloss.Style) string {
	label := item.Label
	hk := unicode.ToLower(item.Hotkey)

	runes := []rune(label)
	for i, r := range runes {
		if unicode.ToLower(r) == hk {
			before := string(runes[:i])
			match := string(runes[i : i+1])
			after := string(runes[i+1:])
			return labelStyle.Render(before) +
				hotStyle.Render("["+match+"]") +
				labelStyle.Render(after)
		}
	}

	// Hotkey not found in label — show as prefix.
	return hotStyle.Render("["+string(item.Hotkey)+"]") + " " + labelStyle.Render(label)
}
