package ugh_test

import (
	"fmt"
	"testing"

	"charm.land/huh/v2"
	"git.sr.ht/~timofurrer/ugh"
)

// ---------------------------------------------------------------------------
// Confirm - extended coverage
// ---------------------------------------------------------------------------

func TestConfirm_WithDescription(t *testing.T) {
	t.Parallel()

	var confirmed bool

	c := newTestConsole(t)
	c.Expect(ugh.String("This action is irreversible")).
		Expect(ugh.Confirm("Proceed?")).
		Do(ugh.Affirm)

	runTestForm(t, c,
		huh.NewGroup(
			huh.NewConfirm().
				Title("Proceed?").
				Description("This action is irreversible").
				Value(&confirmed),
		),
	)

	if !confirmed {
		t.Fatal("expected confirmed")
	}
}

func TestConfirm_CustomLabels(t *testing.T) {
	t.Parallel()

	var confirmed bool

	c := newTestConsole(t)
	c.Expect(ugh.String("Do it!")).
		Expect(ugh.Confirm("Delete everything?")).
		Do(ugh.Reject)

	runTestForm(t, c,
		huh.NewGroup(
			huh.NewConfirm().
				Title("Delete everything?").
				Affirmative("Do it!").
				Negative("Nope").
				Value(&confirmed),
		),
	)

	if confirmed {
		t.Fatal("expected rejected")
	}
}

func TestConfirm_InitialValueTrue(t *testing.T) {
	t.Parallel()

	confirmed := true

	c := newTestConsole(t)
	c.Expect(ugh.Confirm("Keep going?")).
		Do(ugh.Reject)

	runTestForm(t, c,
		huh.NewGroup(
			huh.NewConfirm().
				Title("Keep going?").
				Value(&confirmed),
		),
	)

	if confirmed {
		t.Fatal("expected rejected even with initial true")
	}
}

func TestConfirm_Validate(t *testing.T) {
	t.Parallel()

	var accepted bool

	c := newTestConsole(t)
	c.Expect(ugh.Confirm("Accept terms?")).
		Do(ugh.Reject).
		Expect(ugh.String("you must accept")).
		Expect(ugh.Confirm("Accept terms?")).
		Do(ugh.Affirm)

	runTestForm(t, c,
		huh.NewGroup(
			huh.NewConfirm().
				Title("Accept terms?").
				Value(&accepted).
				Validate(func(v bool) error {
					if !v {
						return fmt.Errorf("you must accept")
					}
					return nil
				}),
		),
	)

	if !accepted {
		t.Fatal("expected accepted after retry")
	}
}

// ---------------------------------------------------------------------------
// Input - extended coverage
// ---------------------------------------------------------------------------

func TestInput_WithDescription(t *testing.T) {
	t.Parallel()

	var email string

	c := newTestConsole(t)
	c.Expect(ugh.String("We'll never share your email")).
		Expect(ugh.Input("Email")).
		Do(ugh.Type("a@b.com"))

	runTestForm(t, c,
		huh.NewGroup(
			huh.NewInput().
				Title("Email").
				Description("We'll never share your email").
				Value(&email),
		),
	)

	if email != "a@b.com" {
		t.Fatalf("expected %q, got %q", "a@b.com", email)
	}
}

func TestInput_Placeholder(t *testing.T) {
	t.Parallel()

	var name string

	c := newTestConsole(t)
	c.Expect(ugh.String("Jane Doe")).
		Expect(ugh.Input("Name")).
		Do(ugh.Type("Alice"))

	runTestForm(t, c,
		huh.NewGroup(
			huh.NewInput().
				Title("Name").
				Placeholder("Jane Doe").
				Value(&name),
		),
	)

	if name != "Alice" {
		t.Fatalf("expected %q, got %q", "Alice", name)
	}
}

func TestInput_Validate_RejectAndRetry(t *testing.T) {
	t.Parallel()

	var name string

	c := newTestConsole(t)
	c.Expect(ugh.Input("Name")).
		Do(ugh.Type("bad")).
		Expect(ugh.String("invalid name")).
		Send(ugh.CtrlA).
		Send(ugh.Delete).Send(ugh.Delete).Send(ugh.Delete).
		SendText("good").
		Send(ugh.Enter)

	runTestForm(t, c,
		huh.NewGroup(
			huh.NewInput().
				Title("Name").
				Value(&name).
				Validate(func(s string) error {
					if s == "bad" {
						return fmt.Errorf("invalid name")
					}
					return nil
				}),
		),
	)

	if name != "good" {
		t.Fatalf("expected %q, got %q", "good", name)
	}
}

