package tui

import (
	"fmt"
	"io"
	"os"
	"sort"
	"strings"

	"github.com/Slug-Boi/cocommit/src/cmd/utils"

	"github.com/charmbracelet/bubbles/key"
	"github.com/charmbracelet/bubbles/list"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
)

const listHeight = 14

var (
	titleStyle             = lipgloss.NewStyle().MarginLeft(2)
	itemStyle              = lipgloss.NewStyle().PaddingLeft(4).Foreground(lipgloss.Color("170"))
	selectedItemStyle      = lipgloss.NewStyle().PaddingLeft(2).Background(lipgloss.Color("236")).Foreground(lipgloss.Color("170"))
	highlightStyle         = lipgloss.NewStyle().PaddingLeft(4).Background(lipgloss.Color("236")).Foreground(lipgloss.Color("170"))
	selectedHighlightStyle = lipgloss.NewStyle().PaddingLeft(2).Background(lipgloss.Color("206")).Foreground(lipgloss.Color("90"))
	deletionStyle          = lipgloss.NewStyle().MarginLeft(2).Foreground(lipgloss.Color("9"))
	paginationStyle        = list.DefaultStyles().PaginationStyle.PaddingLeft(4)
	ActivePaginationDot    = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "170", Dark: "170"})
	helpStyle              = list.DefaultStyles().HelpStyle.PaddingLeft(4).PaddingBottom(1)
)

type item string

var selected = map[string]item{}

var negation = false

var dupProtect = map[string]string{}

var sub_model tea.Model

type listKeyMap struct {
	selectAll    key.Binding
	negation     key.Binding
	groupSelect  key.Binding
	selectOne    key.Binding
	createAuthor key.Binding
	deleteAuthor key.Binding
	tempAdd      key.Binding
}

func newListKeyMap() *listKeyMap {
	return &listKeyMap{
		selectAll: key.NewBinding(
			key.WithKeys("A"),
			key.WithHelp("A", "Add all authors"),
		),
		negation: key.NewBinding(
			key.WithKeys("n"),
			key.WithHelp("n", "Toggle negation and select author"),
		),
		groupSelect: key.NewBinding(
			key.WithKeys("f"),
			key.WithHelp("f", "Select group"),
		),
		selectOne: key.NewBinding(
			key.WithKeys(" "),
			key.WithHelp("space", "Select author"),
		),
		createAuthor: key.NewBinding(
			key.WithKeys("C"),
			key.WithHelp("C", "Create new author"),
		),
		deleteAuthor: key.NewBinding(
			key.WithKeys("D"),
			key.WithHelp("D", "Delete author"),
		),
		tempAdd: key.NewBinding(
			key.WithKeys("T"),
			key.WithHelp("T", "Add temporary author"),
		),
	}
}

func (i item) FilterValue() string { return string(i) }

type itemDelegate struct{}

func (d itemDelegate) Height() int                             { return 1 }
func (d itemDelegate) Spacing() int                            { return 0 }
func (d itemDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil }
func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
	i, ok := listItem.(item)
	if !ok {
		return
	}

	str := fmt.Sprintf("%d. %s", index+1, i)

	fn := itemStyle.Render
	if _, ok := selected[string(i)]; ok {
		fn = func(s ...string) string {
			base := strings.Join(s, " ")
			if negation {
				base = base + " ^"
			}
			if index == m.Index() {
				return selectedHighlightStyle.Render("> " + base + " [X]")
			} else {
				return highlightStyle.Render(base + " [X]")
			}
		}
	} else {
		if index == m.Index() {
			fn = func(s ...string) string {
				return selectedItemStyle.Render("> " + strings.Join(s, " "))
			}
		}
	}

	fmt.Fprint(w, fn(str))
}

type model struct {
	list     list.Model
	keys     *listKeyMap
	quitting bool
}

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

func selectToggle(i item) {
	if _, ok := selected[string(i)]; ok {
		delete(selected, string(i))
		toggleNegation()
	} else {
		selected[string(i)] = i
	}
}

func toggleNegation() {
	if len(selected) == 0 {
		negation = false
	}
}

