/**
 * Type conversion orchestrator – facade that delegates to sub-modules.
 *
 * Sub-modules:
 *   - type-operators.ts: keyof, indexed access, template literals, shared helpers
 *   - value-inference.ts: deterministic value type inference from declarations/expressions
 */
import * as ts from "typescript";
import { convertPrimitiveKeyword } from "./primitives.js";
import { convertTypeReference } from "./references.js";
import { convertArrayType } from "./arrays.js";
import { convertFunctionType } from "./functions.js";
import { convertObjectType } from "./objects.js";
import { convertUnionType, convertIntersectionType, } from "./unions-intersections.js";
import { convertLiteralType } from "./literals.js";
import { toUnionOrSingle, resolveKeyofFromType, resolveIndexedAccessFromTypes, convertTemplateLiteralType, } from "./type-operators.js";
import { getTypeParameterConstraintNode, withTypeParameterConstraint, inferTypeFromValueDeclaration, } from "./value-inference.js";
/**
 * Convert TypeScript type node to IR type
 */
const POLYMORPHIC_THIS_MARKER = "__tsonic_polymorphic_this";
export const convertType = (typeNode, binding) => {
    // Heritage clause type syntax (`extends Foo<T>`, `implements Bar<U>`) is represented
    // as ExpressionWithTypeArguments in the TS AST. This must be treated like a normal
    // type reference so NominalEnv can compute substitution through inheritance chains.
    if (ts.isExpressionWithTypeArguments(typeNode)) {
        const toEntityName = (expr) => {
            if (ts.isIdentifier(expr))
                return expr;
            if (ts.isPropertyAccessExpression(expr)) {
                if (!ts.isIdentifier(expr.name))
                    return undefined;
                const left = toEntityName(expr.expression);
                return left
                    ? ts.factory.createQualifiedName(left, expr.name)
                    : undefined;
            }
            return undefined;
        };
        const entityName = toEntityName(typeNode.expression);
        if (entityName) {
            const ref = ts.factory.createTypeReferenceNode(entityName, typeNode.typeArguments);
            return convertTypeReference(ref, binding, convertType);
        }
        // Fallback: preserve text form (should be rare; computed expressions).
        return {
            kind: "referenceType",
            name: typeNode.expression.getText(),
            typeArguments: typeNode.typeArguments?.map((t) => convertType(t, binding)),
        };
    }
    // Type references (including primitive type names)
    if (ts.isTypeReferenceNode(typeNode)) {
        return convertTypeReference(typeNode, binding, convertType);
    }
    if (ts.isThisTypeNode(typeNode)) {
        return {
            kind: "typeParameterType",
            name: POLYMORPHIC_THIS_MARKER,
        };
    }
    // Primitive keywords
    const primitiveType = convertPrimitiveKeyword(typeNode.kind);
    if (primitiveType) {
        return primitiveType;
    }
    // Array types
    if (ts.isArrayTypeNode(typeNode)) {
        return convertArrayType(typeNode, binding, convertType);
    }
    // Tuple types
    if (ts.isTupleTypeNode(typeNode)) {
        // Check for rest elements.
        const hasRest = typeNode.elements.some((el) => ts.isRestTypeNode(el) ||
            (ts.isNamedTupleMember(el) &&
                (el.dotDotDotToken !== undefined || ts.isRestTypeNode(el.type))));
        if (hasRest) {
            const restIndex = typeNode.elements.findIndex((el) => ts.isRestTypeNode(el) ||
                (ts.isNamedTupleMember(el) &&
                    (el.dotDotDotToken !== undefined || ts.isRestTypeNode(el.type))));
            const elementTypes = [];
            for (const element of typeNode.elements) {
                if (ts.isNamedTupleMember(element)) {
                    if (element.dotDotDotToken !== undefined ||
                        ts.isRestTypeNode(element.type)) {
                        const restType = ts.isRestTypeNode(element.type)
                            ? element.type.type
                            : element.type;
                        if (ts.isArrayTypeNode(restType)) {
                            elementTypes.push(convertType(restType.elementType, binding));
                            continue;
                        }
                        if (ts.isTupleTypeNode(restType)) {
                            for (const nestedElement of restType.elements) {
                                if (ts.isNamedTupleMember(nestedElement)) {
                                    elementTypes.push(convertType(nestedElement.type, binding));
                                }
                                else if (ts.isRestTypeNode(nestedElement)) {
                                    if (ts.isArrayTypeNode(nestedElement.type)) {
                                        elementTypes.push(convertType(nestedElement.type.elementType, binding));
                                    }
                                    else {
                                        elementTypes.push({ kind: "unknownType" });
                                    }
                                }
                                else {
                                    elementTypes.push(convertType(nestedElement, binding));
                                }
                            }
                            continue;
                        }
                        elementTypes.push({ kind: "unknownType" });
                        continue;
                    }
                    elementTypes.push(convertType(element.type, binding));
                    continue;
                }
                if (ts.isRestTypeNode(element)) {
                    const restType = element.type;
                    if (ts.isArrayTypeNode(restType)) {
                        elementTypes.push(convertType(restType.elementType, binding));
                        continue;
                    }
                    if (ts.isTupleTypeNode(restType)) {
                        for (const nestedElement of restType.elements) {
                            if (ts.isNamedTupleMember(nestedElement)) {
                                elementTypes.push(convertType(nestedElement.type, binding));
                            }
                            else if (ts.isRestTypeNode(nestedElement)) {
                                if (ts.isArrayTypeNode(nestedElement.type)) {
                                    elementTypes.push(convertType(nestedElement.type.elementType, binding));
                                }
                                else {
                                    elementTypes.push({ kind: "unknownType" });
                                }
                            }
                            else {
                                elementTypes.push(convertType(nestedElement, binding));
                            }
                        }
                        continue;
                    }
                    elementTypes.push({ kind: "unknownType" });
                    continue;
                }
                elementTypes.push(convertType(element, binding));
            }
            const hasRepresentableSpreadMetadata = restIndex >= 0 &&
                typeNode.elements
                    .slice(restIndex + 1)
                    .every((element) => ts.isRestTypeNode(element) ||
                    (ts.isNamedTupleMember(element) &&
                        (element.dotDotDotToken !== undefined ||
                            ts.isRestTypeNode(element.type))));
            const tuplePrefixElementTypes = hasRepresentableSpreadMetadata
                ? typeNode.elements.slice(0, restIndex).flatMap((element) => {
                    if (ts.isNamedTupleMember(element)) {
                        return [convertType(element.type, binding)];
                    }
                    if (ts.isRestTypeNode(element)) {
                        return [];
                    }
                    return [convertType(element, binding)];
                })
                : undefined;
            const tupleRestElementType = hasRepresentableSpreadMetadata
                ? (() => {
                    const restElement = typeNode.elements[restIndex];
                    if (!restElement)
                        return undefined;
                    if (ts.isNamedTupleMember(restElement)) {
                        const restType = ts.isRestTypeNode(restElement.type)
                            ? restElement.type.type
                            : restElement.type;
                        if (ts.isArrayTypeNode(restType)) {
                            return convertType(restType.elementType, binding);
                        }
                        if (ts.isTupleTypeNode(restType)) {
                            const nestedRest = restType.elements.find((nestedElement) => ts.isRestTypeNode(nestedElement));
                            if (nestedRest && ts.isArrayTypeNode(nestedRest.type)) {
                                return convertType(nestedRest.type.elementType, binding);
                            }
                        }
                        return undefined;
                    }
                    if (ts.isRestTypeNode(restElement)) {
                        const restType = restElement.type;
                        if (ts.isArrayTypeNode(restType)) {
                            return convertType(restType.elementType, binding);
                        }
                        if (ts.isTupleTypeNode(restType)) {
                            const nestedRest = restType.elements.find((nestedElement) => ts.isRestTypeNode(nestedElement));
                            if (nestedRest && ts.isArrayTypeNode(nestedRest.type)) {
                                return convertType(nestedRest.type.elementType, binding);
                            }
                        }
                    }
                    return undefined;
                })()
                : undefined;
            return {
                kind: "arrayType",
                elementType: toUnionOrSingle(elementTypes),
                origin: "explicit",
                ...((tuplePrefixElementTypes?.length ?? 0) > 0
                    ? { tuplePrefixElementTypes }
                    : {}),
                ...(tupleRestElementType ? { tupleRestElementType } : {}),
            };
        }
        const elementTypes = typeNode.elements.map((element) => {
            // Handle named tuple elements (e.g., [name: string, age: number])
            if (ts.isNamedTupleMember(element)) {
                return convertType(element.type, binding);
            }
            return convertType(element, binding);
        });
        return { kind: "tupleType", elementTypes };
    }
    // Function types
    if (ts.isFunctionTypeNode(typeNode)) {
        return convertFunctionType(typeNode, binding, convertType);
    }
    // Constructor types project to their constructed value type.
    // Static-side members are preserved separately by enclosing intersections.
    if (ts.isConstructorTypeNode(typeNode)) {
        return convertType(typeNode.type, binding);
    }
    // Object/interface types
    if (ts.isTypeLiteralNode(typeNode)) {
        return convertObjectType(typeNode, binding, convertType);
    }
    // Union types
    if (ts.isUnionTypeNode(typeNode)) {
        return convertUnionType(typeNode, binding, convertType);
    }
    // Intersection types
    if (ts.isIntersectionTypeNode(typeNode)) {
        return convertIntersectionType(typeNode, binding, convertType);
    }
    // Mapped types
    //
    // Direct mapped syntax is TS-only and has no first-class CLR type equivalent.
    // For deterministic AOT lowering we treat it as `unknown` at IR level
    // (which emits to `object?`) instead of falling back to anyType/ICE.
    if (ts.isMappedTypeNode(typeNode)) {
        return { kind: "unknownType" };
    }
    // Conditional types
    //
    // Utility-conditionals (Extract/Exclude/NonNullable/...) are expanded via
    // type references in convertTypeReference(). Direct conditional syntax is
    // lowered conservatively to `unknown` for stable emission.
    if (ts.isConditionalTypeNode(typeNode)) {
        return { kind: "unknownType" };
    }
    // infer type nodes are only valid within conditional types. If one survives
    // to direct conversion, lower conservatively to unknown.
    if (ts.isInferTypeNode(typeNode)) {
        return { kind: "unknownType" };
    }
    // Literal types
    if (ts.isLiteralTypeNode(typeNode)) {
        return convertLiteralType(typeNode);
    }
    // Parenthesized types
    if (ts.isParenthesizedTypeNode(typeNode)) {
        return convertType(typeNode.type, binding);
    }
    // Type operators
    // - `readonly T[]` should behave like `T[]` at the IR level (we do not model
    //   readonly-ness in emitted C# types).
    // - `keyof T` is lowered deterministically when key information can be
    //   recovered from structural IR type data.
    if (ts.isTypeOperatorNode(typeNode)) {
        if (typeNode.operator === ts.SyntaxKind.ReadonlyKeyword) {
            return convertType(typeNode.type, binding);
        }
        if (typeNode.operator === ts.SyntaxKind.KeyOfKeyword) {
            const constraint = getTypeParameterConstraintNode(typeNode.type, binding);
            const target = constraint ?? typeNode.type;
            return resolveKeyofFromType(convertType(target, binding));
        }
    }
    // Indexed access types: T[K]
    if (ts.isIndexedAccessTypeNode(typeNode)) {
        const objectType = convertType(withTypeParameterConstraint(typeNode.objectType, binding), binding);
        const indexType = convertType(withTypeParameterConstraint(typeNode.indexType, binding), binding);
        return resolveIndexedAccessFromTypes(objectType, indexType);
    }
    // Template literal types.
    if (ts.isTemplateLiteralTypeNode(typeNode)) {
        return convertTemplateLiteralType(typeNode, binding, convertType);
    }
    // Type predicate return types: (x is T) has no direct C# type-level equivalent.
    // MVP: lower to boolean so we can emit valid C# and avoid anyType/ICE.
    if (ts.isTypePredicateNode(typeNode)) {
        return { kind: "primitiveType", name: "boolean" };
    }
    // TypeQuery: typeof X - resolve to the type of the referenced value
    // DETERMINISTIC: Get type from declaration's TypeNode, not TS inference
    if (ts.isTypeQueryNode(typeNode)) {
        const exprName = typeNode.exprName;
        if (ts.isIdentifier(exprName)) {
            const declId = binding.resolveIdentifier(exprName);
            if (declId) {
                const declInfo = binding
                    ._getHandleRegistry()
                    .getDecl(declId);
                const inferred = inferTypeFromValueDeclaration((declInfo?.declNode ??
                    declInfo?.valueDeclNode ??
                    declInfo?.typeDeclNode), binding, new Set([declId.id]), convertType);
                if (inferred) {
                    return inferred;
                }
            }
        }
        // For qualified names (A.B.C), fall through to anyType
        // Qualified name typeof is intentionally unsupported here (IR gate emits TSN7414).
        return { kind: "anyType" };
    }
    // Default: use anyType as marker for unsupported types
    // The IR soundness gate will catch this and emit TSN7414
    return { kind: "anyType" };
};
//# sourceMappingURL=orchestrator.js.map