import { arrayAt, arrayFirst, arrayJoin, isDefined, isEmpty } from "ts-extras";
import { ruleCreator } from "../_internal/rule-creator.js";
const buildFixedTypeText = (sourceCode, unionType) => {
    let nonUndefinedTypeTexts = [];
    for (const typeNode of unionType.types) {
        if (typeNode.type === "TSUndefinedKeyword") {
            continue;
        }
        nonUndefinedTypeTexts = [
            ...nonUndefinedTypeTexts,
            sourceCode.getText(typeNode),
        ];
    }
    if (isEmpty(nonUndefinedTypeTexts) ||
        nonUndefinedTypeTexts.length === unionType.types.length) {
        return undefined;
    }
    return arrayJoin(nonUndefinedTypeTexts, " | ");
};
const unwrapExpression = (expression) => {
    if (expression.type === "TSAsExpression" ||
        expression.type === "TSSatisfiesExpression" ||
        expression.type === "TSNonNullExpression" ||
        expression.type === "TSTypeAssertion") {
        return unwrapExpression(expression.expression);
    }
    return expression;
};
const isDefinitelyDefinedExpression = (expression) => {
    const unwrappedExpression = unwrapExpression(expression);
    if (unwrappedExpression.type === "ArrayExpression") {
        return true;
    }
    if (unwrappedExpression.type === "ArrowFunctionExpression") {
        return true;
    }
    if (unwrappedExpression.type === "ClassExpression") {
        return true;
    }
    if (unwrappedExpression.type === "ConditionalExpression") {
        return (isDefinitelyDefinedExpression(unwrappedExpression.consequent) &&
            isDefinitelyDefinedExpression(unwrappedExpression.alternate));
    }
    if (unwrappedExpression.type === "FunctionExpression") {
        return true;
    }
    if (unwrappedExpression.type === "Literal") {
        return true;
    }
    if (unwrappedExpression.type === "LogicalExpression" &&
        unwrappedExpression.operator === "??") {
        return isDefinitelyDefinedExpression(unwrappedExpression.right);
    }
    if (unwrappedExpression.type === "NewExpression") {
        return true;
    }
    if (unwrappedExpression.type === "ObjectExpression") {
        return true;
    }
    if (unwrappedExpression.type === "SequenceExpression") {
        const lastExpression = arrayAt(unwrappedExpression.expressions, -1);
        return (lastExpression !== undefined &&
            isDefinitelyDefinedExpression(lastExpression));
    }
    if (unwrappedExpression.type === "TemplateLiteral") {
        return true;
    }
    return false;
};
const hasDefinitelyDefinedReturnValue = (node) => {
    const body = node.body;
    if (body.type !== "BlockStatement") {
        return isDefinitelyDefinedExpression(body);
    }
    if (body.body.length !== 1) {
        return false;
    }
    const statement = arrayFirst(body.body);
    if (statement === undefined) {
        return false;
    }
    if (statement.type !== "ReturnStatement") {
        return false;
    }
    if (statement.argument === null) {
        return false;
    }
    return isDefinitelyDefinedExpression(statement.argument);
};
/**
 * Disallow redundant `undefined` in return type unions when the function body
 * deterministically returns a definitely-defined value.
 */
const rule = ruleCreator({
    create: (context) => {
        const sourceCode = context.sourceCode;
        return {
            ":matches(ArrowFunctionExpression, FunctionDeclaration, FunctionExpression)": (node) => {
                if (node.type !== "ArrowFunctionExpression" &&
                    node.type !== "FunctionDeclaration" &&
                    node.type !== "FunctionExpression") {
                    return;
                }
                const returnType = node.returnType;
                if (returnType === undefined) {
                    return;
                }
                if (returnType.typeAnnotation.type !== "TSUnionType") {
                    return;
                }
                if (!hasDefinitelyDefinedReturnValue(node)) {
                    return;
                }
                const fixedTypeText = buildFixedTypeText(sourceCode, returnType.typeAnnotation);
                if (!isDefined(fixedTypeText)) {
                    return;
                }
                const fix = (fixer) => fixer.replaceText(returnType.typeAnnotation, fixedTypeText);
                context.report({
                    fix,
                    messageId: "forbidden",
                    node: returnType.typeAnnotation,
                    suggest: [
                        {
                            fix,
                            messageId: "suggestRemoveRedundantUndefined",
                        },
                    ],
                });
            },
        };
    },
    defaultOptions: [],
    meta: {
        deprecated: false,
        docs: {
            deprecated: false,
            description: "disallow redundant `undefined` in return type unions when the function deterministically returns a definitely-defined value.",
            frozen: false,
            recommended: false,
            url: "https://nick2bad4u.github.io/eslint-plugin-etc-misc/docs/rules/typescript-no-redundant-undefined-return-type",
        },
        fixable: "code",
        hasSuggestions: true,
        messages: {
            forbidden: "Return types should not redundantly include `undefined` when the function deterministically returns a definitely-defined value.",
            suggestRemoveRedundantUndefined: "Remove redundant `undefined` from this return type union.",
        },
        schema: [],
        type: "suggestion",
    },
    name: "typescript/no-redundant-undefined-return-type",
});
export default rule;
//# sourceMappingURL=typescript-no-redundant-undefined-return-type.js.map