"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.handleExtensions = handleExtensions;
const ux_specification_types_1 = require("@sap/ux-specification-types");
const utils_1 = require("../../../common/utils");
const utils_2 = require("../../../common/generate/utils");
const utils_3 = require("./utils");
const utils_4 = require("../../utils/utils");
const ANNOTATION_PREFIX_MAP_REPLACE = new Map([['filterFields', 'SelectionField::']]);
const ANNOTATION_PREFIX_MAP = new Map([
    ['columns', 'DataField::'],
    ['actions', 'DataFieldForAction::'],
    ['fields', 'DataField::']
]);
const PLACEMENT_PROPERTIES = {
    anchor: 'anchor',
    placement: 'placement',
    key: 'key'
};
/**
 * Method checks if passed schema definition is custom extension.
 *
 * @param {SchemaDefinition} schema Schema object which contains custom extensions and annotation elements.
 * @param {SchemaDefinition} appSchema Full app schema.
 * @returns {string} Added property name.
 */
function isCustomExtensionSchema(schema, appSchema) {
    if (typeof schema?.additionalProperties === 'object' && schema?.additionalProperties.$ref) {
        const definition = appSchema.definitions?.[(0, utils_1.getDefinitionKey)(schema.additionalProperties.$ref)];
        const additionalProperties = typeof definition === 'object' ? definition.properties || {} : {};
        return (PLACEMENT_PROPERTIES.anchor in additionalProperties &&
            PLACEMENT_PROPERTIES.placement in additionalProperties);
    }
    return false;
}
/**
 * Method returns annotation prefix for passed macro node name.
 *
 * @param {string} nodeName Macros node name.
 * @returns {AnnotationPrefix | undefined} The annotation prefix object or undefined if no prefix is found.
 */
function getAnnotationPrefix(nodeName) {
    let prefix = ANNOTATION_PREFIX_MAP_REPLACE.get(nodeName);
    const result = { prefix: '', replace: false };
    if (prefix) {
        result.prefix = prefix;
        result.replace = true;
    }
    else {
        prefix = ANNOTATION_PREFIX_MAP.get(nodeName);
        if (prefix) {
            result.prefix = prefix;
        }
    }
    return result;
}
/**
 * Method returns proxy extension object by parsing XML node.
 * Proxy object is required to be able to reuse 'utils.addExtensionToArray' method to recalculate order of annotation and custom extensions.
 *
 * @param {XMLElement} node Parsed XML element.
 * @param {SchemaDefinition} schema Schema object which contains custom extensions and annotation elements.
 * @param {string} property Custom extension key in schema's properties.
 * @param {string} aggregationName Aggregation name of container.
 * @returns {ExtensionData} Proxy extension object.
 */
function getExtensionData(node, schema, property, aggregationName) {
    const schemaNode = schema.properties[property];
    const annotationPrefix = getAnnotationPrefix(aggregationName);
    // Custom extension node
    const xmlPath = schemaNode.metadata.path;
    const xmlExtension = node.subElements[xmlPath[xmlPath.length - 1].index];
    const key = (0, utils_3.getAttribute)(xmlExtension, PLACEMENT_PROPERTIES.key) || property;
    const anchor = (0, utils_3.getAttribute)(xmlExtension, PLACEMENT_PROPERTIES.anchor);
    const placement = (0, utils_3.getAttribute)(xmlExtension, PLACEMENT_PROPERTIES.placement) || ux_specification_types_1.v4.Placement.After;
    const annotationAnchor = annotationPrefix.replace ? `${annotationPrefix.prefix}${anchor}` : anchor;
    const extension = {
        position: {
            anchor: annotationAnchor in schema.properties ? annotationAnchor : anchor,
            placement
        }
    };
    return { key, extension };
}
/**
 * Method updates 'propertyIndex' for passed schema's properties.
 * Method prepares proxy objects and reuses 'utils.addExtensionToArray' method to recalculate order of annotation and custom extensions.
 *
 * @param {XMLElement} node Parsed XML element.
 * @param {SchemaDefinition} schema Schema object which contains custom extensions and annotation elements.
 * @param {string} aggregationName Aggregation name of container.
 */
