// SPDX-License-Identifier: GPL-3.0-or-later

// Package term provides interactive prompt utilities.
package term

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"strings"

	"golang.org/x/term"
)

// Prompter handles interactive prompts.
type Prompter struct {
	reader io.Reader
	writer io.Writer
}

// NewPrompter creates a new prompter with stdin/stdout.
func NewPrompter() *Prompter {
	return &Prompter{
		reader: os.Stdin,
		writer: os.Stdout,
	}
}

// NewPrompterWithIO creates a new prompter with custom IO.
func NewPrompterWithIO(reader io.Reader, writer io.Writer) *Prompter {
	return &Prompter{
		reader: reader,
		writer: writer,
	}
}

// Confirm asks for yes/no confirmation.
// Returns true if user answers yes (y/Y), false for no/empty.
func (p *Prompter) Confirm(prompt string, defaultYes bool) (bool, error) {
	suffix := " [y/N] "
	if defaultYes {
		suffix = " [Y/n] "
	}

	fmt.Fprint(p.writer, prompt+suffix)

	scanner := bufio.NewScanner(p.reader)
	if !scanner.Scan() {
		if err := scanner.Err(); err != nil {
			return false, err
		}
		return defaultYes, nil
	}

	response := strings.TrimSpace(strings.ToLower(scanner.Text()))

	if response == "" {
		return defaultYes, nil
	}

	return response == "y" || response == "yes", nil
}

// ConfirmDangerous asks for confirmation of a dangerous operation.
// User must type the exact confirmation string.
func (p *Prompter) ConfirmDangerous(prompt, confirmation string) (bool, error) {
	fmt.Fprintf(p.writer, "%s\n", prompt)
	fmt.Fprintf(p.writer, "To confirm, type '%s' (all caps): ", confirmation)

	scanner := bufio.NewScanner(p.reader)
	if !scanner.Scan() {
		if err := scanner.Err(); err != nil {
			return false, err
		}
		return false, nil
	}

	response := strings.TrimSpace(scanner.Text())
	return response == confirmation, nil
}

// Input reads a line of input with a prompt.
func (p *Prompter) Input(prompt string) (string, error) {
	fmt.Fprint(p.writer, prompt)

	scanner := bufio.NewScanner(p.reader)
	if !scanner.Scan() {
		if err := scanner.Err(); err != nil {
			return "", err
		}
		return "", nil
	}

	return strings.TrimSpace(scanner.Text()), nil
}

// InputWithDefault reads input with a default value.
func (p *Prompter) InputWithDefault(prompt, defaultValue string) (string, error) {
	if defaultValue != "" {
		prompt = fmt.Sprintf("%s [%s]: ", prompt, defaultValue)
	} else {
		prompt = prompt + ": "
	}

	result, err := p.Input(prompt)
	if err != nil {
		return "", err
	}

	if result == "" {
		return defaultValue, nil
	}

	return result, nil
}

// Password reads a password (hidden input).
func (p *Prompter) Password(prompt string) (string, error) {
	fmt.Fprint(p.writer, prompt)

	// Check if stdin is a terminal
	fd := int(os.Stdin.Fd())
	if term.IsTerminal(fd) {
		password, err := term.ReadPassword(fd)
		fmt.Fprintln(p.writer) // Add newline after hidden input
		if err != nil {
			return "", err
		}
		return string(password), nil
	}

	// Fallback for non-terminal input (e.g., pipes)
	scanner := bufio.NewScanner(p.reader)
	if !scanner.Scan() {
		if err := scanner.Err(); err != nil {
			return "", err
		}
		return "", nil
	}

	return scanner.Text(), nil
}

// Select presents a numbered list and returns the selected index.
func (p *Prompter) Select(prompt string, options []string) (int, error) {
	if len(options) == 0 {
		return -1, fmt.Errorf("no options provided")
	}

	fmt.Fprintln(p.writer, prompt)
	for i, opt := range options {
		fmt.Fprintf(p.writer, "  [%d] %s\n", i+1, opt)
	}
	fmt.Fprintf(p.writer, "\nEnter number (1-%d) or 'q' to quit: ", len(options))

	scanner := bufio.NewScanner(p.reader)
	if !scanner.Scan() {
		if err := scanner.Err(); err != nil {
			return -1, err
		}
		return -1, nil
	}

	response := strings.TrimSpace(strings.ToLower(scanner.Text()))

	if response == "q" || response == "quit" {
		return -1, nil
	}

	var selected int
	_, err := fmt.Sscanf(response, "%d", &selected)
	if err != nil {
		return -1, fmt.Errorf("invalid selection: %s", response)
	}

	if selected < 1 || selected > len(options) {
		return -1, fmt.Errorf("selection out of range: %d", selected)
	}

	return selected - 1, nil
}

// MultiSelect presents a numbered list for multiple selection.
// Returns indices of selected options.
func (p *Prompter) MultiSelect(prompt string, options []string) ([]int, error) {
	if len(options) == 0 {
		return nil, fmt.Errorf("no options provided")
	}

	fmt.Fprintln(p.writer, prompt)
	for i, opt := range options {
		fmt.Fprintf(p.writer, "  [%d] %s\n", i+1, opt)
	}
	fmt.Fprint(p.writer, "\nEnter numbers separated by commas (e.g., 1,3,5) or 'q' to quit: ")

	scanner := bufio.NewScanner(p.reader)
	if !scanner.Scan() {
		if err := scanner.Err(); err != nil {
			return nil, err
		}
		return nil, nil
	}

	response := strings.TrimSpace(scanner.Text())

	if strings.ToLower(response) == "q" || strings.ToLower(response) == "quit" {
		return nil, nil
	}

	parts := strings.Split(response, ",")
	var selected []int

	for _, part := range parts {
		part = strings.TrimSpace(part)
		if part == "" {
			continue
		}

		var num int
		_, err := fmt.Sscanf(part, "%d", &num)
		if err != nil {
			return nil, fmt.Errorf("invalid selection: %s", part)
		}

		if num < 1 || num > len(options) {
			return nil, fmt.Errorf("selection out of range: %d", num)
		}

		selected = append(selected, num-1)
	}

	return selected, nil
}
