package jsbundler

import "testing"

// parseOne parses a single JSX element starting at the first '<'.
func parseOne(t *testing.T, src string) jsxNode {
	t.Helper()
	i := 0
	for i < len(src) && src[i] != '<' {
		i++
	}
	node, next, err := parseJSX(src, i)
	if err != nil {
		t.Fatalf("parseJSX(%q): %v", src, err)
	}
	if next != len(src) {
		t.Fatalf("parseJSX(%q): consumed to %d, want %d (trailing %q)", src, next, len(src), src[next:])
	}
	return node
}

func TestParseStaticElement(t *testing.T) {
	n := parseOne(t, `<div class="x">hi</div>`)
	if n.kind != jsxElement || n.tag != "div" {
		t.Fatalf("kind/tag = %d/%q", n.kind, n.tag)
	}
	if len(n.attrs) != 1 || n.attrs[0].kind != attrStatic || n.attrs[0].name != "class" || n.attrs[0].value != "x" {
		t.Fatalf("attrs = %+v", n.attrs)
	}
	if len(n.children) != 1 || n.children[0].kind != jsxText || n.children[0].text != "hi" {
		t.Fatalf("children = %+v", n.children)
	}
}

func TestParseComponent(t *testing.T) {
	n := parseOne(t, `<Foo bar={1}>child</Foo>`)
	if n.kind != jsxComponent || n.tag != "Foo" {
		t.Fatalf("kind/tag = %d/%q", n.kind, n.tag)
	}
	if len(n.attrs) != 1 || n.attrs[0].kind != attrExpr || n.attrs[0].name != "bar" || n.attrs[0].expr != "1" {
		t.Fatalf("attrs = %+v", n.attrs)
	}
}

func TestParseNested(t *testing.T) {
	n := parseOne(t, `<div><span>a</span><b>{x()}</b></div>`)
	if len(n.children) != 2 {
		t.Fatalf("want 2 element children, got %d: %+v", len(n.children), n.children)
	}
	if n.children[0].tag != "span" || n.children[1].tag != "b" {
		t.Fatalf("child tags = %q, %q", n.children[0].tag, n.children[1].tag)
	}
	b := n.children[1]
	if len(b.children) != 1 || b.children[0].kind != jsxExpr || b.children[0].expr != "x()" {
		t.Fatalf("b children = %+v", b.children)
	}
}

func TestParseFragment(t *testing.T) {
	n := parseOne(t, `<>{a()}{b()}</>`)
	if n.kind != jsxFragment || len(n.children) != 2 {
		t.Fatalf("fragment: kind=%d children=%d", n.kind, len(n.children))
	}
	if n.children[0].expr != "a()" || n.children[1].expr != "b()" {
		t.Fatalf("fragment children = %+v", n.children)
	}
}

func TestParseSelfCloseAndBool(t *testing.T) {
	n := parseOne(t, `<input disabled type="text" />`)
	if n.tag != "input" || len(n.children) != 0 {
		t.Fatalf("input: tag=%q children=%d", n.tag, len(n.children))
	}
	if len(n.attrs) != 2 || !n.attrs[0].boolt || n.attrs[0].name != "disabled" {
		t.Fatalf("attrs = %+v", n.attrs)
	}
	if n.attrs[1].name != "type" || n.attrs[1].value != "text" {
		t.Fatalf("type attr = %+v", n.attrs[1])
	}
}

func TestParseSpread(t *testing.T) {
	n := parseOne(t, `<div {...p} class="x">hi</div>`)
	if len(n.attrs) != 2 || n.attrs[0].kind != attrSpread || n.attrs[0].expr != "p" {
		t.Fatalf("attrs = %+v", n.attrs)
	}
}

// The hardest case: an expression child containing nested JSX AND braces inside
// strings/templates. The parser must capture the whole expression opaquely.
func TestParseExprWithNestedJSX(t *testing.T) {
	n := parseOne(t, "<ul>{items.map((x) => <li>{x}</li>)}</ul>")
	if len(n.children) != 1 || n.children[0].kind != jsxExpr {
		t.Fatalf("children = %+v", n.children)
	}
	if n.children[0].expr != "items.map((x) => <li>{x}</li>)" {
		t.Fatalf("expr = %q", n.children[0].expr)
	}
}

func TestCaptureBracesLexer(t *testing.T) {
	cases := []struct{ in, want string }{
		{`{a + "}" + b}`, `a + "}" + b`},
		{"{`a${nested}b`}", "`a${nested}b`"},
		{`{ {k: 1} }`, ` {k: 1} `},
		{`{f(/[}]/)}`, `f(/[}]/)`},
		{`{x} rest`, `x`},
	}
	for _, c := range cases {
		got, next := captureBraces(c.in, 0)
		if got != c.want {
			t.Errorf("captureBraces(%q) = %q, want %q", c.in, got, c.want)
		}
		_ = next
	}
}
