package ui

import (
	"bytes"
	"context"
	"io"
	"strings"
	"testing"

	"github.com/a-h/templ"
)

// render is a tiny helper that renders a templ.Component to a string for
// substring assertions. Uses context.Background to match production calls.
func render(t *testing.T, ctx context.Context, c templ.Component) string {
	t.Helper()
	var buf bytes.Buffer
	if err := c.Render(ctx, &buf); err != nil {
		t.Fatalf("render: unexpected error: %v", err)
	}
	return buf.String()
}

func TestButton_DefaultSolidMD(t *testing.T) {
	out := render(t, context.Background(), Button(ButtonProps{Label: "Fetch server time"}))

	// Plan 02: multi-class assertions (one check per class token)
	for _, wantClass := range []string{
		`ui-button`,
		`ui-button-solid`,
		`ui-button-default`,
		`ui-button-md`,
	} {
		if !strings.Contains(out, wantClass) {
			t.Errorf("output missing class %q\ngot: %s", wantClass, out)
		}
	}
	if !strings.Contains(out, "Fetch server time") {
		t.Errorf("output missing label literal; got: %s", out)
	}
	if !strings.Contains(out, `type="button"`) {
		t.Errorf("output missing default type=\"button\"; got: %s", out)
	}
}

func TestButton_PassesThroughAttrs(t *testing.T) {
	props := ButtonProps{
		Label: "x",
		Attrs: templ.Attributes{
			"hx-get":    "/demo/time",
			"hx-target": "#demo-out",
		},
	}
	out := render(t, context.Background(), Button(props))

	if !strings.Contains(out, `hx-get="/demo/time"`) {
		t.Errorf("output missing hx-get; got: %s", out)
	}
	if !strings.Contains(out, `hx-target="#demo-out"`) {
		t.Errorf("output missing hx-target; got: %s", out)
	}
}

func TestButton_ExplicitTypeSubmit(t *testing.T) {
	out := render(t, context.Background(), Button(ButtonProps{Label: "Go", Type: "submit"}))
	if !strings.Contains(out, `type="submit"`) {
		t.Errorf("expected type=\"submit\"; got: %s", out)
	}
}


func TestBadge_InfoVariant(t *testing.T) {
	out := render(t, context.Background(), Badge(BadgeProps{Label: "OK", Variant: BadgeVariantInfo}))
	wantClass := `class="ui-badge ui-badge-info"`
	if !strings.Contains(out, wantClass) {
		t.Errorf("output missing %q; got: %s", wantClass, out)
	}
	if !strings.Contains(out, "OK") {
		t.Errorf("output missing label; got: %s", out)
	}
}

func TestBadge_SuccessVariant(t *testing.T) {
	out := render(t, context.Background(), Badge(BadgeProps{Label: "OK", Variant: BadgeVariantSuccess}))
	if !strings.Contains(out, "ui-badge-success") {
		t.Errorf("output missing ui-badge-success; got: %s", out)
	}
}

func TestBadge_ZeroValueDefaultsToInfo(t *testing.T) {
	out := render(t, context.Background(), Badge(BadgeProps{Label: "OK"}))
	if !strings.Contains(out, "ui-badge-info") {
		t.Errorf("zero-value Variant should normalize to info; got: %s", out)
	}
}

func TestButtonClass_String(t *testing.T) {
	got := ButtonClass(ButtonVariantDefault, ButtonToneSolid, SizeMD)
	want := "ui-button ui-button-solid ui-button-default ui-button-md"
	if got != want {
		t.Errorf("ButtonClass = %q; want %q", got, want)
	}
}

func TestBadgeClass_String(t *testing.T) {
	got := BadgeClass(BadgeVariantInfo)
	want := "ui-badge ui-badge-info"
	if got != want {
		t.Errorf("BadgeClass = %q; want %q", got, want)
	}
}

func TestNormalizers_ZeroValueDefaults(t *testing.T) {
	if got := NormalizedSize(""); got != SizeMD {
		t.Errorf("NormalizedSize(\"\") = %q; want %q", got, SizeMD)
	}
	if got := NormalizedButtonVariant(""); got != ButtonVariantDefault {
		t.Errorf("NormalizedButtonVariant(\"\") = %q; want %q", got, ButtonVariantDefault)
	}
	if got := NormalizedButtonTone(""); got != ButtonToneSolid {
		t.Errorf("NormalizedButtonTone(\"\") = %q; want %q", got, ButtonToneSolid)
	}
	if got := NormalizedBadgeVariant(""); got != BadgeVariantInfo {
		t.Errorf("NormalizedBadgeVariant(\"\") = %q; want %q", got, BadgeVariantInfo)
	}
}