func TestInput_CharLimit(t *testing.T) {
	t.Parallel()

	var code string

	c := newTestConsole(t)
	c.Expect(ugh.Input("Code")).
		Do(ugh.Type("ABCDEF"))

	runTestForm(t, c,
		huh.NewGroup(
			huh.NewInput().
				Title("Code").
				CharLimit(4).
				Value(&code),
		),
	)

	if len(code) > 4 {
		t.Fatalf("expected at most 4 chars, got %q (%d)", code, len(code))
	}
}

// ---------------------------------------------------------------------------
// Text - extended coverage
// ---------------------------------------------------------------------------

func TestText_WithDescription(t *testing.T) {
	t.Parallel()

	var bio string

	c := newTestConsole(t)
	c.Expect(ugh.String("Tell us about yourself")).
		Expect(ugh.Text("Bio")).
		Do(ugh.TypeMultiline("I code"))

	runTestForm(t, c,
		huh.NewGroup(
			huh.NewText().
				Title("Bio").
				Description("Tell us about yourself").
				Value(&bio).
				Lines(3),
		),
	)

	if bio != "I code" {
		t.Fatalf("expected %q, got %q", "I code", bio)
	}
}

func TestText_Placeholder(t *testing.T) {
	t.Parallel()

	var notes string

	c := newTestConsole(t)
	c.Expect(ugh.String("Enter notes here...")).
		Expect(ugh.Text("Notes")).
		Do(ugh.TypeMultiline("Done"))

	runTestForm(t, c,
		huh.NewGroup(
			huh.NewText().
				Title("Notes").
				Placeholder("Enter notes here...").
				Value(&notes).
				Lines(3),
		),
	)

	if notes != "Done" {
		t.Fatalf("expected %q, got %q", "Done", notes)
	}
}

func TestText_CharLimit(t *testing.T) {
	t.Parallel()

	var notes string

	c := newTestConsole(t)
	c.Expect(ugh.Text("Notes")).
		Do(ugh.TypeMultiline("ABCDEFGH"))

	runTestForm(t, c,
		huh.NewGroup(
			huh.NewText().
				Title("Notes").
				CharLimit(5).
				Value(&notes).
				Lines(3),
		),
	)

	if len(notes) > 5 {
		t.Fatalf("expected at most 5 chars, got %q (%d)", notes, len(notes))
	}
}

// ---------------------------------------------------------------------------
// Select - extended coverage
// ---------------------------------------------------------------------------

func TestSelect_WithDescription(t *testing.T) {
	t.Parallel()

	var choice string

	c := newTestConsole(t)
	c.Expect(ugh.String("Pick your favorite")).
		Expect(ugh.Select("Color")).
		Do(ugh.SelectIndex(2))

	runTestForm(t, c,
		huh.NewGroup(
			huh.NewSelect[string]().
				Title("Color").
				Description("Pick your favorite").
				Options(huh.NewOptions("Red", "Blue", "Green")...).
				Value(&choice),
		),
	)

	if choice != "Green" {
		t.Fatalf("expected %q, got %q", "Green", choice)
	}
}

func TestSelect_Validate_RejectAndRetry(t *testing.T) {
	t.Parallel()

	var choice string

	c := newTestConsole(t)
	c.Expect(ugh.Select("Burger")).
		Do(ugh.SelectIndex(1)).
		Expect(ugh.String("no fish today")).
		Expect(ugh.Select("Burger")).
		Do(ugh.SelectOption("Veggie"))

	runTestForm(t, c,
		huh.NewGroup(
			huh.NewSelect[string]().
				Title("Burger").
				Options(huh.NewOptions("Classic", "Fish", "Veggie")...).
				Value(&choice).
				Validate(func(s string) error {
					if s == "Fish" {
						return fmt.Errorf("no fish today")
					}
					return nil
				}),
		),
	)

	if choice != "Veggie" {
		t.Fatalf("expected %q, got %q", "Veggie", choice)
	}
}

type Size int

