// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import { UsageFlags, getAccessOverride, isHttpMetadata, } from "@azure-tools/typespec-client-generator-core";
import { NoTarget } from "@typespec/compiler";
import { isReadOnly } from "./utils.js";
export function fromSdkType(sdkContext, sdkType, sdkProperty, namespace) {
    let retVar = sdkContext.__typeCache.types.get(sdkType);
    if (retVar) {
        return retVar;
    }
    switch (sdkType.kind) {
        case "nullable":
            const nullableType = {
                kind: "nullable",
                type: fromSdkType(sdkContext, sdkType.type, sdkProperty, namespace),
                namespace: sdkType.namespace,
                external: fromSdkExternalTypeInfo(sdkType),
            };
            retVar = nullableType;
            break;
        case "model":
            retVar = fromSdkModelType(sdkContext, sdkType);
            break;
        case "enum":
            retVar = fromSdkEnumType(sdkContext, sdkType);
            break;
        case "enumvalue":
            retVar = fromSdkEnumValueType(sdkContext, sdkType);
            break;
        case "dict":
            retVar = fromSdkDictionaryType(sdkContext, sdkType);
            break;
        case "array":
            retVar = fromSdkArrayType(sdkContext, sdkType);
            break;
        case "constant":
            // Don't transform optional Content-Type headers into enums - keep them as constants
            const isContentTypeHeader = sdkProperty &&
                "kind" in sdkProperty &&
                sdkProperty.kind === "header" &&
                "serializedName" in sdkProperty &&
                typeof sdkProperty.serializedName === "string" &&
                sdkProperty.serializedName.toLocaleLowerCase() === "content-type";
            if (sdkProperty &&
                !isContentTypeHeader &&
                (sdkProperty.optional || sdkProperty?.type.kind === "nullable") &&
                sdkProperty?.type.kind !== "boolean" &&
                sdkType.valueType.kind !== "boolean") {
                // turn the constant into an extensible enum
                retVar = createEnumType(sdkContext, sdkType, namespace);
            }
            else {
                retVar = fromSdkConstantType(sdkContext, sdkType);
            }
            break;
        case "union":
            retVar = fromUnionType(sdkContext, sdkType);
            break;
        case "utcDateTime":
        case "offsetDateTime":
            retVar = fromSdkDateTimeType(sdkContext, sdkType);
            break;
        case "duration":
            retVar = fromSdkDurationType(sdkContext, sdkType);
            break;
        case "tuple":
            sdkContext.logger.reportDiagnostic({
                code: "unsupported-sdk-type",
                format: { sdkType: "tuple" },
                target: sdkType.__raw ?? NoTarget,
            });
            const tupleType = {
                kind: "unknown",
                name: "tuple",
                crossLanguageDefinitionId: "",
                decorators: sdkType.decorators,
                external: fromSdkExternalTypeInfo(sdkType),
            };
            retVar = tupleType;
            break;
        // TODO -- endpoint and credential are handled separately in emitter, since we have specific locations for them in input model.
        // We can handle unify the way we handle them in the future, probably by chaning the input model schema and do the conversion in generator.
        case "endpoint":
            retVar = fromSdkEndpointType();
            break;
        case "credential":
            sdkContext.logger.reportDiagnostic({
                code: "unsupported-sdk-type",
                format: { sdkType: "credential" },
                target: sdkType.__raw ?? NoTarget,
            });
            const credentialType = {
                kind: "unknown",
                name: "credential",
                crossLanguageDefinitionId: "",
                decorators: sdkType.decorators,
                external: fromSdkExternalTypeInfo(sdkType),
            };
            retVar = credentialType;
            break;
        default:
            retVar = fromSdkBuiltInType(sdkContext, sdkType);
            break;
    }
    sdkContext.__typeCache.updateSdkTypeReferences(sdkType, retVar);
    // we have to cast to any because TypeScript's type narrowing does not automatically infer the return type for conditional types
    return retVar;
}
function fromSdkModelType(sdkContext, modelType) {
    // get all unique decorators for the model type from the namespace level and the model level
    let decorators = modelType.decorators;
    const namespace = sdkContext.__typeCache.namespaces.get(modelType.namespace);
    if (namespace) {
        decorators = getAllModelDecorators(namespace, modelType.decorators);
    }
    const inputModelType = {
        kind: "model",
        name: modelType.name,
        namespace: modelType.namespace,
        crossLanguageDefinitionId: modelType.crossLanguageDefinitionId,
        access: getAccessOverride(sdkContext, modelType.__raw),
        usage: modelType.usage,
        deprecation: modelType.deprecation,
        doc: modelType.doc,
        summary: modelType.summary,
        discriminatorValue: modelType.discriminatorValue,
        decorators: decorators,
        external: fromSdkExternalTypeInfo(modelType),
        serializationOptions: modelType.serializationOptions,
    };
    sdkContext.__typeCache.updateSdkTypeReferences(modelType, inputModelType);
    inputModelType.additionalProperties = modelType.additionalProperties
        ? fromSdkType(sdkContext, modelType.additionalProperties)
        : undefined;
    const properties = [];
    for (const property of modelType.properties) {
        const ourProperty = fromSdkModelProperty(sdkContext, property, modelType);
        if (ourProperty) {
            properties.push(ourProperty);
        }
    }
    inputModelType.discriminatorProperty = modelType.discriminatorProperty
        ? fromSdkModelProperty(sdkContext, modelType.discriminatorProperty, modelType)
        : undefined;
    inputModelType.baseModel = modelType.baseModel
        ? fromSdkType(sdkContext, modelType.baseModel)
        : undefined;
    inputModelType.properties = properties;
    if (modelType.discriminatedSubtypes) {
        const discriminatedSubtypes = {};
        for (const key in modelType.discriminatedSubtypes) {
            const subtype = modelType.discriminatedSubtypes[key];
            discriminatedSubtypes[key] = fromSdkType(sdkContext, subtype);
        }
        inputModelType.discriminatedSubtypes = discriminatedSubtypes;
    }
    return inputModelType;
}
function fromSdkModelProperty(sdkContext, sdkProperty, sdkModel) {
    // TODO -- this returns undefined because some properties we do not support yet.
    let property = sdkContext.__typeCache.properties.get(sdkProperty);
    if (property) {
        return property;
    }
    const serializedName = sdkProperty.serializationOptions?.json?.name ??
        sdkProperty.serializationOptions?.xml?.name ??
        sdkProperty.serializationOptions?.multipart?.name;
    property = {
        kind: sdkProperty.kind,
        name: sdkProperty.name,
        serializedName: serializedName,
        summary: sdkProperty.summary,
        doc: sdkProperty.doc,
        type: fromSdkType(sdkContext, sdkProperty.type, sdkProperty, sdkModel.namespace),
        optional: sdkProperty.optional,
        readOnly: isReadOnly(sdkProperty),
        discriminator: sdkProperty.discriminator,
        flatten: sdkProperty.flatten,
        decorators: sdkProperty.decorators,
        crossLanguageDefinitionId: sdkProperty.crossLanguageDefinitionId,
        serializationOptions: sdkProperty.serializationOptions,
        // A property is defined to be metadata if it is marked `@header`, `@cookie`, `@query`, `@path`.
        isHttpMetadata: isHttpMetadata(sdkContext, sdkProperty),
        encode: sdkProperty.encode,
    };
    if (property) {
        sdkContext.__typeCache.updateSdkPropertyReferences(sdkProperty, property);
    }
    return property;
}
function fromSdkEnumType(sdkContext, enumType) {
    return createEnumType(sdkContext, enumType, enumType.namespace);
}
function createEnumType(sdkContext, sdkType, namespace) {
    const values = [];
    const inputEnumType = {
        kind: "enum",
        name: sdkType.name,
        crossLanguageDefinitionId: sdkType.kind === "enum" ? sdkType.crossLanguageDefinitionId : "",
        valueType: sdkType.kind === "enum"
            ? fromSdkType(sdkContext, sdkType.valueType)
            : fromSdkBuiltInType(sdkContext, sdkType.valueType),
        values: values,
        // constantType.access, TODO - constant type now does not have access. TCGC will add it later
        access: sdkType.kind === "enum" ? getAccessOverride(sdkContext, sdkType.__raw) : undefined,
        namespace: namespace,
        deprecation: sdkType.deprecation,
        summary: sdkType.summary,
        doc: sdkType.doc,
        isFixed: sdkType.kind === "enum" ? sdkType.isFixed : false,
        isFlags: sdkType.kind === "enum" ? sdkType.isFlags : false,
        // constantType.usage, TODO - constant type now does not have usage. TCGC will add it later
        usage: sdkType.kind === "enum" ? sdkType.usage : UsageFlags.None,
        decorators: sdkType.decorators,
        external: fromSdkExternalTypeInfo(sdkType),
    };
    sdkContext.__typeCache.updateSdkTypeReferences(sdkType, inputEnumType);
    if (sdkType.kind === "enum") {
        for (const v of sdkType.values) {
            values.push(createEnumValueType(sdkContext, v, inputEnumType));
        }
    }
    else {
        values.push(createEnumValueType(sdkContext, sdkType, inputEnumType));
    }
    return inputEnumType;
}
function fromSdkDateTimeType(sdkContext, dateTimeType) {
    return {
        kind: dateTimeType.kind,
        name: dateTimeType.name,
        encode: dateTimeType.encode,
        wireType: fromSdkType(sdkContext, dateTimeType.wireType),
        crossLanguageDefinitionId: dateTimeType.crossLanguageDefinitionId,
        baseType: dateTimeType.baseType ? fromSdkType(sdkContext, dateTimeType.baseType) : undefined,
        decorators: dateTimeType.decorators,
        external: fromSdkExternalTypeInfo(dateTimeType),
    };
}
function fromSdkDurationType(sdkContext, durationType) {
    return {
        kind: durationType.kind,
        name: durationType.name,
        encode: durationType.encode,
        wireType: fromSdkType(sdkContext, durationType.wireType),
        crossLanguageDefinitionId: durationType.crossLanguageDefinitionId,
        baseType: durationType.baseType ? fromSdkType(sdkContext, durationType.baseType) : undefined,
        decorators: durationType.decorators,
        external: fromSdkExternalTypeInfo(durationType),
    };
}
function fromSdkBuiltInType(sdkContext, builtInType) {
    return {
        kind: builtInType.kind,
        name: builtInType.name,
        encode: builtInType.encode !== builtInType.kind ? builtInType.encode : undefined,
        crossLanguageDefinitionId: builtInType.crossLanguageDefinitionId,
        baseType: builtInType.baseType ? fromSdkType(sdkContext, builtInType.baseType) : undefined,
        decorators: builtInType.decorators,
        external: fromSdkExternalTypeInfo(builtInType),
    };
}
function fromUnionType(sdkContext, union) {
    const variantTypes = [];
    for (const value of union.variantTypes) {
        const variantType = fromSdkType(sdkContext, value);
        variantTypes.push(variantType);
    }
    return {
        kind: "union",
        name: union.name,
        variantTypes: variantTypes,
        namespace: union.namespace,
        decorators: union.decorators,
        external: fromSdkExternalTypeInfo(union),
    };
}
function fromSdkConstantType(sdkContext, constantType) {
    const literalType = {
        kind: constantType.kind,
        name: constantType.name,
        namespace: "", // constantType.namespace, TODO - constant type now does not have namespace. TCGC will add it later
        access: undefined, // constantType.access, TODO - constant type now does not have access. TCGC will add it later
        usage: UsageFlags.None, // constantType.usage, TODO - constant type now does not have usage. TCGC will add it later
        valueType: fromSdkType(sdkContext, constantType.valueType),
        value: constantType.value,
        decorators: constantType.decorators,
    };
    sdkContext.__typeCache.updateConstantCache(constantType, literalType);
    return literalType;
}
function fromSdkEnumValueType(sdkContext, enumValueType) {
    return createEnumValueType(sdkContext, enumValueType, enumValueType.enumType);
}
function createEnumValueType(sdkContext, sdkType, enumType) {
    return {
        kind: "enumvalue",
        name: sdkType.kind === "constant"
            ? sdkType.value === null
                ? "Null"
                : sdkType.value.toString()
            : sdkType.name,
        value: typeof sdkType.value === "boolean" ? (sdkType.value ? 1 : 0) : sdkType.value,
        valueType: sdkType.kind === "constant" ? sdkType.valueType : fromSdkType(sdkContext, sdkType.valueType),
        enumType: enumType,
        summary: sdkType.summary,
        doc: sdkType.doc,
        decorators: sdkType.decorators,
    };
}
function fromSdkDictionaryType(sdkContext, dictionaryType) {
    return {
        kind: "dict",
        keyType: fromSdkType(sdkContext, dictionaryType.keyType),
        valueType: fromSdkType(sdkContext, dictionaryType.valueType),
        decorators: dictionaryType.decorators,
        external: fromSdkExternalTypeInfo(dictionaryType),
    };
}
function fromSdkArrayType(sdkContext, arrayType) {
    return {
        kind: "array",
        name: arrayType.name,
        valueType: fromSdkType(sdkContext, arrayType.valueType),
        crossLanguageDefinitionId: arrayType.crossLanguageDefinitionId,
        decorators: arrayType.decorators,
        external: fromSdkExternalTypeInfo(arrayType),
    };
}
function fromSdkEndpointType() {
    return {
        kind: "string",
        name: "string",
        crossLanguageDefinitionId: "TypeSpec.string",
    };
}
/**
 * @beta
 */
export function getAllModelDecorators(namespace, modelDecorators) {
    const decoratorMap = new Map();
    // Add namespace decorators first
    if (namespace.decorators) {
        for (const decorator of namespace.decorators) {
            decoratorMap.set(decorator.name, decorator);
        }
    }
    // Add model decorators (will override namespace decorators with same name)
    if (modelDecorators) {
        for (const decorator of modelDecorators) {
            decoratorMap.set(decorator.name, decorator);
        }
    }
    return Array.from(decoratorMap.values());
}
/**
 * Converts TCGC external type information to InputExternalTypeMetadata
 * @param sdkType - The SDK type that may have external type information
 * @returns InputExternalTypeMetadata if the type has external info, undefined otherwise
 */
function fromSdkExternalTypeInfo(sdkType) {
    const external = sdkType.external;
    if (!external) {
        return undefined;
    }
    return {
        identity: external.identity,
        package: external.package,
        minVersion: external.minVersion,
    };
}
//# sourceMappingURL=type-converter.js.map