/**
 * Member access expression converter orchestrator
 *
 * Combines member-resolution and binding-resolution to convert
 * property access and element access expressions.
 */
import * as ts from "typescript";
import { getSourceSpan } from "../helpers.js";
import { convertExpression } from "../../../expression-converter.js";
import { getDeclaredPropertyType, classifyComputedAccess, deriveElementType, hasDeclaredMemberByName, resolveComputedAccessProtocol, } from "./member-resolution.js";
import { getCurrentTypeForAccessExpression, hasAccessPathNarrowing, } from "../../access-paths.js";
import { shouldWrapExpressionWithAssertion } from "../../assertion-wrapping.js";
import { resolveHierarchicalBinding, resolveHierarchicalBindingFromMemberId, resolveExtensionMethodsBinding, } from "./binding-resolution.js";
import { createDiagnostic } from "../../../../types/diagnostic.js";
import { SUPPORTED_IMPORT_META_FIELDS, tryConvertImportMetaProperty, } from "../import-meta.js";
import { isWellKnownSymbolPropertyName, tryResolveDeterministicPropertyNameFromExpression, } from "../../../syntax/property-names.js";
import { tryGetObjectLiteralMethodArgumentCapture, tryGetObjectLiteralMethodArgumentsLength, } from "../../../../object-literal-method-runtime.js";
/**
 * Convert property access or element access expression
 */
