package parser

import (
	"testing"

	"github.com/a-h/parse"
	"github.com/google/go-cmp/cmp"
)

func TestIfExpression(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected *IfExpression
	}{
		{
			name: "if: simple expression",
			input: `if p.Test {
<span>
  { "span content" }
</span>
}
`,
			expected: &IfExpression{
				Expression: Expression{
					Value: `p.Test`,
					Range: Range{
						From: Position{
							Index: 3,
							Line:  0,
							Col:   3,
						},
						To: Position{
							Index: 9,
							Line:  0,
							Col:   9,
						},
					},
				},
				Then: []Node{
					&Element{
						Name: "span",
						NameRange: Range{
							From: Position{Index: 13, Line: 1, Col: 1},
							To:   Position{Index: 17, Line: 1, Col: 5},
						},

						Children: []Node{
							&Whitespace{Range: Range{
								From: Position{
									Index: 18,
									Line:  1,
									Col:   6,
								},
								To: Position{
									Index: 21,
									Line:  2,
									Col:   2,
								},
							},
								Value: "\n  "},
							&StringExpression{
								Expression: Expression{
									Value: `"span content"`,
									Range: Range{
										From: Position{
											Index: 23,
											Line:  2,
											Col:   4,
										},
										To: Position{
											Index: 37,
											Line:  2,
											Col:   18,
										},
									},
								},
								TrailingSpace: SpaceVertical,
							},
						},
						IndentChildren: true,
						TrailingSpace:  SpaceVertical,
						Range: Range{
							From: Position{Index: 12, Line: 1, Col: 0},
							To:   Position{Index: 48, Line: 4, Col: 0},
						},
					},
				},
				Range: Range{
					From: Position{Index: 0, Line: 0, Col: 0},
					To:   Position{Index: 49, Line: 4, Col: 1},
				},
			},
		},
		{
			name: "if: else",
			input: `if p.A {
	{ "A" }
} else {
	{ "B" }
}`,
			expected: &IfExpression{
				Expression: Expression{
					Value: `p.A`,
					Range: Range{
						From: Position{
							Index: 3,
							Line:  0,
							Col:   3,
						},
						To: Position{
							Index: 6,
							Line:  0,
							Col:   6,
						},
					},
				},
				Then: []Node{
					&Whitespace{Range: Range{
						From: Position{
							Index: 9,
							Line:  1,
							Col:   0,
						},
						To: Position{
							Index: 10,
							Line:  1,
							Col:   1,
						},
					},
						Value: "\t"},
					&StringExpression{
						Expression: Expression{
							Value: `"A"`,
							Range: Range{
								From: Position{
									Index: 12,
									Line:  1,
									Col:   3,
								},
								To: Position{
									Index: 15,
									Line:  1,
									Col:   6,
								},
							},
						},
						TrailingSpace: SpaceVertical,
					},
				},
				Else: []Node{
					&StringExpression{
						Expression: Expression{
							Value: `"B"`,
							Range: Range{
								From: Position{
									Index: 30,
									Line:  3,
									Col:   3,
								},
								To: Position{
									Index: 33,
									Line:  3,
									Col:   6,
								},
							},
						},
						TrailingSpace: SpaceVertical,
					},
				},
				Range: Range{
					From: Position{Index: 0, Line: 0, Col: 0},
					To:   Position{Index: 37, Line: 4, Col: 1},
				},
			},
		},
		{
			name: "if: expressions can have a space after the opening brace",
			input: `if p.Test {` + " " + `
  text
}
`,
			expected: &IfExpression{
				Expression: Expression{
					Value: `p.Test`,
					Range: Range{
						From: Position{
							Index: 3,
							Line:  0,
							Col:   3,
						},
						To: Position{
							Index: 9,
							Line:  0,
							Col:   9,
						},
					},
				},
				Then: []Node{
					&Whitespace{Range: Range{
						From: Position{
							Index: 13,
							Line:  1,
							Col:   0,
						},
						To: Position{
							Index: 15,
							Line:  1,
							Col:   2,
						},
					},
						Value: "  "},
					&Text{
						Value: "text",
						Range: Range{
							From: Position{Index: 15, Line: 1, Col: 2},
							To:   Position{Index: 19, Line: 1, Col: 6},
						},
						TrailingSpace: SpaceVertical,
					},
				},
				Range: Range{
					From: Position{Index: 0, Line: 0, Col: 0},
					To:   Position{Index: 21, Line: 2, Col: 1},
				},
			},
		},
		{
			name: "if: simple expression, without spaces",
			input: `if p.Test {
<span>
  { "span content" }
</span>
}
`,
			expected: &IfExpression{
				Expression: Expression{
					Value: `p.Test`,
					Range: Range{
						From: Position{
							Index: 3,
							Line:  0,
							Col:   3,
						},
						To: Position{
							Index: 9,
							Line:  0,
							Col:   9,
						},
					},
				},
				Then: []Node{
					&Element{
						Name: "span",
						NameRange: Range{
							From: Position{Index: 13, Line: 1, Col: 1},
							To:   Position{Index: 17, Line: 1, Col: 5},
						},

						Children: []Node{
							&Whitespace{Range: Range{
								From: Position{
									Index: 18,
									Line:  1,
									Col:   6,
								},
								To: Position{
									Index: 21,
									Line:  2,
									Col:   2,
								},
							},
								Value: "\n  "},
							&StringExpression{
								Expression: Expression{
									Value: `"span content"`,
									Range: Range{
										From: Position{
											Index: 23,
											Line:  2,
											Col:   4,
										},
										To: Position{
											Index: 37,
											Line:  2,
											Col:   18,
										},
									},
								},
								TrailingSpace: SpaceVertical,
							},
						},
						IndentChildren: true,
						TrailingSpace:  SpaceVertical,
						Range: Range{
							From: Position{Index: 12, Line: 1, Col: 0},
							To:   Position{Index: 48, Line: 4, Col: 0},
						},
					},
				},
				Range: Range{
					From: Position{Index: 0, Line: 0, Col: 0},
					To:   Position{Index: 49, Line: 4, Col: 1},
				},
			},
		},
		{
			name: "if: else, without spaces",
			input: `if p.A{
	{ "A" }
} else {
	{ "B" }
}`,
			expected: &IfExpression{
				Expression: Expression{
					Value: `p.A`,
					Range: Range{
						From: Position{
							Index: 3,
							Line:  0,
							Col:   3,
						},
						To: Position{
							Index: 6,
							Line:  0,
							Col:   6,
						},
					},
				},
				Then: []Node{
					&Whitespace{Range: Range{
						From: Position{
							Index: 8,
							Line:  1,
							Col:   0,
						},
						To: Position{
							Index: 9,
							Line:  1,
							Col:   1,
						},
					},
						Value: "\t"},
					&StringExpression{
						Expression: Expression{
							Value: `"A"`,
							Range: Range{
								From: Position{
									Index: 11,
									Line:  1,
									Col:   3,
								},
								To: Position{
									Index: 14,
									Line:  1,
									Col:   6,
								},
							},
						},
						TrailingSpace: SpaceVertical,
					},
				},
				Else: []Node{
					&StringExpression{
						Expression: Expression{
							Value: `"B"`,
							Range: Range{
								From: Position{
									Index: 29,
									Line:  3,
									Col:   3,
								},
								To: Position{
									Index: 32,
									Line:  3,
									Col:   6,
								},
							},
						},
						TrailingSpace: SpaceVertical,
					},
				},
				Range: Range{
					From: Position{Index: 0, Line: 0, Col: 0},
					To:   Position{Index: 36, Line: 4, Col: 1},
				},
			},
		},
		{
			name: "if: nested",
			input: `if p.A {
					if p.B {
						<div>{ "B" }</div>
					}
				}`,
			expected: &IfExpression{
				Expression: Expression{
					Value: `p.A`,
					Range: Range{
						From: Position{
							Index: 3,
							Line:  0,
							Col:   3,
						},
						To: Position{
							Index: 6,
							Line:  0,
							Col:   6,
						},
					},
				},
				Then: []Node{
					&Whitespace{Range: Range{
						From: Position{
							Index: 9,
							Line:  1,
							Col:   0,
						},
						To: Position{
							Index: 14,
							Line:  1,
							Col:   5,
						},
					},
						Value: "\t\t\t\t\t"},
					&IfExpression{
						Expression: Expression{
							Value: `p.B`,
							Range: Range{
								From: Position{
									Index: 17,
									Line:  1,
									Col:   8,
								},
								To: Position{
									Index: 20,
									Line:  1,
									Col:   11,
								},
							},
						},
						Then: []Node{
							&Whitespace{Range: Range{
								From: Position{
									Index: 23,
									Line:  2,
									Col:   0,
								},
								To: Position{
									Index: 29,
									Line:  2,
									Col:   6,
								},
							},
								Value: "\t\t\t\t\t\t"},
							&Element{
								Name: "div",
								NameRange: Range{
									From: Position{Index: 30, Line: 2, Col: 7},
									To:   Position{Index: 33, Line: 2, Col: 10},
								},

								Children: []Node{
									&StringExpression{
										Expression: Expression{
											Value: `"B"`,
											Range: Range{
												From: Position{
													Index: 36,
													Line:  2,
													Col:   13,
												},
												To: Position{
													Index: 39,
													Line:  2,
													Col:   16,
												},
											},
										},
									},
								},
								TrailingSpace: SpaceVertical,
								Range: Range{
									From: Position{Index: 29, Line: 2, Col: 6},
									To:   Position{Index: 53, Line: 3, Col: 5},
								},
							},
						},
						Range: Range{
							From: Position{Index: 14, Line: 1, Col: 5},
							To:   Position{Index: 54, Line: 3, Col: 6},
						},
					},
					&Whitespace{Range: Range{
						From: Position{
							Index: 54,
							Line:  3,
							Col:   6,
						},
						To: Position{
							Index: 59,
							Line:  4,
							Col:   4,
						},
					},
						Value: "\n\t\t\t\t"},
				},
				Range: Range{
					From: Position{Index: 0, Line: 0, Col: 0},
					To:   Position{Index: 60, Line: 4, Col: 5},
				},
			},
		},
		{
			name: "if: else if",
			input: `if p.A {
	{ "A" }
} else if p.B {
	{ "B" }
}`,
			expected: &IfExpression{
				Expression: Expression{
					Value: `p.A`,
					Range: Range{
						From: Position{Index: 3, Line: 0, Col: 3},
						To:   Position{Index: 6, Line: 0, Col: 6},
					},
				},
				Then: []Node{
					&Whitespace{Range: Range{
						From: Position{
							Index: 9,
							Line:  1,
							Col:   0,
						},
						To: Position{
							Index: 10,
							Line:  1,
							Col:   1,
						},
					},
						Value: "\t"},
					&StringExpression{
						Expression: Expression{
							Value: `"A"`,
							Range: Range{
								From: Position{Index: 12, Line: 1, Col: 3},
								To:   Position{Index: 15, Line: 1, Col: 6},
							},
						},
						TrailingSpace: SpaceVertical,
					},
				},
				ElseIfs: []ElseIfExpression{
					{
						Expression: Expression{
							Value: `p.B`,
							Range: Range{
								From: Position{Index: 28, Line: 2, Col: 10},
								To:   Position{Index: 31, Line: 2, Col: 13},
							},
						},
						Then: []Node{
							&Whitespace{Range: Range{
								From: Position{
									Index: 34,
									Line:  3,
									Col:   0,
								},
								To: Position{
									Index: 35,
									Line:  3,
									Col:   1,
								},
							},
								Value: "\t"},
							&StringExpression{
								Expression: Expression{
									Value: `"B"`,
									Range: Range{
										From: Position{Index: 37, Line: 3, Col: 3},
										To:   Position{Index: 40, Line: 3, Col: 6},
									},
								},
								TrailingSpace: SpaceVertical,
							},
						},
						Range: Range{
							From: Position{Index: 18, Line: 2, Col: 0},
							To:   Position{Index: 43, Line: 4, Col: 0},
						},
					},
				},
				Range: Range{
					From: Position{Index: 0, Line: 0, Col: 0},
					To:   Position{Index: 44, Line: 4, Col: 1},
				},
			},
		},
		{
			name: "if: else if, else if",
			input: `if p.A {
	{ "A" }
} else if p.B {
	{ "B" }
} else if p.C {
	{ "C" }
}`,
			expected: &IfExpression{
				Expression: Expression{
					Value: `p.A`,
					Range: Range{
						From: Position{Index: 3, Line: 0, Col: 3},
						To:   Position{Index: 6, Line: 0, Col: 6},
					},
				},
				Then: []Node{
					&Whitespace{Range: Range{
						From: Position{
							Index: 9,
							Line:  1,
							Col:   0,
						},
						To: Position{
							Index: 10,
							Line:  1,
							Col:   1,
						},
					},
						Value: "\t"},
					&StringExpression{
						Expression: Expression{
							Value: `"A"`,
							Range: Range{
								From: Position{Index: 12, Line: 1, Col: 3},
								To:   Position{Index: 15, Line: 1, Col: 6},
							},
						},
						TrailingSpace: SpaceVertical,
					},
				},
				ElseIfs: []ElseIfExpression{
					{
						Expression: Expression{
							Value: `p.B`,
							Range: Range{
								From: Position{Index: 28, Line: 2, Col: 10},
								To:   Position{Index: 31, Line: 2, Col: 13},
							},
						},
						Then: []Node{
							&Whitespace{Range: Range{
								From: Position{
									Index: 34,
									Line:  3,
									Col:   0,
								},
								To: Position{
									Index: 35,
									Line:  3,
									Col:   1,
								},
							},
								Value: "\t"},
							&StringExpression{
								Expression: Expression{
									Value: `"B"`,
									Range: Range{
										From: Position{Index: 37, Line: 3, Col: 3},
										To:   Position{Index: 40, Line: 3, Col: 6},
									},
								},
								TrailingSpace: SpaceVertical,
							},
						},
						Range: Range{
							From: Position{Index: 18, Line: 2, Col: 0},
							To:   Position{Index: 43, Line: 4, Col: 0},
						},
					},
					{
						Expression: Expression{
							Value: `p.C`,
							Range: Range{
								From: Position{Index: 53, Line: 4, Col: 10},
								To:   Position{Index: 56, Line: 4, Col: 13},
							},
						},
						Then: []Node{
							&Whitespace{Range: Range{
								From: Position{
									Index: 59,
									Line:  5,
									Col:   0,
								},
								To: Position{
									Index: 60,
									Line:  5,
									Col:   1,
								},
							},
								Value: "\t"},
							&StringExpression{
								Expression: Expression{
									Value: `"C"`,
									Range: Range{
										From: Position{Index: 62, Line: 5, Col: 3},
										To:   Position{Index: 65, Line: 5, Col: 6},
									},
								},
								TrailingSpace: SpaceVertical,
							},
						},
						Range: Range{
							From: Position{Index: 43, Line: 4, Col: 0},
							To:   Position{Index: 68, Line: 6, Col: 0},
						},
					},
				},
				Range: Range{
					From: Position{Index: 0, Line: 0, Col: 0},
					To:   Position{Index: 69, Line: 6, Col: 1},
				},
			},
		},
		{
			name: "if: else if, else if, else",
			input: `if p.A {
	{ "A" }
} else if p.B {
	{ "B" }
} else if p.C {
	{ "C" }
} else {
	{ "D" }
}`,
			expected: &IfExpression{
				Expression: Expression{
					Value: `p.A`,
					Range: Range{
						From: Position{Index: 3, Line: 0, Col: 3},
						To:   Position{Index: 6, Line: 0, Col: 6},
					},
				},
				Then: []Node{
					&Whitespace{Range: Range{
						From: Position{
							Index: 9,
							Line:  1,
							Col:   0,
						},
						To: Position{
							Index: 10,
							Line:  1,
							Col:   1,
						},
					},
						Value: "\t"},
					&StringExpression{
						Expression: Expression{
							Value: `"A"`,
							Range: Range{
								From: Position{Index: 12, Line: 1, Col: 3},
								To:   Position{Index: 15, Line: 1, Col: 6},
							},
						},
						TrailingSpace: SpaceVertical,
					},
				},
				ElseIfs: []ElseIfExpression{
					{
						Expression: Expression{
							Value: `p.B`,
							Range: Range{
								From: Position{Index: 28, Line: 2, Col: 10},
								To:   Position{Index: 31, Line: 2, Col: 13},
							},
						},
						Then: []Node{
							&Whitespace{Range: Range{
								From: Position{
									Index: 34,
									Line:  3,
									Col:   0,
								},
								To: Position{
									Index: 35,
									Line:  3,
									Col:   1,
								},
							},
								Value: "\t"},
							&StringExpression{
								Expression: Expression{
									Value: `"B"`,
									Range: Range{
										From: Position{Index: 37, Line: 3, Col: 3},
										To:   Position{Index: 40, Line: 3, Col: 6},
									},
								},
								TrailingSpace: SpaceVertical,
							},
						},
						Range: Range{
							From: Position{Index: 18, Line: 2, Col: 0},
							To:   Position{Index: 43, Line: 4, Col: 0},
						},
					},
					{
						Expression: Expression{
							Value: `p.C`,
							Range: Range{
								From: Position{Index: 53, Line: 4, Col: 10},
								To:   Position{Index: 56, Line: 4, Col: 13},
							},
						},
						Then: []Node{
							&Whitespace{Range: Range{
								From: Position{
									Index: 59,
									Line:  5,
									Col:   0,
								},
								To: Position{
									Index: 60,
									Line:  5,
									Col:   1,
								},
							},
								Value: "\t"},
							&StringExpression{
								Expression: Expression{
									Value: `"C"`,
									Range: Range{
										From: Position{Index: 62, Line: 5, Col: 3},
										To:   Position{Index: 65, Line: 5, Col: 6},
									},
								},
								TrailingSpace: SpaceVertical,
							},
						},
						Range: Range{
							From: Position{Index: 43, Line: 4, Col: 0},
							To:   Position{Index: 68, Line: 6, Col: 0},
						},
					},
				},
				Else: []Node{
					&StringExpression{
						Expression: Expression{
							Value: `"D"`,
							Range: Range{
								From: Position{Index: 80, Line: 7, Col: 3},
								To:   Position{Index: 83, Line: 7, Col: 6},
							},
						},
						TrailingSpace: SpaceVertical,
					},
				},
				Range: Range{
					From: Position{Index: 0, Line: 0, Col: 0},
					To:   Position{Index: 87, Line: 8, Col: 1},
				},
			},
		},
		{
			name: "if: else with comment and indentation",
			input: `if p.A {
		// this is a comment
	} else {
		{ "B" }
	}`,
			expected: &IfExpression{
				Expression: Expression{
					Value: `p.A`,
					Range: Range{
						From: Position{
							Index: 3,
							Line:  0,
							Col:   3,
						},
						To: Position{
							Index: 6,
							Line:  0,
							Col:   6,
						},
					},
				},
				Then: []Node{
					&Whitespace{Range: Range{
						From: Position{
							Index: 9,
							Line:  1,
							Col:   0,
						},
						To: Position{
							Index: 11,
							Line:  1,
							Col:   2,
						},
					},
						Value: "\t\t"},
					&GoComment{
						Contents:  " this is a comment",
						Multiline: false,
						Range: Range{
							From: Position{Index: 11, Line: 1, Col: 2},
							To:   Position{Index: 31, Line: 1, Col: 22},
						},
					},
					&Whitespace{Range: Range{
						From: Position{
							Index: 31,
							Line:  1,
							Col:   22,
						},
						To: Position{
							Index: 33,
							Line:  2,
							Col:   1,
						},
					},
						Value: "\n\t"},
				},
				Else: []Node{
					&StringExpression{
						Expression: Expression{
							Value: `"B"`,
							Range: Range{
								From: Position{
									Index: 46,
									Line:  3,
									Col:   4,
								},
								To: Position{
									Index: 49,
									Line:  3,
									Col:   7,
								},
							},
						},
						TrailingSpace: SpaceVertical,
					},
				},
				Range: Range{
					From: Position{Index: 0, Line: 0, Col: 0},
					To:   Position{Index: 54, Line: 4, Col: 2},
				},
			},
		},
	}
	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			input := parse.NewInput(tt.input)
			actual, matched, err := ifExpression.Parse(input)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if !matched {
				t.Fatalf("unexpected failure for input %q", tt.input)
			}
			if diff := cmp.Diff(tt.expected, actual); diff != "" {
				t.Error(diff)
			}
		})
	}
}

func TestIncompleteIf(t *testing.T) {
	t.Run("no opening brace", func(t *testing.T) {
		input := parse.NewInput(`if a tree falls in the woods`)
		_, _, err := ifExpression.Parse(input)
		if err == nil {
			t.Fatal("expected an error, got nil")
		}
		pe, isParseError := err.(parse.ParseError)
		if !isParseError {
			t.Fatalf("expected a parse error, got %T", err)
		}
		if pe.Msg != "if: "+unterminatedMissingCurly {
			t.Fatalf("unexpected error: %v", err)
		}
		if pe.Pos.Line != 0 {
			t.Fatalf("unexpected line: %d", pe.Pos.Line)
		}
	})
	t.Run("capitalised If", func(t *testing.T) {
		input := parse.NewInput(`If a tree falls in the woods`)
		_, matched, err := ifExpression.Parse(input)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if matched {
			t.Fatal("expected a non match")
		}
	})
}