// Phase 13 Plan 01 — new enum and class function tests (TDD RED)

func TestButtonVariantGhost_Normalizer(t *testing.T) {
	got := NormalizedButtonVariant(ButtonVariantGhost)
	if got != ButtonVariantGhost {
		t.Errorf("NormalizedButtonVariant(ButtonVariantGhost) = %q; want %q", got, ButtonVariantGhost)
	}
}

func TestButtonClass_GhostVariant(t *testing.T) {
	got := ButtonClass(ButtonVariantGhost, ButtonToneSolid, SizeMD)
	// Plan 02: ghost variant omits the tone class and emits "ui-button-ghost" standalone.
	want := "ui-button ui-button-ghost ui-button-md"
	if got != want {
		t.Errorf("ButtonClass(Ghost, Solid, MD) = %q; want %q", got, want)
	}
}

func TestBadgeVariantPrimary_Normalizer(t *testing.T) {
	got := NormalizedBadgeVariant(BadgeVariantPrimary)
	if got != BadgeVariantPrimary {
		t.Errorf("NormalizedBadgeVariant(BadgeVariantPrimary) = %q; want %q", got, BadgeVariantPrimary)
	}
}

func TestBadgeClass_PrimaryVariant(t *testing.T) {
	got := BadgeClass(BadgeVariantPrimary)
	want := "ui-badge ui-badge-primary"
	if got != want {
		t.Errorf("BadgeClass(BadgeVariantPrimary) = %q; want %q", got, want)
	}
}

func TestIconButtonClass_GhostNeutral(t *testing.T) {
	got := IconButtonClass(IconButtonVariantNeutral, IconButtonToneGhost)
	if !strings.Contains(got, "borderless-icon-button") {
		t.Errorf("IconButtonClass(Neutral, Ghost) = %q; want to contain \"borderless-icon-button\"", got)
	}
}

func TestIconButtonClass_SolidNeutral(t *testing.T) {
	got := IconButtonClass(IconButtonVariantNeutral, IconButtonToneSolid)
	if !strings.Contains(got, "ui-icon-button-solid") {
		t.Errorf("IconButtonClass(Neutral, Solid) = %q; want to contain \"ui-icon-button-solid\"", got)
	}
}

func TestSpaceXClass_MD(t *testing.T) {
	got := SpaceXClass(SpacingStepMD)
	want := "ui-space-x ui-space-x-md"
	if got != want {
		t.Errorf("SpaceXClass(SpacingStepMD) = %q; want %q", got, want)
	}
}

func TestSpaceYClass_LG(t *testing.T) {
	got := SpaceYClass(SpacingStepLG)
	want := "ui-space-y ui-space-y-lg"
	if got != want {
		t.Errorf("SpaceYClass(SpacingStepLG) = %q; want %q", got, want)
	}
}

// textComponent is a test helper that wraps a raw string as a templ.Component,
// used for asserting typed CardProps fields (Header/Body/Footer).
func textComponent(text string) templ.Component {
	return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
		_, err := w.Write([]byte(text))
		return err
	})
}

// Phase 13 Plan 02 — typed Card API and Badge primary variant tests (TDD RED)

func TestCard_RendersTypedRegions(t *testing.T) {
	out := render(t, context.Background(), Card(CardProps{
		Header: textComponent("header"),
		Body:   textComponent("body"),
	}))
	for _, want := range []string{
		`ui-card-header`,
		`header`,
		`ui-card-body`,
		`body`,
	} {
		if !strings.Contains(out, want) {
			t.Fatalf("expected %q in %q", want, out)
		}
	}
	// nil footer must not emit ui-card-footer
	if strings.Contains(out, "ui-card-footer") {
		t.Errorf("expected no ui-card-footer when Footer is nil; got: %s", out)
	}
}

func TestBadge_PrimaryVariant(t *testing.T) {
	out := render(t, context.Background(), Badge(BadgeProps{Label: "new", Variant: BadgeVariantPrimary}))
	if !strings.Contains(out, "ui-badge-primary") {
		t.Errorf("expected ui-badge-primary in output; got: %s", out)
	}
}

// Phase 13 Plan 03 — Input and Textarea component tests (TDD RED)

func TestInput_DefaultType(t *testing.T) {
	out := render(t, context.Background(), Input(InputProps{Name: "x"}))
	if !strings.Contains(out, `type="text"`) {
		t.Errorf("expected type=\"text\" for empty Type; got: %s", out)
	}
	if !strings.Contains(out, `class="ui-input"`) {
		t.Errorf("expected class=\"ui-input\"; got: %s", out)
	}
}

