"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.traitRefIsExtension = exports.processExtensionTraitToObject = exports.convertObjectTypeToString = exports.processExtensionFromJson = exports.standardImportDetection = exports.addImportDocsToManifest = exports.extensionDocName = void 0;
const internal_1 = require("../../internal");
const cdmObjectTypeGuards_1 = require("../../Utilities/cdmObjectTypeGuards");
/**
 * Dictionary used to cache documents with trait definitions by file name.
 */
const cachedDefDocs = new Map();
/**
 * Set of extensions that are officially supported and have their definitions in the extensions folder.
 */
const supportedExtensions = new Set(['pbi']);
/**
 * Constant used to store the prefix that is the mark of extension traits.
 */
const extensionTraitNamePrefix = 'is.extension.';
exports.extensionDocName = 'custom.extension.cdm.json';
const TAG = 'ExtensionHelper';
/**
 * Adds the list of documents with extensions schema definitions to the manifest.
 * @param ctx The context
 * @param importDocs The list of paths of documents with relevant schema definitions.
 * @param document The manifest that needs to import the docs.
 */
function addImportDocsToManifest(ctx, importDocs, document) {
    for (const importDoc of importDocs) {
        if (!document.imports.allItems.find((importPresent) => importPresent.corpusPath === importDoc.corpusPath)) {
            document.imports.push(importDoc);
        }
    }
}
exports.addImportDocsToManifest = addImportDocsToManifest;
/**
 * For all the definitions (name, type) we have found for extensions, search in CDM Standard Schema for definition files.
 * If we find a definition in a CDM Standard Schema file, we add that file to importsList.
 * At the same time, the found definition is removed from extensionTraitDefList.
 * When this function returns, extensionTraitDefList only contains definitions that are not present in any CDM Standard Schema file,
 * and a list of CDM Standard Schema files with relevant definitions is returned.
 * @param ctx  The context
 * @param extensionTraitDefList The list of all definitions for all found extensions.
 * Function modifies this list by removing definitions found in CDM Standard Schema files.
 * @param localExtensionTraitDefList The list of all definitions for all found extensions in local scope.
 * @returns A list of CDM Standard Schema files to import.
 */
