import { getAwaitedIrType, referenceTypeIdentity, } from "../types/type-ops.js";
import { collectResolutionArguments, resolveCallableCandidate, } from "../converters/expressions/calls/call-resolution.js";
import { getBoundGlobalCallParameterTypes } from "../converters/expressions/calls/bound-global-call-parameters.js";
import { finalizeInvocationMetadata, getAuthoritativeDirectCalleeParameterTypes, getDirectStructuralMemberType, } from "../converters/expressions/calls/invocation-finalization.js";
const preserveResolvedReturnType = (current, next, hasDeclaredReturnType) => {
    const nextIsBroadOrVoid = next?.kind === "voidType" ||
        next?.kind === "unknownType" ||
        next?.kind === "anyType";
    const currentIsConcrete = current &&
        current.kind !== "voidType" &&
        current.kind !== "unknownType" &&
        current.kind !== "anyType";
    if (currentIsConcrete && nextIsBroadOrVoid) {
        return current;
    }
    if (hasDeclaredReturnType === false && current && next && nextIsBroadOrVoid) {
        return current;
    }
    return next ?? current;
};
const preserveConcreteRefreshExpectedType = (current, explicitExpected, sourceBackedReturnType) => {
    if (explicitExpected) {
        return explicitExpected;
    }
    const concreteCurrent = current &&
        current.kind !== "voidType" &&
        current.kind !== "unknownType" &&
        current.kind !== "anyType"
        ? current
        : undefined;
    if (concreteCurrent) {
        return concreteCurrent;
    }
    return sourceBackedReturnType &&
        sourceBackedReturnType.kind !== "voidType" &&
        sourceBackedReturnType.kind !== "unknownType" &&
        sourceBackedReturnType.kind !== "anyType"
        ? sourceBackedReturnType
        : explicitExpected;
};
const getDeterministicReferenceIdentity = (type) => {
    const identity = referenceTypeIdentity(type);
    return identity !== undefined &&
        (identity.startsWith("id:") || identity.startsWith("clr:"))
        ? identity
        : undefined;
};
const hasDeterministicIdentityConflict = (left, right) => {
    if (!left || !right || left.kind !== right.kind) {
        return false;
    }
    switch (left.kind) {
        case "referenceType": {
            if (right.kind !== "referenceType") {
                return false;
            }
            const leftIdentity = getDeterministicReferenceIdentity(left);
            const rightIdentity = getDeterministicReferenceIdentity(right);
            if (leftIdentity && rightIdentity && leftIdentity !== rightIdentity) {
                return true;
            }
            const leftArgs = left.typeArguments ?? [];
            const rightArgs = right.typeArguments ?? [];
            if (leftArgs.length !== rightArgs.length) {
                return false;
            }
            return leftArgs.some((typeArgument, index) => hasDeterministicIdentityConflict(typeArgument, rightArgs[index]));
        }
        case "arrayType":
            return (right.kind === "arrayType" &&
                hasDeterministicIdentityConflict(left.elementType, right.elementType));
        case "dictionaryType":
            return (right.kind === "dictionaryType" &&
                (hasDeterministicIdentityConflict(left.keyType, right.keyType) ||
                    hasDeterministicIdentityConflict(left.valueType, right.valueType)));
        case "tupleType":
            return (right.kind === "tupleType" &&
                left.elementTypes.length === right.elementTypes.length &&
                left.elementTypes.some((typeElement, index) => hasDeterministicIdentityConflict(typeElement, right.elementTypes[index])));
        case "functionType":
            return (right.kind === "functionType" &&
                left.parameters.length === right.parameters.length &&
                (hasDeterministicIdentityConflict(left.returnType, right.returnType) ||
                    left.parameters.some((parameter, index) => hasDeterministicIdentityConflict(parameter.type, right.parameters[index]?.type))));
        default:
            return false;
    }
};
const cohereSourceBackedReturnType = (inferredType, sourceBackedReturnType, ctx) => {
    if (inferredType &&
        sourceBackedReturnType &&
        !ctx.typeSystem.typesEqual(inferredType, sourceBackedReturnType) &&
        hasDeterministicIdentityConflict(inferredType, sourceBackedReturnType)) {
        return inferredType;
    }
    return sourceBackedReturnType;
};
const cohereAwaitedSourceBackedReturnType = (awaitedInferredType, sourceBackedReturnType, ctx) => {
    if (!awaitedInferredType || !sourceBackedReturnType) {
        return sourceBackedReturnType;
    }
    const sourceAwaitedType = getAwaitedIrType(sourceBackedReturnType) ?? sourceBackedReturnType;
    if (ctx.typeSystem.typesEqual(awaitedInferredType, sourceAwaitedType) ||
        !hasDeterministicIdentityConflict(awaitedInferredType, sourceAwaitedType)) {
        return sourceBackedReturnType;
    }
    return getAwaitedIrType(sourceBackedReturnType) &&
        sourceBackedReturnType.kind === "referenceType" &&
        (sourceBackedReturnType.typeArguments?.length ?? 0) === 1
        ? {
            ...sourceBackedReturnType,
            typeArguments: [awaitedInferredType],
        }
        : awaitedInferredType;
};
const refreshSpreadArgument = (argument, ctx) => argument?.kind === "spread"
    ? (() => {
        const expression = refreshExpression(argument.expression, ctx);
        return {
            ...argument,
            expression,
            inferredType: expression.inferredType,
        };
    })()
    : argument
        ? refreshExpression(argument, ctx)
        : argument;
