package selector

import (
	"github.com/charmbracelet/bubbles/key"
	"github.com/charmbracelet/bubbles/list"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
)

// Selector is a list of items that can be selected.
type Selector struct {
	list.Model
	common      common.Common
	active      int
	filterState list.FilterState
}

// IdentifiableItem is an item that can be identified by a string. Implements
// list.DefaultItem.
type IdentifiableItem interface {
	list.DefaultItem
	ID() string
}

// ItemDelegate is a wrapper around list.ItemDelegate.
type ItemDelegate interface {
	list.ItemDelegate
}

// SelectMsg is a message that is sent when an item is selected.
type SelectMsg struct{ IdentifiableItem }

// ActiveMsg is a message that is sent when an item is active but not selected.
type ActiveMsg struct{ IdentifiableItem }

// New creates a new selector.
func New(common common.Common, items []IdentifiableItem, delegate ItemDelegate) *Selector {
	itms := make([]list.Item, len(items))
	for i, item := range items {
		itms[i] = item
	}
	l := list.New(itms, delegate, common.Width, common.Height)
	s := &Selector{
		Model:  l,
		common: common,
	}
	s.SetSize(common.Width, common.Height)
	return s
}

// PerPage returns the number of items per page.
func (s *Selector) PerPage() int {
	return s.Model.Paginator.PerPage
}

// SetPage sets the current page.
func (s *Selector) SetPage(page int) {
	s.Model.Paginator.Page = page
}

// Page returns the current page.
func (s *Selector) Page() int {
	return s.Model.Paginator.Page
}

// TotalPages returns the total number of pages.
func (s *Selector) TotalPages() int {
	return s.Model.Paginator.TotalPages
}

// Select selects the item at the given index.
func (s *Selector) Select(index int) {
	s.Model.Select(index)
}

// SetShowTitle sets the show title flag.
func (s *Selector) SetShowTitle(show bool) {
	s.Model.SetShowTitle(show)
}

// SetShowHelp sets the show help flag.
func (s *Selector) SetShowHelp(show bool) {
	s.Model.SetShowHelp(show)
}

// SetShowStatusBar sets the show status bar flag.
func (s *Selector) SetShowStatusBar(show bool) {
	s.Model.SetShowStatusBar(show)
}

// DisableQuitKeybindings disables the quit keybindings.
func (s *Selector) DisableQuitKeybindings() {
	s.Model.DisableQuitKeybindings()
}

// SetShowFilter sets the show filter flag.
func (s *Selector) SetShowFilter(show bool) {
	s.Model.SetShowFilter(show)
}

// SetShowPagination sets the show pagination flag.
func (s *Selector) SetShowPagination(show bool) {
	s.Model.SetShowPagination(show)
}

// SetFilteringEnabled sets the filtering enabled flag.
func (s *Selector) SetFilteringEnabled(enabled bool) {
	s.Model.SetFilteringEnabled(enabled)
}

// SetSize implements common.Component.
func (s *Selector) SetSize(width, height int) {
	s.common.SetSize(width, height)
	s.Model.SetSize(width, height)
}

// SetItems sets the items in the selector.
func (s *Selector) SetItems(items []IdentifiableItem) tea.Cmd {
	its := make([]list.Item, len(items))
	for i, item := range items {
		its[i] = item
	}
	return s.Model.SetItems(its)
}

// Index returns the index of the selected item.
func (s *Selector) Index() int {
	return s.Model.Index()
}

// Init implements tea.Model.
func (s *Selector) Init() tea.Cmd {
	return s.activeCmd
}

// Update implements tea.Model.
func (s *Selector) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	cmds := make([]tea.Cmd, 0)
	switch msg := msg.(type) {
	case tea.KeyMsg:
		filterState := s.Model.FilterState()
		switch {
		case key.Matches(msg, s.common.KeyMap.Help):
			if filterState == list.Filtering {
				return s, tea.Batch(cmds...)
			}
		case key.Matches(msg, s.common.KeyMap.Select):
			if filterState != list.Filtering {
				cmds = append(cmds, s.selectCmd)
			}
		}
	case list.FilterMatchesMsg:
		cmds = append(cmds, s.activeFilterCmd)
	}
	m, cmd := s.Model.Update(msg)
	s.Model = m
	if cmd != nil {
		cmds = append(cmds, cmd)
	}
	// Track filter state and update active item when filter state changes.
	filterState := s.Model.FilterState()
	if s.filterState != filterState {
		cmds = append(cmds, s.activeFilterCmd)
	}
	s.filterState = filterState
	// Send ActiveMsg when index change.
	if s.active != s.Model.Index() {
		cmds = append(cmds, s.activeCmd)
	}
	s.active = s.Model.Index()
	return s, tea.Batch(cmds...)
}

// View implements tea.Model.
func (s *Selector) View() string {
	return s.Model.View()
}

// SelectItem is a command that selects the currently active item.
func (s *Selector) SelectItem() tea.Msg {
	return s.selectCmd()
}

func (s *Selector) selectCmd() tea.Msg {
	item := s.Model.SelectedItem()
	i, ok := item.(IdentifiableItem)
	if !ok {
		return SelectMsg{}
	}
	return SelectMsg{i}
}

func (s *Selector) activeCmd() tea.Msg {
	item := s.Model.SelectedItem()
	i, ok := item.(IdentifiableItem)
	if !ok {
		return ActiveMsg{}
	}
	return ActiveMsg{i}
}

func (s *Selector) activeFilterCmd() tea.Msg {
	// Here we use VisibleItems because when list.FilterMatchesMsg is sent,
	// VisibleItems is the only way to get the list of filtered items. The list
	// bubble should export something like list.FilterMatchesMsg.Items().
	items := s.Model.VisibleItems()
	if len(items) == 0 {
		return nil
	}
	item := items[0]
	i, ok := item.(IdentifiableItem)
	if !ok {
		return nil
	}
	return ActiveMsg{i}
}
