/**
 * Interface declaration converter
 */
import * as ts from "typescript";
import { hasExportModifier, hasReadonlyModifier, convertTypeParameters, convertParameters, } from "../helpers.js";
import { tryResolveDeterministicPropertyName } from "../../../syntax/property-names.js";
/**
 * Convert interface member
 */
export const convertInterfaceMember = (node, ctx) => {
    if (ts.isPropertySignature(node) && node.type) {
        const memberName = tryResolveDeterministicPropertyName(node.name);
        if (!memberName)
            return null;
        return {
            kind: "propertySignature",
            name: memberName,
            type: ctx.typeSystem.typeFromSyntax(ctx.binding.captureTypeSyntax(node.type)),
            isOptional: !!node.questionToken,
            isReadonly: hasReadonlyModifier(node),
        };
    }
    if (ts.isMethodSignature(node)) {
        const memberName = tryResolveDeterministicPropertyName(node.name);
        if (!memberName)
            return null;
        return {
            kind: "methodSignature",
            name: memberName,
            typeParameters: convertTypeParameters(node.typeParameters, ctx),
            parameters: convertParameters(node.parameters, ctx),
            returnType: node.type
                ? ctx.typeSystem.typeFromSyntax(ctx.binding.captureTypeSyntax(node.type))
                : undefined,
        };
    }
    return null;
};
/**
 * Check if a type reference is the struct marker.
 * DETERMINISTIC: Uses only the AST expression text, not TypeScript type resolution.
 */
const isStructMarker = (typeRef) => {
    // Check the expression directly - it should be an identifier named "struct" or "Struct"
    if (ts.isIdentifier(typeRef.expression)) {
        const name = typeRef.expression.text;
        return name === "struct" || name === "Struct";
    }
    return false;
};
/**
 * Unwrap `Interface<T>` in heritage clauses.
 *
 * Nominal CLR interface brands add internal `__tsonic_iface_*` members. We provide
 * `Interface<IFoo>` (from @tsonic/core/lang) to strip those at the TS layer.
 *
 * For IR + C# emission, we want the underlying CLR interface `IFoo`.
 */
const unwrapInterfaceHeritageType = (typeRef) => {
    if (ts.isIdentifier(typeRef.expression) &&
        typeRef.expression.text === "Interface" &&
        typeRef.typeArguments?.length === 1) {
        const only = typeRef.typeArguments[0];
        if (only)
            return only;
    }
    // ExpressionWithTypeArguments is a TypeNode in TS's AST for heritage clauses.
    return typeRef;
};
/**
 * Check if an interface has only index signatures (no property/method members).
 * Returns the dictionary type info if so, undefined otherwise.
 *
 * This handles interfaces like:
 *   interface NumberIndexed { [key: number]: string; }
 *
 * These should be lowered to type aliases for Dictionary<K, V>.
 */
const extractIndexSignatureOnlyInterface = (node, ctx) => {
    const members = node.members;
    // Must have exactly one member
    if (members.length !== 1) {
        return undefined;
    }
    const member = members[0];
    if (!member || !ts.isIndexSignatureDeclaration(member)) {
        return undefined;
    }
    // Extract key type from the index signature parameter
    const param = member.parameters[0];
    if (!param || !param.type) {
        return undefined;
    }
    const keyType = ctx.typeSystem.typeFromSyntax(ctx.binding.captureTypeSyntax(param.type));
    // Only allow string or number keys (enforced by TSN7413)
    if (keyType.kind !== "primitiveType" ||
        (keyType.name !== "string" && keyType.name !== "number")) {
        return undefined;
    }
    // Extract value type
    if (!member.type) {
        return undefined;
    }
    const valueType = ctx.typeSystem.typeFromSyntax(ctx.binding.captureTypeSyntax(member.type));
    return { keyType, valueType };
};
const convertCallSignatureType = (node, ctx) => {
    if (node.typeParameters && node.typeParameters.length > 0) {
        return undefined;
    }
    return {
        kind: "functionType",
        parameters: convertParameters(node.parameters, ctx),
        returnType: node.type
            ? ctx.typeSystem.typeFromSyntax(ctx.binding.captureTypeSyntax(node.type))
            : { kind: "voidType" },
    };
};
const extractCallableInterfaceOnlyType = (node, ctx) => {
    if (node.typeParameters && node.typeParameters.length > 0) {
        return undefined;
    }
    if (node.heritageClauses && node.heritageClauses.length > 0) {
        return undefined;
    }
    if (node.members.length === 0) {
        return undefined;
    }
    if (!node.members.every((member) => ts.isCallSignatureDeclaration(member))) {
        return undefined;
    }
    const signatures = [];
    for (const member of node.members) {
        if (!ts.isCallSignatureDeclaration(member)) {
            return undefined;
        }
        const signature = convertCallSignatureType(member, ctx);
        if (!signature) {
            return undefined;
        }
        signatures.push(signature);
    }
    if (signatures.length === 0) {
        return undefined;
    }
    if (signatures.length === 1) {
        return signatures[0];
    }
    return {
        kind: "intersectionType",
        types: signatures,
    };
};
/**
 * Check if an interface declaration IS the struct marker itself (should be filtered out)
 */
