from std/internals import setprop;
from std/path/z import ZPath;
from test/more import *;

setprop( "paths", ZPath );

function fresh_report () {
	return {
		meta: {
			count: 4,
			title: "Read 2026",
		},
		users: [
			{
				age: 1,
				name: "Ada",
			},
			{
				age: 2,
				name: "Bob",
			},
		],
	};
}

{
	let sample := fresh_report();
	is( ( sample @ "/meta/count" )++, 4, "@ postfix ++ returns old scalar" );
	is( sample{meta}{count}, 5, "@ postfix ++ mutates focused target" );
}

{
	let sample := fresh_report();
	is( ++( sample @ "/meta/count" ), 5, "@ prefix ++ returns new scalar" );
	is( sample{meta}{count}, 5, "@ prefix ++ mutates focused target" );
}

{
	let sample := fresh_report();
	is( ( sample @ "/meta/count" )--, 4, "@ postfix -- returns old scalar" );
	is( sample{meta}{count}, 3, "@ postfix -- mutates focused target" );
}

{
	let sample := fresh_report();
	is( --( sample @ "/meta/count" ), 3, "@ prefix -- returns new scalar" );
	is( sample{meta}{count}, 3, "@ prefix -- mutates focused target" );
}

{
	let sample := fresh_report();
	is(
		( sample @@ "/users/*/age" )++,
		[ 1, 2 ],
		"@@ postfix ++ returns array of old values",
	);
	is( sample{users}[0]{age}, 2, "@@ postfix ++ mutates first target" );
	is( sample{users}[1]{age}, 3, "@@ postfix ++ mutates later target" );
}

{
	let sample := fresh_report();
	is(
		++( sample @@ "/users/*/age" ),
		[ 2, 3 ],
		"@@ prefix ++ returns array of new values",
	);
	is( sample{users}[0]{age}, 2, "@@ prefix ++ mutates first target" );
	is( sample{users}[1]{age}, 3, "@@ prefix ++ mutates later target" );
}

{
	let sample := fresh_report();
	is(
		( sample @@ "/users/*/age" )--,
		[ 1, 2 ],
		"@@ postfix -- returns array of old values",
	);
	is( sample{users}[0]{age}, 0, "@@ postfix -- mutates first target" );
	is( sample{users}[1]{age}, 1, "@@ postfix -- mutates later target" );
}

{
	let sample := fresh_report();
	is(
		--( sample @@ "/users/*/age" ),
		[ 0, 1 ],
		"@@ prefix -- returns array of new values",
	);
	is( sample{users}[0]{age}, 0, "@@ prefix -- mutates first target" );
	is( sample{users}[1]{age}, 1, "@@ prefix -- mutates later target" );
}

{
	let sample := fresh_report();
	ok( ( sample @? "/meta/count" )++, "@? postfix ++ returns true on match" );
	is( sample{meta}{count}, 5, "@? postfix ++ mutates matched target" );
}

{
	let sample := fresh_report();
	ok( ++( sample @? "/meta/count" ), "@? prefix ++ returns true on match" );
	is( sample{meta}{count}, 5, "@? prefix ++ mutates matched target" );
}

{
	let sample := fresh_report();
	ok( ( sample @? "/meta/count" )--, "@? postfix -- returns true on match" );
	is( sample{meta}{count}, 3, "@? postfix -- mutates matched target" );
}

{
	let sample := fresh_report();
	ok( --( sample @? "/meta/count" ), "@? prefix -- returns true on match" );
	is( sample{meta}{count}, 3, "@? prefix -- mutates matched target" );
}

{
	let sample := fresh_report();
	let miss := exception( function () {
		++( sample @ "/meta/missing" );
	} );
	isnt( miss, null, "@ update throws on selector miss" );
}

{
	let sample := fresh_report();
	is(
		++( sample @@ "/users/*/missing" ),
		[],
		"@@ prefix ++ returns empty array on selector miss",
	);
}

{
	let sample := fresh_report();
	ok( not ++( sample @? "/meta/missing" ), "@? prefix ++ returns false on selector miss" );
}

{
	let sample := fresh_report();
	let title_ref := \( sample @ "/meta/title" );
	is( title_ref(), "Read 2026", "\\ @ returns one ref getter" );
	is( title_ref("Done"), "Done", "\\ @ ref setter returns assigned value" );
	is( sample{meta}{title}, "Done", "\\ @ ref setter mutates target" );
}

{
	let sample := fresh_report();
	let age_refs := \( sample @@ "/users/*/age" );
	is( age_refs.length(), 2, "\\ @@ returns one ref per selected target" );
	is( age_refs[0](), 1, "\\ @@ refs follow traversal order" );
	is( age_refs[1]( 9 ), 9, "\\ @@ refs remain assignable" );
	is( sample{users}[1]{age}, 9, "\\ @@ ref setter mutates selected target" );
}

{
	let sample := fresh_report();
	let maybe_ref := \( sample @? "/meta/title" );
	isnt( maybe_ref, null, "\\ @? returns ref on match" );
	is( maybe_ref(), "Read 2026", "\\ @? returned ref is assignable" );
}

{
	let sample := fresh_report();
	let miss_refs := \( sample @@ "/users/*/missing" );
	is( miss_refs.length(), 0, "\\ @@ returns [] on selector miss" );
}

{
	let sample := fresh_report();
	let maybe_ref := \( sample @? "/meta/missing" );
	is( maybe_ref, null, "\\ @? returns null on selector miss" );
}

{
	let sample := fresh_report();
	let miss := exception( function () {
		\( sample @ "/meta/missing" );
	} );
	isnt( miss, null, "\\ @ throws on selector miss" );
}

done_testing();
