package ugh

import (
	"context"
	"fmt"
	"io"
)

// Action is a function that runs at execution time after an expectation matches.
// It receives a *Response providing access to the screen state and the ability
// to send keystrokes.
type Action func(r *Response)

// Response provides the action function with screen state and keystroke sending.
type Response struct {
	console  *Console
	appInput io.Writer
	screen   string
	text     string
}

// Send writes a single key's ANSI byte sequence to the application's stdin.
func (r *Response) Send(key Key) {
	r.console.steps = append(r.console.steps, &sendStep{key: key})
}

// SendText types text as individual characters with an inter-key delay.
func (r *Response) SendText(text string) {
	r.console.steps = append(r.console.steps, &sendTextStep{text: text})
}

// Screen returns the virtual terminal screen contents at the moment the expectation matched.
func (r *Response) Screen() string {
	return r.screen
}

// Text returns Matcher.String() from the expectation that triggered this action.
func (r *Response) Text() string {
	return r.text
}

// Expectation represents a pending screen expectation that can be completed with Do.
type Expectation struct {
	console *Console
	text    string
}

// Do attaches an action to run when the expectation matches and returns
// the Expectation for further chaining.
func (e *Expectation) Do(action Action) *Expectation {
	e.console.steps = append(e.console.steps, &doStep{
		text:   e.text,
		action: action,
	})
	return e
}

// Expect chains another expectation after this one.
func (e *Expectation) Expect(m Matcher) *Expectation {
	e.console.steps = append(e.console.steps, &expectMatcherStep{matcher: m, timeout: e.console.timeout})
	return &Expectation{console: e.console, text: m.String()}
}

// Send records a keystroke step and returns the Expectation for chaining.
func (e *Expectation) Send(key Key) *Expectation {
	e.console.steps = append(e.console.steps, &sendStep{key: key})
	return e
}

// SendText records a text-typing step and returns the Expectation for chaining.
func (e *Expectation) SendText(text string) *Expectation {
	e.console.steps = append(e.console.steps, &sendTextStep{text: text})
	return e
}

type doStep struct {
	text   string
	action Action
}

func (s *doStep) execute(ctx context.Context, c *Console, appInput io.Writer) error {
	r := &Response{
		console:  c,
		appInput: appInput,
		screen:   c.Screen(),
		text:     s.text,
	}

	before := len(c.steps)
	s.action(r)
	added := c.steps[before:]

	for _, step := range added {
		if err := step.execute(ctx, c, appInput); err != nil {
			return err
		}
	}

	c.steps = c.steps[:before]
	return nil
}

func (s *doStep) String() string {
	return fmt.Sprintf("Do(text=%q)", s.text)
}

// Affirm is an Action that sends "y" to affirm a huh Confirm field.
func Affirm(r *Response) {
	r.SendText("y")
}

// Reject is an Action that sends "n" to reject a huh Confirm field.
func Reject(r *Response) {
	r.SendText("n")
}

// PressEnter is an Action that sends the Enter key.
func PressEnter(r *Response) {
	r.Send(Enter)
}

// Type returns an Action that types text and sends Enter. For Input fields.
func Type(text string) Action {
	return func(r *Response) {
		r.SendText(text)
		r.Send(Enter)
	}
}

// TypeMultiline returns an Action that types text and sends Tab. For Text fields,
// where Enter inserts newlines and Tab advances to the next field.
func TypeMultiline(text string) Action {
	return func(r *Response) {
		r.SendText(text)
		r.Send(Tab)
	}
}

// SelectIndex returns an Action that sends Down×n then Enter.
// For Select fields, selecting by 0-based index.
func SelectIndex(index int) Action {
	return func(r *Response) {
		for range index {
			r.Send(Down)
		}
		r.Send(Enter)
	}
}

// SelectOption returns an Action that parses the screen to find the option label,
// navigates with Up/Down, then sends Enter. Uses Response.Text() as the field title
// for screen parsing.
func SelectOption(label string) Action {
	return func(r *Response) {
		r.console.steps = append(r.console.steps, &selectByLabelStep{
			title:   r.Text(),
			label:   label,
			timeout: r.console.timeout,
		})
		r.Send(Enter)
	}
}

// ToggleIndices returns an Action that navigates to each index and sends Space
// to toggle, then Enter to submit. For MultiSelect fields.
func ToggleIndices(indices ...int) Action {
	return func(r *Response) {
		r.console.steps = append(r.console.steps, &multiSelectByIndicesStep{
			title:   r.Text(),
			indices: indices,
		})
		r.Send(Enter)
	}
}

// ToggleOptions returns an Action that navigates to each label and sends Space
// to toggle, then Enter to submit. For MultiSelect fields.
func ToggleOptions(labels ...string) Action {
	return func(r *Response) {
		r.console.steps = append(r.console.steps, &multiSelectByLabelsStep{
			title:  r.Text(),
			labels: labels,
		})
		r.Send(Enter)
	}
}
