// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import { createDiagnosticCollector } from "@typespec/compiler";
import { fromSdkClients } from "./client-converter.js";
import { fromSdkNamespaces } from "./namespace-converter.js";
import { processServiceAuthentication } from "./service-authentication.js";
import { fromSdkType } from "./type-converter.js";
import { firstLetterToUpperCase, getClientNamespaceString } from "./utils.js";
/**
 * Creates the code model from the SDK context.
 * This function follows TypeSpec best practices by returning diagnostics alongside the result.
 *
 * @example
 * ```typescript
 * import { createModel } from "@typespec/http-client-csharp";
 *
 * const sdkContext = createCSharpEmitterContext(context, logger);
 * const [codeModel, diagnostics] = createModel(sdkContext);
 * // Process the code model and handle diagnostics
 * ```
 *
 * @param sdkContext - The SDK context
 * @returns A tuple containing the code model and any diagnostics that were generated
 * @beta
 */
export function createModel(sdkContext) {
    const diagnostics = createDiagnosticCollector();
    const sdkPackage = sdkContext.sdkPackage;
    // TO-DO: Consider exposing the namespace hierarchy in the code model https://github.com/microsoft/typespec/issues/8332
    diagnostics.pipe(fromSdkNamespaces(sdkContext, sdkPackage.namespaces));
    // TO-DO: Consider using the TCGC model + enum cache once https://github.com/Azure/typespec-azure/issues/3180 is resolved
    diagnostics.pipe(navigateModels(sdkContext));
    const types = Array.from(sdkContext.__typeCache.types.values());
    const [models, enums] = [
        types.filter((type) => type.kind === "model"),
        types.filter((type) => type.kind === "enum"),
    ];
    const rootClients = sdkPackage.clients;
    const rootApiVersions = parseApiVersions(sdkPackage.enums, rootClients);
    const inputClients = diagnostics.pipe(fromSdkClients(sdkContext, rootClients, rootApiVersions));
    // TODO -- TCGC now does not have constants field in its sdkPackage, they might add it in the future.
    const constants = Array.from(sdkContext.__typeCache.constants.values());
    // Fix naming conflicts for constants, enums, and models
    fixNamingConflicts(models, constants);
    const clientModel = {
        name: getClientNamespaceString(sdkContext),
        apiVersions: rootApiVersions,
        enums: enums,
        constants: constants,
        models: models,
        clients: inputClients,
        auth: diagnostics.pipe(processServiceAuthentication(sdkContext, sdkPackage)),
    };
    return diagnostics.wrap(clientModel);
}
/**
 * Parses and returns the correct API versions for the library.
 * Handles both regular and multiservice client libraries.
 *
 * @param enums - Array of enums from the SDK package
 * @param rootClients - Array of root clients from the SDK package
 * @returns Array of API version strings
 */
function parseApiVersions(enums, rootClients) {
    // Always use client.apiVersions as the source of truth.
    return rootClients[0]?.apiVersions ?? [];
}
/**
 * Fixes naming conflicts for constants, enums, and models.
 *
 * TODO - TCGC has two issues which come from the same root cause: the name determination algorithm based on the typespec node of the constant.
 * Typespec itself will always use the same node/Type instance for the same value constant, therefore a lot of names are not correct.
 * issues:
 * - https://github.com/Azure/typespec-azure/issues/2572 (constants in operations)
 * - https://github.com/Azure/typespec-azure/issues/2563 (constants in models)
 *
 * @param models - Array of input model types
 * @param enums - Array of input enum types
 * @param constants - Array of input literal types (constants)
 */
function fixNamingConflicts(models, constants) {
    // First, fix names for constants and constant-derived enums in model properties
    for (const model of models) {
        for (const property of model.properties) {
            const type = property.type;
            if (type.kind === "constant") {
                // Fix constant property names
                type.name = `${model.name}${firstLetterToUpperCase(property.name)}`;
                type.namespace = model.namespace;
                type.access = model.access;
                type.usage = model.usage;
            }
            else if (type.kind === "enum" && type.crossLanguageDefinitionId === "") {
                // Fix enum names for enums created from constants
                type.name = `${model.name}${firstLetterToUpperCase(property.name)}`;
                type.namespace = model.namespace;
                type.access = model.access;
                type.usage = model.usage;
            }
        }
    }
    // Second, handle remaining naming conflicts by numbering duplicates
    // This covers constants used as operation parameters and other edge cases
    const constantNameMap = new Map();
    for (const constant of constants) {
        const count = constantNameMap.get(constant.name);
        if (count) {
            constantNameMap.set(constant.name, count + 1);
            constant.name = `${constant.name}${count}`;
        }
        else {
            constantNameMap.set(constant.name, 1);
        }
    }
    // Third, handle duplicate model names within the same namespace
    // This can occur when namespace option is specified and models from different
    // source namespaces end up in the same target namespace
    const modelNameMap = new Map();
    for (const model of models) {
        // Use namespace + name as the key to detect duplicates within the same namespace
        const key = `${model.namespace}.${model.name}`;
        const count = modelNameMap.get(key);
        if (count) {
            modelNameMap.set(key, count + 1);
            model.name = `${model.name}${count}`;
        }
        else {
            modelNameMap.set(key, 1);
        }
    }
}
function navigateModels(sdkContext) {
    const diagnostics = createDiagnosticCollector();
    for (const m of sdkContext.sdkPackage.models) {
        diagnostics.pipe(fromSdkType(sdkContext, m));
    }
    for (const e of sdkContext.sdkPackage.enums) {
        diagnostics.pipe(fromSdkType(sdkContext, e));
    }
    return diagnostics.wrap(undefined);
}
//# sourceMappingURL=client-model-builder.js.map