/**
 * Expression converter helpers — numeric kind extraction, this-type inference,
 * parenthesis unwrapping, unknown-type checking, identifier storage type resolution,
 * and nullish stripping.
 */
import * as ts from "typescript";
import { TSONIC_TO_NUMERIC_KIND } from "./types.js";
/**
 * Extract the NumericKind from a type node if it references a known numeric alias.
 *
 * Examples:
 * - `int` → "Int32"
 * - `byte` → "Byte"
 * - `long` → "Int64"
 * - `string` → undefined (not numeric)
 */
export const getNumericKindFromTypeNode = (typeNode) => {
    // Handle type reference nodes (e.g., `int`, `byte`, `Int32`)
    if (ts.isTypeReferenceNode(typeNode)) {
        const typeName = typeNode.typeName;
        if (ts.isIdentifier(typeName)) {
            const name = typeName.text;
            // Look up the type alias name in our mapping
            const kind = TSONIC_TO_NUMERIC_KIND.get(name);
            if (kind !== undefined) {
                return kind;
            }
        }
    }
    return undefined;
};
export const inferThisType = (node) => {
    let current = node;
    while (current) {
        if (ts.isClassDeclaration(current) || ts.isClassExpression(current)) {
            const className = current.name?.text;
            if (!className)
                return undefined;
            const typeArguments = current.typeParameters?.map((tp) => ({ kind: "typeParameterType", name: tp.name.text })) ?? [];
            return {
                kind: "referenceType",
                name: className,
                ...(typeArguments.length > 0 ? { typeArguments } : {}),
            };
        }
        current = current.parent;
    }
    return undefined;
};
const entityNameToText = (entityName) => ts.isIdentifier(entityName)
    ? entityName.text
    : `${entityNameToText(entityName.left)}.${entityName.right.text}`;
const getEnclosingGeneratorNextTypeNode = (node) => {
    let current = node.parent;
    while (current) {
        if (ts.isFunctionDeclaration(current) ||
            ts.isFunctionExpression(current) ||
            ts.isMethodDeclaration(current)) {
            if (!current.asteriskToken) {
                current = current.parent;
                continue;
            }
            const returnType = current.type;
            if (!returnType || !ts.isTypeReferenceNode(returnType)) {
                return undefined;
            }
            const generatorName = entityNameToText(returnType.typeName);
            const lastSegment = generatorName.split(".").pop() ?? generatorName;
            if (lastSegment !== "Generator" && lastSegment !== "AsyncGenerator") {
                return undefined;
            }
            return returnType.typeArguments?.[2];
        }
        current = current.parent;
    }
    return undefined;
};
export const inferYieldReceivedType = (node, ctx) => {
    const nextTypeNode = getEnclosingGeneratorNextTypeNode(node);
    if (!nextTypeNode) {
        return undefined;
    }
    return ctx.typeSystem.typeFromSyntax(ctx.binding.captureTypeSyntax(nextTypeNode));
};
export const unwrapParens = (node) => {
    let current = node;
    while (ts.isParenthesizedExpression(current)) {
        current = current.expression;
    }
    return current;
};
export const isExplicitUnknownTypeNode = (node) => !!node && node.kind === ts.SyntaxKind.UnknownKeyword;
export const hasExplicitUnknownStorageInitializer = (node) => {
    if (!node)
        return false;
    const current = unwrapParens(node);
    if (ts.isAsExpression(current) || ts.isTypeAssertionExpression(current)) {
        return isExplicitUnknownTypeNode(current.type);
    }
    if (ts.isSatisfiesExpression(current)) {
        return isExplicitUnknownTypeNode(current.type);
    }
    return false;
};
const isPureNullishType = (type) => {
    if (!type)
        return false;
    if (type.kind === "primitiveType") {
        return type.name === "null" || type.name === "undefined";
    }
    if (type.kind !== "unionType") {
        return false;
    }
    return (type.types.length > 0 &&
        type.types.every((member) => isPureNullishType(member)));
};
export const shouldPreserveExplicitStorageType = (ctx, declId, fromDecl, fromEnv) => {
    if (!fromDecl || !fromEnv || !declId) {
        return false;
    }
    const declInfo = ctx.binding
        ._getHandleRegistry()
        .getDecl(declId);
    if (!declInfo?.typeNode) {
        return false;
    }
    return isPureNullishType(fromEnv) && !isPureNullishType(fromDecl);
};
export const getIdentifierStorageType = (ctx, declId, fromDecl, fromEnv) => {
    if (shouldPreserveExplicitStorageType(ctx, declId, fromDecl, fromEnv)) {
        return fromDecl;
    }
    if (fromEnv) {
        return fromEnv;
    }
    if (!fromDecl)
        return fromEnv;
    if (!declId)
        return fromEnv ?? fromDecl;
    const declInfo = ctx.binding
        ._getHandleRegistry()
        .getDecl(declId);
    if (!declInfo)
        return fromEnv ?? fromDecl;
    if (declInfo.typeNode)
        return fromDecl;
    const declNode = declInfo.declNode;
    if (declNode) {
        if (ts.isVariableDeclaration(declNode) ||
            ts.isParameter(declNode) ||
            ts.isBindingElement(declNode)) {
            if (hasExplicitUnknownStorageInitializer(declNode.initializer)) {
                return fromDecl;
            }
        }
    }
    return fromEnv ?? fromDecl;
};
export const stripNullish = (type) => {
    if (!type)
        return undefined;
    if (type.kind !== "unionType")
        return type;
    const nonNullish = type.types.filter((t) => !(t.kind === "primitiveType" &&
        (t.name === "null" || t.name === "undefined")));
    if (nonNullish.length === 0)
        return undefined;
    if (nonNullish.length === 1)
        return nonNullish[0];
    return { kind: "unionType", types: nonNullish };
};
//# sourceMappingURL=expression-converter-helpers.js.map