const (
	Small  Size = 1
	Medium Size = 2
	Large  Size = 3
)

func TestSelect_TypedGenericValue(t *testing.T) {
	t.Parallel()

	var size Size

	c := newTestConsole(t)
	c.Expect(ugh.Select("Size")).
		Do(ugh.SelectOption("Large"))

	runTestForm(t, c,
		huh.NewGroup(
			huh.NewSelect[Size]().
				Title("Size").
				Options(
					huh.NewOption("Small", Small),
					huh.NewOption("Medium", Medium),
					huh.NewOption("Large", Large),
				).
				Value(&size),
		),
	)

	if size != Large {
		t.Fatalf("expected Large (%d), got %d", Large, size)
	}
}

func TestSelect_ManyOptions_Scrolling(t *testing.T) {
	t.Parallel()

	var choice string
	options := []string{
		"Alpha", "Bravo", "Charlie", "Delta", "Echo",
		"Foxtrot", "Golf", "Hotel", "India", "Juliet",
		"Kilo", "Lima", "Mike", "November", "Oscar",
	}

	c := newTestConsole(t)
	c.Expect(ugh.Select("Phonetic")).
		Do(ugh.SelectIndex(12))

	runTestForm(t, c,
		huh.NewGroup(
			huh.NewSelect[string]().
				Title("Phonetic").
				Options(huh.NewOptions(options...)...).
				Value(&choice),
		),
	)

	if choice != "Mike" {
		t.Fatalf("expected %q, got %q", "Mike", choice)
	}
}

// ---------------------------------------------------------------------------
// MultiSelect - extended coverage
// ---------------------------------------------------------------------------

func TestMultiSelect_Validate_MinSelection(t *testing.T) {
	t.Parallel()

	var picks []string

	c := newTestConsole(t)
	c.Expect(ugh.String("Fruits")).
		Send(ugh.Enter).
		Expect(ugh.String("pick at least one")).
		Expect(ugh.MultiSelect("Fruits")).
		Do(ugh.ToggleIndices(1))

	runTestForm(t, c,
		huh.NewGroup(
			huh.NewMultiSelect[string]().
				Title("Fruits").
				Options(
					huh.NewOption("Apple", "Apple"),
					huh.NewOption("Banana", "Banana"),
					huh.NewOption("Cherry", "Cherry"),
				).
				Value(&picks).
				Validate(func(v []string) error {
					if len(v) < 1 {
						return fmt.Errorf("pick at least one")
					}
					return nil
				}),
		),
	)

	if len(picks) != 1 || picks[0] != "Banana" {
		t.Fatalf("expected [Banana], got %v", picks)
	}
}

func TestMultiSelect_Limit(t *testing.T) {
	t.Parallel()

	var picks []string

	c := newTestConsole(t)
	c.Expect(ugh.MultiSelect("Pick 2 max")).
		Do(ugh.ToggleIndices(0, 1, 2))

	runTestForm(t, c,
		huh.NewGroup(
			huh.NewMultiSelect[string]().
				Title("Pick 2 max").
				Options(
					huh.NewOption("A", "A"),
					huh.NewOption("B", "B"),
					huh.NewOption("C", "C"),
					huh.NewOption("D", "D"),
				).
				Value(&picks).
				Limit(2),
		),
	)

	if len(picks) > 2 {
		t.Fatalf("expected at most 2 picks, got %d: %v", len(picks), picks)
	}
}

func TestMultiSelect_ManyOptions_ScrollByIndex(t *testing.T) {
	t.Parallel()

	opts := make([]huh.Option[string], 15)
	for i := range opts {
		name := fmt.Sprintf("Item_%02d", i)
		opts[i] = huh.NewOption(name, name)
	}
	var picks []string

	c := newTestConsole(t)
	c.Expect(ugh.MultiSelect("Items")).
		Do(ugh.ToggleIndices(0, 14))

	runTestForm(t, c,
		huh.NewGroup(
			huh.NewMultiSelect[string]().
				Title("Items").
				Options(opts...).
				Value(&picks),
		),
	)

	if len(picks) != 2 {
		t.Fatalf("expected 2 picks, got %d: %v", len(picks), picks)
	}
	if picks[0] != "Item_00" || picks[1] != "Item_14" {
		t.Fatalf("expected [Item_00, Item_14], got %v", picks)
	}
}
