package utils

import (
	"bufio"
	"context"
	"fmt"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/go-errors/errors"
	"github.com/supabase/cli/pkg/cast"
	"golang.org/x/term"
)

type Console struct {
	IsTTY bool
	stdin *bufio.Scanner
	token chan string
	mu    sync.Mutex
}

func NewConsole() *Console {
	return &Console{
		IsTTY: term.IsTerminal(int(os.Stdin.Fd())),
		stdin: bufio.NewScanner(os.Stdin),
		token: make(chan string),
		mu:    sync.Mutex{},
	}
}

// Prevent interactive terminals from hanging more than 10 minutes
const ttyTimeout = time.Minute * 10

func (c *Console) ReadLine(ctx context.Context) string {
	// Wait a few ms for input
	timeout := time.Millisecond
	if c.IsTTY {
		timeout = ttyTimeout
	}
	timer := time.NewTimer(timeout)
	defer timer.Stop()
	// Read from stdin in background
	go func() {
		c.mu.Lock()
		defer c.mu.Unlock()
		// Scan one line from input or file
		if c.stdin.Scan() {
			c.token <- strings.TrimSpace(c.stdin.Text())
		}
	}()
	var input string
	select {
	case input = <-c.token:
	case <-ctx.Done():
	case <-timer.C:
	}
	return input
}

// PromptYesNo asks yes/no questions using the label.
func (c *Console) PromptYesNo(ctx context.Context, label string, def bool) (bool, error) {
	choices := "Y/n"
	if !def {
		choices = "y/N"
	}
	labelWithChoice := fmt.Sprintf("%s [%s] ", label, choices)
	// Any error will be handled as default value
	input, err := c.PromptText(ctx, labelWithChoice)
	if len(input) > 0 {
		if answer := parseYesNo(input); answer != nil {
			return *answer, nil
		}
	}
	return def, err
}

func parseYesNo(s string) *bool {
	s = strings.ToLower(s)
	if s == "y" || s == "yes" {
		return cast.Ptr(true)
	}
	if s == "n" || s == "no" {
		return cast.Ptr(false)
	}
	return nil
}

// PromptText asks for input using the label.
func (c *Console) PromptText(ctx context.Context, label string) (string, error) {
	fmt.Fprint(os.Stderr, label)
	input := c.ReadLine(ctx)
	// Echo to stderr for non-interactive terminals
	if !c.IsTTY {
		fmt.Fprintln(os.Stderr, input)
	}
	if err := ctx.Err(); err != nil {
		return "", errors.New(err)
	}
	return input, nil
}
