package pkg

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"strconv"
	"strings"

	"gitlab.com/M0M097/pkg/cli"
	"gitlab.com/M0M097/pkg/sborepo"
)

// UI defines the user-interaction operations used by RunWithUI.
type UI interface {
	// Alert displays a message.
	Alert(string)
	// ListLines displays lines of text.
	ListLines([]string)
	// ChoosePkgs prompts the user to choose packages from the provided iterator.
	ChoosePkgs(sborepo.PkgIter) sborepo.PkgIter
	// Display shows one or more files to the user.
	Display(...string) error
	// Prompt asks the user a yes/no question.
	Prompt(string) bool
	// NewProgressReporter creates a per-package progress reporter.
	NewProgressReporter(taskLabels []string) cli.ProgressReporter
	// Fatal reports a fatal error and must terminate the current run.
	Fatal(v ...interface{})
}

// CliUI implements UI with command-line streams and a pager.
type CliUI struct {
	stdout *bufio.Writer
	stdin  io.Reader
	stderr io.Writer
	pager  func(files ...string) error
}

// Fatal reports a fatal error and exits the process.
func (c *CliUI) Fatal(v ...interface{}) { log.Fatal(v...) }

// Prompt asks the user a yes/no question on the configured streams.
func (c *CliUI) Prompt(msg string) bool { return cli.PromptUser(c.stdout, c.stdin, msg) }

// Display shows files using the configured pager.
func (c *CliUI) Display(files ...string) error { return c.pager(files...) }

// ListLines writes each line followed by a newline.
func (c *CliUI) ListLines(lines []string) {
	for _, l := range lines {
		c.Alert(l)
	}
}

// Alert writes msg followed by a newline.
func (c *CliUI) Alert(msg string) {
	c.stdout.WriteString(msg)
	c.stdout.WriteString("\n")
}

// ChoosePkgs prompts the user to select packages from listPkgs and returns an
// iterator over the selected packages.
func (c *CliUI) ChoosePkgs(listPkgs sborepo.PkgIter) sborepo.PkgIter {
	var (
		i        int
		pkgArray []*sborepo.Package
	)
	// Ask user which packages to exclude
	listPkgs(func(pkg *sborepo.Package) {
		i++
		fmt.Fprintln(c.stdout, i, pkg.Description())
		pkgArray = append(pkgArray, pkg)
	})
	c.stdout.WriteString("Space separated list of packages to exclude:\n")
	c.stdout.Flush()
	// Parse user input
	scanner := bufio.NewScanner(c.stdin)
	scanner.Scan()
	var indices []int
	for _, field := range strings.Fields(scanner.Text()) {
		num, err := strconv.Atoi(field)
		if err != nil || num < 1 || num > len(pkgArray) {
			fmt.Fprintf(c.stdout, "Invalid input: `%s` Repeat...\n", field)
			return c.ChoosePkgs(listPkgs)
		}
		indices = append(indices, num-1)
	}
	return func(f sborepo.PkgFunc) {
		for _, idx := range indices {
			f(pkgArray[idx])
		}
	}
}

// NewProgressReporter creates a CLI progress reporter for taskLabels.
func (c *CliUI) NewProgressReporter(taskLabels []sborepo.PkgName) cli.ProgressReporter {
	return cli.NewProgressReporter(taskLabels, c.stderr)
}