const isMarkerInterface = (node) => {
    const name = node.name.text;
    if (name !== "struct" && name !== "Struct") {
        return false;
    }
    // Check if it has only the __brand property
    const members = node.members;
    if (members.length !== 1) {
        return false;
    }
    const member = members[0];
    if (!member || !ts.isPropertySignature(member)) {
        return false;
    }
    const memberName = member.name && ts.isIdentifier(member.name) ? member.name.text : "";
    return memberName === "__brand";
};
/**
 * Convert interface declaration
 * Returns null for marker interfaces that should be filtered out.
 * Returns a type alias for index-signature-only interfaces (lowered to Dictionary).
 */
export const convertInterfaceDeclaration = (node, ctx) => {
    // Filter out marker interfaces completely
    if (isMarkerInterface(node)) {
        return null;
    }
    // Check for index-signature-only interface → lower to type alias for dictionary
    const dictInfo = extractIndexSignatureOnlyInterface(node, ctx);
    if (dictInfo) {
        return {
            kind: "typeAliasDeclaration",
            name: node.name.text,
            typeParameters: convertTypeParameters(node.typeParameters, ctx),
            type: {
                kind: "dictionaryType",
                keyType: dictInfo.keyType,
                valueType: dictInfo.valueType,
            },
            isExported: hasExportModifier(node),
            isStruct: false,
        };
    }
    const callableType = extractCallableInterfaceOnlyType(node, ctx);
    if (callableType) {
        return {
            kind: "typeAliasDeclaration",
            name: node.name.text,
            typeParameters: [],
            type: callableType,
            isExported: hasExportModifier(node),
            isStruct: false,
        };
    }
    // Detect struct marker in extends clause
    let isStruct = false;
    const extendsClause = node.heritageClauses?.find((h) => h.token === ts.SyntaxKind.ExtendsKeyword);
    const extendsTypes = extendsClause?.types
        .filter((t) => {
        if (isStructMarker(t)) {
            isStruct = true;
            return false; // Remove marker from extends
        }
        return true;
    })
        .map((t) => ctx.typeSystem.typeFromSyntax(ctx.binding.captureTypeSyntax(unwrapInterfaceHeritageType(t)))) ?? [];
    const allMembers = node.members
        .map((m) => convertInterfaceMember(m, ctx))
        .filter((m) => m !== null);
    // Filter out __brand property if this is a struct
    const finalMembers = isStruct
        ? allMembers.filter((m) => m.kind !== "propertySignature" || m.name !== "__brand")
        : allMembers;
    return {
        kind: "interfaceDeclaration",
        name: node.name.text,
        typeParameters: convertTypeParameters(node.typeParameters, ctx),
        extends: extendsTypes,
        members: finalMembers,
        isExported: hasExportModifier(node),
        isStruct,
    };
};
//# sourceMappingURL=interfaces.js.map