<?php declare(strict_types=1);

use Nette\Schema\{Expect, JsonSchema, Processor};
use Tester\Assert;

require __DIR__ . '/../bootstrap.php';


test('without items', function () {
	$schema = Expect::tuple([]);

	Assert::equal([], (new Processor)->process($schema, []));

	checkValidationErrors(function () use ($schema) {
		(new Processor)->process($schema, [1, 2, 3]);
	}, ["Unexpected item '0'.", "Unexpected item '1'.", "Unexpected item '2'."]);

	checkValidationErrors(function () use ($schema) {
		(new Processor)->process($schema, ['key' => 'val']);
	}, ['The item expects to be list, array given.']);

	checkValidationErrors(function () use ($schema) {
		(new Processor)->process($schema, 'one');
	}, ["The item expects to be list, 'one' given."]);

	checkValidationErrors(function () use ($schema) {
		(new Processor)->process($schema, true);
	}, ['The item expects to be list, true given.']);

	checkValidationErrors(function () use ($schema) {
		(new Processor)->process($schema, 123);
	}, ['The item expects to be list, 123 given.']);

	// unlike a structure, an empty block is not a tuple of defaults
	checkValidationErrors(function () use ($schema) {
		(new Processor)->process($schema, null);
	}, ['The item expects to be list, null given.']);

	Assert::null((new Processor)->process(Expect::anyOf($schema, null), null));
});


testException('non-indexed array', function () {
	$schema = Expect::tuple(['a' => Expect::string()]);
}, Nette\InvalidArgumentException::class, 'Tuple shape must be indexed array.');


test('accepts object', function () {
	$schema = Expect::tuple([Expect::string()]);

	Assert::equal([null], (new Processor)->process($schema, []));

	Assert::equal(['foo'], (new Processor)->process($schema, ['foo']));

	checkValidationErrors(function () use ($schema) {
		(new Processor)->process($schema, [1]);
	}, ["The item '0' expects to be string, 1 given."]);

	$schema = Expect::tuple([Expect::string()->before('strrev')]);

	Assert::equal(['oof'], (new Processor)->process($schema, ['foo']));

	Assert::equal(
		['rab'],
		(new Processor)->processMultiple($schema, [['foo'], ['bar']]),
	);
});


test('scalar items', function () {
	$schema = Expect::tuple([
		Expect::string(),
		Expect::int(),
		Expect::bool(),
		Expect::scalar(),
		Expect::type('string'),
		Expect::type('int'),
		Expect::string('abc'),
		Expect::string(123),
		Expect::type('string')->default(123),
		Expect::anyOf(1, 2),
	]);

	Assert::equal(
		[null, null, null, null, null, null, 'abc', 123, 123, null],
		(new Processor)->process($schema, []),
	);
});


testException(
	'default value must be readonly',
	fn() => Expect::tuple([])->default([]),
	Nette\InvalidStateException::class,
);


test('a missing tuple is null, like a missing scalar', function () {
	$schema = Expect::structure(['t' => Expect::tuple([Expect::int(1)])])->castTo('array');
	Assert::same(['t' => null], (new Processor)->process($schema, []));

	checkValidationErrors(function () {
		(new Processor)->process(Expect::structure(['t' => Expect::tuple([Expect::int(1)])->required()]), []);
	}, ["The mandatory item 't' is missing."]);

	Assert::null((new Processor)->process(Expect::tuple([Expect::int()])->nullable(), null));
});




test('with items', function () {
	$schema = Expect::tuple([
		Expect::string(),
		Expect::arrayOf('string'),
	]);

	$processor = new Processor;

	Assert::equal(
		[null, []],
		$processor->process($schema, []),
	);

	// no layer at all is null, which a tuple refuses just as a scalar type does
	checkValidationErrors(function () use ($processor, $schema) {
		$processor->processMultiple($schema, []);
	}, ['The item expects to be list, null given.']);

	checkValidationErrors(function () use ($processor, $schema) {
		$processor->process($schema, [1, 2, 3]);
	}, [
		"The item '0' expects to be string, 1 given.",
		"The item '1' expects to be array, 2 given.",
		"Unexpected item '2'.",
	]);

	Assert::equal(
		['newval3', []],
		$processor->process($schema, ['newval3']),
	);

	Assert::equal(
		['newval4', []],
		$processor->processMultiple($schema, [['newval2', 'newval3'], ['newval4']]),
	);
});


test('rest elements via otherItems()', function () {
	$schema = Expect::tuple([Expect::string()])->otherItems('int');

	Assert::equal(['a', 1, 2], (new Processor)->process($schema, ['a', 1, 2]));

	checkValidationErrors(function () use ($schema) {
		(new Processor)->process($schema, ['a', 'b']);
	}, ["The item '1' expects to be int, 'b' given."]);
});


test('the input is a list, so the output is one too', function () {
	checkValidationErrors(function () {
		(new Processor)->process(Expect::tuple([Expect::string()])->otherItems('int'), ['a', 'oops' => 3]);
	}, ['The item expects to be list, array given.']);

	checkValidationErrors(function () {
		(new Processor)->process(Expect::tuple([Expect::string(), Expect::int()]), [1 => 3, 0 => 'a']);
	}, ['The item expects to be list, array given.']);

	Assert::same(['a', 5], (new Processor)->process(Expect::tuple([Expect::string(), Expect::int(5)]), ['a']));
});


test('extend() appends positions', function () {
	$schema = Expect::tuple([Expect::string()])->extend([Expect::int()]);
	Assert::type(Nette\Schema\Elements\TupleType::class, $schema);
	Assert::same(['a', 1], (new Processor)->process($schema, ['a', 1]));

	$schema = Expect::tuple([Expect::string()])->extend(Expect::tuple([Expect::int(), Expect::bool()]));
	Assert::same(['a', 1, true], (new Processor)->process($schema, ['a', 1, true]));

	Assert::exception(
		fn() => Expect::tuple([Expect::string()])->extend(['a' => Expect::int()]),
		Nette\InvalidArgumentException::class,
		'Tuple shape must be indexed array.',
	);
});


test('describe() and JSON Schema export', function () {
	$schema = Expect::tuple([Expect::string(), Expect::int()->nullable()]);

	Assert::same(Nette\Schema\Kind::Tuple, $schema->describe()['kind']);

	Assert::same(
		[
			'type' => 'array',
			'prefixItems' => [
				['type' => 'string'],
				['type' => ['integer', 'null']],
			],
			'items' => false,
		],
		JsonSchema::export($schema),
	);

	Assert::same(
		['type' => 'array', 'prefixItems' => [['type' => 'string']], 'items' => ['type' => 'integer']],
		JsonSchema::export(Expect::tuple([Expect::string()])->otherItems('int')),
	);
});


test('getShape', function () {
	Assert::equal(
		[Expect::int(), Expect::string()],
		Expect::tuple([Expect::int(), Expect::string()])->getShape(),
	);
});
