package config

import (
	"errors"
	"fmt"
	"io"
	"os"
	"strings"

	"golang.org/x/term"
)

// ANSI escape sequences for terminal formatting.
const (
	ansiHideCursor = "\033[?25l"
	ansiShowCursor = "\033[?25h"
	ansiBold       = "\033[1m"
	ansiReset      = "\033[0m"
	ansiGray       = "\033[37m"
	ansiClearDown  = "\033[J"
)

const maxDisplayedItems = 10

var errCancelled = errors.New("cancelled")

type selectItem struct {
	Name        string
	Description string
}

type inputEvent int

const (
	eventNone inputEvent = iota
	eventEnter
	eventEscape
	eventUp
	eventDown
	eventTab
	eventBackspace
	eventChar
)

type selectState struct {
	items        []selectItem
	filter       string
	selected     int
	scrollOffset int
}

func newSelectState(items []selectItem) *selectState {
	return &selectState{items: items}
}

func (s *selectState) filtered() []selectItem {
	return filterItems(s.items, s.filter)
}

func (s *selectState) handleInput(event inputEvent, char byte) (done bool, result string, err error) {
	filtered := s.filtered()

	switch event {
	case eventEnter:
		if len(filtered) > 0 && s.selected < len(filtered) {
			return true, filtered[s.selected].Name, nil
		}
	case eventEscape:
		return true, "", errCancelled
	case eventBackspace:
		if len(s.filter) > 0 {
			s.filter = s.filter[:len(s.filter)-1]
			s.selected = 0
			s.scrollOffset = 0
		}
	case eventUp:
		if s.selected > 0 {
			s.selected--
			if s.selected < s.scrollOffset {
				s.scrollOffset = s.selected
			}
		}
	case eventDown:
		if s.selected < len(filtered)-1 {
			s.selected++
			if s.selected >= s.scrollOffset+maxDisplayedItems {
				s.scrollOffset = s.selected - maxDisplayedItems + 1
			}
		}
	case eventChar:
		s.filter += string(char)
		s.selected = 0
		s.scrollOffset = 0
	}

	return false, "", nil
}

type multiSelectState struct {
	items         []selectItem
	itemIndex     map[string]int
	filter        string
	highlighted   int
	scrollOffset  int
	checked       map[int]bool
	checkOrder    []int
	focusOnButton bool
}

func newMultiSelectState(items []selectItem, preChecked []string) *multiSelectState {
	s := &multiSelectState{
		items:     items,
		itemIndex: make(map[string]int, len(items)),
		checked:   make(map[int]bool),
	}

	for i, item := range items {
		s.itemIndex[item.Name] = i
	}

	for _, name := range preChecked {
		if idx, ok := s.itemIndex[name]; ok {
			s.checked[idx] = true
			s.checkOrder = append(s.checkOrder, idx)
		}
	}

	return s
}

func (s *multiSelectState) filtered() []selectItem {
	return filterItems(s.items, s.filter)
}

func (s *multiSelectState) toggleItem() {
	filtered := s.filtered()
	if len(filtered) == 0 || s.highlighted >= len(filtered) {
		return
	}

	item := filtered[s.highlighted]
	origIdx := s.itemIndex[item.Name]

	if s.checked[origIdx] {
		delete(s.checked, origIdx)
		for i, idx := range s.checkOrder {
			if idx == origIdx {
				s.checkOrder = append(s.checkOrder[:i], s.checkOrder[i+1:]...)
				break
			}
		}
	} else {
		s.checked[origIdx] = true
		s.checkOrder = append(s.checkOrder, origIdx)
	}
}

func (s *multiSelectState) handleInput(event inputEvent, char byte) (done bool, result []string, err error) {
	filtered := s.filtered()

	switch event {
	case eventEnter:
		if s.focusOnButton && len(s.checkOrder) > 0 {
			var res []string
			for _, idx := range s.checkOrder {
				res = append(res, s.items[idx].Name)
			}
			return true, res, nil
		} else if !s.focusOnButton {
			s.toggleItem()
		}
	case eventTab:
		if len(s.checkOrder) > 0 {
			s.focusOnButton = !s.focusOnButton
		}
	case eventEscape:
		return true, nil, errCancelled
	case eventBackspace:
		if len(s.filter) > 0 {
			s.filter = s.filter[:len(s.filter)-1]
			s.highlighted = 0
			s.scrollOffset = 0
			s.focusOnButton = false
		}
	case eventUp:
		if s.focusOnButton {
			s.focusOnButton = false
		} else if s.highlighted > 0 {
			s.highlighted--
			if s.highlighted < s.scrollOffset {
				s.scrollOffset = s.highlighted
			}
		}
	case eventDown:
		if s.focusOnButton {
			s.focusOnButton = false
		} else if s.highlighted < len(filtered)-1 {
			s.highlighted++
			if s.highlighted >= s.scrollOffset+maxDisplayedItems {
				s.scrollOffset = s.highlighted - maxDisplayedItems + 1
			}
		}
	case eventChar:
		s.filter += string(char)
		s.highlighted = 0
		s.scrollOffset = 0
		s.focusOnButton = false
	}

	return false, nil, nil
}

