package spinner

import (
	"context"
	"fmt"
	"io"
	"time"

	"github.com/mitchellh/cli"
	progressbar "github.com/schollz/progressbar/v3"
	"github.com/vercel/turbo/cli/internal/ui"
)

// getWriterAndColor unwraps cli.Ui instances until it gets to a BasicUi.
// If it happens to spot a ColoredUi along the way, it marks that color is
// enabled.
func getWriterAndColor(terminal cli.Ui, useColor bool) (io.Writer, bool) {
	switch terminal := terminal.(type) {
	case *cli.BasicUi:
		return terminal.Writer, useColor
	case *ui.BasicUI:
		return terminal.Writer, useColor
	case *cli.ColoredUi:
		return getWriterAndColor(terminal.Ui, true)
	case *cli.ConcurrentUi:
		return getWriterAndColor(terminal.Ui, useColor)
	case *cli.PrefixedUi:
		return getWriterAndColor(terminal.Ui, useColor)
	case *cli.MockUi:
		return terminal.OutputWriter, false
	default:
		panic(fmt.Sprintf("unknown Ui: %v", terminal))
	}
}

// WaitFor runs fn, and prints msg to the terminal if it takes longer
// than initialDelay to complete. Depending on the terminal configuration, it may
// display a single instance of msg, or an infinite spinner, updated every 250ms.
func WaitFor(ctx context.Context, fn func(), terminal cli.Ui, msg string, initialDelay time.Duration) error {
	doneCh := make(chan struct{})
	go func() {
		fn()
		close(doneCh)
	}()
	if ui.IsTTY {
		select {
		case <-ctx.Done():
			return nil
		case <-time.After(initialDelay):
			writer, useColor := getWriterAndColor(terminal, false)
			bar := progressbar.NewOptions(
				-1,
				progressbar.OptionEnableColorCodes(useColor),
				progressbar.OptionSetDescription(fmt.Sprintf("[yellow]%v[reset]", msg)),
				progressbar.OptionSpinnerType(14),
				progressbar.OptionSetWriter(writer),
			)
			for {
				select {
				case <-doneCh:
					err := bar.Finish()
					terminal.Output("")
					return err
				case <-time.After(250 * time.Millisecond):
					if err := bar.Add(1); err != nil {
						return err
					}
				case <-ctx.Done():
					return nil
				}
			}
		case <-doneCh:
			return nil
		}
	} else {
		// wait for the timeout before displaying a message, even with no tty
		select {
		case <-ctx.Done():
			return nil
		case <-doneCh:
			return nil
		case <-time.After(initialDelay):
			terminal.Output(msg)
		}
		select {
		case <-ctx.Done():
		case <-doneCh:
		}
		return nil
	}
}
