"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.CdmEntityAttributeDefinition = void 0;
const internal_1 = require("../internal");
class CdmEntityAttributeDefinition extends internal_1.CdmAttribute {
    constructor(ctx, name) {
        super(ctx, name);
        this.TAG = CdmEntityAttributeDefinition.name;
        // let bodyCode = () =>
        {
            this.objectType = internal_1.cdmObjectType.entityAttributeDef;
            this.traitToPropertyMap = new internal_1.traitToPropertyMap(this);
        }
        // return p.measure(bodyCode);
    }
    static get objectType() {
        return internal_1.cdmObjectType.entityAttributeDef;
    }
    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);
    }
    /**
     * The entity attribute's entity reference.
     */
    get entity() {
        return this._entity;
    }
    ;
    set entity(value) {
        if (value) {
            value.owner = this;
        }
        this._entity = value;
    }
    getObjectType() {
        // let bodyCode = () =>
        {
            return internal_1.cdmObjectType.entityAttributeDef;
        }
        // return p.measure(bodyCode);
    }
    isDerivedFrom(base, resOpt) {
        // let bodyCode = () =>
        {
            return false;
        }
        // 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 CdmEntityAttributeDefinition(this.ctx, this.name);
            }
            else {
                copy = host;
                copy.name = this.name;
            }
            copy.entity = this.entity.copy(resOpt);
            this.copyAtt(resOpt, copy);
            return copy;
        }
        // return p.measure(bodyCode);
    }
    validate() {
        // let bodyCode = () =>
        {
            const missingFields = [];
            if (!this.name) {
                missingFields.push('name');
            }
            if (!this.entity) {
                missingFields.push('entity');
            }
            if (this.cardinality) {
                if (!this.cardinality.minimum) {
                    missingFields.push('cardinality.minimum');
                }
                if (!this.cardinality.maximum) {
                    missingFields.push('cardinality.maximum');
                }
            }
            if (missingFields.length > 0) {
                internal_1.Logger.error(this.ctx, this.TAG, this.validate.name, this.atCorpusPath, internal_1.cdmLogCode.ErrValdnIntegrityCheckFailure, this.atCorpusPath, missingFields.map((s) => `'${s}'`).join(', '));
                return false;
            }
            if (this.cardinality) {
                if (!internal_1.CardinalitySettings.isMinimumValid(this.cardinality.minimum)) {
                    internal_1.Logger.error(this.ctx, this.TAG, this.validate.name, this.atCorpusPath, internal_1.cdmLogCode.ErrValdnInvalidMinCardinality, this.cardinality.minimum);
                    return false;
                }
                if (!internal_1.CardinalitySettings.isMaximumValid(this.cardinality.maximum)) {
                    internal_1.Logger.error(this.ctx, this.TAG, this.validate.name, this.atCorpusPath, internal_1.cdmLogCode.ErrValdnInvalidMaxCardinality, this.cardinality.maximum);
                    return false;
                }
            }
            return true;
        }
        // return p.measure(bodyCode);
    }
    visit(pathFrom, preChildren, postChildren) {
        // let bodyCode = () =>
        {
            if (!this.entity) {
                return false;
            }
            const path = this.fetchDeclaredPath(pathFrom);
            if (preChildren && preChildren(this, path)) {
                return false;
            }
            if (this.entity.visit(`${path}/entity/`, preChildren, postChildren)) {
                return true;
            }
            if (this.visitAtt(path, preChildren, postChildren)) {
                return true;
            }
            if (postChildren && postChildren(this, path)) {
                return true;
            }
            return false;
        }
        // return p.measure(bodyCode);
    }
    /**
     * @internal
     */
    constructResolvedTraits(rtsb, resOpt) {
        // let bodyCode = () =>
        {
            // // get from purpose
            if (this.purpose) {
                rtsb.takeReference(this.purpose
                    .fetchResolvedTraits(resOpt));
            }
            this.addResolvedTraitsApplied(rtsb, resOpt);
        }
        // return p.measure(bodyCode);
    }
    /**
     * @internal
     */
    fetchObjectFromCache(resOpt, acpInContext) {
        const kind = 'rasb';
        const ctx = this.ctx;
        // once resolution guidance is fully deprecated, this line can be removed
        const arc = !this.entity.isProjection ? this.fetchAttResContext(resOpt) : undefined;
        // update the depth info and check cache at the correct depth for entity attributes
        resOpt.depthInfo.updateToNextLevel(resOpt, this.isPolymorphicSource, arc);
        const cacheTag = ctx.corpus.createDefinitionCacheTag(resOpt, this, kind, acpInContext ? 'ctx' : '');
        return cacheTag ? ctx.attributeCache.get(cacheTag) : undefined;
    }
    /**
     * @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):
            // the entity used as an attribute, traits applied to that entity,
            // the purpose of the attribute, any traits applied to the attribute.
            let rasb = new internal_1.ResolvedAttributeSetBuilder();
            const underAtt = under;
            let acpEnt;
            if (!resOpt.inCircularReference) {
                if ((_a = this.entity) === null || _a === void 0 ? void 0 : _a.isProjection) {
                    // A Projection
                    // if the max depth is exceeded it should not try to execute the projection
                    if (!resOpt.depthInfo.maxDepthExceeded) {
                        const projDef = this.entity.fetchObjectDefinition(resOpt);
                        ;
                        const projDirective = new internal_1.ProjectionDirective(resOpt, this, this.entity);
                        const projCtx = projDef.constructProjectionContext(projDirective, under);
                        rasb.ras = projDef.extractResolvedAttributes(projCtx, under);
                        // from the traits of purpose and applied here
                        rasb.ras.applyTraits(this.fetchResolvedTraits(resOpt));
                    }
                }
                else {
                    // Resolution guidance
                    resOpt.usedResolutionGuidance = true;
                    const arc = this.fetchAttResContext(resOpt);
                    const relInfo = arc.getRelationshipInfo();
                    if (underAtt) {
                        // make a context for this attribute that holds the attributes that come up from the entity
                        acpEnt = {
                            under: underAtt,
                            type: internal_1.cdmAttributeContextType.entity,
                            name: this.entity.fetchObjectDefinitionName(),
                            regarding: this.entity,
                            includeTraits: true
                        };
                    }
                    if (relInfo.isByRef) {
                        // make the entity context that a real recursion would have give us
                        if (under) {
                            under = rasb.ras.createAttributeContext(resOpt, acpEnt);
                        }
                        // if selecting from one of many attributes, then make a context for each one
                        if (under && relInfo.selectsOne) {
                            // the right way to do this is to get a resolved entity from the embedded entity and then
                            // look through the attribute context hierarchy for non-nested entityReferenceAsAttribute nodes
                            // that seems like a disaster waiting to happen given endless looping, etc.
                            // for now, just insist that only the top level entity attributes declared in the ref entity will work
                            const entPickFrom = this.entity.fetchObjectDefinition(resOpt);
                            const attsPick = entPickFrom.attributes;
                            if (entPickFrom && attsPick) {
                                const l = attsPick.length;
                                for (let i = 0; i < l; i++) {
                                    if (attsPick.allItems[i].getObjectType() === internal_1.cdmObjectType.entityAttributeDef) {
                                        // a table within a table. as expected with a selectsOne attribute
                                        // since this is by ref, we won't get the atts from the table, but we do need the traits that hold the key
                                        // these are the same contexts that would get created if we recursed
                                        // first this attribute
                                        const acpEntAtt = {
                                            under: under,
                                            type: internal_1.cdmAttributeContextType.attributeDefinition,
                                            name: attsPick.allItems[i].fetchObjectDefinitionName(),
                                            regarding: attsPick.allItems[i],
                                            includeTraits: true
                                        };
                                        const pickUnder = rasb.ras.createAttributeContext(resOpt, acpEntAtt);
                                        // and the entity under that attribute
                                        const pickEnt = attsPick.allItems[i].entity;
                                        const pickEntType = (pickEnt.fetchObjectDefinition(resOpt).objectType === internal_1.cdmObjectType.projectionDef) ?
                                            internal_1.cdmAttributeContextType.projection :
                                            internal_1.cdmAttributeContextType.entity;
                                        const acpEntAttEnt = {
                                            under: pickUnder,
                                            type: pickEntType,
                                            name: pickEnt.fetchObjectDefinitionName(),
                                            regarding: pickEnt,
                                            includeTraits: true
                                        };
                                        rasb.ras.createAttributeContext(resOpt, acpEntAttEnt);
                                    }
                                }
                            }
                        }
                        // if we got here because of the max depth, need to impose the directives to make the trait work as expected
                        if (resOpt.depthInfo.maxDepthExceeded) {
                            if (!arc.resOpt.directives) {
                                arc.resOpt.directives = new internal_1.AttributeResolutionDirectiveSet();
                            }
                            arc.resOpt.directives.add('referenceOnly');
                        }
                    }
                    else {
                        const resLink = resOpt.copy();
                        resLink.symbolRefSet = resOpt.symbolRefSet;
                        rasb.mergeAttributes(this.entity.fetchResolvedAttributes(resLink, acpEnt));
                        // need to pass up maxDepthExceeded if it was hit
                        if (resLink.depthInfo && resLink.depthInfo.maxDepthExceeded) {
                            resOpt.depthInfo = resLink.depthInfo.copy();
                        }
                    }
                    // from the traits of purpose and applied here, see if new attributes get generated
                    rasb.ras.setAttributeContext(underAtt);
                    rasb.applyTraits(arc);
                    rasb.generateApplierAttributes(arc, true); // true = apply the prepared traits to new atts
                    // this may have added symbols to the dependencies, so merge them
                    resOpt.symbolRefSet.merge(arc.resOpt.symbolRefSet);
                    // use the traits for linked entity identifiers to record the actual foreign key links
                    if (rasb.ras && rasb.ras.set && (relInfo.isByRef)) {
                        rasb.ras.set.forEach((att) => {
                            if (att.resolvedTraits) {
                                const reqdTrait = att.resolvedTraits.find(resOpt, 'is.linkedEntity.identifier');
                                if (!reqdTrait) {
                                    return;
                                }
                                if (reqdTrait.parameterValues === undefined || reqdTrait.parameterValues.length === 0) {
                                    internal_1.Logger.warning(this.ctx, this.TAG, this.constructResolvedAttributes.name, this.atCorpusPath, internal_1.cdmLogCode.WarnLinkEntIdentArgsNotSupported, att.displayName, this.entity.namedReference);
                                    return;
                                }
                                const entReferences = [];
                                const attReferences = [];
                                const addEntityReference = (entityRef, namespace) => {
                                    const entDef = entityRef.fetchObjectDefinition(resOpt);
                                    if (entDef) {
                                        const otherResTraits = entityRef.fetchResolvedTraits(resOpt);
                                        const identifyingTrait = otherResTraits.find(resOpt, 'is.identifiedBy');
                                        if (otherResTraits && identifyingTrait) {
                                            const attRef = identifyingTrait.parameterValues
                                                .fetchParameterValueByName('attribute').value;
                                            const attNamePath = attRef.namedReference;
                                            const attName = attNamePath.split('/')
                                                .pop();
                                            // path should be absolute and without a namespace
                                            let absoluteEntPath = this.ctx.corpus.storage.createAbsoluteCorpusPath(entDef.atCorpusPath, entDef.inDocument);
                                            entReferences.push(absoluteEntPath);
                                            attReferences.push(attName);
                                        }
                                    }
                                };
                                if (relInfo.selectsOne) {
                                    const entPickFrom = this.entity.fetchObjectDefinition(resOpt);
                                    const attsPick = entPickFrom ? entPickFrom.attributes : undefined;
                                    if (entPickFrom && attsPick) {
                                        const l = attsPick.length;
                                        for (let i = 0; i < l; i++) {
                                            if (attsPick.allItems[i].getObjectType() === internal_1.cdmObjectType.entityAttributeDef) {
                                                const entAtt = attsPick.allItems[i];
                                                addEntityReference(entAtt.entity, this.inDocument.namespace);
                                            }
                                        }
                                    }
                                }
                                else {
                                    addEntityReference(this.entity, this.inDocument.namespace);
                                }
                                const cEnt = this.ctx.corpus.MakeObject(internal_1.cdmObjectType.constantEntityDef);
                                cEnt.setEntityShape(this.ctx.corpus.MakeRef(internal_1.cdmObjectType.entityRef, 'entityGroupSet', true));
                                cEnt.setConstantValues(entReferences.map((entityRef, idx) => [entityRef, attReferences[idx]]));
                                const param = this.ctx.corpus.MakeRef(internal_1.cdmObjectType.entityRef, cEnt, false);
                                reqdTrait.parameterValues.setParameterValue(resOpt, 'entityReferences', param);
                            }
                        });
                    }
                    // a 'structured' directive wants to keep all entity attributes together in a group
                    if (arc.resOpt.directives && arc.resOpt.directives.has('structured')) {
                        const raSub = new internal_1.ResolvedAttribute(arc.traitsToApply.resOpt, rasb.ras, this.name, rasb.ras.attributeContext);
                        if (relInfo.isArray) {
                            // put a resolved trait on this att group, yuck,
                            //  hope I never need to do this again and then need to make a function for this
                            const tr = this.ctx.corpus.MakeObject(internal_1.cdmObjectType.traitRef, 'is.linkedEntity.array', true);
                            const t = tr.fetchObjectDefinition(resOpt);
                            const rt = new internal_1.ResolvedTrait(t, undefined, [], [], undefined, undefined);
                            raSub.resolvedTraits = raSub.resolvedTraits.merge(rt, true);
                        }
                        const depth = rasb.ras.depthTraveled;
                        rasb = new internal_1.ResolvedAttributeSetBuilder();
                        rasb.ras.attributeContext = raSub.attCtx; // this got set to null with the new builder
                        rasb.ownOne(raSub);
                        rasb.ras.depthTraveled = depth;
                    }
                }
            }
            // how ever they got here, mark every attribute from this entity attribute as now being 'owned' by this entityAtt
            rasb.ras.setAttributeOwnership(this.name);
            rasb.ras.depthTraveled += 1;
            return rasb;
        }
        // return p.measure(bodyCode);
    }
    /**
     * @deprecated
     * For internal use only.
     */
    // the only thing we need this code for is testing!!!
    fetchResolvedEntityReference(resOpt) {
        // let bodyCode = () =>
        {
            if (!resOpt) {
                resOpt = new internal_1.resolveOptions(this, this.ctx.corpus.defaultResolutionDirectives);
            }
            else {
                // need to copy so that relationship depth of parent is not overwritten
                resOpt = resOpt.copy();
            }
            const rtsThisAtt = this.fetchResolvedTraits(resOpt);
            const resGuide = this.resolutionGuidance;
            // this context object holds all of the info about what needs to happen to resolve these attributes
            const arc = new internal_1.AttributeResolutionContext(resOpt, resGuide, rtsThisAtt);
            const relInfo = arc.getRelationshipInfo();
            if (relInfo.isByRef && !relInfo.isArray) {
                // only place this is used, so logic here instead of encapsulated.
                // make a set and the one ref it will hold
                const rers = new internal_1.ResolvedEntityReferenceSet(resOpt);
                const rer = new internal_1.ResolvedEntityReference();
                // referencing attribute(s) come from this attribute
                rer.referencing.rasb.mergeAttributes(this.fetchResolvedAttributes(resOpt));
                const resolveSide = (entRef) => {
                    const sideOther = new internal_1.ResolvedEntityReferenceSide();
                    if (entRef) {
                        // reference to the other entity, hard part is the attribue name.
                        // by convention, this is held in a trait that identifies the key
                        sideOther.entity = entRef.fetchObjectDefinition(resOpt);
                        if (sideOther.entity) {
                            let otherAttribute;
                            const otherOpts = new internal_1.resolveOptions(resOpt.wrtDoc, resOpt.directives);
                            const t = entRef.fetchResolvedTraits(otherOpts)
                                .find(otherOpts, 'is.identifiedBy');
                            if (t && t.parameterValues && t.parameterValues.length) {
                                const otherRef = (t.parameterValues.fetchParameterValueByName('attribute').value);
                                if (otherRef && typeof (otherRef) === 'object' && 'fetchObjectDefinition' in otherRef
                                    && typeof (otherRef.fetchObjectDefinition) === 'function') {
                                    otherAttribute = (otherRef).fetchObjectDefinition(otherOpts);
                                    if (otherAttribute) {
                                        if (!otherAttribute.getName) {
                                            otherAttribute.getName();
                                        }
                                        const sideOtherRas = sideOther.entity.fetchResolvedAttributes(otherOpts);
                                        if (sideOtherRas !== undefined) {
                                            sideOther.rasb.ownOne(sideOtherRas
                                                .get(otherAttribute.getName())
                                                .copy());
                                        }
                                    }
                                }
                            }
                        }
                    }
                    return sideOther;
                };
                // either several or one entity
                // for now, a sub for the 'select one' idea
                if ((this.entity).explicitReference) {
                    const entPickFrom = this.entity.fetchObjectDefinition(resOpt);
                    const attsPick = entPickFrom.attributes;
                    if (entPickFrom && attsPick) {
                        const l = attsPick.length;
                        for (let i = 0; i < l; i++) {
                            if (attsPick.allItems[i].getObjectType() === internal_1.cdmObjectType.entityAttributeDef) {
                                const er = attsPick.allItems[i].entity;
                                rer.referenced.push(resolveSide(er));
                            }
                        }
                    }
                }
                else {
                    rer.referenced.push(resolveSide(this.entity));
                }
                rers.set.push(rer);
                return rers;
            }
            return undefined;
        }
        // return p.measure(bodyCode);
    }
    /**
     * Creates an AttributeResolutionContext object based off of resolution guidance information
     * @param resOpt The resolve options
     */
    fetchAttResContext(resOpt) {
        const rtsThisAtt = this.fetchResolvedTraits(resOpt);
        // this context object holds all of the info about what needs to happen to resolve these attributes.
        // make a copy and add defaults if missing
        const resGuideWithDefault = this.resolutionGuidance === undefined
            ? new internal_1.CdmAttributeResolutionGuidance(this.ctx) : this.resolutionGuidance.copy(resOpt);
        resGuideWithDefault.updateAttributeDefaults(this.name, this);
        return new internal_1.AttributeResolutionContext(resOpt, resGuideWithDefault, rtsThisAtt);
    }
}
exports.CdmEntityAttributeDefinition = CdmEntityAttributeDefinition;
//# sourceMappingURL=CdmEntityAttributeDefinition.js.map