func TestInput_EmailType(t *testing.T) {
	out := render(t, context.Background(), Input(InputProps{Name: "email", Type: "email"}))
	if !strings.Contains(out, `type="email"`) {
		t.Errorf("expected type=\"email\"; got: %s", out)
	}
}

func TestInput_IDFallback(t *testing.T) {
	out := render(t, context.Background(), Input(InputProps{Name: "email"}))
	if !strings.Contains(out, `id="email"`) {
		t.Errorf("expected id=\"email\" (fallback from name); got: %s", out)
	}
}

func TestInput_ExplicitID(t *testing.T) {
	out := render(t, context.Background(), Input(InputProps{ID: "my-id", Name: "x"}))
	if !strings.Contains(out, `id="my-id"`) {
		t.Errorf("expected id=\"my-id\"; got: %s", out)
	}
}

func TestTextarea_RendersClass(t *testing.T) {
	out := render(t, context.Background(), Textarea(TextareaProps{Name: "body"}))
	if !strings.Contains(out, `class="ui-textarea"`) {
		t.Errorf("expected class=\"ui-textarea\"; got: %s", out)
	}
}

func TestTextarea_DefaultRows(t *testing.T) {
	out := render(t, context.Background(), Textarea(TextareaProps{Name: "x", Rows: 0}))
	if !strings.Contains(out, `rows="4"`) {
		t.Errorf("expected rows=\"4\" for zero Rows; got: %s", out)
	}
}

func TestTextarea_ExplicitRows(t *testing.T) {
	out := render(t, context.Background(), Textarea(TextareaProps{Name: "x", Rows: 6}))
	if !strings.Contains(out, `rows="6"`) {
		t.Errorf("expected rows=\"6\"; got: %s", out)
	}
}

// Phase 13 Plan 03 — Select and FormField component tests (TDD RED)

func TestSelect_RendersControl(t *testing.T) {
	out := render(t, context.Background(), Select(SelectProps{
		Name:    "status",
		Options: []SelectOption{{Value: "a", Label: "Alpha"}},
	}))
	if !strings.Contains(out, "ui-select-control") {
		t.Errorf("expected ui-select-control in output; got: %s", out)
	}
}

func TestSelect_HasInlineScript(t *testing.T) {
	out := render(t, context.Background(), Select(SelectProps{
		Name:    "status",
		Options: []SelectOption{{Value: "a", Label: "Alpha"}},
	}))
	if !strings.Contains(out, "__uiSelectInitAll") {
		t.Errorf("expected __uiSelectInitAll in output (inline JS); got: %s", out)
	}
}

func TestSelect_HasHtmxListener(t *testing.T) {
	out := render(t, context.Background(), Select(SelectProps{
		Name:    "status",
		Options: []SelectOption{{Value: "a", Label: "Alpha"}},
	}))
	if !strings.Contains(out, "htmx:afterSwap") {
		t.Errorf("expected htmx:afterSwap in output (re-init listener); got: %s", out)
	}
}

func TestFormField_RendersLabel(t *testing.T) {
	out := render(t, context.Background(), FormField(FormFieldProps{
		Label: "Name",
		For:   "name-input",
	}))
	for _, want := range []string{"ui-form-field", "ui-form-label"} {
		if !strings.Contains(out, want) {
			t.Errorf("expected %q in output; got: %s", want, out)
		}
	}
}

func TestFormField_RendersError(t *testing.T) {
	out := render(t, context.Background(), FormField(FormFieldProps{
		Label: "Name",
		For:   "name-input",
		Error: "This field is required",
	}))
	if !strings.Contains(out, "ui-form-error") {
		t.Errorf("expected ui-form-error in output when Error is set; got: %s", out)
	}
}

func TestFormField_NoErrorWhenEmpty(t *testing.T) {
	out := render(t, context.Background(), FormField(FormFieldProps{
		Label: "Name",
		For:   "name-input",
		Error: "",
	}))
	if strings.Contains(out, "ui-form-error") {
		t.Errorf("expected NO ui-form-error in output when Error is empty; got: %s", out)
	}
}

// Phase 13 Plan 04 — Modal, EmptyState, Table component tests (TDD RED)

func TestModal_RendersBackdropAndPanel(t *testing.T) {
	out := render(t, context.Background(), Modal(ModalProps{Title: "Confirm action"}))
	for _, want := range []string{"ui-modal-backdrop", "ui-modal-panel"} {
		if !strings.Contains(out, want) {
			t.Errorf("expected %q in output; got: %s", want, out)
		}
	}
}

