<?php declare(strict_types=1);

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

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


Assert::with(Structure::class, function () {
	$schema = Expect::from(new stdClass);

	Assert::type(Structure::class, $schema);
	Assert::same([], $schema->items);
	Assert::type(stdClass::class, (new Processor)->process($schema, []));
});


Assert::with(Structure::class, function () {
	$schema = Expect::from($obj = new class {
		/** @var string */
		public $dsn = 'mysql';

		/** @var string|null */
		public $user;

		/** @var ?string */
		public $password;

		/** @var string[] */
		public $options = [1];

		/** @var bool */
		public $debugger = true;
		public $mixed;

		/** @var array|null */
		public $arr;

		/** @var string */
		public $required;
	});

	Assert::type(Structure::class, $schema);
	Assert::equal([
		'dsn' => Expect::string('mysql'),
		'user' => Expect::type('string|null'),
		'password' => Expect::type('?string'),
		'options' => Expect::type('string[]')->default([1]),
		'debugger' => Expect::bool(true),
		'mixed' => Expect::mixed(),
		'arr' => Expect::type('array|null')->default(null),
		'required' => Expect::type('string')->required(),
	], $schema->items);
	Assert::type($obj, (new Processor)->process($schema, ['required' => '']));
});


Assert::exception(function () {
	Expect::from(new class {
		/** @var Unknown */
		public $unknown;
	});
}, Nette\NotImplementedException::class, 'Anonymous classes are not supported.');


Assert::with(Structure::class, function () { // overwritten item
	$schema = Expect::from(new class {
		/** @var string */
		public $dsn = 'mysql';

		/** @var string|null */
		public $user;
	}, ['dsn' => Expect::int(123)]);

	Assert::equal([
		'dsn' => Expect::int(123),
		'user' => Expect::type('string|null'),
	], $schema->items);
});


Assert::with(Structure::class, function () { // nested object
	$obj = new class {
		/** @var object */
		public $inner;
	};
	$obj->inner = new class {
		/** @var string */
		public $name;
	};

	$schema = Expect::from($obj);

	Assert::equal([
		'inner' => Expect::structure([
			'name' => Expect::string()->required(),
		])->castTo($obj->inner::class),
	], $schema->items);
});


class PrivateStateDto
{
	public static int $instances = 0;
	public int $a = 1;
	protected int $counter = 0;
	private array $cache = [];
}

$dto = (new Processor)->process(Expect::from(new PrivateStateDto), ['a' => 2]);
Assert::type(PrivateStateDto::class, $dto);
Assert::same(2, $dto->a);
Assert::same(['a'], array_keys(Expect::from(new PrivateStateDto)->getShape()));


class VariadicDto
{
	public function __construct(string ...$args)
	{
	}
}

Assert::exception(
	fn() => Expect::from(new VariadicDto),
	Nette\InvalidArgumentException::class,
	'Variadic parameter $args of VariadicDto::__construct() is not supported by Expect::from().',
);
