package choose

import (
	"fmt"
	"strings"

	"charm.land/bubbles/v2/textinput"
	tea "charm.land/bubbletea/v2"
	"charm.land/lipgloss/v2"
	"github.com/idursun/jjui/internal/ui/actions"
	"github.com/idursun/jjui/internal/ui/common"
	"github.com/idursun/jjui/internal/ui/intents"
	"github.com/idursun/jjui/internal/ui/layout"
	"github.com/idursun/jjui/internal/ui/render"
)

type SelectedMsg struct {
	Value string
}

type CancelledMsg struct{}

type itemClickMsg struct {
	Index int
}

type itemScrollMsg struct {
	Delta      int
	Horizontal bool
}

func (m itemScrollMsg) SetDelta(delta int, horizontal bool) tea.Msg {
	m.Delta = delta
	m.Horizontal = horizontal
	return m
}

var _ common.ImmediateModel = (*Model)(nil)

type Model struct {
	options             []string
	filteredOptions     []string
	selected            int
	title               string
	listRenderer        *render.ListRenderer
	ensureCursorVisible bool
	filterable          bool
	filtering           bool
	ordered             bool
	input               textinput.Model
}

const maxVisibleItems = 20

func New(options []string) *Model {
	return NewWithTitle(options, "", false)
}

func NewWithTitle(options []string, title string, filterable bool) *Model {
	return NewWithOptions(options, title, filterable, false)
}

func NewWithOptions(options []string, title string, filterable bool, ordered bool) *Model {
	ti := textinput.New()
	ti.Prompt = "/"
	ti.Placeholder = "filter..."
	ti.CharLimit = 100
	ti.SetWidth(20)
	ti.SetVirtualCursor(false)

	m := &Model{
		options:         options,
		filteredOptions: options,
		title:           title,
		listRenderer:    render.NewListRenderer(itemScrollMsg{}),
		filterable:      filterable,
		ordered:         ordered,
		input:           ti,
	}
	m.listRenderer.Z = render.ZMenuContent
	return m
}

func (m *Model) Init() tea.Cmd {
	return nil
}

func (m *Model) IsEditing() bool {
	return m.filtering
}

func (m *Model) Scopes() []common.Scope {
	if m.IsEditing() {
		return []common.Scope{
			{
				Name:    actions.ScopeChoose + ".filter",
				Leak:    common.LeakNone,
				Handler: m,
			},
			{
				Name:    actions.ScopeChoose,
				Leak:    common.LeakNone,
				Handler: m,
			},
		}
	}
	return []common.Scope{
		{
			Name:    actions.ScopeChoose,
			Leak:    common.LeakAll,
			Handler: m,
		},
	}
}

func (m *Model) HandleIntent(intent intents.Intent) (tea.Cmd, bool) {
	switch intent := intent.(type) {
	case intents.ChooseNavigate:
		m.move(intent.Delta)
		return nil, true
	case intents.ChooseApply:
		return m.selectCurrent(), true
	case intents.ChooseCancel:
		if m.filtering {
			m.filtering = false
			m.input.Reset()
			m.filteredOptions = m.options
			m.selected = 0
			return nil, true
		}
		return newCmd(CancelledMsg{}), true
	}
	return nil, false
}