export const convertMemberExpression = (node, ctx) => {
    const isOptional = node.questionDotToken !== undefined;
    const sourceSpan = getSourceSpan(node);
    if (ts.isPropertyAccessExpression(node)) {
        const importMetaExpr = tryConvertImportMetaProperty(node, ctx);
        if (importMetaExpr)
            return importMetaExpr;
        const objectMethodArgumentsLength = tryGetObjectLiteralMethodArgumentsLength(node);
        if (objectMethodArgumentsLength !== undefined) {
            return {
                kind: "literal",
                value: objectMethodArgumentsLength,
                raw: String(objectMethodArgumentsLength),
                inferredType: { kind: "primitiveType", name: "int" },
                sourceSpan,
            };
        }
        if (ts.isMetaProperty(node.expression) &&
            node.expression.keywordToken === ts.SyntaxKind.ImportKeyword &&
            node.expression.name.text === "meta" &&
            !SUPPORTED_IMPORT_META_FIELDS.has(node.name.text)) {
            ctx.diagnostics.push(createDiagnostic("TSN2001", "error", `import.meta.${node.name.text} is not supported in strict AOT mode`, getSourceSpan(node), "Supported fields: import.meta.url, import.meta.filename, import.meta.dirname"));
            return {
                kind: "literal",
                value: undefined,
                raw: "undefined",
                inferredType: { kind: "primitiveType", name: "undefined" },
                sourceSpan,
            };
        }
        const object = convertExpression(node.expression, ctx, undefined);
        const propertyName = node.name.text;
        const currentReceiverType = getCurrentTypeForAccessExpression(node.expression, ctx);
        const bindingResolutionObject = currentReceiverType !== undefined
            ? { ...object, inferredType: currentReceiverType }
            : object;
        const exactMemberId = ctx.binding.resolvePropertyAccess(node);
        const exactDeclaringTypeName = exactMemberId !== undefined
            ? ctx.binding.getDeclaringTypeNameOfMember(exactMemberId)
            : undefined;
        const exactMemberBinding = exactMemberId !== undefined &&
            !exactDeclaringTypeName?.startsWith("__Ext_") &&
            !exactDeclaringTypeName?.startsWith("__TsonicExtMethods_")
            ? resolveHierarchicalBindingFromMemberId(node, propertyName, bindingResolutionObject, ctx)
            : undefined;
        // Try to resolve hierarchical binding
        const memberBinding = resolveExtensionMethodsBinding(node, propertyName, bindingResolutionObject, ctx) ??
            exactMemberBinding ??
            (exactMemberId === undefined
                ? resolveHierarchicalBinding(bindingResolutionObject, propertyName, ctx)
                : undefined);
        // DETERMINISTIC TYPING: Property type comes from explicit TypeSystem queries only.
        //
        // The receiver's inferredType enables NominalEnv to walk inheritance chains
        // and substitute type parameters correctly for inherited generic members.
        //
        // Built-ins like string.length work because globals declare them with proper types.
        // If getDeclaredPropertyType returns undefined, it means the property declaration
        // is missing - use unknownType as poison so validation can emit TSN5203.
        //
        // EXCEPTION: If memberBinding exists AND declaredType is undefined, return undefined.
        // This handles pure CLR-bound methods like Console.WriteLine that have no TS declaration.
        const narrowedAccessType = hasAccessPathNarrowing(node, ctx) || currentReceiverType !== undefined
            ? getCurrentTypeForAccessExpression(node, ctx)
            : undefined;
        const declaredType = getDeclaredPropertyType(node, currentReceiverType ?? object.inferredType, ctx);
        // Hierarchical bindings: namespace.type is a static type reference, not a runtime
        // value. When this pattern is present in the binding manifest, avoid poisoning the
        // receiver with unknownType; the emitter uses "no inferredType" to classify the
        // receiver as a static type, enabling global::Type.Member emission.
        const isNamespaceTypeReference = object.kind === "identifier" &&
            ctx.bindings
                .getNamespace(object.name)
                ?.types.some((t) => t.alias === propertyName) === true;
        // DETERMINISTIC TYPING: Set inferredType for validation passes (like numeric proof).
        // The emitter uses memberBinding separately for C# casing (e.g., length -> Length).
        //
        // Priority order for inferredType:
        // 1. If declaredType exists, use it (covers built-ins like string.length -> int)
        // 2. If memberBinding exists but no declaredType, use undefined (pure CLR-bound)
        // 3. Otherwise, poison with unknownType for validation (TSN5203)
        //
        // Note: Both memberBinding AND inferredType can be set - they serve different purposes:
        // - memberBinding: used by emitter for C# member names
        // - inferredType: used by validation passes for type checking
        //
        // Class fields without explicit type annotations will emit TSN5203.
        // Users must add explicit types like `count: int = 0` instead of `count = 0`.
        const propertyInferredType = declaredType
            ? declaredType
            : isNamespaceTypeReference
                ? undefined
                : memberBinding
                    ? undefined
                    : { kind: "unknownType" };
        const baseMemberAccess = {
            kind: "memberAccess",
            object,
            property: propertyName,
            isComputed: false,
            isOptional,
            inferredType: declaredType ?? propertyInferredType,
            sourceSpan,
            memberBinding,
        };
        if (narrowedAccessType &&
            shouldWrapExpressionWithAssertion(ctx, declaredType, narrowedAccessType)) {
            return {
                kind: "typeAssertion",
                expression: baseMemberAccess,
                targetType: narrowedAccessType,
                inferredType: narrowedAccessType,
                sourceSpan,
            };
        }
        return {
            ...baseMemberAccess,
            inferredType: propertyInferredType,
        };
    }
    else {
        const objectMethodArgumentCapture = tryGetObjectLiteralMethodArgumentCapture(node);
        if (objectMethodArgumentCapture) {
            const inferredType = objectMethodArgumentCapture.parameter.type
                ? ctx.typeSystem.typeFromSyntax(ctx.binding.captureTypeSyntax(objectMethodArgumentCapture.parameter.type))
                : undefined;
            return {
                kind: "identifier",
                name: objectMethodArgumentCapture.tempName,
                inferredType,
                sourceSpan,
            };
        }
        // Element access (computed): obj[expr]
        const object = convertExpression(node.expression, ctx, undefined);
        const deterministicPropertyName = (() => {
            const arg = node.argumentExpression;
            if (!arg)
                return undefined;
            return tryResolveDeterministicPropertyNameFromExpression(arg);
        })();
        const computedAccessKind = classifyComputedAccess(object.inferredType, ctx);
        const deterministicSymbolAccess = deterministicPropertyName !== undefined &&
            isWellKnownSymbolPropertyName(deterministicPropertyName);
        const hasDeterministicMember = deterministicPropertyName !== undefined &&
            hasDeclaredMemberByName(object.inferredType, deterministicPropertyName, ctx);
        if (deterministicPropertyName !== undefined &&
            computedAccessKind !== "dictionary" &&
            (deterministicSymbolAccess || object.inferredType !== undefined)) {
            const currentAccessType = hasAccessPathNarrowing(node, ctx) || object.inferredType !== undefined
                ? getCurrentTypeForAccessExpression(node, ctx)
                : undefined;
            const declaredType = object.inferredType !== undefined
                ? ctx.typeSystem.typeOfMember(object.inferredType, {
                    kind: "byName",
                    name: deterministicPropertyName,
                })
                : { kind: "unknownType" };
            if (declaredType.kind !== "unknownType" ||
                deterministicSymbolAccess ||
                hasDeterministicMember) {
                const memberBinding = resolveHierarchicalBinding(object, deterministicPropertyName, ctx);
                const baseMemberAccess = {
                    kind: "memberAccess",
                    object,
                    property: deterministicPropertyName,
                    isComputed: false,
                    isOptional,
                    inferredType: declaredType,
                    sourceSpan,
                    memberBinding,
                };
                if (currentAccessType &&
                    shouldWrapExpressionWithAssertion(ctx, declaredType, currentAccessType)) {
                    return {
                        kind: "typeAssertion",
                        expression: baseMemberAccess,
                        targetType: currentAccessType,
                        inferredType: currentAccessType,
                        sourceSpan,
                    };
                }
                return baseMemberAccess;
            }
        }
        // DETERMINISTIC TYPING: Use object's inferredType (not getInferredType)
        const currentReceiverType = getCurrentTypeForAccessExpression(node.expression, ctx);
        const objectType = currentReceiverType ?? object.inferredType;
        // Classify the access kind for proof pass
        // This determines whether Int32 proof is required for the index
        const accessKind = classifyComputedAccess(objectType, ctx);
        const narrowedAccessType = hasAccessPathNarrowing(node, ctx) || objectType !== undefined
            ? getCurrentTypeForAccessExpression(node, ctx)
            : undefined;
        // Derive element type from object type
        const elementType = narrowedAccessType ??
            deriveElementType(objectType, ctx, node.argumentExpression);
        const accessProtocol = resolveComputedAccessProtocol(objectType, ctx);
        const baseElementAccess = {
            kind: "memberAccess",
            object,
            property: convertExpression(node.argumentExpression, ctx, undefined),
            isComputed: true,
            isOptional,
            inferredType: deriveElementType(objectType, ctx, node.argumentExpression),
            sourceSpan,
            accessKind,
            accessProtocol,
        };
        if (narrowedAccessType &&
            shouldWrapExpressionWithAssertion(ctx, deriveElementType(objectType, ctx, node.argumentExpression), narrowedAccessType)) {
            return {
                kind: "typeAssertion",
                expression: baseElementAccess,
                targetType: narrowedAccessType,
                inferredType: narrowedAccessType,
                sourceSpan,
            };
        }
        return {
            ...baseElementAccess,
            inferredType: elementType,
        };
    }
};
//# sourceMappingURL=access-converter.js.map