"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.CdmDocumentDefinition = void 0;
const internal_1 = require("../internal");
const Logger_1 = require("../Utilities/Logging/Logger");
const using_statement_1 = require("using-statement");
class CdmDocumentDefinition extends internal_1.cdmObjectSimple {
    constructor(ctx, name, hasImports = false) {
        super(ctx);
        this.TAG = CdmDocumentDefinition.name;
        /**
         * @internal
         */
        this.isDirty = true;
        // let bodyCode = () =>
        {
            this.inDocument = this;
            this.objectType = internal_1.cdmObjectType.documentDef;
            this.name = name;
            this.jsonSchemaSemanticVersion = CdmDocumentDefinition.currentJsonSchemaSemanticVersion;
            this.documentVersion = undefined;
            this.needsIndexing = true;
            this.importsIndexed = false;
            this.declarationsIndexed = false;
            this.isDirty = true;
            this.currentlyIndexing = false;
            this.isValid = true;
            this.namespace = null;
            this.imports = new internal_1.CdmImportCollection(ctx, this);
            this.definitions = new internal_1.CdmDefinitionCollection(ctx, this);
        }
        // return p.measure(bodyCode);
    }
    static get objectType() {
        return internal_1.cdmObjectType.documentDef;
    }
    /**
     * @deprecated Use atCorpusPath instead.
     */
    get corpusPath() {
        return `${this.namespace || this.folder.namespace}:${this.folderPath}${this.name}`;
    }
    /**
     * @deprecated Use owner property instead.
     */
    get folder() {
        return this.owner;
    }
    /**
     * @deprecated Use owner property instead.
     */
    set folder(value) {
        this.owner = value;
    }
    /**
     * Validates all the objects in this document.
     * @internal
     */
    checkIntegrity() {
        // let bodyCode = () =>
        {
            let errorCount = 0;
            for (const obj of this.internalObjects) {
                if (!obj.validate()) {
                    errorCount++;
                }
                else {
                    obj.ctx = this.ctx;
                }
                internal_1.Logger.info(this.ctx, this.TAG, this.checkIntegrity.name, obj.atCorpusPath, `checked '${obj.atCorpusPath}'`);
            }
            this.isValid = errorCount === 0;
        }
        // return p.measure(bodyCode);
    }
    /**
     * Clear all document's internal caches and update the declared path of every object contained by this document.
     * @internal
     */
    clearCaches() {
        // Clean all internal caches and flags
        this.internalObjects = [];
        this.declarationsIndexed = false;
        this.internalDeclarations = new Map();
        this.importsIndexed = false;
        this.importPriorities = undefined;
        // Collects all the objects contained by this document and updates their DeclaredPath.
        this.visit('', (obj, objPath) => {
            const objectBase = obj;
            // Update the DeclaredPath property.
            objectBase.declaredPath = objPath;
            this.internalObjects.push(objectBase);
            return false;
        }, undefined);
    }
    /**
     * Indexes all definitions contained by this document.
     * @internal
     */
    declareObjectDefinitions() {
        // let bodyCode = () =>
        {
            const corpusPathRoot = this.folderPath + this.name;
            for (const obj of this.internalObjects) {
                // I can't think of a better time than now to make sure any recently changed or added things have an in doc
                obj.inDocument = this;
                const objPath = obj.declaredPath;
                if (objPath.indexOf('(unspecified)') !== -1) {
                    continue;
                }
                let skipDuplicates = false;
                switch (obj.objectType) {
                    case internal_1.cdmObjectType.constantEntityDef:
                        // if there is a duplicate, don't complain, the path just finds the first one
                        skipDuplicates = true;
                    case internal_1.cdmObjectType.attributeGroupDef:
                    case internal_1.cdmObjectType.entityDef:
                    case internal_1.cdmObjectType.parameterDef:
                    case internal_1.cdmObjectType.traitDef:
                    case internal_1.cdmObjectType.traitGroupDef:
                    case internal_1.cdmObjectType.purposeDef:
                    case internal_1.cdmObjectType.dataTypeDef:
                    case internal_1.cdmObjectType.typeAttributeDef:
                    case internal_1.cdmObjectType.entityAttributeDef:
                    case internal_1.cdmObjectType.attributeContextDef:
                    case internal_1.cdmObjectType.localEntityDeclarationDef:
                    case internal_1.cdmObjectType.referencedEntityDeclarationDef:
                    case internal_1.cdmObjectType.projectionDef:
                    case internal_1.cdmObjectType.operationAddCountAttributeDef:
                    case internal_1.cdmObjectType.operationAddSupportingAttributeDef:
                    case internal_1.cdmObjectType.operationAddTypeAttributeDef:
                    case internal_1.cdmObjectType.operationExcludeAttributesDef:
                    case internal_1.cdmObjectType.operationArrayExpansionDef:
                    case internal_1.cdmObjectType.operationCombineAttributesDef:
                    case internal_1.cdmObjectType.operationRenameAttributesDef:
                    case internal_1.cdmObjectType.operationReplaceAsForeignKeyDef:
                    case internal_1.cdmObjectType.operationIncludeAttributesDef:
                    case internal_1.cdmObjectType.operationAddAttributeGroupDef:
                    case internal_1.cdmObjectType.operationAlterTraitsDef:
                    case internal_1.cdmObjectType.operationAddArtifactAttributeDef:
                        const corpusPath = `${corpusPathRoot}/${objPath}`;
                        if (this.internalDeclarations.has(objPath) && !skipDuplicates) {
                            internal_1.Logger.error(this.ctx, this.TAG, this.declareObjectDefinitions.name, this.atCorpusPath, internal_1.cdmLogCode.ErrPathIsDuplicate, objPath, corpusPath);
                        }
                        else {
                            this.internalDeclarations.set(objPath, obj);
                            this.ctx.corpus.registerSymbol(objPath, this);
                            internal_1.Logger.info(this.ctx, this.TAG, this.declareObjectDefinitions.name, this.atCorpusPath, `declared '${objPath}'`);
                        }
                    default:
                }
            }
        }
        // return p.measure(bodyCode);
    }
    /**
     * @internal
     */
    finishIndexing(loadedImports) {
        internal_1.Logger.debug(this.ctx, this.TAG, this.finishIndexing.name, this.atCorpusPath, `index finish: ${this.atCorpusPath}`);
        const wasIndexedPreviously = this.declarationsIndexed;
        this.ctx.corpus.documentLibrary.markDocumentAsIndexed(this);
        this.importsIndexed = this.importsIndexed || loadedImports;
        this.declarationsIndexed = true;
        this.needsIndexing = !loadedImports;
        this.internalObjects = undefined;
        // if the thisument declarations were indexed previously, do not log again.
        if (!wasIndexedPreviously && this.isValid) {
            for (const def of this.definitions.allItems) {
                if (internal_1.isEntityDefinition(def)) {
                    internal_1.Logger.debug(this.ctx, this.TAG, this.finishIndexing.name, def.atCorpusPath, `indexed entity: ${def.atCorpusPath}`);
                }
            }
        }
    }
    /**
     * Fetches the corresponding object definition for every object reference.
     * @internal
     */
    resolveObjectDefinitions(resOpt) {
        // let bodyCode = () =>
        {
            const ctx = this.ctx;
            resOpt.indexingDoc = this;
            for (const obj of this.internalObjects) {
                switch (obj.objectType) {
                    case internal_1.cdmObjectType.attributeRef:
                    case internal_1.cdmObjectType.attributeGroupRef:
                    case internal_1.cdmObjectType.attributeContextRef:
                    case internal_1.cdmObjectType.dataTypeRef:
                    case internal_1.cdmObjectType.entityRef:
                    case internal_1.cdmObjectType.purposeRef:
                    case internal_1.cdmObjectType.traitRef:
                        ctx.relativePath = obj.declaredPath;
                        const ref = obj;
                        if (internal_1.CdmObjectReferenceBase.offsetAttributePromise(ref.namedReference) < 0) {
                            const resNew = ref.fetchObjectDefinition(resOpt);
                            if (!resNew) {
                                const messagePath = this.folderPath + obj.declaredPath;
                                // It's okay if references can't be resolved when shallow validation is enabled.
                                if (resOpt.shallowValidation) {
                                    internal_1.Logger.warning(ctx, this.TAG, this.resolveObjectDefinitions.name, this.atCorpusPath, internal_1.cdmLogCode.WarnResolveReferenceFailure, ref.namedReference);
                                }
                                else {
                                    internal_1.Logger.error(this.ctx, this.TAG, this.resolveObjectDefinitions.name, this.atCorpusPath, internal_1.cdmLogCode.ErrResolveReferenceFailure, ref.namedReference);
                                }
                                // don't check in this file without both of these comments. handy for debug of failed lookups
                                //const resTest: CdmObjectDefinitionBase = ref.fetchObjectDefinition(resOpt);
                            }
                            else {
                                internal_1.Logger.info(ctx, this.TAG, this.resolveObjectDefinitions.name, this.atCorpusPath, `resolved '${ref.namedReference}'`);
                            }
                        }
                    default:
                }
            }
            resOpt.indexingDoc = undefined;
        }
        // return p.measure(bodyCode);
    }
    /**
     * Verifies if the trait argument data type matches what is specified on the trait definition.
     * @internal
     */
    resolveTraitArguments(resOpt) {
        // let bodyCode = () =>
        {
            const ctx = this.ctx;
            for (const obj of this.internalObjects) {
                if (obj.objectType === internal_1.cdmObjectType.traitRef) {
                    const traitRef = obj;
                    const traitDef = obj.fetchObjectDefinition(resOpt);
                    if (!traitDef) {
                        continue;
                    }
                    for (let argumentIndex = 0; argumentIndex < traitRef.arguments.length; ++argumentIndex) {
                        const argument = traitRef.arguments.allItems[argumentIndex];
                        try {
                            ctx.relativePath = argument.declaredPath;
                            const params = traitDef.fetchAllParameters(resOpt);
                            const paramFound = params.resolveParameter(argumentIndex, argument.getName());
                            argument.resolvedParameter = paramFound;
                            // if parameter type is entity, then the value should be an entity or ref to one
                            // same is true of 'dataType' dataType
                            const argumentValue = paramFound.constTypeCheck(resOpt, this, argument.value);
                            if (argumentValue) {
                                argument.setValue(argumentValue);
                            }
                        }
                        catch (e) {
                            internal_1.Logger.error(this.ctx, this.TAG, this.resolveTraitArguments.name, this.atCorpusPath, internal_1.cdmLogCode.ErrTraitResolutionFailure, e.toString(), traitDef.getName());
                        }
                    }
                    traitRef.resolvedArguments = true;
                }
            }
        }
        // return p.measure(bodyCode);
    }
    /**
     * @internal
     * finds any relative corpus paths that are held within this document and makes them relative to the new folder instead
     */
    localizeCorpusPaths(newFolder) {
        let allWentWell = true;
        let worked;
        let corpPath;
        // shout into the void
        internal_1.Logger.info(this.ctx, this.TAG, this.localizeCorpusPaths.name, this.atCorpusPath, `Localizing corpus paths in document '${this.name}'`);
        // find anything in the document that is a corpus path
        this.visit('', (iObject, path) => {
            // i don't like that document needs to know a little about these objects
            // in theory, we could create a virtual function on cdmObject that localizes properties
            // but then every object would need to know about the documents and paths and such ...
            // also, i already wrote this code.
            switch (iObject.objectType) {
                case internal_1.cdmObjectType.import: {
                    const typeObj = iObject;
                    [corpPath, worked] = this.localizeCorpusPath(typeObj.corpusPath, newFolder);
                    if (worked === false) {
                        allWentWell = false;
                    }
                    else {
                        typeObj.corpusPath = corpPath;
                    }
                    break;
                }
                case internal_1.cdmObjectType.localEntityDeclarationDef:
                case internal_1.cdmObjectType.referencedEntityDeclarationDef: {
                    const typeObj = iObject;
                    [corpPath, worked] = this.localizeCorpusPath(typeObj.entityPath, newFolder);
                    if (worked === false) {
                        allWentWell = false;
                    }
                    else {
                        typeObj.entityPath = corpPath;
                    }
                    break;
                }
                case internal_1.cdmObjectType.dataPartitionDef: {
                    const typeObj = iObject;
                    [corpPath, worked] = this.localizeCorpusPath(typeObj.location, newFolder);
                    if (worked === false) {
                        allWentWell = false;
                    }
                    else {
                        typeObj.location = corpPath;
                    }
                    [corpPath, worked] = this.localizeCorpusPath(typeObj.specializedSchema, newFolder);
                    if (worked === false) {
                        allWentWell = false;
                    }
                    else {
                        typeObj.specializedSchema = corpPath;
                    }
                    break;
                }
                case internal_1.cdmObjectType.dataPartitionPatternDef: {
                    const typeObj = iObject;
                    [corpPath, worked] = this.localizeCorpusPath(typeObj.rootLocation, newFolder);
                    if (worked === false) {
                        allWentWell = false;
                    }
                    else {
                        typeObj.rootLocation = corpPath;
                    }
                    [corpPath, worked] = this.localizeCorpusPath(typeObj.specializedSchema, newFolder);
                    if (worked === false) {
                        allWentWell = false;
                    }
                    else {
                        typeObj.specializedSchema = corpPath;
                    }
                    break;
                }
                case internal_1.cdmObjectType.e2eRelationshipDef: {
                    const typeObj = iObject;
                    [corpPath, worked] = this.localizeCorpusPath(typeObj.toEntity, newFolder);
                    if (worked === false) {
                        allWentWell = false;
                    }
                    else {
                        typeObj.toEntity = corpPath;
                    }
                    [corpPath, worked] = this.localizeCorpusPath(typeObj.fromEntity, newFolder);
                    if (worked === false) {
                        allWentWell = false;
                    }
                    else {
                        typeObj.fromEntity = corpPath;
                    }
                    break;
                }
                case internal_1.cdmObjectType.manifestDeclarationDef: {
                    const typeObj = iObject;
                    [corpPath, worked] = this.localizeCorpusPath(typeObj.definition, newFolder);
                    if (worked === false) {
                        allWentWell = false;
                    }
                    else {
                        typeObj.definition = corpPath;
                    }
                    break;
                }
                default:
            }
            return false;
        }, undefined);
        return allWentWell;
    }
    getObjectType() {
        // let bodyCode = () =>
        {
            return internal_1.cdmObjectType.documentDef;
        }
        // return p.measure(bodyCode);
    }
    fetchObjectDefinition(resOpt) {
        return undefined;
    }
    fetchObjectDefinitionName() {
        return this.name;
    }
    copy(resOpt, host) {
        // let bodyCode = () =>
        {
            if (!resOpt) {
                resOpt = new internal_1.resolveOptions(this, this.ctx.corpus.defaultResolutionDirectives);
            }
            let copy;
            if (!host) {
                copy = new CdmDocumentDefinition(this.ctx, this.name);
            }
            else {
                copy = host;
                copy.ctx = this.ctx;
                copy.name = this.name;
                copy.definitions.clear();
                copy.declarationsIndexed = false;
                copy.internalDeclarations = new Map();
                copy.needsIndexing = true;
                copy.imports.clear();
                copy.importsIndexed = false;
                copy.importPriorities = undefined;
            }
            copy.inDocument = copy;
            copy.isDirty = true;
            copy.folderPath = this.folderPath;
            copy.schema = this.schema;
            copy.jsonSchemaSemanticVersion = this.jsonSchemaSemanticVersion;
            copy.documentVersion = this.documentVersion;
            for (const def of this.definitions) {
                copy.definitions.push(def);
            }
            for (const imp of this.imports) {
                copy.imports.push(imp);
            }
            return copy;
        }
        // return p.measure(bodyCode);
    }
    validate() {
        // let bodyCode = () =>
        {
            if (!this.name) {
                let missingFields = ['name'];
                internal_1.Logger.error(this.ctx, this.TAG, this.validate.name, this.atCorpusPath, internal_1.cdmLogCode.ErrValdnIntegrityCheckFailure, missingFields.map((s) => `'${s}'`).join(', '), this.atCorpusPath);
                return false;
            }
            return true;
        }
        // return p.measure(bodyCode);
    }
    /**
     * @internal
     */
    constructResolvedAttributes(resOpt, under) {
        // let bodyCode = () =>
        {
            return undefined;
        }
        // return p.measure(bodyCode);
    }
    /**
     * @internal
     */
    constructResolvedTraits(rtsb, resOpt) {
        // let bodyCode = () =>
        {
            return undefined;
        }
        // return p.measure(bodyCode);
    }
    getSchema() {
        // let bodyCode = () =>
        {
            return this.schema;
        }
        // return p.measure(bodyCode);
    }
    getName() {
        // let bodyCode = () =>
        {
            return this.name;
        }
        // return p.measure(bodyCode);
    }
    setName(name) {
        // let bodyCode = () =>
        {
            this.name = name;
            return this.name;
        }
        // return p.measure(bodyCode);
    }
    getSchemaVersion() {
        // let bodyCode = () =>
        {
            return this.jsonSchemaSemanticVersion;
        }
        // return p.measure(bodyCode);
    }
    getDefinitions() {
        // let bodyCode = () =>
        {
            return this.definitions;
        }
        // return p.measure(bodyCode);
    }
    getFolder() {
        return this.owner;
    }
    get atCorpusPath() {
        if (!this.owner) {
            return `NULL:/${this.name}`;
        }
        else {
            return `${this.owner.atCorpusPath}${this.name}`;
        }
    }
    visit(pathFrom, preChildren, postChildren) {
        // let bodyCode = () =>
        {
            if (preChildren && preChildren(this, pathFrom)) {
                return false;
            }
            if (this.imports) {
                if (this.imports.visitArray(pathFrom, preChildren, postChildren)) {
                    return true;
                }
            }
            if (this.definitions) {
                if (this.definitions.visitArray(pathFrom, preChildren, postChildren)) {
                    return true;
                }
            }
            if (postChildren && postChildren(this, pathFrom)) {
                return true;
            }
            return false;
        }
        // return p.measure(bodyCode);
    }
    /**
     * saves the document back through the adapter in the requested format
     * format is specified via document name/extension based on conventions:
     * 'model.json' for back compat model, '*.manifest.cdm.json' for manifest, '*.cdm.json' for cdm defs
     * saveReferenced (default false) when true will also save any schema defintion documents that are
     * linked from the source doc and that have been modified. existing document names are used for those.
     * returns false on any failure
     */
    saveAsAsync(newName, saveReferenced = false, options) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield using_statement_1.using(Logger_1.enterScope(CdmDocumentDefinition.name, this.ctx, this.saveAsAsync.name), (_) => __awaiter(this, void 0, void 0, function* () {
                if (!options) {
                    options = new internal_1.copyOptions();
                }
                const resOpt = new internal_1.resolveOptions(this, this.ctx.corpus.defaultResolutionDirectives);
                if (!(yield this.indexIfNeeded(resOpt))) {
                    internal_1.Logger.error(this.ctx, this.TAG, this.saveAsAsync.name, this.atCorpusPath, internal_1.cdmLogCode.ErrIndexFailed, this.name);
                    return false;
                }
                // if save to the same document name, then we are no longer 'dirty'
                if (newName === this.name) {
                    this.isDirty = false;
                }
                if ((yield this.ctx.corpus.persistence.saveDocumentAsAsync(this, options, newName, saveReferenced)) === false) {
                    return false;
                }
                if (this instanceof internal_1.CdmManifestDefinition) {
                    for (const entity of this.entities) {
                        if (entity instanceof internal_1.CdmLocalEntityDeclarationDefinition) {
                            entity.resetLastFileModifiedOldTime();
                        }
                        for (const relationship of this.relationships) {
                            relationship.resetLastFileModifiedOldTime();
                        }
                    }
                    // Log the telemetry if the document is a manifest
                    internal_1.Logger.ingestManifestTelemetry(this, this.ctx, this.TAG, this.saveAsAsync.name, this.atCorpusPath);
                }
                else {
                    // Log the telemetry of all entities contained in the document
                    for (const obj of this.definitions) {
                        if (obj instanceof internal_1.CdmEntityDefinition) {
                            internal_1.Logger.ingestEntityTelemetry(obj, this.ctx, this.TAG, this.saveAsAsync.name, obj.atCorpusPath);
                        }
                    }
                }
                return true;
            }));
        });
    }
    refreshAsync(resOpt) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!resOpt) {
                resOpt = new internal_1.resolveOptions(this, this.ctx.corpus.defaultResolutionDirectives);
            }
            this.needsIndexing = true;
            this.declarationsIndexed = false;
            this.isValid = true;
            return this.indexIfNeeded(resOpt, true);
        });
    }
    /**
     * @internal
     * Remove any old document content from caches and re-declare and resolve with new content
     */
    indexIfNeeded(resOpt, loadImports = false) {
        return __awaiter(this, void 0, void 0, function* () {
            // let bodyCode = () =>
            {
                if (!this.owner) {
                    internal_1.Logger.error(this.ctx, this.TAG, this.indexIfNeeded.name, this.atCorpusPath, internal_1.cdmLogCode.ErrValdnMissingDoc, this.name);
                    return false;
                }
                const corpus = this.owner.corpus;
                const needsIndexing = corpus.documentLibrary.markDocumentForIndexing(this);
                if (!needsIndexing) {
                    return true;
                }
                // if the imports load strategy is "lazyLoad", loadImports value will be the one sent by the called function.
                if (resOpt.importsLoadStrategy === internal_1.importsLoadStrategy.doNotLoad) {
                    loadImports = false;
                }
                else if (resOpt.importsLoadStrategy === internal_1.importsLoadStrategy.load) {
                    loadImports = true;
                }
                // make the internal machinery pay attention to this document for this call.
                const docsLoading = new Set([this.atCorpusPath]);
                if (loadImports) {
                    yield corpus.resolveImportsAsync(this, docsLoading, resOpt);
                }
                // make the corpus internal machinery pay attention to this document for this call.
                corpus.documentLibrary.markDocumentForIndexing(this);
                return corpus.indexDocuments(resOpt, loadImports, this, docsLoading);
            }
            // return p.measure(bodyCode);
        });
    }
    /**
     * @internal
     */
    getImportPriorities() {
        if (this.importPriorities === undefined) {
            const importPriorities = new internal_1.ImportPriorities();
            importPriorities.importPriority.set(this, new internal_1.ImportInfo(0, false));
            this.prioritizeImports(new Set(), importPriorities, 1, false);
            this.importPriorities = importPriorities;
        }
        // make a copy so the caller doesn't mess these up
        return this.importPriorities.copy();
    }
    /**
     * @internal
     */
    fetchObjectFromDocumentPath(objectPath, resOpt) {
        // let bodyCode = () =>
        {
            // in current document?
            if (this.internalDeclarations.has(objectPath)) {
                return this.internalDeclarations.get(objectPath);
            }
            else {
                // this might be a request for an object def drill through of a reference.
                // path/(object)/paths
                // there can be several such requests in one path AND some of the requested
                // defintions might be defined inline inside a reference meaning the declared path
                // includes that reference name and could still be inside this document. example:
                // /path/path/refToInline/(object)/member1/refToSymbol/(object)/member2
                // the full path is not in this doc but /path/path/refToInline/(object)/member1/refToSymbol
                // is declared in this document. we then need to go to the doc for refToSymbol and
                // search for refToSymbol/member2
                // work backward until we find something in this document
                let lastObj = objectPath.lastIndexOf('/(object)');
                while (lastObj > 0) {
                    const thisDocPart = objectPath.slice(0, lastObj);
                    if (this.internalDeclarations.has(thisDocPart)) {
                        const thisDocObjRef = this.internalDeclarations.get(thisDocPart);
                        const thatDocObjDef = thisDocObjRef.fetchObjectDefinition(resOpt);
                        if (thatDocObjDef !== undefined) {
                            // get from other document.
                            // but first fix the path to look like it is relative to that object as declared in that doc
                            let thatDocPart = objectPath.slice(lastObj + '/(object)'.length);
                            thatDocPart = `${thatDocObjDef.declaredPath}${thatDocPart}`;
                            if (thatDocPart === objectPath) {
                                // we got back to were we started. probably because something is just not found.
                                return undefined;
                            }
                            return thatDocObjDef.inDocument.fetchObjectFromDocumentPath(thatDocPart, resOpt);
                        }
                        return undefined;
                    }
                    lastObj = thisDocPart.lastIndexOf('/(object)');
                }
            }
            return undefined;
        }
        // return p.measure(bodyCode);
    }
    /**
     * @internal
     */
    reload() {
        return __awaiter(this, void 0, void 0, function* () {
            yield (this.ctx.corpus).fetchObjectAsync(this.corpusPath);
        });
    }
    /**
     * @internal
     */
    saveLinkedDocuments(options) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!options) {
                options = new internal_1.copyOptions();
            }
            // the only linked documents would be the imports
            if (this.imports !== undefined) {
                for (const imp of this.imports) {
                    // get the document object from the import
                    const docPath = this.ctx.corpus.storage.createAbsoluteCorpusPath(imp.corpusPath, this);
                    const docImp = yield this.ctx.corpus.fetchObjectAsync(docPath);
                    if (docImp !== undefined && docImp.isDirty) {
                        // save it with the same name
                        if ((yield docImp.saveAsAsync(docImp.name, true, options)) === false) {
                            internal_1.Logger.error(this.ctx, this.TAG, this.saveLinkedDocuments.name, docImp.atCorpusPath, internal_1.cdmLogCode.ErrDocImportSavingFailure, this.name);
                            return false;
                        }
                    }
                }
            }
            return true;
        });
    }
    /**
     * changes a relative corpus path to be relative to the new folder
     */
    localizeCorpusPath(path, newFolder) {
        // if this isn't a local path, then don't do anything to it
        if (!path) {
            return [path, true];
        }
        // but first, if there was no previous folder (odd) then just localize as best we can
        const oldFolder = this.owner;
        let newPath;
        if (!oldFolder) {
            newPath = this.ctx.corpus.storage.createRelativeCorpusPath(path, newFolder);
        }
        else {
            // if the current value != the absolute path, then assume it is a relative path
            const absPath = this.ctx.corpus.storage.createAbsoluteCorpusPath(path, oldFolder);
            if (absPath === path) {
                newPath = absPath; // leave it alone
            }
            else {
                // make it relative to the new folder then
                newPath = this.ctx.corpus.storage.createRelativeCorpusPath(absPath, newFolder);
            }
        }
        if (newPath === undefined) {
            return [newPath, false];
        }
        return [newPath, true];
    }
    prioritizeImports(processedSet, importPriorities, sequence, skipMonikered) {
        // goal is to make a map from the reverse order of imports (breadth first) to the first (aka last) sequence number in that list.
        // This gives the semantic that the 'last/shallowest' definition for a duplicate symbol wins,
        // the lower in this list a document shows up, the higher priority its definitions are for resolving conflicts.
        // for 'moniker' imports, keep track of the 'last/shallowest' use of each moniker tag.
        // maps document to priority.
        const priorityMap = importPriorities.importPriority;
        // maps moniker to document.
        const monikerMap = importPriorities.monikerPriorityMap;
        // if already in list, don't do this again
        if (processedSet.has(this)) {
            // if the first document in the priority map is this then the document was the starting point of the recursion.
            // and if this document is present in the processedSet we know that there is a circular list of imports.
            if (priorityMap.has(this) && priorityMap.get(this).priority === 0) {
                importPriorities.hasCircularImport = true;
            }
            return sequence;
        }
        processedSet.add(this);
        if (this.imports) {
            const revImp = this.imports.allItems.slice()
                .reverse();
            const monikerImports = [];
            // first add the imports done at this level only in reverse order.
            for (const imp of revImp) {
                const impDoc = imp.document;
                // moniker imports will be added to the end of the priority list later.
                if (impDoc) {
                    if (imp.document && !imp.moniker && !priorityMap.has(impDoc)) {
                        // add doc.
                        priorityMap.set(impDoc, new internal_1.ImportInfo(sequence, false));
                        sequence++;
                    }
                    else {
                        monikerImports.push(impDoc);
                    }
                }
                else {
                    internal_1.Logger.warning(this.ctx, this.TAG, this.prioritizeImports.name, imp.atCorpusPath, internal_1.cdmLogCode.WarnDocImportNotLoaded, imp.corpusPath);
                }
            }
            // now add the imports of the imports.
            for (const imp of revImp) {
                const impDoc = imp.document;
                const isMoniker = !!imp.moniker;
                if (!impDoc) {
                    internal_1.Logger.warning(this.ctx, this.TAG, this.prioritizeImports.name, imp.atCorpusPath, internal_1.cdmLogCode.WarnDocImportNotLoaded, imp.corpusPath);
                }
                // if the document has circular imports its order on the impDoc.ImportPriorities list is not correct.
                // since the document itself will always be the first one on the list.
                if (impDoc !== undefined && impDoc.importPriorities !== undefined && !impDoc.importPriorities.hasCircularImport) {
                    // lucky, already done so avoid recursion and copy.
                    const impPriSub = impDoc.getImportPriorities();
                    impPriSub.importPriority.delete(impDoc); // because already added above.
                    impPriSub.importPriority.forEach((v, k) => {
                        // if the document is imported with moniker in another document do not include it in the priority list of this one.
                        // moniker imports are only added to the priority list of the document that directly imports them.
                        if (!priorityMap.has(k) && !v.isMoniker) {
                            // add doc.
                            priorityMap.set(k, new internal_1.ImportInfo(sequence, false));
                            sequence++;
                        }
                    });
                    // if the import is not monikered then merge its monikerMap to this one.
                    if (!isMoniker) {
                        impPriSub.monikerPriorityMap.forEach((v, k) => {
                            monikerMap.set(k, v);
                        });
                    }
                }
                else if (impDoc !== undefined) {
                    // skip the monikered imports from here if this is a monikered import itself 
                    // and we are only collecting the dependencies.
                    sequence = impDoc.prioritizeImports(processedSet, importPriorities, sequence, isMoniker);
                }
            }
            // skip the monikered imports from here if this is a monikered import itself and we are only collecting the dependencies.
            if (!skipMonikered) {
                // moniker imports are prioritized by the 'closest' use of the moniker to the starting doc.
                // so last one found in this recursion.
                for (const imp of this.imports) {
                    if (imp.document && imp.moniker) {
                        monikerMap.set(imp.moniker, imp.document);
                    }
                }
                // if the document index is zero, the document being processed is the root of the imports chain.
                // in this case add the monikered imports to the end of the priorityMap.
                if (priorityMap.has(this) && priorityMap.get(this).priority === 0) {
                    for (const imp of monikerImports) {
                        if (!priorityMap.has(imp)) {
                            priorityMap.set(imp, new internal_1.ImportInfo(sequence, true));
                            sequence++;
                        }
                    }
                }
            }
        }
        return sequence;
    }
    /**
     * @internal
     */
    importPathToDoc(docDest) {
        const avoidLoop = new Set();
        const internalImportPathToDoc = (docCheck, path) => {
            if (docCheck === docDest) {
                return '';
            }
            if (avoidLoop.has(docCheck)) {
                return undefined;
            }
            avoidLoop.add(docCheck);
            // if the docDest is one of the monikered imports of docCheck, then add the moniker and we are cool
            if (docCheck.importPriorities && docCheck.importPriorities.monikerPriorityMap && docCheck.importPriorities.monikerPriorityMap.size > 0) {
                for (const monPair of docCheck.importPriorities.monikerPriorityMap) {
                    if (monPair[1] === docDest) {
                        return `${path}${monPair[0]}/`;
                    }
                }
            }
            // ok, what if the document can be reached directly from the imports here
            let impInfo;
            if (docCheck.importPriorities && docCheck.importPriorities.importPriority && !docCheck.importPriorities.importPriority.has(docDest)) {
                impInfo = undefined;
            }
            if (impInfo && !impInfo.isMoniker) {
                // good enough
                return path;
            }
            // still nothing, now we need to check those docs deeper
            if (docCheck.importPriorities && docCheck.importPriorities.monikerPriorityMap && docCheck.importPriorities.monikerPriorityMap.size > 0) {
                for (const monPair of docCheck.importPriorities.monikerPriorityMap) {
                    const pathFound = internalImportPathToDoc(monPair[1], `${path}${monPair[0]}/`);
                    if (pathFound != null) {
                        return pathFound;
                    }
                }
            }
            if (docCheck.importPriorities && docCheck.importPriorities.importPriority && docCheck.importPriorities.importPriority.size > 0) {
                for (const impInfoPair of docCheck.importPriorities.importPriority) {
                    if (!impInfoPair[1].isMoniker) {
                        const pathFound = internalImportPathToDoc(impInfoPair[0], path);
                        if (pathFound) {
                            return pathFound;
                        }
                    }
                }
            }
            return undefined;
        };
        return internalImportPathToDoc(this, '');
    }
}
exports.CdmDocumentDefinition = CdmDocumentDefinition;
/**
 * The maximum json semantic version supported by this ObjectModel version.
 */
CdmDocumentDefinition.currentJsonSchemaSemanticVersion = '1.4.0';
//# sourceMappingURL=CdmDocumentDefinition.js.map