import { containsAllTypesByName, getConstrainedTypeAtLocation, isTypeArrayTypeOrUnionOfArrayTypes, } from "@typescript-eslint/type-utils";
import { ESLintUtils } from "@typescript-eslint/utils";
import { arrayFirst, setHas } from "ts-extras";
import { ruleCreator } from "../_internal/rule-creator.js";
const arrayCallbackMethodNames = new Set([
    "every",
    "find",
    "findIndex",
    "findLast",
    "findLastIndex",
    "flatMap",
    "forEach",
    "map",
    "some",
]);
const readonlyArrayTypeNames = new Set(["ReadonlyArray"]);
const callbackSelector = "CallExpression[callee.type='MemberExpression'][callee.property.type='Identifier'] > :matches(FunctionExpression, ArrowFunctionExpression):not([returnType])";
const isArrayLikeType = (typeChecker, type) => {
    const apparentType = typeChecker.getApparentType(type);
    return (typeChecker.isArrayType(apparentType) ||
        typeChecker.isTupleType(apparentType) ||
        isTypeArrayTypeOrUnionOfArrayTypes(type, typeChecker) ||
        containsAllTypesByName(type, false, readonlyArrayTypeNames, true));
};
const isFirstCallbackArgument = (callExpression, callback) => arrayFirst(callExpression.arguments) === callback;
/**
 * Require explicit return types for array callback functions.
 */
const rule = ruleCreator({
    create: (context) => {
        const parserServices = ESLintUtils.getParserServices(context);
        const typeChecker = parserServices.program.getTypeChecker();
        return {
            [callbackSelector]: (callback) => {
                const parentNode = callback.parent;
                if (parentNode?.type !== "CallExpression") {
                    return;
                }
                if (!isFirstCallbackArgument(parentNode, callback)) {
                    return;
                }
                const { callee } = parentNode;
                if (callee.type !== "MemberExpression" ||
                    callee.object.type === "Super" ||
                    callee.property.type !== "Identifier" ||
                    !setHas(arrayCallbackMethodNames, callee.property.name)) {
                    return;
                }
                const objectType = getConstrainedTypeAtLocation(parserServices, callee.object);
                if (!isArrayLikeType(typeChecker, objectType)) {
                    return;
                }
                context.report({
                    messageId: "forbidden",
                    node: callback,
                });
            },
        };
    },
    defaultOptions: [],
    meta: {
        deprecated: false,
        docs: {
            deprecated: false,
            description: "require explicit return types for array callback functions.",
            frozen: false,
            recommended: false,
            requiresTypeChecking: true,
            url: "https://nick2bad4u.github.io/eslint-plugin-etc-misc/docs/rules/typescript-array-callback-return-type",
        },
        hasSuggestions: false,
        messages: {
            forbidden: "Specify the callback return type explicitly.",
        },
        schema: [],
        type: "suggestion",
    },
    name: "typescript/array-callback-return-type",
});
export default rule;
//# sourceMappingURL=typescript-array-callback-return-type.js.map