function updatePropertyIndices(node, schema, aggregationName) {
    // Reuse "addExtensionToArray" method for calculating extensions position as it is done for manifest based extensions
    // Logic requires to create 2 proxy objects to be able to reuse same logic
    // Custom extension entries
    const extensions = {};
    // Annotation entries
    const sortedExtensionsArray = [];
    // Custom extension mapping between schema property key and extension key in xml
    const extensionSchemaKeyMap = {};
    // Populate annotation and custom extension entries
    for (const property in schema.properties) {
        const schemaNode = schema.properties[property];
        if ('metadata' in schemaNode && schemaNode.metadata?.path !== undefined) {
            // Custom extension node (has a path to an XML element)
            const { extension, key } = getExtensionData(node, schema, property, aggregationName);
            extensions[key] = extension;
            // Mapping between extension key in xml and key in schema
            extensionSchemaKeyMap[key] = property;
        }
        else {
            // Annotation based node
            sortedExtensionsArray.push([property, schemaNode]);
        }
    }
    // Reuse method to calculate order
    (0, utils_4.addExtensionToArray)(extensions, sortedExtensionsArray, '', 'position');
    // Apply order to 'propertyIndex' properties in schema
    for (let i = 0; i < sortedExtensionsArray.length; i++) {
        const key = sortedExtensionsArray[i][0];
        let schemaNode = schema.properties[key];
        if (sortedExtensionsArray[i][1].$ref === '#/definitions/' && key in extensionSchemaKeyMap) {
            // Entry of custom extension
            schemaNode = schema.properties[extensionSchemaKeyMap[key]];
        }
        if (schemaNode) {
            // Update index to affect ordering
            schemaNode.propertyIndex = i;
        }
    }
}
/**
 * Method applies correction to anchor enum entry based on collection element and anchor type(annotation or custom extension).
 *
 * @param {XMLElement} node Parsed XML element.
 * @param {string} originalKey Original full enum key
 * @param {AnnotationPrefix} annotationPrefix Annotation prefix data - data depends on collection element.
 * @param {SchemaDefinition} property Reference/anchored element from schema.
 * @returns {string} Corrected anchor enum key.
 */
function getCorrectedAnchorEnumKey(node, originalKey, annotationPrefix, property) {
    let key = originalKey;
    if (annotationPrefix.prefix && key.startsWith(annotationPrefix.prefix)) {
        if (annotationPrefix.replace) {
            // Remove annotation prefix - it is not required for some macros elements(currently FilterField)
            key = key.replace(annotationPrefix.prefix, '');
        }
    }
    else if (property?.metadata?.path) {
        // Reference to custom extension - use key for enum value
        const index = property.metadata.path[property.metadata.path.length - 1].index;
        key = (0, utils_3.getAttribute)(node.subElements[index], 'key') || key;
    }
    return key;
}
/**
 * Method refreshes custom extension schema's 'anchor' property with 'oneOf' enum entires.
 *
 * @param {XMLElement} node Parsed XML element.
 * @param {SchemaDefinition} schema Schema object which contains custom extensions and annotation elements.
 * @param {SchemaDefinition} appSchema Full app schema.
 * @param {string} aggregationName Aggregation name of container.
 */
function applyEnumsForAnchors(node, schema, appSchema, aggregationName) {
    const annotationPrefix = getAnnotationPrefix(aggregationName);
    for (const name in schema.properties) {
        const anchorProperty = 'anchor';
        const property = schema.properties[name];
        if (!property.metadata) {
            continue;
        }
        (0, utils_2.addDescriptiveEnumDefinition)(appSchema, schema, {
            property: anchorProperty,
            schema: property
        }, {
            excludeKey: name,
            resolveDefinition: true
        });
        // Remove annotation prefix from anchor enum entries - it is difference between manifest and macros anchoring
        const oneOf = property.properties?.[anchorProperty]?.oneOf || [];
        for (const entry of oneOf) {
            if (typeof entry === 'object' && typeof entry.const === 'string') {
                entry.const = getCorrectedAnchorEnumKey(node, entry.const, annotationPrefix, schema.properties[entry.const]);
            }
        }
    }
}
/**
 * Method handles custom extension - resolve positions and ordering.
 * Currently it handles `sap.fe.macros.FilterField`, but code is generic and can handle also other extensions - method relies on 'anchor' and 'placement' properties.
 *
 * @param node Parsed XML element.
 * @param schema Schema object which contains custom extensions and annotation elements.
 * @param appSchema Full app schema.
 * @param aggregationName Name of the aggregation container.
 */
function handleExtensions(node, schema, appSchema, aggregationName) {
    if (!isCustomExtensionSchema(schema, appSchema)) {
        // Schema does not support custom extensions
        return;
    }
    aggregationName = aggregationName || node.name;
    // Update ordering of nodes by updating 'propertyIndex' for each node
    updatePropertyIndices(node, schema, aggregationName);
    // Enhance anchor property to add enum
    applyEnumsForAnchors(node, schema, appSchema, aggregationName);
}
//# sourceMappingURL=extensions.js.map