import {removeSpecifier} from './fix/index.js';
import assertToken from './utils/assert-token.js';

const MESSAGE_ID = 'no-named-default';
const messages = {
	[MESSAGE_ID]: 'Prefer using the default {{type}} over named {{type}}.',
};

const isValueImport = node => !node.importKind || node.importKind === 'value';
const isValueExport = node => !node.exportKind || node.exportKind === 'value';

const fixImportSpecifier = (importSpecifier, context) => function * (fixer) {
	const {sourceCode} = context;
	const declaration = importSpecifier.parent;

	yield removeSpecifier(importSpecifier, fixer, context, /* keepDeclaration */ true);

	const nameText = sourceCode.getText(importSpecifier.local);
	const hasDefaultImport = declaration.specifiers.some(({type}) => type === 'ImportDefaultSpecifier');

	// Insert a new `ImportDeclaration`
	if (hasDefaultImport) {
		const fromToken = sourceCode.getTokenBefore(declaration.source, token => token.type === 'Identifier' && token.value === 'from');
		const [startOfFromToken] = sourceCode.getRange(fromToken);
		const [, endOfDeclaration] = sourceCode.getRange(declaration);
		const text = `import ${nameText} ${sourceCode.text.slice(startOfFromToken, endOfDeclaration)}`;
		yield fixer.insertTextBefore(declaration, `${text}\n`);

		return;
	}

	const importToken = sourceCode.getFirstToken(declaration);
	assertToken(importToken, {
		expected: {type: 'Keyword', value: 'import'},
		ruleId: 'no-named-default',
	});

	const shouldAddComma = declaration.specifiers.some(specifier => specifier !== importSpecifier && specifier.type === importSpecifier.type);
	yield fixer.insertTextAfter(importToken, ` ${nameText}${shouldAddComma ? ',' : ''}`);
};

const fixExportSpecifier = (exportSpecifier, context) => function * (fixer) {
	const declaration = exportSpecifier.parent;
	yield removeSpecifier(exportSpecifier, fixer, context);

	const text = `export default ${context.sourceCode.getText(exportSpecifier.local)};`;
	yield fixer.insertTextBefore(declaration, `${text}\n`);
};

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
	context.on('ImportSpecifier', specifier => {
		if (!(
			isValueImport(specifier)
			&& specifier.imported.name === 'default'
			&& isValueImport(specifier.parent)
		)) {
			return;
		}

		return {
			node: specifier,
			messageId: MESSAGE_ID,
			data: {type: 'import'},
			fix: fixImportSpecifier(specifier, context),
		};
	});

	context.on('ExportSpecifier', specifier => {
		if (!(
			isValueExport(specifier)
			&& specifier.exported.name === 'default'
			&& isValueExport(specifier.parent)
			&& !specifier.parent.source
		)) {
			return;
		}

		return {
			node: specifier,
			messageId: MESSAGE_ID,
			data: {type: 'export'},
			fix: fixExportSpecifier(specifier, context),
		};
	});
};

/** @type {import('eslint').Rule.RuleModule} */
const config = {
	create,
	meta: {
		type: 'suggestion',
		docs: {
			description: 'Disallow named usage of default import and export.',
			recommended: 'unopinionated',
		},
		fixable: 'code',
		messages,
	},
};

export default config;
