from std/path/simple import SimplePath;
from test/more import *;

let report := {
	store: {
		title: "Before",
		books: [
			{
				author: "Ada",
				role: "admin",
				tags: [ "math", "logic" ],
			},
			{
				author: "Bob",
				role: "reader",
				tags: [ "ops" ],
			},
			{
				author: "Cara",
				role: "reader",
				tags: [ "docs", "qa" ],
			},
		],
	},
};

is( report @ "/store/title", "Before", "outer scope still uses ZPath before SimplePath.use" );

{
	SimplePath.use();

	let focused_title := report @ "store.title" := "After";
	is( focused_title, "After", "@ assignment returns assigned value in SimplePath scope" );
	is( report{store}{title}, "After", "@ assignment updates dict target in SimplePath scope" );

	let focused_author := report @ "store.books[*].author" := "ALPHA";
	is( focused_author, "ALPHA", "@ assignment through [*] returns assigned value" );
	is( report{store}{books}[0]{author}, "ALPHA", "@ assignment updates first wildcard match" );
	is( report{store}{books}[1]{author}, "Bob", "@ assignment leaves later wildcard matches untouched" );
	is( report{store}{books}[2]{author}, "Cara", "@ assignment keeps final wildcard match unchanged" );

	let nested_tag := report @ "store.books[2].tags[1]" := "testing";
	is( nested_tag, "testing", "@ assignment reaches nested array index target" );
	is( report{store}{books}[2]{tags}[1], "testing", "@ assignment mutates nested array index target" );

	let bulk_roles := report @@ "store.books[*].role" := "member";
	is( bulk_roles, "member", "@@ assignment returns assigned value in SimplePath scope" );
	is( report{store}{books}[0]{role}, "member", "@@ assignment updates first role" );
	is( report{store}{books}[1]{role}, "member", "@@ assignment updates second role" );
	is( report{store}{books}[2]{role}, "member", "@@ assignment updates third role" );

	let bulk_tag_heads := report @@ "store.books[*].tags[0]" := "core";
	is( bulk_tag_heads, "core", "@@ assignment updates repeated nested array targets" );
	is( report{store}{books}[0]{tags}[0], "core", "@@ assignment updates first nested array head" );
	is( report{store}{books}[1]{tags}[0], "core", "@@ assignment updates second nested array head" );
	is( report{store}{books}[2]{tags}[0], "core", "@@ assignment updates third nested array head" );

	let bulk_miss := report @@ "store.books[99].role" := "ghost";
	is( bulk_miss, "ghost", "@@ assignment with no matches still returns assigned value" );
	is( report{store}{books}[0]{role}, "member", "@@ no-match leaves first role unchanged" );
	is( report{store}{books}[1]{role}, "member", "@@ no-match leaves second role unchanged" );
	is( report{store}{books}[2]{role}, "member", "@@ no-match leaves third role unchanged" );

	let focused_miss := exception( function () {
		report @ "store.books[99].role" := "ghost";
	} );
	isnt( focused_miss, null, "@ assignment throws when SimplePath selector has no matches" );
	like( focused_miss{message}, /SimplePath assignment found no matches/, "@ no-match exception message is stable in SimplePath scope" );

	function fresh_compound_report () {
		return {
			store: {
				plus: 8,
				minus: 8,
				mul: 4,
				div: 8,
				pow: 2,
				maybe: null,
				text: "AB",
				title: "Read 2026",
				books: [
					{
						plus: 1,
						minus: 5,
						mul: 2,
						div: 8,
						pow: 2,
						maybe: null,
						text: "A",
						name: "Ada 10",
					},
					{
						plus: 2,
						minus: 7,
						mul: 3,
						div: 12,
						pow: 3,
						maybe: null,
						text: "B",
						name: "Bob 20",
					},
				],
			},
		};
	}

	{
		let sample := fresh_compound_report();
		is( sample @ "store.plus" += 2, 10, "SimplePath @ += returns new value" );
		is( sample{store}{plus}, 10, "SimplePath @ += mutates focused target" );
	}

	{
		let sample := fresh_compound_report();
		is( sample @ "store.minus" -= 3, 5, "SimplePath @ -= returns new value" );
		is( sample{store}{minus}, 5, "SimplePath @ -= mutates focused target" );
	}

	{
		let sample := fresh_compound_report();
		is( sample @ "store.mul" *= 3, 12, "SimplePath @ *= returns new value" );
		is( sample{store}{mul}, 12, "SimplePath @ *= mutates focused target" );
	}

	{
		let sample := fresh_compound_report();
		is( sample @ "store.mul" ×= 3, 12, "SimplePath @ ×= returns new value" );
		is( sample{store}{mul}, 12, "SimplePath @ ×= mutates focused target" );
	}

	{
		let sample := fresh_compound_report();
		is( sample @ "store.div" /= 2, 4, "SimplePath @ /= returns new value" );
		is( sample{store}{div}, 4, "SimplePath @ /= mutates focused target" );
	}

	{
		let sample := fresh_compound_report();
		is( sample @ "store.div" ÷= 2, 4, "SimplePath @ ÷= returns new value" );
		is( sample{store}{div}, 4, "SimplePath @ ÷= mutates focused target" );
	}

	{
		let sample := fresh_compound_report();
		is( sample @ "store.pow" **= 3, 8, "SimplePath @ **= returns new value" );
		is( sample{store}{pow}, 8, "SimplePath @ **= mutates focused target" );
	}

	{
		let sample := fresh_compound_report();
		is( sample @ "store.text" _= "!", "AB!", "SimplePath @ _= returns new value" );
		is( sample{store}{text}, "AB!", "SimplePath @ _= mutates focused target" );
	}

	{
		let sample := fresh_compound_report();
		is( sample @ "store.maybe" ?:= 9, 9, "SimplePath @ ?:= returns assigned value when target is null" );
		is( sample{store}{maybe}, 9, "SimplePath @ ?:= mutates focused null target" );
	}

	{
		let sample := fresh_compound_report();
		is( sample @@ "store.books[*].plus" += 2, 2, "SimplePath @@ += returns RHS contract" );
		is( sample{store}{books}[0]{plus}, 3, "SimplePath @@ += mutates first selected target" );
		is( sample{store}{books}[1]{plus}, 4, "SimplePath @@ += mutates later selected target" );
	}

	{
		let sample := fresh_compound_report();
		is( sample @@ "store.books[*].minus" -= 2, 2, "SimplePath @@ -= returns RHS contract" );
		is( sample{store}{books}[0]{minus}, 3, "SimplePath @@ -= mutates first selected target" );
		is( sample{store}{books}[1]{minus}, 5, "SimplePath @@ -= mutates later selected target" );
	}

	{
		let sample := fresh_compound_report();
		is( sample @@ "store.books[*].mul" *= 2, 2, "SimplePath @@ *= returns RHS contract" );
		is( sample{store}{books}[0]{mul}, 4, "SimplePath @@ *= mutates first selected target" );
		is( sample{store}{books}[1]{mul}, 6, "SimplePath @@ *= mutates later selected target" );
	}

	{
		let sample := fresh_compound_report();
		is( sample @@ "store.books[*].mul" ×= 2, 2, "SimplePath @@ ×= returns RHS contract" );
		is( sample{store}{books}[0]{mul}, 4, "SimplePath @@ ×= mutates first selected target" );
		is( sample{store}{books}[1]{mul}, 6, "SimplePath @@ ×= mutates later selected target" );
	}

	{
		let sample := fresh_compound_report();
		is( sample @@ "store.books[*].div" /= 2, 2, "SimplePath @@ /= returns RHS contract" );
		is( sample{store}{books}[0]{div}, 4, "SimplePath @@ /= mutates first selected target" );
		is( sample{store}{books}[1]{div}, 6, "SimplePath @@ /= mutates later selected target" );
	}

	{
		let sample := fresh_compound_report();
		is( sample @@ "store.books[*].div" ÷= 2, 2, "SimplePath @@ ÷= returns RHS contract" );
		is( sample{store}{books}[0]{div}, 4, "SimplePath @@ ÷= mutates first selected target" );
		is( sample{store}{books}[1]{div}, 6, "SimplePath @@ ÷= mutates later selected target" );
	}

	{
		let sample := fresh_compound_report();
		is( sample @@ "store.books[*].pow" **= 2, 2, "SimplePath @@ **= returns RHS contract" );
		is( sample{store}{books}[0]{pow}, 4, "SimplePath @@ **= mutates first selected target" );
		is( sample{store}{books}[1]{pow}, 9, "SimplePath @@ **= mutates later selected target" );
	}

	{
		let sample := fresh_compound_report();
		is( sample @@ "store.books[*].text" _= "!", "!", "SimplePath @@ _= returns RHS contract" );
		is( sample{store}{books}[0]{text}, "A!", "SimplePath @@ _= mutates first selected target" );
		is( sample{store}{books}[1]{text}, "B!", "SimplePath @@ _= mutates later selected target" );
	}

	{
		let sample := fresh_compound_report();
		is( sample @@ "store.books[*].maybe" ?:= 7, 7, "SimplePath @@ ?:= returns RHS contract" );
		is( sample{store}{books}[0]{maybe}, 7, "SimplePath @@ ?:= mutates first selected null target" );
		is( sample{store}{books}[1]{maybe}, 7, "SimplePath @@ ?:= mutates later selected null target" );
	}

	{
		let sample := fresh_compound_report();
		ok( sample @? "store.plus" += 2, "SimplePath @? += returns true on match" );
		is( sample{store}{plus}, 10, "SimplePath @? += mutates matched target" );
	}

	{
		let sample := fresh_compound_report();
		ok( sample @? "store.minus" -= 3, "SimplePath @? -= returns true on match" );
		is( sample{store}{minus}, 5, "SimplePath @? -= mutates matched target" );
	}

	{
		let sample := fresh_compound_report();
		ok( sample @? "store.mul" *= 3, "SimplePath @? *= returns true on match" );
		is( sample{store}{mul}, 12, "SimplePath @? *= mutates matched target" );
	}

	{
		let sample := fresh_compound_report();
		ok( sample @? "store.mul" ×= 3, "SimplePath @? ×= returns true on match" );
		is( sample{store}{mul}, 12, "SimplePath @? ×= mutates matched target" );
	}

	{
		let sample := fresh_compound_report();
		ok( sample @? "store.div" /= 2, "SimplePath @? /= returns true on match" );
		is( sample{store}{div}, 4, "SimplePath @? /= mutates matched target" );
	}

	{
		let sample := fresh_compound_report();
		ok( sample @? "store.div" ÷= 2, "SimplePath @? ÷= returns true on match" );
		is( sample{store}{div}, 4, "SimplePath @? ÷= mutates matched target" );
	}

	{
		let sample := fresh_compound_report();
		ok( sample @? "store.pow" **= 3, "SimplePath @? **= returns true on match" );
		is( sample{store}{pow}, 8, "SimplePath @? **= mutates matched target" );
	}

	{
		let sample := fresh_compound_report();
		ok( sample @? "store.text" _= "!", "SimplePath @? _= returns true on match" );
		is( sample{store}{text}, "AB!", "SimplePath @? _= mutates matched target" );
	}

	{
		let sample := fresh_compound_report();
		ok( sample @? "store.maybe" ?:= 9, "SimplePath @? ?:= returns true on match" );
		is( sample{store}{maybe}, 9, "SimplePath @? ?:= mutates matched target" );
	}

	{
		let sample := fresh_compound_report();
		is(
			sample @ "store.title" ~= /([A-Za-z]+) ([0-9]+)/ -> `${m[1]}:${m[2]}`,
			"Read:2026",
			"SimplePath @ ~= supports captures",
		);
		is( sample{store}{title}, "Read:2026", "SimplePath @ ~= mutates focused target" );
	}

	{
		let sample := fresh_compound_report();
		is(
			sample @@ "store.books[*].name" ~= /([A-Za-z]+) ([0-9]+)/ -> do {
				let who := m[1];
				let digits := m[2];
				who _ ":" _ digits;
			},
			"Bob:20",
			"SimplePath @@ ~= returns last replaced value",
		);
		is( sample{store}{books}[0]{name}, "Ada:10", "SimplePath @@ ~= mutates first selected target" );
		is( sample{store}{books}[1]{name}, "Bob:20", "SimplePath @@ ~= mutates later selected target" );
	}

	{
		let sample := fresh_compound_report();
		ok(
			sample @? "store.title" ~= /([A-Za-z]+) ([0-9]+)/ -> `${m[1]}:${m[2]}`,
			"SimplePath @? ~= returns true on match",
		);
		is( sample{store}{title}, "Read:2026", "SimplePath @? ~= mutates matched target" );
	}

	{
		let sample := fresh_compound_report();
		let miss := exception( function () {
			sample @ "store.missing" += 2;
		} );
		isnt( miss, null, "SimplePath @ += throws on selector miss" );
	}

	{
		let sample := fresh_compound_report();
		is( sample @@ "store.missing" += 2, 2, "SimplePath @@ += no-match still returns RHS" );
		is( sample{store}{plus}, 8, "SimplePath @@ += no-match leaves structure unchanged" );
	}

	{
		let sample := fresh_compound_report();
		is(
			sample @? "store.missing" += 2,
			false,
			"SimplePath @? += returns false on selector miss",
		);
		is( sample{store}{plus}, 8, "SimplePath @? += no-match leaves structure unchanged" );
	}

	{
		let sample := fresh_compound_report();
		let miss := exception( function () {
			sample @ "store.missing" ~= /x/ -> "y";
		} );
		isnt( miss, null, "SimplePath @ ~= throws on selector miss" );
	}

	{
		let sample := fresh_compound_report();
		sample @@ "store.missing" ~= /x/ -> "y";
		is( sample{store}{title}, "Read 2026", "SimplePath @@ ~= no-match leaves structure unchanged" );
	}

	{
		let sample := fresh_compound_report();
		is(
			sample @? "store.missing" ~= /x/ -> "y",
			false,
			"SimplePath @? ~= returns false on selector miss",
		);
		is( sample{store}{title}, "Read 2026", "SimplePath @? ~= no-match leaves structure unchanged" );
	}
}

is( report @ "/store/title", "After", "outer scope falls back to ZPath after SimplePath block" );

done_testing();