const refreshExpression = (expr, ctx) => {
    switch (expr.kind) {
        case "call": {
            const callee = refreshExpression(expr.callee, ctx);
            const arguments_ = expr.arguments.map((argument) => argument.kind === "spread"
                ? (() => {
                    const expression = refreshExpression(argument.expression, ctx);
                    return {
                        ...argument,
                        expression,
                        inferredType: expression.inferredType,
                    };
                })()
                : refreshExpression(argument, ctx));
            const dynamicImportNamespace = expr.dynamicImportNamespace
                ? refreshExpression(expr.dynamicImportNamespace, ctx)
                : undefined;
            if (callee.kind === "identifier" && callee.name === "super") {
                return {
                    ...expr,
                    callee,
                    arguments: arguments_,
                    dynamicImportNamespace,
                };
            }
            if (!expr.signatureId) {
                return {
                    ...expr,
                    callee,
                    arguments: arguments_,
                    dynamicImportNamespace,
                };
            }
            const resolutionArgs = collectResolutionArguments(arguments_);
            const argumentCount = resolutionArgs.argumentCount > 0
                ? resolutionArgs.argumentCount
                : arguments_.length;
            const argTypes = resolutionArgs.argumentCount > 0
                ? resolutionArgs.argTypes
                : arguments_.map((argument) => argument.kind === "spread" ? undefined : argument.inferredType);
            const selection = ctx.typeSystem.selectBestCallCandidate(expr.signatureId, expr.candidateSignatureIds, {
                argumentCount,
                receiverType: callee.kind === "memberAccess"
                    ? callee.object.inferredType
                    : undefined,
                explicitTypeArgs: expr.typeArguments,
                argTypes,
                expectedReturnType: expr.resolutionExpectedReturnType,
            });
            const resolved = selection.resolved;
            const usesAuthoritativeSurfaceBindings = ctx.surface !== "clr";
            const boundGlobalCallParameterTypes = getBoundGlobalCallParameterTypes(callee, argumentCount, ctx);
            const authoritativeBoundGlobalSurfaceParameterTypes = usesAuthoritativeSurfaceBindings
                ? boundGlobalCallParameterTypes?.parameterTypes
                : undefined;
            const authoritativeBoundGlobalReturnType = usesAuthoritativeSurfaceBindings
                ? boundGlobalCallParameterTypes?.returnType
                : undefined;
            const preservedAmbientBoundGlobalSurfaceParameterTypes = !usesAuthoritativeSurfaceBindings && boundGlobalCallParameterTypes
                ? expr.surfaceParameterTypes
                : undefined;
            const directStructuralResolution = callee.kind === "memberAccess" && typeof callee.property === "string"
                ? (() => {
                    const directStructuralMemberType = getDirectStructuralMemberType(callee.object.inferredType, callee.property);
                    return directStructuralMemberType
                        ? resolveCallableCandidate(directStructuralMemberType, argumentCount, ctx, argTypes, expr.typeArguments, expr.resolutionExpectedReturnType)
                        : undefined;
                })()
                : undefined;
            const directCalleeResolution = callee.inferredType && callee.inferredType.kind !== "unknownType"
                ? resolveCallableCandidate(callee.inferredType, argumentCount, ctx, argTypes, expr.typeArguments, expr.resolutionExpectedReturnType)
                : undefined;
            const authoritativeDirectCalleeParameterTypes = getAuthoritativeDirectCalleeParameterTypes(callee, argumentCount, ctx);
            const preserveAuthoritativeDirectCalleeSurfaceIdentity = !!authoritativeDirectCalleeParameterTypes &&
                !authoritativeBoundGlobalSurfaceParameterTypes &&
                !expr.sourceBackedSurfaceParameterTypes &&
                !preservedAmbientBoundGlobalSurfaceParameterTypes;
            const finalizedInvocationMetadata = finalizeInvocationMetadata({
                ctx,
                callee,
                receiverType: callee.kind === "memberAccess"
                    ? callee.object.inferredType
                    : undefined,
                callableType: directStructuralResolution?.callableType ??
                    directCalleeResolution?.callableType ??
                    (callee.inferredType?.kind === "functionType"
                        ? callee.inferredType
                        : undefined),
                argumentCount,
                argTypes,
                explicitTypeArgs: expr.typeArguments,
                expectedType: expr.resolutionExpectedReturnType,
                boundGlobalParameterTypes: boundGlobalCallParameterTypes?.parameterTypes,
                authoritativeBoundGlobalSurfaceParameterTypes,
                authoritativeBoundGlobalReturnType,
                sourceBackedParameterTypes: expr.sourceBackedParameterTypes,
                sourceBackedSurfaceParameterTypes: expr.sourceBackedSurfaceParameterTypes,
                sourceBackedReturnType: expr.sourceBackedReturnType,
                ambientBoundGlobalSurfaceParameterTypes: preservedAmbientBoundGlobalSurfaceParameterTypes,
                authoritativeDirectParameterTypes: authoritativeDirectCalleeParameterTypes,
                resolvedParameterTypes: resolved?.parameterTypes,
                resolvedSurfaceParameterTypes: resolved?.surfaceParameterTypes,
                resolvedReturnType: resolved?.returnType,
                fallbackParameterTypes: expr.parameterTypes,
                fallbackSurfaceParameterTypes: expr.surfaceParameterTypes,
                exactParameterCandidates: [
                    directStructuralResolution?.resolved?.parameterTypes,
                    directCalleeResolution?.resolved?.parameterTypes,
                ],
                exactSurfaceParameterCandidates: [
                    directStructuralResolution?.resolved?.surfaceParameterTypes ??
                        directStructuralResolution?.resolved?.parameterTypes,
                    directCalleeResolution?.resolved?.surfaceParameterTypes ??
                        directCalleeResolution?.resolved?.parameterTypes,
                ],
                exactReturnCandidates: [
                    directStructuralResolution?.resolved?.returnType,
                    directCalleeResolution?.resolved?.returnType,
                ],
                preserveDirectSurfaceIdentity: preserveAuthoritativeDirectCalleeSurfaceIdentity,
            });
            const refreshedRestParameter = boundGlobalCallParameterTypes
                ? boundGlobalCallParameterTypes.restParameter
                : (expr.sourceBackedRestParameter ??
                    resolved?.restParameter ??
                    expr.restParameter);
            const refreshedSurfaceRestParameter = boundGlobalCallParameterTypes
                ? boundGlobalCallParameterTypes.restParameter
                : (expr.sourceBackedRestParameter ??
                    resolved?.surfaceRestParameter ??
                    expr.surfaceRestParameter);
            const coherentSourceBackedReturnType = cohereSourceBackedReturnType(expr.inferredType, finalizedInvocationMetadata.sourceBackedReturnType, ctx);
            return {
                ...expr,
                callee,
                arguments: arguments_,
                dynamicImportNamespace,
                inferredType: preserveResolvedReturnType(expr.inferredType, coherentSourceBackedReturnType ?? resolved?.returnType, resolved?.hasDeclaredReturnType),
                parameterTypes: finalizedInvocationMetadata.parameterTypes,
                surfaceParameterTypes: finalizedInvocationMetadata.surfaceParameterTypes,
                restParameter: refreshedRestParameter,
                surfaceRestParameter: refreshedSurfaceRestParameter,
                sourceBackedParameterTypes: finalizedInvocationMetadata.sourceBackedParameterTypes,
                sourceBackedSurfaceParameterTypes: finalizedInvocationMetadata.sourceBackedSurfaceParameterTypes,
                sourceBackedReturnType: coherentSourceBackedReturnType,
            };
        }
        case "new": {
            const callee = refreshExpression(expr.callee, ctx);
            const arguments_ = expr.arguments.map((argument) => argument.kind === "spread"
                ? (() => {
                    const expression = refreshExpression(argument.expression, ctx);
                    return {
                        ...argument,
                        expression,
                        inferredType: expression.inferredType,
                    };
                })()
                : refreshExpression(argument, ctx));
            if (!expr.signatureId) {
                return {
                    ...expr,
                    callee,
                    arguments: arguments_,
                };
            }
            const argTypes = arguments_.map((argument) => argument.kind === "spread" ? undefined : argument.inferredType);
            const refreshedExpectedReturnType = preserveConcreteRefreshExpectedType(expr.inferredType, expr.resolutionExpectedReturnType, expr.sourceBackedReturnType);
            const resolved = ctx.typeSystem.resolveCall({
                sigId: expr.signatureId,
                argumentCount: arguments_.length,
                explicitTypeArgs: expr.typeArguments,
                argTypes,
                expectedReturnType: refreshedExpectedReturnType,
            });
            const finalizedInvocationMetadata = finalizeInvocationMetadata({
                ctx,
                callee,
                receiverType: callee.kind === "memberAccess"
                    ? callee.object.inferredType
                    : undefined,
                callableType: callee.inferredType?.kind === "functionType"
                    ? callee.inferredType
                    : undefined,
                argumentCount: arguments_.length,
                argTypes,
                explicitTypeArgs: expr.typeArguments,
                expectedType: refreshedExpectedReturnType,
                boundGlobalParameterTypes: undefined,
                authoritativeBoundGlobalSurfaceParameterTypes: undefined,
                authoritativeBoundGlobalReturnType: undefined,
                sourceBackedParameterTypes: expr.sourceBackedParameterTypes,
                sourceBackedSurfaceParameterTypes: expr.sourceBackedSurfaceParameterTypes,
                sourceBackedReturnType: expr.sourceBackedReturnType,
                ambientBoundGlobalSurfaceParameterTypes: undefined,
                authoritativeDirectParameterTypes: undefined,
                resolvedParameterTypes: resolved.parameterTypes,
                resolvedSurfaceParameterTypes: resolved.surfaceParameterTypes,
                resolvedReturnType: resolved.returnType,
                fallbackParameterTypes: expr.parameterTypes,
                fallbackSurfaceParameterTypes: expr.surfaceParameterTypes,
                exactParameterCandidates: [],
                exactSurfaceParameterCandidates: [],
                exactReturnCandidates: [],
                preserveDirectSurfaceIdentity: false,
            });
            const coherentSourceBackedReturnType = cohereSourceBackedReturnType(expr.inferredType, finalizedInvocationMetadata.sourceBackedReturnType ??
                expr.sourceBackedReturnType, ctx);
            return {
                ...expr,
                callee,
                arguments: arguments_,
                inferredType: coherentSourceBackedReturnType ??
                    resolved.returnType ??
                    expr.inferredType,
                parameterTypes: finalizedInvocationMetadata.parameterTypes ?? expr.parameterTypes,
                surfaceParameterTypes: finalizedInvocationMetadata.surfaceParameterTypes ??
                    expr.surfaceParameterTypes,
                sourceBackedParameterTypes: finalizedInvocationMetadata.sourceBackedParameterTypes ??
                    expr.sourceBackedParameterTypes,
                sourceBackedSurfaceParameterTypes: finalizedInvocationMetadata.sourceBackedSurfaceParameterTypes ??
                    expr.sourceBackedSurfaceParameterTypes,
                sourceBackedRestParameter: expr.sourceBackedRestParameter,
                sourceBackedReturnType: coherentSourceBackedReturnType,
                surfaceRestParameter: expr.sourceBackedRestParameter ??
                    resolved.surfaceRestParameter ??
                    expr.surfaceRestParameter,
            };
        }
        case "memberAccess":
            return {
                ...expr,
                object: refreshExpression(expr.object, ctx),
                property: typeof expr.property === "string"
                    ? expr.property
                    : refreshExpression(expr.property, ctx),
            };
        case "binary":
        case "logical":
            return {
                ...expr,
                left: refreshExpression(expr.left, ctx),
                right: refreshExpression(expr.right, ctx),
            };
        case "conditional":
            return {
                ...expr,
                condition: refreshExpression(expr.condition, ctx),
                whenTrue: refreshExpression(expr.whenTrue, ctx),
                whenFalse: refreshExpression(expr.whenFalse, ctx),
            };
        case "assignment":
            return {
                ...expr,
                left: expr.left.kind === "identifierPattern" ||
                    expr.left.kind === "arrayPattern" ||
                    expr.left.kind === "objectPattern"
                    ? expr.left
                    : refreshExpression(expr.left, ctx),
                right: refreshExpression(expr.right, ctx),
            };
        case "await": {
            const expression = refreshExpression(expr.expression, ctx);
            if (!("sourceBackedReturnType" in expression)) {
                return {
                    ...expr,
                    expression,
                };
            }
            const coherentSourceBackedReturnType = cohereAwaitedSourceBackedReturnType(expr.inferredType, expression.sourceBackedReturnType, ctx);
            return {
                ...expr,
                expression: {
                    ...expression,
                    inferredType: coherentSourceBackedReturnType ?? expression.inferredType,
                    sourceBackedReturnType: coherentSourceBackedReturnType,
                },
            };
        }
        case "unary":
        case "update":
        case "typeAssertion":
        case "numericNarrowing":
        case "asinterface":
        case "trycast":
            return {
                ...expr,
                expression: refreshExpression(expr.expression, ctx),
            };
        case "yield":
            return {
                ...expr,
                expression: expr.expression
                    ? refreshExpression(expr.expression, ctx)
                    : undefined,
            };
        case "templateLiteral":
            return {
                ...expr,
                expressions: expr.expressions.map((expression) => refreshExpression(expression, ctx)),
            };
        case "array":
            return {
                ...expr,
                elements: expr.elements.map((element) => refreshSpreadArgument(element, ctx)),
            };
        case "object":
            return {
                ...expr,
                properties: expr.properties.map((property) => property.kind === "spread"
                    ? {
                        ...property,
                        expression: refreshExpression(property.expression, ctx),
                    }
                    : property.kind === "property"
                        ? {
                            ...property,
                            value: refreshExpression(property.value, ctx),
                        }
                        : property),
            };
        case "arrowFunction":
            return {
                ...expr,
                body: expr.body.kind === "blockStatement"
                    ? refreshStatement(expr.body, ctx)
                    : refreshExpression(expr.body, ctx),
            };
        case "functionExpression":
            return {
                ...expr,
                body: refreshStatement(expr.body, ctx),
            };
        default:
            return expr;
    }
};
const refreshStatement = (stmt, ctx) => {
    switch (stmt.kind) {
        case "expressionStatement":
            return {
                ...stmt,
                expression: refreshExpression(stmt.expression, ctx),
            };
        case "returnStatement":
            return {
                ...stmt,
                expression: stmt.expression
                    ? refreshExpression(stmt.expression, ctx)
                    : undefined,
            };
        case "variableDeclaration":
            return {
                ...stmt,
                declarations: stmt.declarations.map((declaration) => ({
                    ...declaration,
                    initializer: declaration.initializer
                        ? refreshExpression(declaration.initializer, ctx)
                        : undefined,
                })),
            };
        case "ifStatement":
            return {
                ...stmt,
                condition: refreshExpression(stmt.condition, ctx),
                thenStatement: refreshStatement(stmt.thenStatement, ctx),
                elseStatement: stmt.elseStatement
                    ? refreshStatement(stmt.elseStatement, ctx)
                    : undefined,
            };
        case "blockStatement":
            return {
                ...stmt,
                statements: stmt.statements.map((statement) => refreshStatement(statement, ctx)),
            };
        case "forStatement":
            return {
                ...stmt,
                initializer: stmt.initializer && stmt.initializer.kind !== "variableDeclaration"
                    ? refreshExpression(stmt.initializer, ctx)
                    : stmt.initializer
                        ? refreshStatement(stmt.initializer, ctx)
                        : undefined,
                condition: stmt.condition
                    ? refreshExpression(stmt.condition, ctx)
                    : undefined,
                update: stmt.update ? refreshExpression(stmt.update, ctx) : undefined,
                body: refreshStatement(stmt.body, ctx),
            };
        case "forOfStatement":
        case "forInStatement":
            return {
                ...stmt,
                expression: refreshExpression(stmt.expression, ctx),
                body: refreshStatement(stmt.body, ctx),
            };
        case "whileStatement":
            return {
                ...stmt,
                condition: refreshExpression(stmt.condition, ctx),
                body: refreshStatement(stmt.body, ctx),
            };
        case "switchStatement":
            return {
                ...stmt,
                expression: refreshExpression(stmt.expression, ctx),
                cases: stmt.cases.map((switchCase) => ({
                    ...switchCase,
                    test: switchCase.test
                        ? refreshExpression(switchCase.test, ctx)
                        : undefined,
                    statements: switchCase.statements.map((statement) => refreshStatement(statement, ctx)),
                })),
            };
        case "throwStatement":
            return {
                ...stmt,
                expression: refreshExpression(stmt.expression, ctx),
            };
        case "tryStatement":
            return {
                ...stmt,
                tryBlock: refreshStatement(stmt.tryBlock, ctx),
                catchClause: stmt.catchClause
                    ? {
                        ...stmt.catchClause,
                        body: refreshStatement(stmt.catchClause.body, ctx),
                    }
                    : undefined,
                finallyBlock: stmt.finallyBlock
                    ? refreshStatement(stmt.finallyBlock, ctx)
                    : undefined,
            };
        case "functionDeclaration":
            return {
                ...stmt,
                body: refreshStatement(stmt.body, ctx),
            };
        case "classDeclaration":
            return {
                ...stmt,
                members: stmt.members.map((member) => {
                    if (member.kind === "methodDeclaration" && member.body) {
                        return {
                            ...member,
                            body: refreshStatement(member.body, ctx),
                        };
                    }
                    if (member.kind === "constructorDeclaration" && member.body) {
                        return {
                            ...member,
                            body: refreshStatement(member.body, ctx),
                        };
                    }
                    if (member.kind === "propertyDeclaration") {
                        return {
                            ...member,
                            initializer: member.initializer
                                ? refreshExpression(member.initializer, ctx)
                                : undefined,
                            getterBody: member.getterBody
                                ? refreshStatement(member.getterBody, ctx)
                                : undefined,
                            setterBody: member.setterBody
                                ? refreshStatement(member.setterBody, ctx)
                                : undefined,
                        };
                    }
                    return member;
                }),
            };
        default:
            return stmt;
    }
};
export const runCallResolutionRefreshPass = (modules, ctx) => ({
    ok: true,
    modules: modules.map((module) => ({
        ...module,
        body: module.body.map((statement) => refreshStatement(statement, ctx)),
        exports: module.exports.map((entry) => entry.kind === "declaration"
            ? {
                ...entry,
                declaration: refreshStatement(entry.declaration, ctx),
            }
            : entry),
    })),
});
//# sourceMappingURL=call-resolution-refresh-pass.js.map