var deletion bool

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	if sub_model != nil {
		var cmd tea.Cmd
		sub_model, cmd = sub_model.Update(msg)
		if sub_model_mod, ok := sub_model.(model); ok {
			m.list = sub_model_mod.list
			sub_model = nil
			return m, nil
		}
		return m, cmd
	}

	switch msg := msg.(type) {
	case tea.WindowSizeMsg:
		m.list.SetWidth(msg.Width)
		return m, nil

	// If filtering is enabled, skip key handling
	case tea.KeyMsg:
		// deletion toggle with confirmation required
		b := false
		defer func(b *bool) { deletion = *b }(&b)
		if m.list.FilterState() == list.Filtering {
			switch msg.String() {
			case "ctrl+c":
				selected = nil
				return m, tea.Quit
			}
			break
		}
		// Handle keys from keyList (help menu)
		switch {
		case key.Matches(msg, m.keys.negation):
			i, ok := m.list.SelectedItem().(item)
			if ok {
				negation = true
				selectToggle(i)
			}

		case key.Matches(msg, m.keys.selectOne):
			i, ok := m.list.SelectedItem().(item)
			if ok {
				selectToggle(i)
			}

		case key.Matches(msg, m.keys.selectAll):
			//TODO: maybe look at behavior of this when auth are already selected
			negation = false
			for _, i := range m.list.Items() {
				selectToggle(i.(item))
			}

		case key.Matches(msg, m.keys.groupSelect):
			// TODO: Look into how to select multiple groups
			sub_model = newModel()
			return m, tea.ClearScreen

		case key.Matches(msg, m.keys.tempAdd):

			sub_model = tempAuthorModel(&m)
			return m, tea.ClearScreen

		case key.Matches(msg, m.keys.createAuthor):

			sub_model = createAuthorModel(&m)
			return m, tea.ClearScreen
		case key.Matches(msg, m.keys.deleteAuthor):
			if deletion {
				author_str := string(m.list.SelectedItem().(item))
				author := dupProtect[author_str]
				utils.DeleteOneAuthor(author)
				delete(dupProtect, author_str)
				m.list.RemoveItem(m.list.Index())
				return m, nil
			}
			b = true
			return m, nil
		}
		// extra key options
		switch keypress := msg.String(); keypress {
		case "q", "ctrl+c", "esc":
			if sub_model != nil {
				var cmd tea.Cmd
				sub_model, cmd = sub_model.Update(msg)
				return m, cmd
			}
			m.quitting = true
			selected = nil
			return m, tea.Quit

		case "enter":
			if sub_model != nil {
				var cmd tea.Cmd
				sub_model, cmd = sub_model.Update(msg)
				return m, cmd
			}
			m.quitting = true
			return m, tea.Quit
		}

	}

	var cmd tea.Cmd
	m.list, cmd = m.list.Update(msg)
	return m, cmd
}

func (m model) View() string {
	if sub_model != nil {
		return sub_model.View()
	}
	if m.quitting {
		return "" //quitTextStyle.Render(strings.Join(m.choice, " "))
	}

	sb := strings.Builder{}

	sb.WriteString("\n" + m.list.View())

	if deletion {
		sb.WriteString(deletionStyle.Render("\n  D: Confirm delete author"))
	}

	return sb.String()
}

func listModel() model {
	items := []list.Item{}

	selected = map[string]item{}

	dupProtect = map[string]string{}

	listKeys := newListKeyMap()

	// Add items to the list
	for short, user := range utils.Users {
		// if items already contains the user, skip it
		str_user := user.Username + " - " + user.Email
		if _, ok := dupProtect[str_user]; ok {
			continue
		}
		items = append(items, item(str_user))
		dupProtect[str_user] = short
	}

	sort.Slice(items, func(i, j int) bool {
		return items[i].(item) < items[j].(item)
	})

	const defaultWidth = 20

	l := list.New(items, itemDelegate{}, defaultWidth, listHeight)
	l.Title = "Select authors to add to commit"
	l.SetShowStatusBar(false)
	l.SetFilteringEnabled(true) // Enable filtering
	l.Styles.Title = titleStyle
	l.Styles.PaginationStyle = paginationStyle
	l.Paginator.ActiveDot = ActivePaginationDot.Render("•")
	l.AdditionalShortHelpKeys = // Add help keys (main page)
		func() []key.Binding {
			return []key.Binding{
				listKeys.selectOne,
			}
		}
	l.AdditionalFullHelpKeys = // Add help keys (help menu)
		func() []key.Binding {
			return []key.Binding{
				listKeys.selectAll,
				listKeys.negation,
				listKeys.groupSelect,
				listKeys.createAuthor,
				listKeys.tempAdd,
			}
		}
	l.Styles.HelpStyle = helpStyle

	return model{list: l, keys: listKeys}
}

// TODO: pass list in as a param to allow for group selection using same template
func Entry() []string {

	m := listModel()

	f, err := tea.NewProgram(m).Run()
	if err != nil {
		fmt.Println("Error running program:", err)
		os.Exit(1)
	}

	// Assert the final tea.Model to our local model and print the choice.

	output := []string{}
	if len(selected) == 0 {
		os.Exit(0)
	}
	for i := range selected {
		short := dupProtect[i]
		if negation {
			short = "^" + short
		}

		output = append(output, short)
	}

	if _, ok := f.(model); ok && len(output) > 0 {
		return output
	}
	return nil
}
