"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.CdmEntityDefinition = void 0;
const internal_1 = require("../internal");
const cdmObjectTypeGuards_1 = require("../Utilities/cdmObjectTypeGuards");
const using_statement_1 = require("using-statement");
const Logger_1 = require("../Utilities/Logging/Logger");
class CdmEntityDefinition extends internal_1.CdmObjectDefinitionBase {
    constructor(ctx, entityName, extendsEntity) {
        super(ctx);
        this.TAG = CdmEntityDefinition.name;
        this.resolvingEntityReferences = false;
        // let bodyCode = () =>
        {
            this.objectType = internal_1.cdmObjectType.entityDef;
            this.entityName = entityName;
            if (extendsEntity) {
                this.extendsEntity = extendsEntity;
            }
            this.attributes = new internal_1.CdmCollection(this.ctx, this, internal_1.cdmObjectType.typeAttributeDef);
            this.traitToPropertyMap = new internal_1.traitToPropertyMap(this);
        }
        // return p.measure(bodyCode);
    }
    static get objectType() {
        return internal_1.cdmObjectType.entityDef;
    }
    get sourceName() {
        return this.traitToPropertyMap.fetchPropertyValue('sourceName');
    }
    set sourceName(val) {
        this.traitToPropertyMap.updatePropertyValue('sourceName', val);
    }
    get description() {
        return this.traitToPropertyMap.fetchPropertyValue('description');
    }
    set description(val) {
        this.traitToPropertyMap.updatePropertyValue('description', val);
    }
    get displayName() {
        return this.traitToPropertyMap.fetchPropertyValue('displayName');
    }
    set displayName(val) {
        this.traitToPropertyMap.updatePropertyValue('displayName', val);
    }
    get version() {
        return this.traitToPropertyMap.fetchPropertyValue('version');
    }
    set version(val) {
        this.traitToPropertyMap.updatePropertyValue('version', val);
    }
    get cdmSchemas() {
        return this.traitToPropertyMap.fetchPropertyValue('cdmSchemas');
    }
    set cdmSchemas(val) {
        this.traitToPropertyMap.updatePropertyValue('cdmSchemas', val);
    }
    get primaryKey() {
        return this.traitToPropertyMap.fetchPropertyValue('primaryKey');
    }
    /**
     * @internal
     */
    get isResolved() {
        return this.traitToPropertyMap.fetchPropertyValue('isResolved');
    }
    get extendsEntity() {
        return this._extendsEntity;
    }
    set extendsEntity(value) {
        if (value) {
            value.owner = this;
        }
        this._extendsEntity = value;
    }
    /**
     * @internal
     */
    getProperty(propertyName) {
        return this.traitToPropertyMap.fetchPropertyValue(propertyName, true);
    }
    getObjectType() {
        // let bodyCode = () =>
        {
            return internal_1.cdmObjectType.entityDef;
        }
        // return p.measure(bodyCode);
    }
    copy(resOpt, host) {
        // let bodyCode = () =>
        {
            if (!resOpt) {
                resOpt = new internal_1.resolveOptions(this, this.ctx.corpus.defaultResolutionDirectives);
            }
            let copy;
            if (!host) {
                copy = new CdmEntityDefinition(this.ctx, this.entityName, undefined);
            }
            else {
                copy = host;
                copy.entityName = this.entityName;
                copy.attributes.clear();
            }
            copy.extendsEntity = this.extendsEntity ? this.extendsEntity.copy(resOpt) : undefined;
            copy.extendsEntityResolutionGuidance = this.extendsEntityResolutionGuidance
                ? this.extendsEntityResolutionGuidance.copy(resOpt) : undefined;
            copy.attributeContext = this.attributeContext ? this.attributeContext.copy(resOpt) : undefined;
            for (const att of this.attributes) {
                copy.attributes.push(att.copy(resOpt));
            }
            this.copyDef(resOpt, copy);
            return copy;
        }
        // return p.measure(bodyCode);
    }
    validate() {
        // let bodyCode = () =>
        {
            if (!this.entityName) {
                let missingFields = ['entityName'];
                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);
    }
    getName() {
        // let bodyCode = () =>
        {
            return this.entityName;
        }
        // return p.measure(bodyCode);
    }
    /**
     * @internal
     */
    addAttributeDef(attDef) {
        // let bodyCode = () =>
        {
            if (!this.attributes) {
                this.attributes = new internal_1.CdmCollection(this.ctx, this, internal_1.cdmObjectType.typeAttributeDef);
            }
            this.attributes.push(attDef);
            return attDef;
        }
        // return p.measure(bodyCode);
    }
    visit(pathFrom, preChildren, postChildren) {
        // let bodyCode = () =>
        {
            const path = this.fetchDeclaredPath(pathFrom);
            if (preChildren && preChildren(this, path)) {
                return false;
            }
            if (this.extendsEntity && this.extendsEntity.visit(`${path}/extendsEntity/`, preChildren, postChildren)) {
                return true;
            }
            if (this.extendsEntityResolutionGuidance) {
                this.extendsEntityResolutionGuidance.owner = this;
                if (this.extendsEntityResolutionGuidance.visit(`${path}/extendsEntityResolutionGuidance/`, preChildren, postChildren)) {
                    return true;
                }
            }
            if (this.visitDef(path, preChildren, postChildren)) {
                return true;
            }
            if (this.attributeContext) {
                this.attributeContext.owner = this;
                if (this.attributeContext.visit(`${path}/attributeContext/`, preChildren, postChildren)) {
                    return true;
                }
            }
            if (this.attributes) {
                if (this.attributes.visitArray(`${path}/hasAttributes/`, preChildren, postChildren)) {
                    return true;
                }
            }
            if (postChildren && postChildren(this, path)) {
                return true;
            }
            return false;
        }
        // return p.measure(bodyCode);
    }
    isDerivedFrom(base, resOpt) {
        // let bodyCode = () =>
        {
            if (!resOpt) {
                resOpt = new internal_1.resolveOptions(this, this.ctx.corpus.defaultResolutionDirectives);
            }
            return this.isDerivedFromDef(resOpt, this.extendsEntity, this.getName(), base);
        }
        // return p.measure(bodyCode);
    }
    /**
     * @internal
     */
    constructResolvedTraits(rtsb, resOpt) {
        // let bodyCode = () =>
        {
            // base traits then add any elevated from attributes then add things exhibited by the att.
            const base = this.extendsEntity;
            if (base) {
                // merge in all from base class
                rtsb.mergeTraits(base.fetchResolvedTraits(resOpt));
            }
            if (this.attributes) {
                let rtsElevated = new internal_1.ResolvedTraitSet(resOpt);
                for (const att of this.attributes) {
                    const rtsAtt = att.fetchResolvedTraits(resOpt);
                    if (rtsAtt && rtsAtt.hasElevated) {
                        rtsElevated = rtsElevated.mergeSet(rtsAtt, true);
                    }
                }
                rtsb.mergeTraits(rtsElevated);
            }
            this.constructResolvedTraitsDef(undefined, rtsb, resOpt);
        }
        // return p.measure(bodyCode);
    }
    /**
     * @internal
     */
    constructResolvedAttributes(resOpt, under) {
        var _a;
        // let bodyCode = () =>
        {
            // find and cache the complete set of attributes
            // attributes definitions originate from and then get modified by subsequent re-defintions from (in this order):
            // an extended entity, traits applied to extended entity, exhibited traits of main entity,
            // the (datatype or entity) used as an attribute, traits applied to that datatype or entity,
            // the relationsip of the attribute, the attribute definition itself and included attribute groups,
            // any traits applied to the attribute.
            this.rasb = new internal_1.ResolvedAttributeSetBuilder();
            // local var needed because we now allow reentry
            const rasb = this.rasb;
            rasb.ras.setAttributeContext(under);
            if (this.extendsEntity) {
                const extRef = this.extendsEntity;
                let extendsRefUnder;
                let acpExtEnt;
                if (under) {
                    const acpExt = {
                        under: under,
                        type: internal_1.cdmAttributeContextType.entityReferenceExtends,
                        name: 'extends',
                        regarding: undefined,
                        includeTraits: false
                    };
                    extendsRefUnder = rasb.ras.createAttributeContext(resOpt, acpExt);
                }
                if (extRef.explicitReference && extRef.fetchObjectDefinition(resOpt).objectType === internal_1.cdmObjectType.projectionDef) {
                    // A Projection
                    const extRefObjDef = extRef.fetchObjectDefinition(resOpt);
                    if (extendsRefUnder) {
                        acpExtEnt = {
                            under: extendsRefUnder,
                            type: internal_1.cdmAttributeContextType.projection,
                            name: extRefObjDef.getName(),
                            regarding: extRef,
                            includeTraits: false
                        };
                    }
                    const projDirective = new internal_1.ProjectionDirective(resOpt, this, extRef);
                    const projDef = extRefObjDef;
                    const projCtx = projDef.constructProjectionContext(projDirective, extendsRefUnder);
                    rasb.ras = projDef.extractResolvedAttributes(projCtx, under);
                }
                else {
                    // An Entity Reference
                    if (extendsRefUnder) {
                        acpExtEnt = {
                            under: extendsRefUnder,
                            type: internal_1.cdmAttributeContextType.entity,
                            name: (_a = extRef.namedReference) !== null && _a !== void 0 ? _a : extRef.explicitReference.getName(),
                            regarding: extRef,
                            includeTraits: true // "Entity" is the thing with traits - That perches in the tree - And sings the tune and never waits - To see what it should be.
                        };
                    }
                    rasb.mergeAttributes(this.extendsEntity
                        .fetchResolvedAttributes(resOpt, acpExtEnt));
                    if (!resOpt.checkAttributeCount(rasb.ras.resolvedAttributeCount)) {
                        internal_1.Logger.error(this.ctx, this.TAG, this.constructResolvedAttributes.name, null, internal_1.cdmLogCode.ErrRelMaxResolvedAttrReached, this.entityName);
                        return undefined;
                    }
                    if (this.extendsEntityResolutionGuidance) {
                        resOpt.usedResolutionGuidance = true;
                        /**
                         * some guidance was given on how to integrate the base attributes into the set. Apply that guidance
                         */
                        const rtsBase = this.fetchResolvedTraits(resOpt);
                        /**
                         * this context object holds all of the info about what needs to happen to resolve these attributes
                         * make a copy and set defaults if needed.
                         */
                        const resGuide = this.extendsEntityResolutionGuidance.copy(resOpt);
                        resGuide.updateAttributeDefaults(resGuide.fetchObjectDefinitionName(), this);
                        /**
                         * holds all the info needed by the resolver code
                         */
                        const arc = new internal_1.AttributeResolutionContext(resOpt, resGuide, rtsBase);
                        rasb.generateApplierAttributes(arc, false); /** true = apply the prepared traits to new atts */
                    }
                }
            }
            rasb.markInherited();
            rasb.ras.setAttributeContext(under);
            if (this.attributes) {
                let furthestChildDepth = 0;
                for (const att of this.attributes) {
                    let acpAtt;
                    if (under) {
                        acpAtt = {
                            under: under,
                            type: internal_1.cdmAttributeContextType.attributeDefinition,
                            name: att.fetchObjectDefinitionName(),
                            regarding: att,
                            includeTraits: false
                        };
                    }
                    const attRas = att.fetchResolvedAttributes(resOpt, acpAtt);
                    // we can now set depth now that children nodes have been resolved
                    if (cdmObjectTypeGuards_1.isEntityAttributeDefinition(att)) {
                        furthestChildDepth = attRas.depthTraveled > furthestChildDepth ? attRas.depthTraveled : furthestChildDepth;
                    }
                    // before we just merge, need to handle the case of 'attribute restatement' AKA an entity with an attribute having the same name as an attribute
                    // from a base entity. thing might come out with different names, if they do, then any attributes owned by a similar named attribute before
                    // that didn't just pop out of that same named attribute now need to go away.
                    // mark any attributes formerly from this named attribute that don't show again as orphans
                    rasb.ras.markOrphansForRemoval(att.fetchObjectDefinitionName(), attRas);
                    // now merge
                    rasb.mergeAttributes(attRas);
                    if (!resOpt.checkAttributeCount(rasb.ras.resolvedAttributeCount)) {
                        internal_1.Logger.error(this.ctx, this.TAG, this.constructResolvedAttributes.name, null, internal_1.cdmLogCode.ErrRelMaxResolvedAttrReached, this.entityName);
                        return undefined;
                    }
                }
                rasb.ras.depthTraveled = furthestChildDepth;
            }
            rasb.markOrder();
            rasb.ras.setAttributeContext(under);
            // things that need to go away
            rasb.removeRequestedAtts();
            // recursively sets the target owner's to be this entity.
            // required because the replaceAsForeignKey operation uses the owner to generate the `is.linkedEntity.identifier` trait.
            rasb.ras.setTargetOwner(this);
            return rasb;
        }
        // return p.measure(bodyCode);
    }
    /**
     * @internal
     */
    countInheritedAttributes(resOpt) {
        // let bodyCode = () =>
        {
            // ensures that cache exits
            this.fetchResolvedAttributes(resOpt);
            return this.rasb.inheritedMark;
        }
        // return p.measure(bodyCode);
    }
    /**
     * @internal
     * @deprecated
     */
    getResolvedEntity(resOpt) {
        return new internal_1.ResolvedEntity(resOpt, this);
    }
    fetchResolvedEntityReference(resOpt) {
        // let bodyCode = () =>
        {
            const wasPreviouslyResolving = this.ctx.corpus.isCurrentlyResolving;
            this.ctx.corpus.isCurrentlyResolving = true;
            if (!resOpt) {
                resOpt = new internal_1.resolveOptions(this, this.ctx.corpus.defaultResolutionDirectives);
            }
            // this whole resolved entity ref goo will go away when resolved documents are done.
            // for now, it breaks if structured att sets get made.
            resOpt = resOpt.copy();
            resOpt.directives = new internal_1.AttributeResolutionDirectiveSet(new Set(['normalized', 'referenceOnly']));
            const entRefSet = new internal_1.ResolvedEntityReferenceSet(resOpt);
            if (!this.resolvingEntityReferences) {
                this.resolvingEntityReferences = true;
                // get from any base class and then 'fix' those to point here instead.
                const extRef = this.extendsEntity;
                if (extRef) {
                    let extDef = extRef.fetchObjectDefinition(resOpt);
                    if (extDef) {
                        const inherited = extDef.fetchResolvedEntityReference(resOpt);
                        if (inherited) {
                            inherited.set.forEach((res) => {
                                res = res.copy();
                                res.referencing.entity = this;
                                entRefSet.set.push(res);
                            });
                        }
                    }
                }
                if (this.attributes) {
                    for (const attribute of this.attributes) {
                        // if any refs come back from attributes, they don't know who we are, so they don't set the entity
                        const sub = attribute.fetchResolvedEntityReference(resOpt);
                        if (sub) {
                            sub.set.forEach((res) => {
                                res.referencing.entity = this;
                            });
                            entRefSet.add(sub);
                        }
                    }
                }
                this.resolvingEntityReferences = false;
            }
            this.ctx.corpus.isCurrentlyResolving = wasPreviouslyResolving;
            return entRefSet;
        }
        // return p.measure(bodyCode);
    }
    /**
     * @internal
     */
    fetchAttributesWithTraits(resOpt, queryFor) {
        // let bodyCode = () =>
        {
            const ras = this.fetchResolvedAttributes(resOpt);
            if (ras !== undefined) {
                return ras.fetchAttributesWithTraits(resOpt, queryFor);
            }
            return undefined;
        }
        // return p.measure(bodyCode);
    }
    // tslint:disable-next-line:no-suspicious-comment
    // TODO: refactor and split this function to be more structured.
    // tslint:disable-next-line:max-func-body-length
    createResolvedEntityAsync(newEntName, resOpt, folder, newDocName) {
        return __awaiter(this, void 0, void 0, function* () {
            // let bodyCode = () =>
            {
                return yield using_statement_1.using(Logger_1.enterScope(CdmEntityDefinition.name, this.ctx, this.createResolvedEntityAsync.name), (_) => __awaiter(this, void 0, void 0, function* () {
                    if (!resOpt) {
                        resOpt = new internal_1.resolveOptions(this, this.ctx.corpus.defaultResolutionDirectives);
                    }
                    // if the wrtDoc needs to be indexed (like it was just modified) then do that first
                    if (!resOpt.wrtDoc) {
                        internal_1.Logger.error(this.ctx, this.TAG, this.createResolvedEntityAsync.name, null, internal_1.cdmLogCode.ErrDocWrtDocNotfound);
                        return undefined;
                    }
                    if (!newEntName || newEntName === '') {
                        internal_1.Logger.error(this.ctx, this.TAG, this.createResolvedEntityAsync.name, null, internal_1.cdmLogCode.ErrResolveNewEntityNameNotSet);
                        return undefined;
                    }
                    // if the wrtDoc needs to be indexed (like it was just modified) then do that first
                    if (!(yield resOpt.wrtDoc.indexIfNeeded(resOpt, true))) {
                        internal_1.Logger.error(this.ctx, this.TAG, this.createResolvedEntityAsync.name, null, internal_1.cdmLogCode.ErrIndexFailed);
                        return undefined;
                    }
                    if (!folder) {
                        folder = this.inDocument.owner;
                    }
                    const fileName = (newDocName === undefined || newDocName === '') ? `${newEntName}.cdm.json` : newDocName;
                    let origDoc = this.inDocument.atCorpusPath;
                    // Don't overwite the source document
                    const targetAtCorpusPath = `${this.ctx.corpus.storage.createAbsoluteCorpusPath(folder.atCorpusPath, folder)}${fileName}`;
                    if (internal_1.StringUtils.equalsWithIgnoreCase(targetAtCorpusPath, origDoc)) {
                        internal_1.Logger.error(this.ctx, this.TAG, this.createResolvedEntityAsync.name, null, internal_1.cdmLogCode.ErrDocEntityReplacementFailure, targetAtCorpusPath);
                        return undefined;
                    }
                    // make sure the corpus has a set of default artifact attributes 
                    yield this.ctx.corpus.prepareArtifactAttributesAsync();
                    // make the top level attribute context for this entity
                    // for this whole section where we generate the attribute context tree and get resolved attributes
                    // set the flag that keeps all of the parent changes and document dirty from from happening
                    const wasResolving = this.ctx.corpus.isCurrentlyResolving;
                    this.ctx.corpus.isCurrentlyResolving = true;
                    const entName = newEntName;
                    const ctx = this.ctx;
                    let attCtxEnt = ctx.corpus.MakeObject(internal_1.cdmObjectType.attributeContextDef, entName, true);
                    attCtxEnt.ctx = ctx;
                    attCtxEnt.inDocument = this.inDocument;
                    // cheating a bit to put the paths in the right place
                    const acp = {
                        under: attCtxEnt,
                        type: internal_1.cdmAttributeContextType.attributeGroup,
                        name: 'attributeContext'
                    };
                    const attCtxAC = internal_1.CdmAttributeContext.createChildUnder(resOpt, acp);
                    // this is the node that actually is first in the context we save. all definition refs should take the new perspective that they
                    // can only be understood through the resolvedFrom moniker
                    const entRefThis = ctx.corpus.MakeObject(internal_1.cdmObjectType.entityRef, this.getName(), true);
                    entRefThis.owner = this;
                    entRefThis.inDocument = this.inDocument; // need to set owner and inDocument to this starting entity so the ref will be portable to the new document
                    const prevOwner = this.owner;
                    entRefThis.explicitReference = this;
                    // we don't want to change the owner of this entity to the entity reference
                    // re-assign whatever was there before
                    this.owner = prevOwner;
                    const acpEnt = {
                        under: attCtxAC,
                        type: internal_1.cdmAttributeContextType.entity,
                        name: entName,
                        regarding: entRefThis
                    };
                    // reset previous depth information in case there are left overs
                    resOpt.depthInfo.reset();
                    // use this whenever we need to keep references pointing at things that were already found.
                    // used when 'fixing' references by localizing to a new document
                    const resOptCopy = internal_1.CdmAttributeContext.prepareOptionsForResolveAttributes(resOpt);
                    // resolve attributes with this context. the end result is that each resolved attribute
                    // points to the level of the context where it was last modified, merged, created
                    const ras = this.fetchResolvedAttributes(resOptCopy, acpEnt);
                    if (resOptCopy.usedResolutionGuidance) {
                        internal_1.Logger.warning(ctx, this.TAG, this.createResolvedEntityAsync.name, this.atCorpusPath, internal_1.cdmLogCode.WarnDeprecatedResolutionGuidance);
                    }
                    if (ras === undefined) {
                        return undefined;
                    }
                    this.ctx.corpus.isCurrentlyResolving = wasResolving;
                    // make a new document in given folder if provided or the same folder as the source entity
                    folder.documents.remove(fileName);
                    const docRes = folder.documents.push(fileName);
                    // add an import of the source document
                    origDoc = this.ctx.corpus.storage.createRelativeCorpusPath(origDoc, docRes); // just in case we missed the prefix
                    docRes.imports.push(origDoc, 'resolvedFrom');
                    if (this.inDocument.imports.item(internal_1.constants.FOUNDATIONS_CORPUS_PATH) !== undefined) {
                        docRes.imports.push(internal_1.constants.FOUNDATIONS_CORPUS_PATH);
                    }
                    docRes.documentVersion = this.inDocument.documentVersion;
                    // make the empty entity
                    let entResolved = docRes.definitions.push(entName);
                    // grab that context that comes from fetchResolvedAttributes. We are sure this tree is a copy that we can own, so no need to copy it again
                    let attCtx;
                    if (attCtxAC && attCtxAC.contents && attCtxAC.contents.length === 1) {
                        attCtx = attCtxAC.contents.allItems[0];
                    }
                    entResolved.attributeContext = attCtx;
                    if (attCtx) {
                        // fix all of the definition references, parent and lineage references, owner documents, etc. in the context tree
                        attCtx.finalizeAttributeContext(resOptCopy, `${entName}/attributeContext/`, docRes, this.inDocument, 'resolvedFrom', true);
                        // TEST CODE
                        // run over the resolved attributes and make sure none have the dummy context
                        //testResolveAttributeCtx = (rasSub) =>
                        // const testResolveAttributeCtx: (rasSub: ResolvedAttributeSet) => void
                        //   = (rasSub: ResolvedAttributeSet): void => {
                        //    if (rasSub.set.size !== 0 && rasSub.attributeContext.atCoprusPath.startsWith('cacheHolder')) {
                        //      console.log('Bad');
                        //    }
                        //    for (const ra of rasSub.set) {
                        //        if (ra.AttCtx.AtCoprusPath.StartsWith("cacheHolder")) {
                        //          console.log('Bad');
                        //        }
                        //        // the target for a resolved att can be a typeAttribute OR it can be another resolvedAttributeSet (meaning a group)
                        //        if (ra.target instanceof ResolvedAttributeSet)
                        //        {
                        //            testResolveAttributeCtx(ra.target as ResolvedAttributeSet);
                        //        }
                        //    }
                        //}
                        //testResolveAttributeCtx(ras);
                    }
                    // add the traits of the entit, also add to attribute context top node
                    const rtsEnt = this.fetchResolvedTraits(resOpt);
                    rtsEnt.set.forEach((rt) => {
                        let traitRef = internal_1.CdmObjectBase.resolvedTraitToTraitRef(resOptCopy, rt);
                        entResolved.exhibitsTraits.push(traitRef);
                        traitRef = internal_1.CdmObjectBase.resolvedTraitToTraitRef(resOptCopy, rt); // fresh copy
                        if (entResolved.attributeContext) {
                            entResolved.attributeContext.exhibitsTraits.push(traitRef);
                        }
                    });
                    // special trait to explain this is a resolved entity
                    entResolved.indicateAbstractionLevel('resolved', resOpt);
                    if (entResolved.attributeContext) {
                        // the attributes have been named, shaped, etc for this entity so now it is safe to go and
                        // make each attribute context level point at these final versions of attributes
                        // what we have is a resolved attribute set (maybe with structure) where each ra points at the best tree node
                        // we also have the tree of context, we need to flip this around so that the right tree nodes point at the paths to the
                        // right attributes. so run over the resolved atts and then add a path reference to each one into the context contents where is last lived
                        const attPath2Order = new Map();
                        const finishedGroups = new Set();
                        const allPrimaryCtx = new Set(); // keep a list so it is easier to think about these later
                        const pointContextAtResolvedAtts = (rasSub, path) => {
                            for (const ra of rasSub.set) {
                                const raCtx = ra.attCtx;
                                const refs = raCtx.contents;
                                allPrimaryCtx.add(raCtx);
                                let attRefPath = `${path}${ra.resolvedName}`;
                                // the target for a resolved att can be a typeAttribute OR it can be another resolvedAttributeSet (meaning a group)
                                const target = ra.target;
                                if (target && target.objectType) {
                                    // it was an attribute, add to the content of the context, also, keep track of the ordering for all of the att paths we make up
                                    // as we go through the resolved attributes, this is the order of atts in the final resolved entity
                                    if (!attPath2Order.has(attRefPath)) {
                                        var attRef = this.ctx.corpus.MakeObject(internal_1.cdmObjectType.attributeRef, attRefPath, true);
                                        // only need one explanation for this path to the insert order
                                        attPath2Order.set(attRef.namedReference, ra.insertOrder);
                                        raCtx.contents.push(attRef);
                                    }
                                }
                                else {
                                    // a group, so we know an attribute group will get created later with the name of the group and the things it contains will be in
                                    // the members of that group
                                    attRefPath = `${attRefPath}/members/`;
                                    if (!finishedGroups.has(attRefPath)) {
                                        pointContextAtResolvedAtts(ra.target, attRefPath);
                                        finishedGroups.add(attRefPath);
                                    }
                                }
                            }
                        };
                        pointContextAtResolvedAtts(ras, `${entName}/hasAttributes/`);
                        // the generated attribute structures sometimes has a LOT of extra nodes that don't say anything or explain anything
                        // our goal now is to prune that tree to just have the stuff one may need
                        // do this by keeping the leafs and all of the lineage nodes for the attributes that end up in the resolved entity
                        // along with some special nodes that explain entity structure and inherit
                        if (!attCtx.pruneToScope(allPrimaryCtx)) {
                            // TODO: log error
                            return undefined;
                        }
                        // create an all-up ordering of attributes at the leaves of this tree based on insert order
                        // sort the attributes in each context by their creation order and mix that with the other sub-contexts that have been sorted
                        let getOrderNum;
                        const orderContents = (under) => {
                            if (under.lowestOrder === undefined) {
                                under.lowestOrder = -1; // used for group with nothing but traits
                                if (under.contents.length === 1) {
                                    under.lowestOrder = getOrderNum(under.contents.allItems[0]);
                                }
                                else {
                                    under.contents.allItems = under.contents.allItems.sort((l, r) => {
                                        const lNum = getOrderNum(l);
                                        const rNum = getOrderNum(r);
                                        if (lNum !== -1 && (under.lowestOrder === -1 || lNum < under.lowestOrder)) {
                                            under.lowestOrder = lNum;
                                        }
                                        if (rNum !== -1 && (under.lowestOrder === -1 || rNum < under.lowestOrder)) {
                                            under.lowestOrder = rNum;
                                        }
                                        return lNum - rNum;
                                    });
                                }
                            }
                            return under.lowestOrder;
                        };
                        getOrderNum = (item) => {
                            if (item.getObjectType() === internal_1.cdmObjectType.attributeContextDef) {
                                return orderContents(item);
                            }
                            else if (cdmObjectTypeGuards_1.isAttributeReference(item)) {
                                return attPath2Order.get(item.namedReference);
                            }
                            else {
                                return -1; // put the mystery item on top.
                            }
                        };
                        orderContents(attCtx);
                        // resolved attributes can gain traits that are applied to an entity when referenced
                        // since these traits are described in the context, it is redundant and messy to list them in the attribute
                        // so, remove them. create and cache a set of names to look for per context
                        // there is actually a hierarchy to all attributes from the base entity should have all traits applied independently of the
                        // sub-context they come from. Same is true of attribute entities. so do this recursively top down
                        const ctx2traitNames = new Map();
                        const collectContextTraits = (subAttCtx, inheritedTraitNames) => {
                            const traitNamesHere = new Set(inheritedTraitNames);
                            const traitsHere = subAttCtx.exhibitsTraits;
                            if (traitsHere) {
                                traitsHere.allItems.forEach((tat) => { traitNamesHere.add(tat.namedReference); });
                            }
                            ctx2traitNames.set(subAttCtx, traitNamesHere);
                            for (const cr of subAttCtx.contents.allItems) {
                                if (cr.getObjectType() === internal_1.cdmObjectType.attributeContextDef) {
                                    // do this for all types?
                                    collectContextTraits(cr, traitNamesHere);
                                }
                            }
                        };
                        collectContextTraits(attCtx, new Set());
                        // add the attributes, put them in attribute groups if structure needed
                        const resAtt2RefPath = new Map();
                        const addAttributes = (rasSub, container, path) => {
                            for (const ra of rasSub.set) {
                                const attPath = `${path}${ra.resolvedName}`;
                                // use the path of the context associated with this attribute to find the new context that matches on path
                                const raCtx = ra.attCtx;
                                if (ra.target instanceof internal_1.ResolvedAttributeSet) {
                                    // this is a set of attributes.
                                    // make an attribute group to hold them
                                    const attGrp = this.ctx.corpus.MakeObject(internal_1.cdmObjectType.attributeGroupDef, ra.resolvedName, false);
                                    attGrp.attributeContext = this.ctx.corpus.MakeObject(internal_1.cdmObjectType.attributeContextRef, raCtx.atCorpusPath, true);
                                    // debugLineage
                                    //attGrp.AttributeContext.NamedReference = $"{ raCtx.AtCoprusPath}({raCtx.Id})";
                                    // take any traits from the set and make them look like traits exhibited by the group
                                    const avoidSet = ctx2traitNames.get(raCtx);
                                    // traits with the same name can show up on entities and attributes AND have different meanings.
                                    avoidSet.clear();
                                    const rtsAtt = ra.resolvedTraits;
                                    for (const rt of rtsAtt.set) {
                                        if (!rt.trait.ugly) {
                                            // don't mention your ugly traits
                                            if (avoidSet && !avoidSet.has(rt.traitName)) {
                                                // avoid the ones from the context
                                                const traitRef = internal_1.CdmObjectBase.resolvedTraitToTraitRef(resOptCopy, rt);
                                                attGrp.exhibitsTraits.push(traitRef);
                                            }
                                        }
                                    }
                                    // wrap it in a reference and then recurse with this as the new container
                                    const attGrpRef = this.ctx.corpus.MakeObject(internal_1.cdmObjectType.attributeGroupRef, undefined, false);
                                    attGrpRef.explicitReference = attGrp;
                                    container.addAttributeDef(attGrpRef);
                                    // isn't this where ...
                                    addAttributes(ra.target, attGrp, `${attPath}/members/`);
                                }
                                else {
                                    const att = this.ctx.corpus.MakeObject(internal_1.cdmObjectType.typeAttributeDef, ra.resolvedName, false);
                                    att.attributeContext = this.ctx.corpus.MakeObject(internal_1.cdmObjectType.attributeContextRef, raCtx.atCorpusPath, true);
                                    // debugLineage
                                    //att.attributeContext.namedReference = `${raCtx.atCorpusPath}(${raCtx.id})";
                                    const avoidSet = ctx2traitNames.get(raCtx);
                                    // i don't know why i thought this was the right thing to do,
                                    // traits with the same name can show up on entities and attributes AND have different meanings.
                                    avoidSet.clear();
                                    // i really need to figure out the effects of this before making this change
                                    // without it, some traits don't make it to the resolved entity
                                    // with it, too many traits might make it there
                                    const rtsAtt = ra.resolvedTraits;
                                    for (const rt of rtsAtt.set) {
                                        if (!rt.trait.ugly) { // don't mention your ugly traits
                                            if (avoidSet && !avoidSet.has(rt.traitName)) { // avoid the ones from the context
                                                const traitRef = internal_1.CdmObjectBase.resolvedTraitToTraitRef(resOptCopy, rt);
                                                att.appliedTraits.push(traitRef);
                                                // the trait that points at other entities for foreign keys, that is trouble
                                                // the string value in the table needs to be a relative path from the document of this entity
                                                // to the document of the related entity. but, right now it is a relative path to the source entity
                                                // so find those traits, and adjust the paths in the tables they hold
                                                if (rt.traitName === 'is.linkedEntity.identifier') {
                                                    // grab the content of the table from the new ref (should be a copy of orig) 
                                                    let linkTable;
                                                    if (traitRef.arguments && traitRef.arguments.length > 0) {
                                                        if (traitRef.arguments) {
                                                            const argValue = traitRef.arguments.allItems[0].value;
                                                            if (argValue) {
                                                                const expRef = argValue.explicitReference;
                                                                if (expRef) {
                                                                    linkTable = expRef.constantValues;
                                                                }
                                                            }
                                                        }
                                                    }
                                                    if (linkTable && linkTable.length > 0) {
                                                        for (const row of linkTable) {
                                                            if (row.length === 2 || row.length === 3) // either the old table or the new one with relationship name can be there
                                                             {
                                                                // entity path an attribute name
                                                                let fixedPath = row[0];
                                                                fixedPath = this.ctx.corpus.storage.createAbsoluteCorpusPath(fixedPath, this.inDocument); // absolute from relative to this
                                                                fixedPath = this.ctx.corpus.storage.createRelativeCorpusPath(fixedPath, docRes); // realtive to new entity
                                                                row[0] = fixedPath;
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                    // none of the dataformat traits have the bit set that will make them turn into a property
                                    // this is intentional so that the format traits make it into the resolved object
                                    // but, we still want a guess as the data format, so get it and set it.
                                    const impliedDataFormat = att.dataFormat;
                                    if (impliedDataFormat !== internal_1.cdmDataFormat.unknown) {
                                        att.dataFormat = impliedDataFormat;
                                    }
                                    container.addAttributeDef(att);
                                    resAtt2RefPath.set(ra, attPath);
                                }
                            }
                        };
                        addAttributes(ras, entResolved, `${entName}/hasAttributes/`);
                        // any resolved traits that hold arguments with attribute refs should get 'fixed' here
                        const replaceTraitAttRef = (tr, entityHint) => {
                            if (tr.arguments) {
                                for (const arg of tr.arguments.allItems) {
                                    const v = arg.unresolvedValue ? arg.unresolvedValue : arg.getValue();
                                    // is this an attribute reference?
                                    if (v
                                        && v.getObjectType
                                        && v.getObjectType() === internal_1.cdmObjectType.attributeRef) {
                                        // only try this if the reference has no path to it (only happens with intra-entity att refs)
                                        const attRef = v;
                                        if (attRef.namedReference && attRef.namedReference.indexOf('/') === -1) {
                                            if (!arg.unresolvedValue) {
                                                arg.unresolvedValue = arg.value;
                                            }
                                            // give a promise that can be worked out later.
                                            // assumption is that the attribute must come from this entity.
                                            const newAttRef = this.ctx.corpus.MakeObject(internal_1.cdmObjectType.attributeRef, `${entityHint}/(resolvedAttributes)/${attRef.namedReference}`, true);
                                            // inDocument is not propagated during resolution, so set it here
                                            newAttRef.inDocument = arg.inDocument;
                                            arg.setValue(newAttRef);
                                        }
                                    }
                                }
                            }
                        };
                        // fix entity traits
                        if (entResolved.exhibitsTraits) {
                            entResolved.exhibitsTraits.allItems
                                .forEach((et) => {
                                if (et instanceof internal_1.CdmTraitReference) {
                                    replaceTraitAttRef(et, newEntName, false);
                                }
                            });
                        }
                        // fix context traits
                        const fixContextTraits = (subAttCtx, entityHint) => {
                            const traitsHere = subAttCtx.exhibitsTraits;
                            if (traitsHere) {
                                traitsHere.allItems.forEach((tr) => {
                                    if (tr instanceof internal_1.CdmTraitReference) {
                                        replaceTraitAttRef(tr, entityHint, true);
                                    }
                                });
                            }
                            for (const cr of subAttCtx.contents.allItems) {
                                if (cr.getObjectType() === internal_1.cdmObjectType.attributeContextDef) {
                                    // if this is a new entity context, get the name to pass along
                                    const subSubAttCtx = cr;
                                    let subEntityHint = entityHint;
                                    if (subSubAttCtx.type === internal_1.cdmAttributeContextType.entity && subSubAttCtx.definition) {
                                        subEntityHint = subSubAttCtx.definition.namedReference;
                                    }
                                    // do this for all types
                                    fixContextTraits(subSubAttCtx, subEntityHint);
                                }
                            }
                        };
                        fixContextTraits(attCtx, newEntName);
                        // and the attribute traits
                        const entAtts = entResolved.attributes;
                        if (entAtts) {
                            for (const attribute of entAtts.allItems) {
                                const attTraits = attribute.appliedTraits;
                                if (attTraits) {
                                    attTraits.allItems.forEach((tr) => {
                                        if (tr instanceof internal_1.CdmTraitReference) {
                                            replaceTraitAttRef(tr, newEntName, false);
                                        }
                                    });
                                }
                            }
                        }
                    }
                    // we are about to put this content created in the context of various documents (like references to attributes from base entities, etc.)
                    // into one specific document. all of the borrowed refs need to work. so, re-write all string references to work from this new document
                    // the catch-22 is that the new document needs these fixes done before it can be used to make these fixes.
                    // the fix needs to happen in the middle of the refresh
                    // trigger the document to refresh current content into the resolved OM
                    if (attCtx) {
                        attCtx.parent = undefined; // remove the fake parent that made the paths work
                    }
                    const resOptNew = resOpt.copy();
                    resOptNew.localizeReferencesFor = docRes;
                    resOptNew.wrtDoc = docRes;
                    if (!(yield docRes.refreshAsync(resOptNew))) {
                        internal_1.Logger.error(this.ctx, this.TAG, this.createResolvedEntityAsync.name, null, internal_1.cdmLogCode.ErrIndexFailed);
                        return undefined;
                    }
                    // get a fresh ref
                    entResolved = docRes.fetchObjectFromDocumentPath(entName, resOptNew);
                    this.ctx.corpus.resEntMap.set(this.atCorpusPath, entResolved.atCorpusPath);
                    return entResolved;
                }));
            }
            // return p.measure(bodyCode);
        });
    }
    /**
     * creates or sets the has.entitySchemaAbstractionLevel trait to logical, composition, resolved or unknown
     * todo: consider making this public API
     */
    indicateAbstractionLevel(level, resOpt) {
        // see if entitySchemaAbstractionLevel is a known trait to this entity
        if (resOpt && !this.ctx.corpus.resolveSymbolReference(resOpt, this.inDocument, 'has.entitySchemaAbstractionLevel', internal_1.cdmObjectType.traitDef, false)) {
            return;
        }
        // get or add the trait
        let traitRef = this.exhibitsTraits.item('has.entitySchemaAbstractionLevel');
        if (!traitRef) {
            traitRef = new internal_1.CdmTraitReference(this.ctx, 'has.entitySchemaAbstractionLevel', false, true);
            this.exhibitsTraits.push(traitRef);
        }
        // get or add the one argument
        let argDef;
        if (traitRef.arguments && traitRef.arguments.length === 1) {
            argDef = traitRef.arguments.allItems[0];
        }
        else {
            argDef = new internal_1.CdmArgumentDefinition(this.ctx, 'level');
            traitRef.arguments.push(argDef);
        }
        // set it
        argDef.value = level;
    }
    /**
     * Query the entity for a set of attributes that match an input query
     * querySpec = a JSON object (or a string that can be parsed into one) of the form
     * {"attributeName":"", "attributeOrdinal": -1, "traits":[see next]}
     * matched attributes have each of the traits in the array
     * the trait object is specified as
     * {"traitName": queried trait name or base trait name, (optional) "arguments":[{"argumentName": queried argument, "value": queried value}]}
     * null for 0 results or an array of json objects, each matching the shape of the input query, with attribute names filled in
     */
    queryOnTraits(querySpec) {
        return __awaiter(this, void 0, void 0, function* () {
            return undefined;
        });
    }
}
exports.CdmEntityDefinition = CdmEntityDefinition;
//# sourceMappingURL=CdmEntityDefinition.js.map