import { getConstrainedTypeAtLocation, isPromiseLike, } from "@typescript-eslint/type-utils";
import { ESLintUtils, } from "@typescript-eslint/utils";
import { arrayFirst } from "ts-extras";
import { ruleCreator } from "../_internal/rule-creator.js";
const defaultOptions = [{}];
const isPromiseRejectionCall = (callExpression, parserServices, program) => {
    const { callee } = callExpression;
    if (callee.type !== "MemberExpression" || callee.object.type === "Super") {
        return false;
    }
    const objectType = getConstrainedTypeAtLocation(parserServices, callee.object);
    return isPromiseLike(program, objectType);
};
const isParenthesized = (sourceCode, node) => {
    const tokenBefore = sourceCode.getTokenBefore(node);
    const tokenAfter = sourceCode.getTokenAfter(node);
    if (tokenBefore === null || tokenAfter === null) {
        return false;
    }
    return (tokenBefore.value === "(" &&
        tokenBefore.range[1] <= arrayFirst(node.range) &&
        tokenAfter.value === ")" &&
        arrayFirst(tokenAfter.range) >= node.range[1]);
};
const replaceWithUnknownFix = (fixer, typeAnnotation) => fixer.replaceText(typeAnnotation, ": unknown");
const annotateImplicitParamFixes = (fixer, sourceCode, parameter) => {
    if (isParenthesized(sourceCode, parameter)) {
        return [fixer.insertTextAfter(parameter, ": unknown")];
    }
    return [
        fixer.insertTextBefore(parameter, "("),
        fixer.insertTextAfter(parameter, ": unknown)"),
    ];
};
/**
 * Require explicit `unknown` (or optionally `any`) for Promise rejection
 * callbacks.
 */
const rule = ruleCreator({
    create: (context) => {
        const parserServices = ESLintUtils.getParserServices(context);
        const sourceCode = context.sourceCode;
        const [{ allowExplicitAny = false } = {}] = context.options;
        const checkRejectionCallback = (callExpression, callback) => {
            if (callback.type !== "ArrowFunctionExpression" &&
                callback.type !== "FunctionExpression") {
                return;
            }
            const [parameter] = callback.params;
            if (parameter?.type !== "Identifier") {
                return;
            }
            if (!isPromiseRejectionCall(callExpression, parserServices, parserServices.program)) {
                return;
            }
            if (parameter.typeAnnotation === undefined) {
                context.report({
                    fix: (fixer) => annotateImplicitParamFixes(fixer, sourceCode, parameter),
                    messageId: "implicitAny",
                    node: parameter,
                    suggest: [
                        {
                            fix: (fixer) => annotateImplicitParamFixes(fixer, sourceCode, parameter),
                            messageId: "suggestExplicitUnknown",
                        },
                    ],
                });
                return;
            }
            const { typeAnnotation } = parameter;
            const annotationType = typeAnnotation.typeAnnotation.type;
            if (annotationType === "TSUnknownKeyword") {
                return;
            }
            if (annotationType === "TSAnyKeyword") {
                if (allowExplicitAny) {
                    return;
                }
                context.report({
                    fix: (fixer) => replaceWithUnknownFix(fixer, typeAnnotation),
                    messageId: "explicitAny",
                    node: parameter,
                    suggest: [
                        {
                            fix: (fixer) => replaceWithUnknownFix(fixer, typeAnnotation),
                            messageId: "suggestExplicitUnknown",
                        },
                    ],
                });
                return;
            }
            context.report({
                messageId: "narrowed",
                node: parameter,
                suggest: [
                    {
                        fix: (fixer) => replaceWithUnknownFix(fixer, typeAnnotation),
                        messageId: "suggestExplicitUnknown",
                    },
                ],
            });
        };
        return {
            "CallExpression[callee.type='MemberExpression'][callee.property.type='Identifier'][callee.property.name='catch']": (callExpression) => {
                const [callback] = callExpression.arguments;
                if (callback !== undefined) {
                    checkRejectionCallback(callExpression, callback);
                }
            },
            "CallExpression[callee.type='MemberExpression'][callee.property.type='Identifier'][callee.property.name='then']": (callExpression) => {
                const callback = callExpression.arguments[1];
                if (callback !== undefined) {
                    checkRejectionCallback(callExpression, callback);
                }
            },
        };
    },
    defaultOptions,
    meta: {
        defaultOptions: [{}],
        deprecated: false,
        docs: {
            deprecated: false,
            description: "require explicit unknown for Promise rejection callback parameters.",
            frozen: false,
            recommended: true,
            requiresTypeChecking: true,
            suggestion: true,
            url: "https://nick2bad4u.github.io/eslint-plugin-etc-misc/docs/rules/no-implicit-any-catch",
        },
        fixable: "code",
        hasSuggestions: true,
        messages: {
            explicitAny: "Explicit `any` in Promise rejection callback.",
            implicitAny: "Implicit `any` in Promise rejection callback.",
            narrowed: "Error type in Promise rejection callback must be `unknown` or `any`.",
            suggestExplicitUnknown: "Use `unknown` to force safe, explicit narrowing before access.",
        },
        schema: [
            {
                additionalProperties: false,
                description: "Configuration for Promise rejection callback annotation enforcement.",
                properties: {
                    allowExplicitAny: {
                        description: "Whether an explicit `any` annotation is allowed for rejection callback parameters.",
                        type: "boolean",
                    },
                },
                type: "object",
            },
        ],
        type: "suggestion",
    },
    name: "no-implicit-any-catch",
});
export default rule;
//# sourceMappingURL=no-implicit-any-catch.js.map