func (s *multiSelectState) selectedCount() int {
	return len(s.checkOrder)
}

// Terminal I/O handling

type terminalState struct {
	fd       int
	oldState *term.State
}

func enterRawMode() (*terminalState, error) {
	fd := int(os.Stdin.Fd())
	oldState, err := term.MakeRaw(fd)
	if err != nil {
		return nil, err
	}
	fmt.Fprint(os.Stderr, ansiHideCursor)
	return &terminalState{fd: fd, oldState: oldState}, nil
}

func (t *terminalState) restore() {
	fmt.Fprint(os.Stderr, ansiShowCursor)
	term.Restore(t.fd, t.oldState)
}

func clearLines(n int) {
	if n > 0 {
		fmt.Fprintf(os.Stderr, "\033[%dA", n)
		fmt.Fprint(os.Stderr, ansiClearDown)
	}
}

func parseInput(r io.Reader) (inputEvent, byte, error) {
	buf := make([]byte, 3)
	n, err := r.Read(buf)
	if err != nil {
		return 0, 0, err
	}

	switch {
	case n == 1 && buf[0] == 13:
		return eventEnter, 0, nil
	case n == 1 && (buf[0] == 3 || buf[0] == 27):
		return eventEscape, 0, nil
	case n == 1 && buf[0] == 9:
		return eventTab, 0, nil
	case n == 1 && buf[0] == 127:
		return eventBackspace, 0, nil
	case n == 3 && buf[0] == 27 && buf[1] == 91 && buf[2] == 65:
		return eventUp, 0, nil
	case n == 3 && buf[0] == 27 && buf[1] == 91 && buf[2] == 66:
		return eventDown, 0, nil
	case n == 1 && buf[0] >= 32 && buf[0] < 127:
		return eventChar, buf[0], nil
	}

	return eventNone, 0, nil
}

// Rendering

func renderSelect(w io.Writer, prompt string, s *selectState) int {
	filtered := s.filtered()

	fmt.Fprintf(w, "%s %s\r\n", prompt, s.filter)
	lineCount := 1

	if len(filtered) == 0 {
		fmt.Fprintf(w, "  %s(no matches)%s\r\n", ansiGray, ansiReset)
		lineCount++
	} else {
		displayCount := min(len(filtered), maxDisplayedItems)

		for i := range displayCount {
			idx := s.scrollOffset + i
			if idx >= len(filtered) {
				break
			}
			item := filtered[idx]
			prefix := "    "
			if idx == s.selected {
				prefix = "  " + ansiBold + "> "
			}
			if item.Description != "" {
				fmt.Fprintf(w, "%s%s%s %s- %s%s\r\n", prefix, item.Name, ansiReset, ansiGray, item.Description, ansiReset)
			} else {
				fmt.Fprintf(w, "%s%s%s\r\n", prefix, item.Name, ansiReset)
			}
			lineCount++
		}

		if remaining := len(filtered) - s.scrollOffset - displayCount; remaining > 0 {
			fmt.Fprintf(w, "  %s... and %d more%s\r\n", ansiGray, remaining, ansiReset)
			lineCount++
		}
	}

	return lineCount
}