function standardImportDetection(ctx, extensionTraitDefList, localExtensionTraitDefList) {
    return __awaiter(this, void 0, void 0, function* () {
        const importsList = [];
        let hasCustomExtensionImport = false;
        for (let traitIndex = localExtensionTraitDefList.length - 1; traitIndex >= 0; traitIndex--) {
            const extensionTraitDef = localExtensionTraitDefList[traitIndex];
            if (!traitDefIsExtension(extensionTraitDef)) {
                internal_1.Logger.error(ctx, TAG, standardImportDetection.name, undefined, internal_1.cdmLogCode.ErrPersistModelJsonInvalidExtensionTrait, extensionTraitDef.traitName, extensionTraitNamePrefix);
                return undefined;
            }
            const extensionBreakdown = removeExtensionTraitNamePrefix(extensionTraitDef.traitName)
                .split(':');
            if (extensionBreakdown.length > 1) {
                const extensionName = extensionBreakdown[0];
                if (!supportedExtensions.has(extensionName)) {
                    if (!hasCustomExtensionImport) {
                        const importObject = ctx.corpus.MakeObject(internal_1.cdmObjectType.import);
                        importObject.corpusPath = exports.extensionDocName;
                        importsList.push(importObject);
                        hasCustomExtensionImport = true;
                    }
                    continue;
                }
                const fileName = `${extensionName}.extension.cdm.json`;
                const fileCorpusPath = `cdm:/extensions/${fileName}`;
                const extensionDoc = yield fetchDefDoc(ctx, fileName);
                /**
                 * If no document was found for that extensionName, the trait does not have a document with it's definition.
                 * Trait will be kept in extensionTraitDefList (a document with its definition will be created locally)
                 */
                if (extensionDoc === undefined) {
                    continue;
                }
                /**
                 * There is a document with extensionName, now we search for the trait in this document.
                 * If we find it, we remove the trait from extensionTraitDefList and add the document to imports
                 */
                const matchingTrait = extensionDoc.definitions.allItems.find((definition) => (0, cdmObjectTypeGuards_1.isCdmTraitDefinition)(definition) && definition.getName() === extensionTraitDef.traitName);
                if (matchingTrait) {
                    const parameterList = matchingTrait.parameters;
                    if (!extensionTraitDef.parameters.allItems.some((extensionParameter) => !parameterList.allItems.some((defParameter) => defParameter.name === extensionParameter.name))) {
                        extensionTraitDefList.splice(extensionTraitDefList.indexOf(extensionTraitDef), 1);
                        if (!importsList.some((importDoc) => importDoc.corpusPath === fileCorpusPath)) {
                            const importObject = ctx.corpus.MakeObject(internal_1.cdmObjectType.import);
                            importObject.corpusPath = fileCorpusPath;
                            importsList.push(importObject);
                        }
                    }
                }
            }
        }
        return importsList;
    });
}
exports.standardImportDetection = standardImportDetection;
function processExtensionFromJson(ctx, sourceObject, baseProperties, traitRefSet, extensionTraitDefList, localExtensionTraitDefList) {
    const extensionKeys = Object.keys(sourceObject);
    for (const baseKey of baseProperties) {
        const index = extensionKeys.indexOf(baseKey);
        if (index > -1) {
            extensionKeys.splice(index, 1);
        }
    }
    for (const extensionKey of extensionKeys) {
        const traitName = addExtensionTraitNamePrefix(extensionKey);
        let extensionTraitDef = extensionTraitDefList.find((trait) => trait.traitName === traitName);
        const traitExists = !!extensionTraitDef;
        if (!traitExists) {
            extensionTraitDef = ctx.corpus.MakeObject(internal_1.cdmObjectType.traitDef, traitName);
            extensionTraitDef.extendsTrait = ctx.corpus.MakeObject(internal_1.cdmObjectType.traitRef, 'is.extension', true);
        }
        const extensionTraitRef = ctx.corpus.MakeObject(internal_1.cdmObjectType.traitRef, traitName);
        const extensionValue = sourceObject[extensionKey];
        const isArray = Array.isArray(extensionValue);
        if (extensionValue !== undefined && typeof extensionValue === 'object' && !isArray) {
            const extVals = Object.keys(extensionValue)
                .filter((extension) => !extension.startsWith('_'));
            for (const extensionProperty of extVals) {
                const extensionPropertyValue = extensionValue[extensionProperty];
                const extensionArgument = ctx.corpus.MakeObject(internal_1.cdmObjectType.argumentDef, extensionProperty);
                let extensionParameter = extensionTraitDef.parameters.allItems.find((parameter) => parameter.name === extensionProperty);
                const parameterExists = !!extensionParameter;
                if (!parameterExists) {
                    extensionParameter = ctx.corpus.MakeObject(internal_1.cdmObjectType.parameterDef, extensionProperty);
                    extensionParameter.dataTypeRef = ctx.corpus.MakeObject(internal_1.cdmObjectType.dataTypeRef, convertObjectTypeToString(typeof extensionPropertyValue), true);
                }
                extensionArgument.value = extensionPropertyValue;
                extensionTraitRef.arguments.push(extensionArgument);
                if (!parameterExists) {
                    extensionTraitDef.parameters.push(extensionParameter);
                }
            }
        }
        else {
            const extensionArgument = ctx.corpus.MakeObject(internal_1.cdmObjectType.argumentDef, traitName);
            let extensionParameter = extensionTraitDef.parameters.allItems.find((parameter) => parameter.name === traitName);
            const parameterExists = !!extensionParameter;
            if (!parameterExists) {
                extensionParameter = ctx.corpus.MakeObject(internal_1.cdmObjectType.parameterDef, traitName);
                extensionParameter.dataTypeRef = ctx.corpus.MakeObject(internal_1.cdmObjectType.dataTypeRef, convertObjectTypeToString(typeof extensionValue), true);
            }
            extensionArgument.value = extensionValue;
            extensionTraitRef.arguments.push(extensionArgument);
            if (!parameterExists) {
                extensionTraitDef.parameters.push(extensionParameter);
            }
        }
        if (!traitExists) {
            extensionTraitDefList.push(extensionTraitDef);
        }
        if (localExtensionTraitDefList) {
            localExtensionTraitDefList.push(extensionTraitDef);
        }
        traitRefSet.push(extensionTraitRef);
    }
}
exports.processExtensionFromJson = processExtensionFromJson;
function convertObjectTypeToString(objType) {
    return objType === 'array' ? 'object' : objType;
}
exports.convertObjectTypeToString = convertObjectTypeToString;
function processExtensionTraitToObject(extensionTraitRef, destination) {
    const originalPropName = extensionTraitRef.namedReference.substr(13);
    if (extensionTraitRef.arguments.length === 1 && extensionTraitRef.arguments.allItems[0].name === extensionTraitRef.namedReference) {
        destination[originalPropName] = extensionTraitRef.arguments.allItems[0].value;
        return;
    }
    const valueObject = {};
    for (const argument of extensionTraitRef.arguments) {
        valueObject[argument.name] = argument.value;
    }
    destination[originalPropName] = valueObject;
}
exports.processExtensionTraitToObject = processExtensionTraitToObject;
/**
 * Checks whether the trait reference base is an extension (by checking whether its name has the extension prefix)
 * @param trait The trait to be checked
 * @returns Whether the trait is an extension.
 */