func (m *Model) Update(msg tea.Msg) tea.Cmd {
	var cmd tea.Cmd
	switch msg := msg.(type) {
	case intents.Intent:
		intentCmd, _ := m.HandleIntent(msg)
		return intentCmd
	case tea.KeyMsg, tea.PasteMsg:
		if m.filtering {
			m.input, cmd = m.input.Update(msg)
			m.filterOptions()
			return cmd
		}
		keyMsg, ok := msg.(tea.KeyMsg)
		if !ok {
			return nil
		}
		if m.ordered {
			if r := keyMsg.String(); len(r) == 1 && r[0] >= '1' && r[0] <= '9' {
				idx := int(r[0] - '1')
				if idx < len(m.filteredOptions) {
					m.selected = idx
					return m.selectCurrent()
				}
			}
		}
		if m.filterable && keyMsg.String() == "/" {
			m.filtering = true
			m.input.Focus()
			return textinput.Blink
		}
		return nil
	case common.CloseViewMsg:
		return newCmd(CancelledMsg{})
	case itemScrollMsg:
		if msg.Horizontal {
			return nil
		}
		if m.listRenderer == nil {
			m.listRenderer = render.NewListRenderer(itemScrollMsg{})
		}
		m.listRenderer.StartLine += msg.Delta
		if m.listRenderer.StartLine < 0 {
			m.listRenderer.StartLine = 0
		}
	case itemClickMsg:
		if msg.Index < 0 || msg.Index >= len(m.filteredOptions) {
			return nil
		}
		m.selected = msg.Index
		return m.selectCurrent()
	}
	return nil
}

func (m *Model) filterOptions() {
	term := strings.ToLower(m.input.Value())
	if term == "" {
		m.filteredOptions = m.options
	} else {
		filtered := []string{}
		for _, opt := range m.options {
			if strings.Contains(strings.ToLower(opt), term) {
				filtered = append(filtered, opt)
			}
		}
		m.filteredOptions = filtered
	}
	if m.selected >= len(m.filteredOptions) {
		m.selected = 0
	}
}

func (m *Model) move(delta int) {
	if len(m.filteredOptions) == 0 {
		return
	}
	next := m.selected + delta
	n := len(m.filteredOptions)
	if next < 0 {
		next = 0
	}
	if next >= n {
		next = n - 1
	}
	if next == m.selected {
		return
	}
	m.selected = next
	m.ensureCursorVisible = true
}

func (m *Model) selectCurrent() tea.Cmd {
	if len(m.filteredOptions) == 0 {
		return newCmd(CancelledMsg{})
	}
	value := m.filteredOptions[m.selected]
	return newCmd(SelectedMsg{Value: value})
}

