/*!
 * Copyright (C) Microsoft Corporation. All rights reserved.
 */
import { isGuid } from '@microsoft/power-apps-common/utils';
import { generateOptionsetEnumName, getDataverseEntitySetName, hasOptionsetData, isDataverseBooleanProperty, isDataverseEntity, isDataverseMultiSelectPicklistProperty, isDataverseNumericProperty, } from './dataverseUtils.js';
import { convertToValidIdentifier, formatPropertyName, normalizePropertyName, sanitizeName, sanitizeTabularResourceName, } from './nameUtils.js';
import { extractReferencedType, getNameNode, getPropertiesNode, getRequiredFields, isJsonObject, mapJsonTypeToTypeScript, } from './schemaUtils.js';
import { getParentFolderName, isSqlStoredProcedure } from './sqlUtils.js';
/**
 * Derives the model name and data source name from a schema.
 * Handles Dataverse entities, shared connectors, GUIDs, SQL stored procedures, and generic connectors.
 */
export function extractModelName(schema, schemaPath, vfs) {
    if (!schema) {
        throw new Error('Schema cannot be null.');
    }
    // Check if this is a Dataverse entity schema
    if (isDataverseEntity(schema)) {
        const entitySetName = getDataverseEntitySetName(schema);
        if (entitySetName) {
            const modelName = capitalizeForFileName(sanitizeName(entitySetName));
            return { modelName, dataSourceName: entitySetName.trim() };
        }
    }
    const nameNode = getNameNode(schema);
    let modelName;
    let dataSourceName;
    if (isSharedName(nameNode)) {
        modelName = getDisplayName(schema);
        dataSourceName = getSharedDataSourceName(nameNode);
    }
    else if (isGuid(nameNode)) {
        modelName = getTitle(schema);
        dataSourceName = modelName;
    }
    else if (isSqlStoredProcedure(schema)) {
        modelName = sanitizeTabularResourceName(nameNode);
        dataSourceName = getParentFolderName(schemaPath, vfs);
    }
    else {
        modelName = sanitizeTabularResourceName(nameNode);
        dataSourceName = modelName;
    }
    modelName = capitalizeForFileName(sanitizeName(modelName));
    return { modelName, dataSourceName };
}
function capitalizeForFileName(name) {
    if (!name) {
        return name;
    }
    return name.charAt(0).toUpperCase() + name.slice(1);
}
function getDisplayName(schema) {
    if (isJsonObject(schema) && isJsonObject(schema.properties)) {
        const displayName = schema.properties.displayName;
        if (typeof displayName !== 'string' || !displayName.trim()) {
            throw new Error("No 'displayName' found in schema properties");
        }
        return displayName;
    }
    throw new Error('Schema must be a JSON object with properties to extract displayName');
}
function isSharedName(nameNode) {
    return nameNode.toLowerCase().startsWith('shared_');
}
function getSharedDataSourceName(nameNode) {
    return sanitizeName(nameNode.replace(/^shared_/i, '')).toLowerCase();
}
function getTitle(schema) {
    if (isJsonObject(schema)) {
        const title = schema.title;
        if (typeof title !== 'string' || !title.trim()) {
            throw new Error("No 'title' found in schema");
        }
        return title;
    }
    throw new Error('Schema must be a JSON object to extract title');
}
function addPropertyComment(lines, propValue, padding) {
    if (propValue?.description && typeof propValue.description === 'string') {
        // Split multi-line descriptions so every line is prefixed with '//'
        const commentLines = propValue.description
            .split(/\r?\n/)
            .map((line) => `${padding}  // ${line}`);
        lines.push(...commentLines);
    }
}
/**
 * Generates a compact single-line TypeScript object type for use inside `allOf` intersection types.
 * Returns a type expression like `{ id: string; name?: string; }`.
 */
export function generateCompactInlineObjectType(modelName, definition) {
    const requiredFields = getRequiredFields(definition);
    const propertiesNode = getPropertiesNode(definition);
    const parts = [];
    for (const [propName, propValue] of Object.entries(propertiesNode)) {
        // Use formatPropertyName(propName) directly to preserve the original JSON key.
        // Do NOT pass through convertToValidIdentifier here — that's for identifier names
        // (type aliases, variable names), not property keys. e.g. "@odata.type" must stay
        // as `"@odata.type"` in the emitted interface to match the JSON wire format.
        let formattedPropName = formatPropertyName(propName);
        formattedPropName = normalizePropertyName(formattedPropName);
        const propValueAsObject = isJsonObject(propValue) ? propValue : undefined;
        const tsType = determineTypeScriptType(modelName, propValueAsObject, '', propName);
        const optionalMark = requiredFields.has(propName) ? '' : '?';
        parts.push(`${formattedPropName}${optionalMark}: ${tsType}`);
    }
    return `{ ${parts.join('; ')} }`;
}
/**
 * Generates an inline TypeScript object type string for a schema node that has nested `properties`.
 * Returns a type expression like `{ street?: string; city?: string; }` suitable for use as a
 * property type in both V1 string output and V2 ts-morph `type` fields.
 */
