<?php declare(strict_types=1);

/**
 * Test: Nette\Utils\Finder mask tests.
 */

use Nette\Utils\Finder;
use Tester\Assert;


require __DIR__ . '/../bootstrap.php';


function export($iterator)
{
	$arr = [];
	foreach ($iterator as $key => $value) {
		$arr[] = strtr($key, '\\', '/');
	}

	sort($arr);
	return $arr;
}


test('multiple file masks find matching files in a directory', function () {
	$finder = Finder::findFiles('*.txt', '*.gif')->from('fixtures.finder');
	Assert::same([
		'fixtures.finder/file.txt',
		'fixtures.finder/images/logo.gif',
		'fixtures.finder/subdir/file.txt',
		'fixtures.finder/subdir/subdir2/file.txt',
	], export($finder));
});


test('array of file masks returns expected matching files', function () {
	$finder = Finder::findFiles(['*.txt', '*.gif'])->from('fixtures.finder');
	Assert::same([
		'fixtures.finder/file.txt',
		'fixtures.finder/images/logo.gif',
		'fixtures.finder/subdir/file.txt',
		'fixtures.finder/subdir/subdir2/file.txt',
	], export($finder));
});


test('multiple masks in a subdirectory return files and directories', function () {
	$finder = Finder::findFiles('*.txt', '*')->in('fixtures.finder/subdir');
	Assert::same([
		'fixtures.finder/subdir/file.txt',
		'fixtures.finder/subdir/readme',
	], export($finder));
});


test('mask with dot character excludes files without extension', function () {
	$finder = Finder::findFiles('*.*')->in('fixtures.finder/subdir');
	Assert::same([
		'fixtures.finder/subdir/file.txt',
	], export($finder));
});


test('excluding subdirectories by pattern filters out unwanted files', function () {
	$finder = Finder::findFiles('*')->exclude('*i*/*')->from('fixtures.finder');
	Assert::same([
		'fixtures.finder/file.txt',
	], export($finder));
});


test('nested wildcard pattern finds files in deeper subdirectories', function () {
	$finder = Finder::findFiles('*/*2/*')->from('fixtures.finder');
	Assert::same([
		'fixtures.finder/subdir/subdir2/file.txt',
	], export($finder));
});


test('excluding files by pattern in subdirectory returns remaining entries', function () {
	$finder = Finder::findFiles('*')->exclude('*i*')->in('fixtures.finder/subdir');
	Assert::same([
		'fixtures.finder/subdir/readme',
	], export($finder));
});


test('excluding nested patterns in base directory filters correctly', function () {
	$finder = Finder::findFiles('*')->exclude('*i*/*')->from('fixtures.finder');
	Assert::same([
		'fixtures.finder/file.txt',
	], export($finder));
});


test('complex mask with character classes matches the expected file', function () {
	$finder = Finder::findFiles('*2*/fi??.*')->from('fixtures.finder');
	Assert::same([
		'fixtures.finder/subdir/subdir2/file.txt',
	], export($finder));
});


test('masks with character classes and escaped brackets match files', function () {
	$finder = Finder::findFiles('*[efd][a-z][!a-r]*')->from('fixtures.finder');
	Assert::same([
		'fixtures.finder/images/logo.gif',
	], export($finder));

	$finder = Finder::findFiles('[[]x[]]/fil[e].*')->in('fixtures.finder2');
	Assert::same([
		'fixtures.finder2/[x]/file.txt',
	], export($finder));
});


test('bracket patterns match directory names correctly', function () {
	$finder = Finder::findFiles('[x]/fil[e].*')->in('fixtures.finder2');
	Assert::same([
		'fixtures.finder2/x/file.txt',
	], export($finder));

	$finder = Finder::findFiles('[x]/fil[e].*')->from('fixtures.finder2');
	Assert::same([
		'fixtures.finder2/x/file.txt',
	], export($finder));
});


test('wildcard with bracketed directory pattern matches files', function () {
	$finder = Finder::findFiles('*')->in('fixtures.finder*/[x]');
	Assert::same([
		'fixtures.finder2/[x]/file.txt',
	], export($finder));

	$finder = Finder::findFiles('*')->from('fixtures.finder*/[x]');
	Assert::same([
		'fixtures.finder2/[x]/file.txt',
	], export($finder));
});


test('trailing slash in exclude() mask excludes the directory and its contents', function () {
	$finder = Finder::find('*')->exclude('subdir/')->from('fixtures.finder');
	Assert::same([
		'fixtures.finder/file.txt',
		'fixtures.finder/images',
		'fixtures.finder/images/logo.gif',
	], export($finder));
});


test('trailing /* in exclude() mask excludes contents but keeps the directory', function () {
	$finder = Finder::find('*')->exclude('subdir/*')->from('fixtures.finder');
	Assert::same([
		'fixtures.finder/file.txt',
		'fixtures.finder/images',
		'fixtures.finder/images/logo.gif',
		'fixtures.finder/subdir',
	], export($finder));
});