function traitRefIsExtension(trait) {
    return traitNameHasExtensionMark(trait.namedReference);
}
exports.traitRefIsExtension = traitRefIsExtension;
/**
 * Tries to fetch the document with definitions that corresponds to the provided fileName.
 * Caches results in a dictionary.
 * Returns null if no such document was found.
 * @param ctx The context
 * @param fileName The name of the file to be fetched or retrieved from the cache.
 * @returns The retrieved document or null if no such document was found.
 */
function fetchDefDoc(ctx, fileName) {
    return __awaiter(this, void 0, void 0, function* () {
        // Since the CachedDefDocs is a static property and there might be multiple corpus running,
        // we need to make sure that each corpus will have its own cached def document.
        // This is achieved by adding the context as part of the key to the document.
        const key = [ctx, fileName];
        if (cachedDefDocs.has(key)) {
            // We already loaded this document and it is in the cache (dictionary)
            return cachedDefDocs.get(key);
        }
        // We retrieve the document and cache in the dictionary for future reference.
        const path = `/extensions/${fileName}`;
        const absPath = ctx.corpus.storage.createAbsoluteCorpusPath(path, ctx.corpus.storage.fetchRootFolder('cdm'));
        const extensionDoc = yield ctx.corpus.fetchObjectAsync(absPath);
        if (!(0, cdmObjectTypeGuards_1.isDocumentDefinition)(extensionDoc)) {
            internal_1.Logger.error(ctx, TAG, fetchDefDoc.name, absPath, internal_1.cdmLogCode.ErrInvalidCast, absPath, "CdmDocumentDefinition");
            return undefined;
        }
        cachedDefDocs.set(key, extensionDoc);
        return extensionDoc;
    });
}
/**
 * Checks whether a trait name has the specific mark of an extension.
 * @param traitName The name of the trait that is to be checked whether it has the extension mark.
 * @returns Whether the traitName has the mark of an extension.
 */
function traitNameHasExtensionMark(traitName) {
    if (internal_1.StringUtils.isBlankByCdmStandard(traitName)) {
        return false;
    }
    return traitName.startsWith(extensionTraitNamePrefix);
}
/**
 * Checks whether a trait definition is an extension.
 * @param trait The trait to be checked
 * @returns Whether the trait is an extension.
 */
function traitDefIsExtension(trait) {
    return traitNameHasExtensionMark(trait.traitName);
}
/**
 * Removes the prefix that was added to the traitName to mark it as an extension.
 * @param traitName The name of the trait that contains the  extension prefix.
 * @returns The name of the trait after the extension prefix was removed.
 */
function removeExtensionTraitNamePrefix(traitName) {
    return traitName.substr(extensionTraitNamePrefix.length);
}
/**
 * Adds a prefix to a trait name to mark it as an extension.
 * @param traitName The name of the trait without extension prefix.
 * @returns The name of the trait after the extension prefix was added.
 */
function addExtensionTraitNamePrefix(traitName) {
    return `${extensionTraitNamePrefix}${traitName}`;
}
//# sourceMappingURL=ExtensionHelper.js.map