export function generateInlineObjectTypeString(modelName, definition, padding) {
    const requiredFields = getRequiredFields(definition);
    const propertiesNode = getPropertiesNode(definition);
    const lines = ['{'];
    for (const [propName, propValue] of Object.entries(propertiesNode)) {
        // Use formatPropertyName(propName) directly — same reason as generateCompactInlineObjectType:
        // property keys must match the JSON wire format, not be sanitized into identifier names.
        let formattedPropName = formatPropertyName(propName);
        formattedPropName = normalizePropertyName(formattedPropName);
        const propValueAsObject = isJsonObject(propValue) ? propValue : undefined;
        const tsType = determineTypeScriptType(modelName, propValueAsObject, padding, propName);
        const optionalMark = requiredFields.has(propName) ? '' : '?';
        addPropertyComment(lines, propValueAsObject, padding);
        lines.push(`${padding}  ${formattedPropName}${optionalMark}: ${tsType};`);
    }
    lines.push(`${padding}}`);
    return lines.join('\n');
}
/** Resolves the TypeScript type for a schema property, dispatching across optionsets, arrays, nested objects, refs, and primitives. */
export function determineTypeScriptType(modelName, propValue, padding, propName) {
    // Dataverse boolean (yes/no) fields must be typed as `boolean`, not as a numeric optionset.
    // The Dataverse Web API expects true/false, not 0/1.
    if (propValue && isDataverseBooleanProperty(propValue)) {
        return 'boolean';
    }
    // Dataverse numeric fields (integer, decimal, double, money, bigint) have `"type": "string"`
    // in the schema but the Dataverse Web API sends/receives them as JSON numbers.
    if (propValue && isDataverseNumericProperty(propValue)) {
        return 'number';
    }
    // Check if this property has optionset data first
    if (propValue && propName && hasOptionsetData(propValue)) {
        const enumName = generateOptionsetEnumName(modelName, propName);
        // Multi-select picklists allow multiple selections, so they are typed as arrays.
        return isDataverseMultiSelectPicklistProperty(propValue) ? `${enumName}[]` : enumName;
    }
    if (propValue?.type === 'array' && isJsonObject(propValue.items)) {
        return determineArrayType(modelName, propValue.items, padding, propName);
    }
    else if (propValue?.properties && typeof propValue.properties === 'object') {
        // Use modelName + propName as the sub-model name so that nested enum type aliases
        // are unique even when two sibling inline objects have a property with the same name
        // (e.g. both `phoneNumbers.type` and `emailAddresses.type` → previously both got
        // `Createperson_Requesttype`, now they get `Createperson_RequestphoneNumberstype`
        // and `Createperson_RequestemailAddressestype`).
        const subModelName = propName ? `${modelName}${convertToValidIdentifier(propName)}` : modelName;
        return generateInlineObjectTypeString(subModelName, propValue, padding + '  ');
    }
    else if (typeof propValue?.$ref === 'string') {
        return extractReferencedType(propValue.$ref);
    }
    else {
        return mapJsonTypeToTypeScript(propValue?.type);
    }
}
/** Resolves the TypeScript element type for an array property. */
function determineArrayType(modelName, itemsNode, padding, propName) {
    if ('properties' in itemsNode && typeof itemsNode.properties === 'object') {
        const subModelName = propName ? `${modelName}${convertToValidIdentifier(propName)}` : modelName;
        const inline = generateInlineObjectTypeString(subModelName, itemsNode, padding + ' ');
        return `${inline}[]`;
    }
    else if ('$ref' in itemsNode && typeof itemsNode.$ref === 'string') {
        return `${extractReferencedType(itemsNode.$ref)}[]`;
    }
    else {
        const itemType = ('type' in itemsNode ? itemsNode.type : 'unknown');
        return mapJsonTypeToTypeScript(itemType) + '[]';
    }
}
//# sourceMappingURL=modelUtils.js.map