package yqlib

import (
	"testing"
)

var stringsOperatorScenarios = []expressionScenario{
	{
		description: "Interpolation",
		document:    "value: things\nanother: stuff",
		expression:  `.message = "I like \(.value) and \(.another)"`,
		expected: []string{
			"D0, P[], (!!map)::value: things\nanother: stuff\nmessage: I like things and stuff\n",
		},
	},
	{
		description: "Interpolation - not a string",
		document:    `value: {an: apple}`,
		expression:  `.message = "I like \(.value)"`,
		expected: []string{
			"D0, P[], (!!map)::value: {an: apple}\nmessage: 'I like {an: apple}'\n",
		},
	},
	{
		skipDoc:     true,
		description: "Interpolation - just escape",
		expression:  `"\\"`,
		expected: []string{
			"D0, P[], (!!str)::\\\n",
		},
	},
	{
		skipDoc:     true,
		description: "Interpolation - nested",
		document:    `value: things`,
		expression:  `"Hi \( (.value) )"`,
		expected: []string{
			"D0, P[], (!!str)::Hi things\n",
		},
	},
	{
		skipDoc:     true,
		description: "Interpolation - don't",
		document:    `value: things`,
		expression:  `"Hi (.value)"`,
		expected: []string{
			"D0, P[], (!!str)::Hi (.value)\n",
		},
	},
	{
		skipDoc:     true,
		description: "Interpolation - don't!",
		document:    `value: things`,
		expression:  `"Hi \\(.value)"`,
		expected: []string{
			"D0, P[], (!!str)::Hi \\(.value)\n",
		},
	},
	{
		skipDoc:     true,
		description: "Interpolation - random close bracket",
		document:    `value: things`,
		expression:  `"Hi )"`,
		expected: []string{
			"D0, P[], (!!str)::Hi )\n",
		},
	},
	{
		skipDoc:     true,
		description: "Interpolation - unclosed interpolation string",
		document:    `value: things`,
		expression:  `"Hi \("`,
		expected: []string{
			"D0, P[], (!!str)::Hi \\(\n",
		},
	},
	{
		skipDoc:     true,
		description: "Interpolation - unclosed interpolation string due to escape",
		document:    `value: things`,
		expression:  `"Hi \(\)"`,
		expected: []string{
			"D0, P[], (!!str)::Hi \\(\\)\n",
		},
	},
	{
		description:    "To up (upper) case",
		subdescription: "Works with unicode characters",
		document:       `água`,
		expression:     "upcase",
		expected: []string{
			"D0, P[], (!!str)::ÁGUA\n",
		},
	},
	{
		skipDoc:    true,
		document:   `!camel água`,
		expression: "upcase",
		expected: []string{
			"D0, P[], (!camel)::ÁGUA\n",
		},
	},
	{
		description:    "To down (lower) case",
		subdescription: "Works with unicode characters",
		document:       `ÁgUA`,
		expression:     "downcase",
		expected: []string{
			"D0, P[], (!!str)::água\n",
		},
	},
	{
		skipDoc:    true,
		document:   `!camel ÁgUA`,
		expression: "downcase",
		expected: []string{
			"D0, P[], (!camel)::água\n",
		},
	},
	{
		description: "Join strings",
		document:    `[cat, meow, 1, null, true]`,
		expression:  `join("; ")`,
		expected: []string{
			"D0, P[], (!!str)::cat; meow; 1; ; true\n",
		},
	},
	{
		description: "Trim strings",
		document:    `[" cat", "dog ", " cow cow ", horse]`,
		expression:  `.[] | trim`,
		expected: []string{
			"D0, P[0], (!!str)::cat\n",
			"D0, P[1], (!!str)::dog\n",
			"D0, P[2], (!!str)::cow cow\n",
			"D0, P[3], (!!str)::horse\n",
		},
	},
	{
		skipDoc:    true,
		document:   `[!horse cat, !goat meow, !frog 1, null, true]`,
		expression: `join("; ")`,
		expected: []string{
			"D0, P[], (!!str)::cat; meow; 1; ; true\n",
		},
	},
	{
		description: "Match string",
		document:    `foo bar foo`,
		expression:  `match("foo")`,
		expected: []string{
			"D0, P[], (!!map)::string: foo\noffset: 0\nlength: 3\ncaptures: []\n",
		},
	},
	{
		skipDoc:    true,
		document:   `!horse foo bar foo`,
		expression: `match("foo")`,
		expected: []string{
			"D0, P[], (!!map)::string: foo\noffset: 0\nlength: 3\ncaptures: []\n",
		},
	},
	{
		description: "Match string, case insensitive",
		document:    `foo bar FOO`,
		expression:  `[match("(?i)foo"; "g")]`,
		expected: []string{
			"D0, P[], (!!seq)::- string: foo\n  offset: 0\n  length: 3\n  captures: []\n- string: FOO\n  offset: 8\n  length: 3\n  captures: []\n",
		},
	},
	{
		description: "Match with global capture group",
		document:    `abc abc`,
		expression:  `[match("(ab)(c)"; "g")]`,
		expected: []string{
			"D0, P[], (!!seq)::- string: abc\n  offset: 0\n  length: 3\n  captures:\n    - string: ab\n      offset: 0\n      length: 2\n    - string: c\n      offset: 2\n      length: 1\n- string: abc\n  offset: 4\n  length: 3\n  captures:\n    - string: ab\n      offset: 4\n      length: 2\n    - string: c\n      offset: 6\n      length: 1\n",
		},
	},
	{
		description: "Match with named capture groups",
		document:    `foo bar foo foo  foo`,
		expression:  `[match("foo (?P<bar123>bar)? foo"; "g")]`,
		expected: []string{
			"D0, P[], (!!seq)::- string: foo bar foo\n  offset: 0\n  length: 11\n  captures:\n    - string: bar\n      offset: 4\n      length: 3\n      name: bar123\n- string: foo  foo\n  offset: 12\n  length: 8\n  captures:\n    - string: null\n      offset: -1\n      length: 0\n      name: bar123\n",
		},
	},
	{
		description: "Capture named groups into a map",
		document:    `xyzzy-14`,
		expression:  `capture("(?P<a>[a-z]+)-(?P<n>[0-9]+)")`,
		expected: []string{
			"D0, P[], (!!map)::a: xyzzy\nn: \"14\"\n",
		},
	},
	{
		skipDoc:    true,
		document:   `!horse xyzzy-14`,
		expression: `capture("(?P<a>[a-z]+)-(?P<n>[0-9]+)")`,
		expected: []string{
			"D0, P[], (!!map)::a: xyzzy\nn: \"14\"\n",
		},
	},
	{
		skipDoc:     true,
		description: "Capture named groups into a map, with null",
		document:    `xyzzy-14`,
		expression:  `capture("(?P<a>[a-z]+)-(?P<n>[0-9]+)(?P<bar123>bar)?")`,
		expected: []string{
			"D0, P[], (!!map)::a: xyzzy\nn: \"14\"\nbar123: null\n",
		},
	},
	{
		description: "Match without global flag",
		document:    `cat cat`,
		expression:  `match("cat")`,
		expected: []string{
			"D0, P[], (!!map)::string: cat\noffset: 0\nlength: 3\ncaptures: []\n",
		},
	},
	{
		description: "Match with global flag",
		document:    `cat cat`,
		expression:  `[match("cat"; "g")]`,
		expected: []string{
			"D0, P[], (!!seq)::- string: cat\n  offset: 0\n  length: 3\n  captures: []\n- string: cat\n  offset: 4\n  length: 3\n  captures: []\n",
		},
	},
	{
		skipDoc:    true,
		document:   `!horse cat cat`,
		expression: `[match("cat"; "g")]`,
		expected: []string{
			"D0, P[], (!!seq)::- string: cat\n  offset: 0\n  length: 3\n  captures: []\n- string: cat\n  offset: 4\n  length: 3\n  captures: []\n",
		},
	},
	{
		skipDoc:     true,
		description: "No match",
		document:    `dog`,
		expression:  `match("cat"; "g")`,
		expected:    []string{},
	},
	{
		skipDoc:     true,
		description: "No match",
		expression:  `"dog" | match("cat", "g")`,
		expected:    []string{},
	},
	{
		skipDoc:     true,
		description: "No match",
		expression:  `"dog" | match("cat")`,
		expected:    []string{},
	},
	{
		description:    "Test using regex",
		subdescription: "Like jq's equivalent, this works like match but only returns true/false instead of full match details",
		document:       `["cat", "dog"]`,
		expression:     `.[] | test("at")`,
		expected: []string{
			"D0, P[0], (!!bool)::true\n",
			"D0, P[1], (!!bool)::false\n",
		},
	},
	{
		skipDoc:    true,
		document:   `[!horse "cat", !cat "dog"]`,
		expression: `.[] | test("at")`,
		expected: []string{
			"D0, P[0], (!!bool)::true\n",
			"D0, P[1], (!!bool)::false\n",
		},
	},
	{
		skipDoc:    true,
		document:   `["cat*", "cat*", "cat"]`,
		expression: `.[] | test("cat\*")`,
		expected: []string{
			"D0, P[0], (!!bool)::true\n",
			"D0, P[1], (!!bool)::true\n",
			"D0, P[2], (!!bool)::false\n",
		},
	},
	{
		description:    "Substitute / Replace string",
		subdescription: "This uses Golang's regex, described [here](https://github.com/google/re2/wiki/Syntax).\nNote the use of `|=` to run in context of the current string value.",
		document:       `a: dogs are great`,
		expression:     `.a |= sub("dogs", "cats")`,
		expected: []string{
			"D0, P[], (!!map)::a: cats are great\n",
		},
	},
	{
		description:    "Substitute / Replace string with regex",
		subdescription: "This uses Golang's regex, described [here](https://github.com/google/re2/wiki/Syntax).\nNote the use of `|=` to run in context of the current string value.",
		document:       "a: cat\nb: heat",
		expression:     `.[] |= sub("(a)", "${1}r")`,
		expected: []string{
			"D0, P[], (!!map)::a: cart\nb: heart\n",
		},
	},
	{
		description:    "Custom types: that are really strings",
		subdescription: "When custom tags are encountered, yq will try to decode the underlying type.",
		document:       "a: !horse cat\nb: !goat heat",
		expression:     `.[] |= sub("(a)", "${1}r")`,
		expected: []string{
			"D0, P[], (!!map)::a: !horse cart\nb: !goat heart\n",
		},
	},
	{
		description: "Split strings",
		document:    `"cat; meow; 1; ; true"`,
		expression:  `split("; ")`,
		expected: []string{
			"D0, P[], (!!seq)::- cat\n- meow\n- \"1\"\n- \"\"\n- \"true\"\n",
		},
	},
	{
		description: "Split strings one match",
		document:    `"word"`,
		expression:  `split("; ")`,
		expected: []string{
			"D0, P[], (!!seq)::- word\n",
		},
	},
	{
		description: "Split splat",
		skipDoc:     true,
		document:    `"word; cat"`,
		expression:  `split("; ")[]`,
		expected: []string{
			"D0, P[0], (!!str)::word\n",
			"D0, P[1], (!!str)::cat\n",
		},
	},
	{
		skipDoc:    true,
		document:   `!horse "word"`,
		expression: `split("; ")`,
		expected: []string{
			"D0, P[], (!!seq)::- word\n",
		},
	},
	{
		skipDoc:    true,
		document:   `""`,
		expression: `split("; ")`,
		expected: []string{
			"D0, P[], (!!seq)::[]\n", // dont actually want this, just not to error
		},
	},
	{
		skipDoc:    true,
		expression: `split("; ")`,
		expected:   []string{},
	},
	{
		description:    "To string",
		subdescription: "Note that you may want to force `yq` to leave scalar values wrapped by passing in `--unwrapScalar=false` or `-r=f`",
		document:       `[1, true, null, ~, cat, {an: object}, [array, 2]]`,
		expression:     ".[] |= to_string",
		expected: []string{
			"D0, P[], (!!seq)::[\"1\", \"true\", \"null\", \"~\", cat, \"{an: object}\", \"[array, 2]\"]\n",
		},
	},
}

func TestStringsOperatorScenarios(t *testing.T) {
	for _, tt := range stringsOperatorScenarios {
		testScenario(t, &tt)
	}
	documentOperatorScenarios(t, "string-operators", stringsOperatorScenarios)
}