func TestModal_RendersTitle(t *testing.T) {
	out := render(t, context.Background(), Modal(ModalProps{Title: "Confirm action"}))
	// Title must appear inside a heading element
	if !strings.Contains(out, "<h2>") || !strings.Contains(out, "Confirm action") {
		t.Errorf("expected <h2> with title text in output; got: %s", out)
	}
}

func TestModal_NilBodyOmitted(t *testing.T) {
	out := render(t, context.Background(), Modal(ModalProps{Title: "Confirm action"}))
	if strings.Contains(out, "ui-modal-body") {
		t.Errorf("expected NO ui-modal-body when Body is nil; got: %s", out)
	}
}

func TestEmptyState_RendersTitle(t *testing.T) {
	out := render(t, context.Background(), EmptyState(EmptyStateProps{Title: "Nothing here yet"}))
	for _, want := range []string{"ui-empty-state", "Nothing here yet"} {
		if !strings.Contains(out, want) {
			t.Errorf("expected %q in output; got: %s", want, out)
		}
	}
}

func TestEmptyState_NilIconOmitted(t *testing.T) {
	out := render(t, context.Background(), EmptyState(EmptyStateProps{Title: "Nothing here yet"}))
	if strings.Contains(out, "ui-empty-state-icon") {
		t.Errorf("expected NO ui-empty-state-icon when Icon is nil; got: %s", out)
	}
}

func TestTable_RendersShell(t *testing.T) {
	out := render(t, context.Background(), Table(TableProps{}))
	for _, want := range []string{"ui-table-shell", "ui-table"} {
		if !strings.Contains(out, want) {
			t.Errorf("expected %q in output; got: %s", want, out)
		}
	}
}

// Phase 13 Plan 04 — IconButton, UIIcon, Space, and Button icon wiring tests (TDD RED)

func TestIconButton_GhostNeutral(t *testing.T) {
	out := render(t, context.Background(), IconButton(IconButtonProps{
		Icon:    "plus",
		Variant: IconButtonVariantNeutral,
		Tone:    IconButtonToneGhost,
		Label:   "Add",
	}))
	if !strings.Contains(out, "borderless-icon-button") {
		t.Errorf("expected borderless-icon-button class; got: %s", out)
	}
	if !strings.Contains(out, `aria-label="Add"`) {
		t.Errorf("expected aria-label=\"Add\"; got: %s", out)
	}
}

func TestIconButton_SolidDanger(t *testing.T) {
	out := render(t, context.Background(), IconButton(IconButtonProps{
		Icon:    "trash",
		Variant: IconButtonVariantDanger,
		Tone:    IconButtonToneSolid,
		Label:   "Delete",
	}))
	if !strings.Contains(out, "ui-icon-button-solid") {
		t.Errorf("expected ui-icon-button-solid class; got: %s", out)
	}
	if !strings.Contains(out, "ui-icon-button-danger") {
		t.Errorf("expected ui-icon-button-danger class; got: %s", out)
	}
}

func TestUIIcon_Plus(t *testing.T) {
	out := render(t, context.Background(), UIIcon("plus"))
	if !strings.Contains(out, "<svg") {
		t.Errorf("expected <svg in output for 'plus' icon; got: %s", out)
	}
}

func TestUIIcon_Fallback(t *testing.T) {
	out := render(t, context.Background(), UIIcon("unknown-kind"))
	if !strings.Contains(out, "<span") {
		t.Errorf("expected <span fallback for unknown icon kind; got: %s", out)
	}
}

func TestSpaceX_MD(t *testing.T) {
	out := render(t, context.Background(), SpaceX(SpaceProps{Size: SpacingStepMD}))
	if !strings.Contains(out, "ui-space-x-md") {
		t.Errorf("expected ui-space-x-md in output; got: %s", out)
	}
}

func TestSpaceY_LG(t *testing.T) {
	out := render(t, context.Background(), SpaceY(SpaceProps{Size: SpacingStepLG}))
	if !strings.Contains(out, "ui-space-y-lg") {
		t.Errorf("expected ui-space-y-lg in output; got: %s", out)
	}
}

func TestButton_IconRendered(t *testing.T) {
	out := render(t, context.Background(), Button(ButtonProps{Label: "Add", Icon: "plus"}))
	if !strings.Contains(out, "<svg") {
		t.Errorf("expected <svg in output when Button.Icon is set; got: %s", out)
	}
}
