"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("@typescript-eslint/utils");
const util_1 = require("../util");
exports.default = (0, util_1.createRule)({
    name: 'consistent-generic-constructors',
    meta: {
        type: 'suggestion',
        docs: {
            description: 'Enforce specifying generic type arguments on type annotation or constructor name of a constructor call',
            recommended: 'stylistic',
        },
        fixable: 'code',
        messages: {
            preferConstructor: 'The generic type arguments should be specified as part of the constructor type arguments.',
            preferTypeAnnotation: 'The generic type arguments should be specified as part of the type annotation.',
        },
        schema: [
            {
                type: 'string',
                description: 'Which constructor call syntax to prefer.',
                enum: ['type-annotation', 'constructor'],
            },
        ],
    },
    defaultOptions: ['constructor'],
    create(context, [mode]) {
        return {
            'VariableDeclarator,PropertyDefinition,AccessorProperty,:matches(FunctionDeclaration,FunctionExpression) > AssignmentPattern'(node) {
                function getLHSRHS() {
                    switch (node.type) {
                        case utils_1.AST_NODE_TYPES.VariableDeclarator:
                            return [node.id, node.init];
                        case utils_1.AST_NODE_TYPES.PropertyDefinition:
                        case utils_1.AST_NODE_TYPES.AccessorProperty:
                            return [node, node.value];
                        case utils_1.AST_NODE_TYPES.AssignmentPattern:
                            return [node.left, node.right];
                        default:
                            throw new Error(`Unhandled node type: ${node.type}`);
                    }
                }
                const [lhsName, rhs] = getLHSRHS();
                const lhs = lhsName.typeAnnotation?.typeAnnotation;
                if (!rhs ||
                    rhs.type !== utils_1.AST_NODE_TYPES.NewExpression ||
                    rhs.callee.type !== utils_1.AST_NODE_TYPES.Identifier) {
                    return;
                }
                if (lhs &&
                    (lhs.type !== utils_1.AST_NODE_TYPES.TSTypeReference ||
                        lhs.typeName.type !== utils_1.AST_NODE_TYPES.Identifier ||
                        lhs.typeName.name !== rhs.callee.name)) {
                    return;
                }
                if (mode === 'type-annotation') {
                    if (!lhs && rhs.typeArguments) {
                        const { callee, typeArguments } = rhs;
                        const typeAnnotation = context.sourceCode.getText(callee) +
                            context.sourceCode.getText(typeArguments);
                        context.report({
                            node,
                            messageId: 'preferTypeAnnotation',
                            fix(fixer) {
                                function getIDToAttachAnnotation() {
                                    if (node.type !== utils_1.AST_NODE_TYPES.PropertyDefinition &&
                                        node.type !== utils_1.AST_NODE_TYPES.AccessorProperty) {
                                        return lhsName;
                                    }
                                    if (!node.computed) {
                                        return node.key;
                                    }
                                    // If the property's computed, we have to attach the
                                    // annotation after the square bracket, not the enclosed expression
                                    return (0, util_1.nullThrows)(context.sourceCode.getTokenAfter(node.key), util_1.NullThrowsReasons.MissingToken(']', 'key'));
                                }
                                return [
                                    fixer.remove(typeArguments),
                                    fixer.insertTextAfter(getIDToAttachAnnotation(), `: ${typeAnnotation}`),
                                ];
                            },
                        });
                    }
                    return;
                }
                if (lhs?.typeArguments && !rhs.typeArguments) {
                    const hasParens = context.sourceCode.getTokenAfter(rhs.callee)?.value === '(';
                    const extraComments = new Set(context.sourceCode.getCommentsInside(lhs.parent));
                    context.sourceCode
                        .getCommentsInside(lhs.typeArguments)
                        .forEach(c => extraComments.delete(c));
                    context.report({
                        node,
                        messageId: 'preferConstructor',
                        *fix(fixer) {
                            yield fixer.remove(lhs.parent);
                            for (const comment of extraComments) {
                                yield fixer.insertTextAfter(rhs.callee, context.sourceCode.getText(comment));
                            }
                            yield fixer.insertTextAfter(rhs.callee, context.sourceCode.getText(lhs.typeArguments));
                            if (!hasParens) {
                                yield fixer.insertTextAfter(rhs.callee, '()');
                            }
                        },
                    });
                }
            },
        };
    },
});
