/**
 * Type environment helpers
 *
 * Extends ProgramContext.typeEnv with deterministic types discovered during conversion.
 *
 * IMPORTANT:
 * - This is NOT TypeScript type-checking.
 * - This is deterministic, IR-derived typing used to avoid "unknown" fallbacks
 *   for common local variables (e.g., `const ok = x !== undefined` → boolean).
 */
import * as ts from "typescript";
import { tryResolveDeterministicPropertyName } from "../syntax/property-names.js";
/**
 * Derive the type from a converted IR expression using deterministic rules.
 * NO TYPESCRIPT FALLBACK - types must be derivable from IR or undefined.
 *
 * DETERMINISTIC TYPING RULES:
 * - Literals → use inferredType (already set deterministically in literals.ts)
 * - Arrays → use inferredType, or derive from first element
 * - All other expressions → use inferredType if present
 */
export const deriveTypeFromExpression = (expr) => {
    if (expr.kind === "literal") {
        return expr.inferredType;
    }
    if (expr.kind === "array") {
        if (expr.inferredType)
            return expr.inferredType;
        const first = expr.elements[0];
        if (!first)
            return undefined;
        const elementType = deriveTypeFromExpression(first);
        return elementType ? { kind: "arrayType", elementType } : undefined;
    }
    if ("inferredType" in expr && expr.inferredType) {
        return expr.inferredType;
    }
    return undefined;
};
const normalizeEnvType = (type) => {
    if (!type)
        return undefined;
    if (type.kind === "unknownType" || type.kind === "anyType")
        return undefined;
    return type;
};
const makeOptionalReadType = (type) => {
    if (type.kind === "unionType") {
        const hasUndefined = type.types.some((member) => member.kind === "primitiveType" && member.name === "undefined");
        if (hasUndefined)
            return type;
        return {
            kind: "unionType",
            types: [...type.types, { kind: "primitiveType", name: "undefined" }],
        };
    }
    if (type.kind === "primitiveType" && type.name === "undefined") {
        return type;
    }
    return {
        kind: "unionType",
        types: [type, { kind: "primitiveType", name: "undefined" }],
    };
};
const getParameterReadType = (parameter) => {
    if (!parameter.type)
        return undefined;
    return parameter.isOptional
        ? makeOptionalReadType(parameter.type)
        : parameter.type;
};
const getTupleElementType = (type, index) => {
    if (!type)
        return undefined;
    if (type.kind === "tupleType") {
        return type.elementTypes[index];
    }
    return undefined;
};
const getArrayElementType = (type, index) => {
    if (!type)
        return undefined;
    if (type.kind === "arrayType")
        return type.elementType;
    const tuple = getTupleElementType(type, index);
    if (tuple)
        return tuple;
    return undefined;
};
const getObjectPropertyType = (ctx, type, propName) => {
    if (!type)
        return undefined;
    if (type.kind === "objectType") {
        const member = type.members.find((m) => m.kind === "propertySignature" && m.name === propName);
        if (member && member.kind === "propertySignature") {
            return member.type;
        }
        return undefined;
    }
    if (type.kind === "referenceType") {
        const memberType = ctx.typeSystem.typeOfMember(type, {
            kind: "byName",
            name: propName,
        });
        return memberType.kind === "unknownType" ? undefined : memberType;
    }
    return undefined;
};
const getPropertyNameText = (name) => tryResolveDeterministicPropertyName(name);
const extendEnvForBindingName = (ctx, name, sourceType, ensureEnv) => {
    const normalizedSource = normalizeEnvType(sourceType);
    if (!normalizedSource)
        return;
    if (ts.isIdentifier(name)) {
        const declId = ctx.binding.resolveIdentifier(name);
        if (declId) {
            ensureEnv().set(declId.id, normalizedSource);
        }
        return;
    }
    if (ts.isArrayBindingPattern(name)) {
        // Each element gets the array element type (or tuple element type if known).
        for (let i = 0; i < name.elements.length; i++) {
            const element = name.elements[i];
            if (!element)
                continue;
            if (ts.isOmittedExpression(element))
                continue;
            const isRest = !!element.dotDotDotToken;
            const elementType = getArrayElementType(normalizedSource, i);
            const boundType = isRest && elementType
                ? { kind: "arrayType", elementType }
                : elementType;
            extendEnvForBindingName(ctx, element.name, boundType, ensureEnv);
        }
        return;
    }
    if (ts.isObjectBindingPattern(name)) {
        for (const element of name.elements) {
            if (element.dotDotDotToken) {
                // Rest object: its truthiness is object-like (always truthy if non-null),
                // so the exact structural type is not required for boolean-context correctness.
                continue;
            }
            const key = element.propertyName !== undefined
                ? getPropertyNameText(element.propertyName)
                : ts.isIdentifier(element.name)
                    ? element.name.text
                    : undefined;
            if (!key)
                continue;
            const propType = getObjectPropertyType(ctx, normalizedSource, key);
            extendEnvForBindingName(ctx, element.name, propType, ensureEnv);
        }
    }
};
export const withBindingNameTypeEnv = (ctx, name, type) => {
    const normalizedType = normalizeEnvType(type);
    if (!normalizedType)
        return ctx;
    let nextEnv;
    const ensureEnv = () => {
        if (!nextEnv)
            nextEnv = new Map(ctx.typeEnv ?? []);
        return nextEnv;
    };
    extendEnvForBindingName(ctx, name, normalizedType, ensureEnv);
    return nextEnv ? { ...ctx, typeEnv: nextEnv } : ctx;
};
export const withParameterTypeEnv = (ctx, tsParameters, parameters) => {
    let nextCtx = ctx;
    for (let i = 0; i < parameters.length; i++) {
        const parameter = parameters[i];
        const tsParameter = tsParameters[i];
        if (!parameter || !tsParameter)
            continue;
        nextCtx = withBindingNameTypeEnv(nextCtx, tsParameter.name, getParameterReadType(parameter));
    }
    return nextCtx;
};
const deriveDeclaratorType = (decl) => {
    const explicitType = normalizeEnvType(decl.type);
    if (explicitType)
        return explicitType;
    const initType = decl.initializer
        ? normalizeEnvType(deriveTypeFromExpression(decl.initializer))
        : undefined;
    return initType;
};
export const withVariableDeclaratorTypeEnv = (ctx, name, decl) => {
    const type = deriveDeclaratorType(decl);
    return withBindingNameTypeEnv(ctx, name, type);
};
/**
 * Extend ctx.typeEnv with deterministic types for declared variables.
 *
 * Used to type later references to locals without resorting to "unknown" fallbacks.
 * This is required for correct emission of operators like `!x` (boolean vs truthiness).
 */
export const withVariableTypeEnv = (ctx, tsDecls, ir) => {
    let nextEnv;
    const ensureEnv = () => {
        if (!nextEnv)
            nextEnv = new Map(ctx.typeEnv ?? []);
        return nextEnv;
    };
    for (let i = 0; i < tsDecls.length; i++) {
        const tsDecl = tsDecls[i];
        const irDecl = ir.declarations[i];
        if (!tsDecl || !irDecl)
            continue;
        const inferredType = deriveDeclaratorType(irDecl);
        if (!inferredType)
            continue;
        extendEnvForBindingName(ctx, tsDecl.name, inferredType, ensureEnv);
    }
    return nextEnv ? { ...ctx, typeEnv: nextEnv } : ctx;
};
//# sourceMappingURL=type-env.js.map