package ugh

import (
	"sync"
	"testing"
	"time"

	"github.com/charmbracelet/x/vt"
)

const (
	defaultWidth   = 120
	defaultHeight  = 40
	defaultTimeout = 10 * time.Second
)

// Option configures a Console.
type Option func(*Console)

// WithSize sets the virtual terminal size. Default: 120x40.
func WithSize(width, height int) Option {
	return func(c *Console) {
		c.width = width
		c.height = height
	}
}

// WithTimeout sets the per-step timeout for expect operations. Default: 10s.
func WithTimeout(d time.Duration) Option {
	return func(c *Console) {
		c.timeout = d
	}
}

// Console drives a huh form through a virtual terminal emulator.
// Interactions are recorded as a chain of expectations and actions,
// then executed via Run or Start.
//
// A t.Cleanup hook is registered automatically to verify that all
// recorded expectations were executed. If expectations are registered
// but Run/Start is never called, or if some expectations remain
// pending after execution, the test fails.
type Console struct {
	t       testing.TB
	width   int
	height  int
	timeout time.Duration
	steps   []step

	mu  sync.Mutex
	emu *vt.Emulator

	started  bool
	executed int
}

// New creates a new test console. The testing.TB is used for failure reporting.
// A t.Cleanup hook is registered to verify all expectations are fulfilled.
func New(t testing.TB, opts ...Option) *Console {
	c := &Console{
		t:       t,
		width:   defaultWidth,
		height:  defaultHeight,
		timeout: defaultTimeout,
	}
	for _, opt := range opts {
		opt(c)
	}
	t.Cleanup(func() {
		t.Helper()
		c.finish()
	})
	return c
}

func (c *Console) finish() {
	c.t.Helper()
	if len(c.steps) == 0 {
		return
	}
	if !c.started {
		c.t.Errorf("ugh: %d expectation(s) registered but Run/Start was never called", len(c.steps))
		return
	}
	pending := len(c.steps) - c.executed
	if pending > 0 {
		c.t.Errorf("ugh: %d expectation(s) still pending (%d/%d executed)", pending, c.executed, len(c.steps))
	}
}

// Screen returns the current virtual terminal screen contents as plain text.
// Safe to call at any time, including during Run from other goroutines or
// after Run completes. Returns an empty string if called before Run.
func (c *Console) Screen() string {
	c.mu.Lock()
	defer c.mu.Unlock()
	if c.emu == nil {
		return ""
	}
	return c.emu.String()
}