test('leading **/ in mask drives recursion with both from() and in()', function () {
	$finder = Finder::findFiles('**/f*')->from('fixtures.finder');
	Assert::same([
		'fixtures.finder/file.txt',
		'fixtures.finder/subdir/file.txt',
		'fixtures.finder/subdir/subdir2/file.txt',
	], export($finder));

	$finder = Finder::findFiles('**/f*')->in('fixtures.finder');
	Assert::same([
		'fixtures.finder/file.txt',
		'fixtures.finder/subdir/file.txt',
		'fixtures.finder/subdir/subdir2/file.txt',
	], export($finder));
});


test('trailing ** in mask searches recursively (globstar short form)', function () {
	$finder = Finder::findFiles('subdir/**')->in('fixtures.finder');
	Assert::same([
		'fixtures.finder/subdir/file.txt',
		'fixtures.finder/subdir/readme',
		'fixtures.finder/subdir/subdir2/file.txt',
	], export($finder));

	$finder = Finder::findFiles('subdir/*')->in('fixtures.finder');
	Assert::same([
		'fixtures.finder/subdir/file.txt',
		'fixtures.finder/subdir/readme',
	], export($finder));
});


test('**.ext is a shortcut for **/*.ext', function () {
	$finder = Finder::findFiles('**.txt')->in('fixtures.finder');
	Assert::same([
		'fixtures.finder/file.txt',
		'fixtures.finder/subdir/file.txt',
		'fixtures.finder/subdir/subdir2/file.txt',
	], export($finder));
});


test('trailing slash in mask means directories only', function () {
	$finder = Finder::find('s*/')->from('fixtures.finder');
	Assert::same([
		'fixtures.finder/subdir',
		'fixtures.finder/subdir/subdir2',
	], export($finder));

	$finder = Finder::find('subdir/**/')->in('fixtures.finder');
	Assert::same([
		'fixtures.finder/subdir/subdir2',
	], export($finder));

	Assert::exception(
		fn() => Finder::findFiles('subdir/'),
		Nette\InvalidArgumentException::class,
		"Invalid mask 'subdir/'",
	);
});


test('** bridges repeated path segments', function () {
	$finder = Finder::findDirectories('a/**/b/c')->in('fixtures.finder4');
	Assert::same([
		'fixtures.finder4/a/b/c',
		'fixtures.finder4/a/b/c/a/b/c',
	], export($finder));

	$finder = Finder::findFiles('**.txt')->from('fixtures.finder4');
	Assert::same([
		'fixtures.finder4/a/b/c/a/b/c/d.txt',
	], export($finder));
});


test('relative path masks with "./" prefix work correctly', function () {
	$finder = Finder::findFiles('./f*')->from('fixtures.finder');
	Assert::same([
		'fixtures.finder/file.txt',
	], export($finder));

	$finder = Finder::findFiles('./*/f*')->from('fixtures.finder');
	Assert::same([
		'fixtures.finder/subdir/file.txt',
	], export($finder));

	$finder = Finder::findFiles('./f*')->in('fixtures.finder');
	Assert::same([
		'fixtures.finder/./file.txt',
	], export($finder));
});


test('parent directory references are handled differently in from() versus in()', function () {
	// not supported
	$finder = Finder::findFiles('../f*')->from('fixtures.finder/subdir');
	Assert::same([], export($finder));

	$finder = Finder::findFiles('../f*')->in('fixtures.finder/subdir');
	Assert::same([
		'fixtures.finder/subdir/../file.txt',
	], export($finder));
});


test('combined relative and recursive wildcards match nested files', function () {
	$finder = Finder::findFiles('./**/f*')->from('fixtures.finder');
	Assert::same([
		'fixtures.finder/file.txt',
		'fixtures.finder/subdir/file.txt',
		'fixtures.finder/subdir/subdir2/file.txt',
	], export($finder));
});


test('finder returns directories matching the given pattern', function () {
	$finder = Finder::find('s*/**')->from('fixtures.finder');
	Assert::same([
		'fixtures.finder/subdir/file.txt',
		'fixtures.finder/subdir/readme',
		'fixtures.finder/subdir/subdir2',
		'fixtures.finder/subdir/subdir2/file.txt',
	], export($finder));
});


test('glob pattern for directory names returns different file sets', function () {
	$finder = Finder::findFiles('f*')->in('*.finder');
	Assert::same([
		'fixtures.finder/file.txt',
	], export($finder));

	$finder = Finder::findFiles('f*')->from('*.finder');
	Assert::same([
		'fixtures.finder/file.txt',
		'fixtures.finder/subdir/file.txt',
		'fixtures.finder/subdir/subdir2/file.txt',
	], export($finder));
});


test('recursive glob patterns yield varying results with in() and from()', function () {
	$finder = Finder::findFiles('f*')->in('**/fixtures.finder');
	Assert::same([
		'fixtures.finder/file.txt',
	], export($finder));

	$finder = Finder::findFiles('f*')->from('**/fixtures.finder');
	Assert::same([
		'fixtures.finder/file.txt',
		'fixtures.finder/subdir/file.txt',
		'fixtures.finder/subdir/subdir2/file.txt',
	], export($finder));
});
