package yqlib

import (
	"testing"
)

var mergeAnchorAssign = `a: &a
  x: OriginalValue
b:
  <<: *a`

var assignOperatorScenarios = []expressionScenario{
	{
		description: "Create yaml file",
		expression:  `.a.b = "cat" | .x = "frog"`,
		expected: []string{
			"D0, P[], ()::a:\n    b: cat\nx: frog\n",
		},
	},
	{
		description: "Create yaml file",
		document:    "a: {b: 3}",
		expression:  `.a |= .`,
		skipDoc:     true,
		expected: []string{
			"D0, P[], (!!map)::a: {b: 3}\n",
		},
	},
	{
		skipDoc:    true,
		document:   "{}",
		expression: `.a |= .b`,
		expected: []string{
			"D0, P[], (!!map)::a: null\n",
		},
	},
	{
		skipDoc:    true,
		document:   mergeAnchorAssign,
		expression: `.c = .b | .a.x = "ModifiedValue" | explode(.)`,
		expected: []string{
			"D0, P[], (!!map)::a:\n    x: ModifiedValue\nb:\n    x: ModifiedValue\nc:\n    x: ModifiedValue\n",
		},
	},
	{
		skipDoc:    true,
		document:   "{}",
		expression: `.a = .b`,
		expected: []string{
			"D0, P[], (!!map)::a: null\n",
		},
	},
	{
		skipDoc:     true,
		description: "self reference",
		document:    "a: cat",
		expression:  `.a = [.a]`,
		expected: []string{
			"D0, P[], (!!map)::a:\n    - cat\n",
		},
	},
	{
		skipDoc:     true,
		description: "change to number when old value is valid number",
		document:    `a: "3"`,
		expression:  `.a = 3`,
		expected: []string{
			"D0, P[], (!!map)::a: 3\n",
		},
	},
	{
		skipDoc:     true,
		description: "change to bool when old value is valid bool",
		document:    `a: "true"`,
		expression:  `.a = true`,
		expected: []string{
			"D0, P[], (!!map)::a: true\n",
		},
	},
	{
		skipDoc:     true,
		description: "update custom tag string, dont clobber style",
		document:    `a: !cat "meow"`,
		expression:  `.a = "woof"`,
		expected: []string{
			"D0, P[], (!!map)::a: !cat \"woof\"\n",
		},
	},
	{
		description: "Update node to be the child value",
		document:    `{a: {b: {g: foof}}}`,
		expression:  `.a |= .b`,
		expected: []string{
			"D0, P[], (!!map)::{a: {g: foof}}\n",
		},
	},
	{
		description: "Double elements in an array",
		document:    `[1,2,3]`,
		expression:  `.[] |= . * 2`,
		expected: []string{
			"D0, P[], (!!seq)::[2, 4, 6]\n",
		},
	},
	{
		description:    "Update node from another file",
		subdescription: "Note this will also work when the second file is a scalar (string/number)",
		document:       `{a: apples}`,
		document2:      "{b: bob}",
		expression:     `select(fileIndex==0).a = select(fileIndex==1) | select(fileIndex==0)`,
		expected: []string{
			"D0, P[], (!!map)::{a: {b: bob}}\n",
		},
	},
	{
		description: "Update node to be the sibling value",
		document:    `{a: {b: child}, b: sibling}`,
		expression:  `.a = .b`,
		expected: []string{
			"D0, P[], (!!map)::{a: sibling, b: sibling}\n",
		},
	},
	{
		description: "Updated multiple paths",
		document:    `{a: fieldA, b: fieldB, c: fieldC}`,
		expression:  `(.a, .c) = "potato"`,
		expected: []string{
			"D0, P[], (!!map)::{a: potato, b: fieldB, c: potato}\n",
		},
	},
	{
		description: "Update string value",
		document:    `{a: {b: apple}}`,
		expression:  `.a.b = "frog"`,
		expected: []string{
			"D0, P[], (!!map)::{a: {b: frog}}\n",
		},
	},
	{
		description:    "Update string value via |=",
		subdescription: "Note there is no difference between `=` and `|=` when the RHS is a scalar",
		document:       `{a: {b: apple}}`,
		expression:     `.a.b |= "frog"`,
		expected: []string{
			"D0, P[], (!!map)::{a: {b: frog}}\n",
		},
	},
	{
		skipDoc:    true,
		document:   `{a: {b: apple}}`,
		expression: `.a.b | (. |= "frog")`,
		expected: []string{
			"D0, P[a b], (!!str)::frog\n",
		},
	},
	{
		skipDoc:    true,
		document:   `{a: {b: apple}}`,
		expression: `.a.b |= 5`,
		expected: []string{
			"D0, P[], (!!map)::{a: {b: 5}}\n",
		},
	},
	{
		skipDoc:    true,
		document:   `{a: {b: apple}}`,
		expression: `.a.b |= 3.142`,
		expected: []string{
			"D0, P[], (!!map)::{a: {b: 3.142}}\n",
		},
	},
	{
		description:    "Update deeply selected results",
		subdescription: "Note that the LHS is wrapped in brackets! This is to ensure we don't first filter out the yaml and then update the snippet.",
		document:       `{a: {b: apple, c: cactus}}`,
		expression:     `(.a[] | select(. == "apple")) = "frog"`,
		expected: []string{
			"D0, P[], (!!map)::{a: {b: frog, c: cactus}}\n",
		},
	},
	{
		skipDoc:    true,
		document:   `{a: {b: apple, c: cactus}}`,
		expression: `(.a.[] | select(. == "apple")) = "frog"`,
		expected: []string{
			"D0, P[], (!!map)::{a: {b: frog, c: cactus}}\n",
		},
	},
	{
		description: "Update array values",
		document:    `[candy, apple, sandy]`,
		expression:  `(.[] | select(. == "*andy")) = "bogs"`,
		expected: []string{
			"D0, P[], (!!seq)::[bogs, apple, bogs]\n",
		},
	},
	{
		description:           "Update empty object",
		dontFormatInputForDoc: true,
		document:              `{}`,
		expression:            `.a.b |= "bogs"`,
		expected: []string{
			"D0, P[], (!!map)::a:\n    b: bogs\n",
		},
	},
	{
		description:           "Update node value that has an anchor",
		subdescription:        "Anchor will remain",
		dontFormatInputForDoc: true,
		document:              `a: &cool cat`,
		expression:            `.a = "dog"`,
		expected: []string{
			"D0, P[], (!!map)::a: &cool dog\n",
		},
	},
	{
		description:           "Update empty object and array",
		dontFormatInputForDoc: true,
		document:              `{}`,
		expression:            `.a.b.[0] |= "bogs"`,
		expected: []string{
			"D0, P[], (!!map)::a:\n    b:\n        - bogs\n",
		},
	},
	{
		skipDoc:    true,
		document:   `{}`,
		expression: `.a.b.[1].c |= "bogs"`,
		expected: []string{
			"D0, P[], (!!map)::a:\n    b:\n        - null\n        - c: bogs\n",
		},
	},
	{
		description: "Custom types are maintained by default",
		document:    "a: !cat meow\nb: !dog woof",
		expression:  `.a = .b`,
		expected: []string{
			"D0, P[], (!!map)::a: !cat woof\nb: !dog woof\n",
		},
	},
	{
		description:    "Custom types: clobber",
		subdescription: "Use the `c` option to clobber custom tags",
		document:       "a: !cat meow\nb: !dog woof",
		expression:     `.a =c .b`,
		expected: []string{
			"D0, P[], (!!map)::a: !dog woof\nb: !dog woof\n",
		},
	},
}

func TestAssignOperatorScenarios(t *testing.T) {
	for _, tt := range assignOperatorScenarios {
		testScenario(t, &tt)
	}
	documentOperatorScenarios(t, "assign-update", assignOperatorScenarios)
}
