// SPDX-License-Identifier: LGPL-3.0-or-later
import assert from 'node:assert/strict';
import { join } from 'node:path/posix';
import { suite, test } from 'node:test';
import { fs } from '../common.js';

const testDir = 'test-dir';
const testFiles = ['file1.txt', 'file2.txt', 'file3.txt'];
const testDirectories = ['subdir1', 'subdir2'];

fs.mkdirSync(testDir);
for (const file of testFiles) {
	fs.writeFileSync(`${testDir}/${file}`, 'Sample content');
}
for (const dir of testDirectories) {
	fs.mkdirSync(`${testDir}/${dir}`);
	for (const file of ['file4.txt', 'file5.txt']) {
		fs.writeFileSync(`${testDir}/${dir}/${file}`, 'Sample content');
	}
}

suite('Directories', () => {
	test('mkdir', async () => {
		await fs.promises.mkdir('/one', 0o755);
		assert(await fs.promises.exists('/one'));
		await assert.rejects(fs.promises.mkdir('/one', 0o755), { code: 'EEXIST' });
	});

	test('mkdirSync', async () => await fs.promises.mkdir('/two', 0o000));

	test('mkdir, nested', async () => {
		await assert.rejects(fs.promises.mkdir('/nested/dir'), { code: 'ENOENT', path: '/nested/dir' });
		assert(!(await fs.promises.exists('/nested/dir')));
	});

	test('mkdir, recursive', async () => {
		assert.equal(await fs.promises.mkdir('/recursiveP/A/B', { recursive: true, mode: 0o755 }), '/recursiveP');
		assert.equal(await fs.promises.mkdir('/recursiveP/A/B/C/D', { recursive: true, mode: 0o777 }), '/recursiveP/A/B/C');
		assert.equal(await fs.promises.mkdir('/recursiveP/A/B/C/D', { recursive: true, mode: 0o700 }), undefined);

		assert.equal((await fs.promises.stat('/recursiveP')).mode, fs.constants.S_IFDIR | 0o755);
		assert.equal((await fs.promises.stat('/recursiveP/A')).mode, fs.constants.S_IFDIR | 0o755);
		assert.equal((await fs.promises.stat('/recursiveP/A/B')).mode, fs.constants.S_IFDIR | 0o755);
		assert.equal((await fs.promises.stat('/recursiveP/A/B/C')).mode, fs.constants.S_IFDIR | 0o777);
		assert.equal((await fs.promises.stat('/recursiveP/A/B/C/D')).mode, fs.constants.S_IFDIR | 0o777);
	});

	test('mkdirSync, recursive', () => {
		assert.equal(fs.mkdirSync('/recursiveS/A/B', { recursive: true, mode: 0o755 }), '/recursiveS');
		assert.equal(fs.mkdirSync('/recursiveS/A/B/C/D', { recursive: true, mode: 0o777 }), '/recursiveS/A/B/C');
		assert.equal(fs.mkdirSync('/recursiveS/A/B/C/D', { recursive: true, mode: 0o700 }), undefined);

		assert.equal(fs.statSync('/recursiveS').mode, fs.constants.S_IFDIR | 0o755);
		assert.equal(fs.statSync('/recursiveS/A').mode, fs.constants.S_IFDIR | 0o755);
		assert.equal(fs.statSync('/recursiveS/A/B').mode, fs.constants.S_IFDIR | 0o755);
		assert.equal(fs.statSync('/recursiveS/A/B/C').mode, fs.constants.S_IFDIR | 0o777);
		assert.equal(fs.statSync('/recursiveS/A/B/C/D').mode, fs.constants.S_IFDIR | 0o777);
	});

	test('rmdir (non-empty)', async () => {
		await fs.promises.mkdir('/rmdirTest');
		await fs.promises.mkdir('/rmdirTest/rmdirTest2');

		await assert.rejects(fs.promises.rmdir('/rmdirTest'), { code: 'ENOTEMPTY' });
	});

	test('readdirSync on file', () => {
		assert.throws(() => fs.readdirSync('a.js'), { code: 'ENOTDIR' });
	});

	test('readdir on file', async () => {
		await assert.rejects(fs.promises.readdir('a.js'), { code: 'ENOTDIR' });
	});

	test('readdirSync on non-existent directory', () => {
		assert.throws(() => fs.readdirSync('/does/not/exist'), { code: 'ENOENT' });
	});

	test('readdir on non-existent directory', async () => {
		await assert.rejects(fs.promises.readdir('/does/not/exist'), { code: 'ENOENT' });
	});

	test('rm recursively', async () => {
		await fs.promises.mkdir('/rmDirRecursively');
		await fs.promises.mkdir('/rmDirRecursively/rmDirNested');
		await fs.promises.writeFile('/rmDirRecursively/rmDirNested/test.txt', 'hello world!');

		await fs.promises.rm('/rmDirRecursively', { recursive: true });
	});

	test('rmSync recursively', () => {
		fs.mkdirSync('/rmDirRecursively');
		fs.mkdirSync('/rmDirRecursively/rmDirNested');
		fs.writeFileSync('/rmDirRecursively/rmDirNested/test.txt', 'hello world!');

		fs.rmSync('/rmDirRecursively', { recursive: true });
	});

	test('readdir returns files and directories', async () => {
		const dirents = await fs.promises.readdir(testDir, { withFileTypes: true });
		const files = dirents.filter(dirent => dirent.isFile()).map(dirent => dirent.name);
		const dirs = dirents.filter(dirent => dirent.isDirectory()).map(dirent => dirent.name);

		assert(testFiles.every(file => files.includes(file)));
		assert(testDirectories.every(dir => dirs.includes(dir)));
	});

	test('readdirSync returns files and directories', () => {
		const dirents = fs.readdirSync(testDir, { withFileTypes: true });
		const files = dirents.filter(dirent => dirent.isFile()).map(dirent => dirent.name);
		const dirs = dirents.filter(dirent => dirent.isDirectory()).map(dirent => dirent.name);

		assert(testFiles.every(file => files.includes(file)));
		assert(testDirectories.every(dir => dirs.includes(dir)));
	});

	test('readdir returns Dirent objects', async () => {
		const dirents = await fs.promises.readdir(testDir, { withFileTypes: true });
		assert(dirents[0] instanceof fs.Dirent);
	});

	test('readdirSync returns Dirent objects', () => {
		const dirents = fs.readdirSync(testDir, { withFileTypes: true });
		assert(dirents[0] instanceof fs.Dirent);
	});

	test('readdir works without withFileTypes option', async () => {
		const files = await fs.promises.readdir(testDir);
		assert(testFiles.every(entry => files.includes(entry)));
		assert(testDirectories.every(entry => files.includes(entry)));
	});

	test('readdirSync works without withFileTypes option', () => {
		const files = fs.readdirSync(testDir);
		assert(testFiles.every(entry => files.includes(entry)));
		assert(testDirectories.every(entry => files.includes(entry)));
	});

	test('readdir returns files recursively', async () => {
		const entries = await fs.promises.readdir(testDir, { recursive: true });
		assert(entries.includes('file1.txt'));
		assert(entries.includes('subdir1/file4.txt'));
		assert(entries.includes('subdir2/file5.txt'));
	});

	test('readdir returns Dirent recursively', async () => {
		const entries = await fs.promises.readdir(testDir, { recursive: true, withFileTypes: true });
		entries.sort((a, b) => join(a.parentPath, a.name).localeCompare(join(b.parentPath, b.name)));
		const values = entries.map(entry => [entry.parentPath, entry.name]);

		assert.deepEqual(values[0], ['.', 'file1.txt']);
		assert.deepEqual(values[4], ['subdir1', 'file4.txt']);
		assert.deepEqual(values[8], ['subdir2', 'file5.txt']);
	});

	test('readdirSync returns files recursively', () => {
		const entries = fs.readdirSync(testDir, { recursive: true }).sort();
		assert.equal(entries[0], 'file1.txt');
		assert.equal(entries[4], 'subdir1/file4.txt');
		assert.equal(entries[8], 'subdir2/file5.txt');
	});

	test('Cyrillic file names', () => {
		fs.writeFileSync('/мой-файл.txt', 'HELLO!', 'utf-8');
		assert(fs.readdirSync('/').includes('мой-файл.txt'));
	});
});