func renderMultiSelect(w io.Writer, prompt string, s *multiSelectState) int {
	filtered := s.filtered()

	fmt.Fprintf(w, "%s %s\r\n", prompt, s.filter)
	lineCount := 1

	if len(filtered) == 0 {
		fmt.Fprintf(w, "  %s(no matches)%s\r\n", ansiGray, ansiReset)
		lineCount++
	} else {
		displayCount := min(len(filtered), maxDisplayedItems)

		for i := range displayCount {
			idx := s.scrollOffset + i
			if idx >= len(filtered) {
				break
			}
			item := filtered[idx]
			origIdx := s.itemIndex[item.Name]

			checkbox := "[ ]"
			if s.checked[origIdx] {
				checkbox = "[x]"
			}

			prefix := "  "
			suffix := ""
			if idx == s.highlighted && !s.focusOnButton {
				prefix = "> "
			}
			if len(s.checkOrder) > 0 && s.checkOrder[0] == origIdx {
				suffix = " " + ansiGray + "(default)" + ansiReset
			}

			if idx == s.highlighted && !s.focusOnButton {
				fmt.Fprintf(w, "  %s%s %s %s%s%s\r\n", ansiBold, prefix, checkbox, item.Name, ansiReset, suffix)
			} else {
				fmt.Fprintf(w, "  %s %s %s%s\r\n", prefix, checkbox, item.Name, suffix)
			}
			lineCount++
		}

		if remaining := len(filtered) - s.scrollOffset - displayCount; remaining > 0 {
			fmt.Fprintf(w, "  %s... and %d more%s\r\n", ansiGray, remaining, ansiReset)
			lineCount++
		}
	}

	fmt.Fprintf(w, "\r\n")
	lineCount++
	count := s.selectedCount()
	switch {
	case count == 0:
		fmt.Fprintf(w, "  %sSelect at least one model.%s\r\n", ansiGray, ansiReset)
	case s.focusOnButton:
		fmt.Fprintf(w, "  %s> [ Continue ]%s %s(%d selected)%s\r\n", ansiBold, ansiReset, ansiGray, count, ansiReset)
	default:
		fmt.Fprintf(w, "    %s[ Continue ] (%d selected) - press Tab%s\r\n", ansiGray, count, ansiReset)
	}
	lineCount++

	return lineCount
}

// selectPrompt prompts the user to select a single item from a list.
func selectPrompt(prompt string, items []selectItem) (string, error) {
	if len(items) == 0 {
		return "", fmt.Errorf("no items to select from")
	}

	ts, err := enterRawMode()
	if err != nil {
		return "", err
	}
	defer ts.restore()

	state := newSelectState(items)
	var lastLineCount int

	render := func() {
		clearLines(lastLineCount)
		lastLineCount = renderSelect(os.Stderr, prompt, state)
	}

	render()

	for {
		event, char, err := parseInput(os.Stdin)
		if err != nil {
			return "", err
		}

		done, result, err := state.handleInput(event, char)
		if done {
			clearLines(lastLineCount)
			if err != nil {
				return "", err
			}
			return result, nil
		}

		render()
	}
}

// multiSelectPrompt prompts the user to select multiple items from a list.
func multiSelectPrompt(prompt string, items []selectItem, preChecked []string) ([]string, error) {
	if len(items) == 0 {
		return nil, fmt.Errorf("no items to select from")
	}

	ts, err := enterRawMode()
	if err != nil {
		return nil, err
	}
	defer ts.restore()

	state := newMultiSelectState(items, preChecked)
	var lastLineCount int

	render := func() {
		clearLines(lastLineCount)
		lastLineCount = renderMultiSelect(os.Stderr, prompt, state)
	}

	render()

	for {
		event, char, err := parseInput(os.Stdin)
		if err != nil {
			return nil, err
		}

		done, result, err := state.handleInput(event, char)
		if done {
			clearLines(lastLineCount)
			if err != nil {
				return nil, err
			}
			return result, nil
		}

		render()
	}
}

func confirmPrompt(prompt string) (bool, error) {
	fd := int(os.Stdin.Fd())
	oldState, err := term.MakeRaw(fd)
	if err != nil {
		return false, err
	}
	defer term.Restore(fd, oldState)

	fmt.Fprintf(os.Stderr, "%s (\033[1my\033[0m/n) ", prompt)

	buf := make([]byte, 1)
	for {
		if _, err := os.Stdin.Read(buf); err != nil {
			return false, err
		}

		switch buf[0] {
		case 'Y', 'y', 13:
			fmt.Fprintf(os.Stderr, "yes\r\n")
			return true, nil
		case 'N', 'n', 27, 3:
			fmt.Fprintf(os.Stderr, "no\r\n")
			return false, nil
		}
	}
}

func filterItems(items []selectItem, filter string) []selectItem {
	if filter == "" {
		return items
	}
	var result []selectItem
	filterLower := strings.ToLower(filter)
	for _, item := range items {
		if strings.Contains(strings.ToLower(item.Name), filterLower) {
			result = append(result, item)
		}
	}
	return result
}
