import path from 'node:path';
import { testSuite, expect } from 'manten';
import { createFixture } from 'fs-fixture';
import { createTsconfigJson, createPackageJson } from '../../../../utils/fixture-helpers.js';
import { getTscTsconfig } from '../../../../utils/typescript-helpers.js';
import { parseTsconfig } from '#get-tsconfig';

export default testSuite('relative path', ({ test }) => {
	test('extensionless file', async () => {
		await using fixture = await createFixture({
			asdf: createTsconfigJson({
				compilerOptions: {
					jsx: 'react',
					allowJs: true,
				},
			}),
			'tsconfig.json': createTsconfigJson({
				extends: './asdf',
				compilerOptions: {
					strict: true,
				},
			}),
			'file.ts': '',
		});

		const expectedTsconfig = await getTscTsconfig(fixture.path);
		delete expectedTsconfig.files;

		const tsconfig = parseTsconfig(fixture.getPath('tsconfig.json'));

		expect(expectedTsconfig).toStrictEqual(tsconfig);
	});

	test('prefers exact match (extensionless file)', async () => {
		await using fixture = await createFixture({
			asdf: createTsconfigJson({
				compilerOptions: {
					jsx: 'react',
					allowJs: true,
				},
			}),
			'asdf.json': createTsconfigJson({
				compilerOptions: {
					jsx: 'react-native',
					allowJs: true,
				},
			}),
			'tsconfig.json': createTsconfigJson({
				extends: './asdf',
				compilerOptions: {
					strict: true,
				},
			}),
			'file.ts': '',
		});

		const expectedTsconfig = await getTscTsconfig(fixture.path);
		delete expectedTsconfig.files;

		const tsconfig = parseTsconfig(fixture.getPath('tsconfig.json'));

		expect(expectedTsconfig).toStrictEqual(tsconfig);
	});

	test('arbitrary extension', async () => {
		await using fixture = await createFixture({
			'asdf.ts': createTsconfigJson({
				compilerOptions: {
					jsx: 'react',
					allowJs: true,
				},
			}),
			'tsconfig.json': createTsconfigJson({
				extends: './asdf.ts',
				compilerOptions: {
					strict: true,
				},
			}),
			'file.ts': '',
		});

		const expectedTsconfig = await getTscTsconfig(fixture.path);
		delete expectedTsconfig.files;

		const tsconfig = parseTsconfig(fixture.getPath('tsconfig.json'));

		expect(expectedTsconfig).toStrictEqual(tsconfig);
	});

	test('parent directory', async () => {
		await using fixture = await createFixture({
			'tsconfig.json': createTsconfigJson({
				compilerOptions: {
					jsx: 'react',
					allowJs: true,
				},
			}),
			tests: {
				'tsconfig.json': createTsconfigJson({
					extends: '..',
					compilerOptions: {
						strict: true,
					},
				}),
				'file.ts': '',
			},
		});

		const testDirectory = fixture.getPath('tests/');
		const expectedTsconfig = await getTscTsconfig(testDirectory);
		delete expectedTsconfig.files;

		const tsconfig = parseTsconfig(path.join(testDirectory, 'tsconfig.json'));

		expect(expectedTsconfig).toStrictEqual(tsconfig);
	});

	test('shoud not resolve directory', async () => {
		await using fixture = await createFixture({
			'directory/tsconfig.json': createTsconfigJson({
				compilerOptions: {
					jsx: 'react',
				},
			}),
			'tsconfig.json': createTsconfigJson({
				extends: './directory',
			}),
		});

		expect(
			() => parseTsconfig(fixture.getPath('tsconfig.json')),
		).toThrow('File \'./directory\' not found.');
	});

	test('shoud not resolve directory even with package.json#tsconfig', async () => {
		await using fixture = await createFixture({
			directory: {
				'package.json': createPackageJson({
					tsconfig: './tsconfig.json',
				}),
				'tsconfig.json': createTsconfigJson({
					compilerOptions: {
						jsx: 'react',
					},
				}),
			},
			'tsconfig.json': createTsconfigJson({
				extends: './directory',
			}),
		});

		expect(
			() => parseTsconfig(fixture.getPath('tsconfig.json')),
		).toThrow('File \'./directory\' not found.');
	});

	test('outDir in extends', async () => {
		await using fixture = await createFixture({
			'a/dep.json': createTsconfigJson({
				compilerOptions: {
					jsx: 'react-native',
					outDir: 'dist',
				},
			}),
			'tsconfig.json': createTsconfigJson({
				extends: './a/dep.json',
			}),
			'file.ts': '',
		});

		const expectedTsconfig = await getTscTsconfig(fixture.path);
		delete expectedTsconfig.files;

		/**
		 * tsc should put the outDir in exclude  but doesn't happen
		 * when it's in extended tsconfig. I think this is a bug in tsc
		 */
		expectedTsconfig.exclude = ['a/dist'];

		const tsconfig = parseTsconfig(fixture.getPath('tsconfig.json'));
		expect(tsconfig).toStrictEqual(expectedTsconfig);
	});

	test('rootDir in extends', async () => {
		await using fixture = await createFixture({
			'a/dep.json': createTsconfigJson({
				compilerOptions: {
					rootDir: 'src',
				},
			}),
			'tsconfig.json': createTsconfigJson({
				extends: './a/dep.json',
			}),
			'file.ts': '',
		});

		const expectedTsconfig = await getTscTsconfig(fixture.path);
		delete expectedTsconfig.files;

		const tsconfig = parseTsconfig(fixture.getPath('tsconfig.json'));
		expect(tsconfig).toStrictEqual(expectedTsconfig);
	});

	test('rootDirs in extends', async () => {
		await using fixture = await createFixture({
			'a/dep.json': createTsconfigJson({
				compilerOptions: {
					rootDirs: ['src', 'generated'],
				},
			}),
			'tsconfig.json': createTsconfigJson({
				extends: './a/dep.json',
			}),
			'file.ts': '',
		});

		const expectedTsconfig = await getTscTsconfig(fixture.path);
		delete expectedTsconfig.files;

		const tsconfig = parseTsconfig(fixture.getPath('tsconfig.json'));
		expect(tsconfig).toStrictEqual(expectedTsconfig);
	});

	test('typeRoots in extends', async () => {
		await using fixture = await createFixture({
			'a/dep.json': createTsconfigJson({
				compilerOptions: {
					typeRoots: ['types'],
				},
			}),
			'tsconfig.json': createTsconfigJson({
				extends: './a/dep.json',
			}),
			'file.ts': '',
		});

		const expectedTsconfig = await getTscTsconfig(fixture.path);
		delete expectedTsconfig.files;

		const tsconfig = parseTsconfig(fixture.getPath('tsconfig.json'));
		expect(tsconfig).toStrictEqual(expectedTsconfig);
	});
});