func (m *Model) ViewRect(dl *render.DisplayContext, box layout.Box) {
	if m.listRenderer == nil {
		m.listRenderer = render.NewListRenderer(itemScrollMsg{})
	}

	borderStyle := common.DefaultPalette.GetBorder("choose border", lipgloss.RoundedBorder())
	surfaceStyle := common.DefaultPalette.Get("choose")
	textStyle := common.DefaultPalette.Get("choose text")
	titleStyle := common.DefaultPalette.Get("choose title")
	selectedStyle := common.DefaultPalette.Get("choose selected")
	inputStyle := common.DefaultPalette.Get("choose input")

	inputStyles := m.input.Styles()
	inputStyles.Focused.Text = inputStyle
	inputStyles.Focused.Prompt = inputStyle
	inputStyles.Focused.Placeholder = inputStyle
	inputStyles.Blurred.Text = inputStyle
	inputStyles.Blurred.Prompt = inputStyle
	inputStyles.Blurred.Placeholder = inputStyle
	m.input.SetStyles(inputStyles)

	maxContentWidth := max(box.R.Dx()-2, 0)
	maxContentHeight := max(box.R.Dy()-2, 0)
	if maxContentWidth <= 0 || maxContentHeight <= 0 {
		return
	}

	titleHeight := 0
	if m.title != "" {
		titleHeight = 1
	}
	inputHeight := 0
	if m.filtering {
		inputHeight = 1
	}

	itemWidth := 0
	orderPrefix := 0
	if m.ordered {
		orderPrefix = 3 // "N. " prefix
	}
	for _, opt := range m.options {
		itemWidth = max(itemWidth, render.StringWidth(opt)+2+orderPrefix)
	}
	if m.title != "" {
		itemWidth = max(itemWidth, render.StringWidth(m.title))
	}
	if m.filtering {
		itemWidth = max(itemWidth, 25)
	}

	contentWidth := min(itemWidth, maxContentWidth)
	listHeightLimit := max(maxContentHeight-titleHeight-inputHeight, 0)
	// Use total options for height calculation to prevent resizing during filtering
	listHeight := min(min(len(m.options), listHeightLimit), maxVisibleItems)
	if listHeight == 0 && len(m.options) > 0 {
		listHeight = 1 // Ensure at least one line if there are options
	}
	if len(m.options) == 0 {
		listHeight = 0
	}

	contentHeight := titleHeight + inputHeight + listHeight
	if contentWidth <= 0 || contentHeight <= 0 {
		return
	}

	frame := box.Center(contentWidth+2, contentHeight+2)
	if frame.R.Dx() <= 0 || frame.R.Dy() <= 0 {
		return
	}

	dl.AddBackdrop(box.R, render.ZMenuBorder-1)
	contentBox := frame.Inset(1)
	if contentBox.R.Dx() <= 0 || contentBox.R.Dy() <= 0 {
		return
	}
	dl.AddFill(contentBox.R, ' ', surfaceStyle, render.ZMenuContent)

	borderBase := lipgloss.NewStyle().Width(contentBox.R.Dx()).Height(contentBox.R.Dy()).Render("")
	dl.AddDraw(frame.R, borderStyle.Render(borderBase), render.ZMenuBorder)

	listBox := contentBox
	if titleHeight > 0 {
		var titleBox layout.Box
		titleBox, listBox = contentBox.CutTop(1)
		dl.AddDraw(titleBox.R, titleStyle.Render(m.title), render.ZMenuContent)
		dl.AddPaint(titleBox.R, titleStyle, render.ZMenuContent)
	}

	if inputHeight > 0 {
		var inputBox layout.Box
		inputBox, listBox = listBox.CutTop(1)
		dl.AddDraw(inputBox.R, inputStyle.Render(m.input.View()), render.ZMenuContent)
		dl.AddPaint(inputBox.R, inputStyle, render.ZMenuContent)
		dl.SetCursorInRect(m.input.Cursor(), inputBox.R, 0, 0)
	}

	if listBox.R.Dx() <= 0 || listBox.R.Dy() <= 0 {
		return
	}

	itemCount := len(m.filteredOptions)
	m.listRenderer.StartLine = render.ClampStartLine(m.listRenderer.StartLine, listBox.R.Dy(), itemCount)
	m.listRenderer.Render(
		dl,
		listBox,
		itemCount,
		m.selected,
		m.ensureCursorVisible,
		func(_ int) int { return 1 },
		func(dl *render.DisplayContext, index int, rect layout.Rectangle) {
			if index < 0 || index >= itemCount || rect.Dx() <= 0 || rect.Dy() <= 0 {
				return
			}
			style := textStyle
			if index == m.selected {
				style = selectedStyle
			}
			label := m.filteredOptions[index]
			if m.ordered && index < 9 {
				label = fmt.Sprintf("%d. %s", index+1, label)
			}
			line := style.Padding(0, 1).Width(rect.Dx()).Render(label)
			dl.AddDraw(rect, line, render.ZMenuContent)
			dl.AddPaint(rect, style, render.ZMenuContent)
		},
		func(index int, _ tea.Mouse) tea.Msg { return itemClickMsg{Index: index} },
	)
	m.listRenderer.RegisterScroll(dl, listBox)
	m.ensureCursorVisible = false
}

func newCmd(msg tea.Msg) tea.Cmd {
	return func() tea.Msg { return msg }
}

func ShowWithTitle(options []string, title string, filter bool) tea.Cmd {
	return func() tea.Msg {
		return common.ShowChooseMsg{Options: options, Title: title, Filter: filter}
	}
}

func ShowOrdered(options []string, title string, filter bool, ordered bool) tea.Cmd {
	return func() tea.Msg {
		return common.ShowChooseMsg{Options: options, Title: title, Filter: filter, Ordered: ordered}
	}
}

func Show(options []string) tea.Cmd {
	return ShowWithTitle(options, "", false)
}
