"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.CdmObjectReferenceBase = void 0;
const util_1 = require("util");
const internal_1 = require("../internal");
class CdmObjectReferenceBase extends internal_1.CdmObjectBase {
    constructor(ctx, referenceTo, simpleReference) {
        super(ctx);
        this.TAG = CdmObjectReferenceBase.name;
        // let bodyCode = () =>
        {
            if (referenceTo) {
                if (typeof (referenceTo) === 'string') {
                    this.namedReference = referenceTo;
                }
                else {
                    this.explicitReference = referenceTo;
                }
            }
            if (simpleReference) {
                this.simpleNamedReference = true;
            }
            this.appliedTraits = new internal_1.CdmTraitCollection(this.ctx, this);
        }
        // return p.measure(bodyCode);
    }
    get explicitReference() {
        return this._explicitReference;
    }
    set explicitReference(value) {
        if (value) {
            value.owner = this;
        }
        this._explicitReference = value;
    }
    /**
     * @internal
     */
    static offsetAttributePromise(ref) {
        if (ref === undefined) {
            return -1;
        }
        return ref.indexOf(this.resAttToken);
    }
    /**
     * @internal
     */
    copyToHost(ctx, refTo, simpleReference) {
        this.ctx = ctx;
        this.explicitReference = undefined;
        this.namedReference = undefined;
        if (refTo !== undefined) {
            if (util_1.isString(refTo)) {
                // NamedReference is a string or JValue
                this.namedReference = refTo;
            }
            else {
                this.explicitReference = refTo;
            }
        }
        this.simpleNamedReference = simpleReference;
        this.appliedTraits.clear();
        return this;
    }
    /**
     * @deprecated Only for internal use.
     */
    fetchResolvedReference(resOpt) {
        // let bodyCode = () =>
        {
            if (!resOpt) {
                resOpt = new internal_1.resolveOptions(this, this.ctx.corpus.defaultResolutionDirectives);
            }
            if (this.explicitReference) {
                return this.explicitReference;
            }
            if (!this.ctx) {
                return undefined;
            }
            const ctx = this.ctx; // what it actually is
            let res;
            // if this is a special request for a resolved attribute, look that up now
            const seekResAtt = CdmObjectReferenceBase.offsetAttributePromise(this.namedReference);
            if (seekResAtt >= 0) {
                const entName = this.namedReference.substring(0, seekResAtt);
                const attName = this.namedReference.slice(seekResAtt + CdmObjectReferenceBase.resAttToken.length);
                // get the entity
                const ent = (this.ctx.corpus).resolveSymbolReference(resOpt, this.inDocument, entName, internal_1.cdmObjectType.entityDef, true);
                if (!ent) {
                    internal_1.Logger.warning(ctx, this.TAG, this.fetchResolvedReference.name, this.atCorpusPath, internal_1.cdmLogCode.WarnResolveEntityFailed, entName, this.namedReference);
                    return undefined;
                }
                // get the resolved attribute
                const ras = ent.fetchResolvedAttributes(resOpt);
                let ra;
                if (ras !== undefined) {
                    ra = ras.get(attName);
                }
                if (ra) {
                    res = ra.target;
                }
                else {
                    internal_1.Logger.warning(this.ctx, this.TAG, this.constructResolvedAttributes.name, this.atCorpusPath, internal_1.cdmLogCode.WarnResolveObjectFailed, this.namedReference);
                }
            }
            else {
                // normal symbolic reference, look up from the Corpus, it knows where everything is
                res = this.ctx.corpus.resolveSymbolReference(resOpt, this.inDocument, this.namedReference, this.objectType, true);
            }
            return res;
        }
        // return p.measure(bodyCode);
    }
    createSimpleReference(resOpt) {
        if (!resOpt) {
            resOpt = new internal_1.resolveOptions(this, this.ctx.corpus.defaultResolutionDirectives);
        }
        if (this.namedReference) {
            return this.copyRefObject(resOpt, this.namedReference, true);
        }
        const newDeclaredPath = this.declaredPath && this.declaredPath.endsWith('/(ref)') ?
            this.declaredPath.substring(0, this.declaredPath.length - 6) : this.declaredPath;
        return this.copyRefObject(resOpt, newDeclaredPath, true);
    }
    /**
     * @internal
     * Creates a 'portable' reference object to this object. portable means there is no symbolic name set until this reference is placed
     * into some final document.
     */
    createPortableReference(resOpt) {
        const cdmObjectDef = this.fetchObjectDefinition(resOpt);
        if (!cdmObjectDef || !this.inDocument) {
            return undefined; // not allowed
        }
        const cdmObjectRef = this.ctx.corpus.MakeObject(internal_1.CdmCorpusDefinition.mapReferenceType(this.objectType), 'portable', true);
        cdmObjectRef.portableReference = cdmObjectDef;
        cdmObjectRef.optional = this.optional;
        cdmObjectRef.inDocument = this.inDocument;
        cdmObjectRef.owner = this.owner;
        return cdmObjectRef;
    }
    /**
     * @internal
     * Creates a 'portable' reference object to this object. portable means there is no symbolic name set until this reference is placed
     * into some final document.
     */
    localizePortableReference(importPath) {
        let newDeclaredPath = this.portableReference.declaredPath;
        newDeclaredPath = newDeclaredPath && newDeclaredPath.endsWith('/(ref)') ? newDeclaredPath.substring(0, newDeclaredPath.length - 6) : newDeclaredPath;
        this.namedReference = `${importPath}${newDeclaredPath}`;
    }
    copy(resOpt, host) {
        if (!resOpt) {
            resOpt = new internal_1.resolveOptions(this, this.ctx.corpus.defaultResolutionDirectives);
        }
        const copy = this.copyRefObject(resOpt, this.namedReference
            ? this.namedReference
            : this.explicitReference, this.simpleNamedReference, host);
        copy.optional = this.optional;
        copy.portableReference = this.portableReference;
        if (resOpt.saveResolutionsOnCopy) {
            copy.explicitReference = this.explicitReference ? this.explicitReference.copy(resOpt) : undefined;
        }
        copy.appliedTraits.clear();
        for (const trait of this.appliedTraits) {
            copy.appliedTraits.push(trait.copy(resOpt));
        }
        // Don't do anything else after this, as it may cause InDocument to become dirty
        copy.inDocument = this.inDocument;
        return copy;
    }
    fetchObjectDefinitionName() {
        // let bodyCode = () =>
        {
            if (this.namedReference) {
                const pathEnd = this.namedReference.lastIndexOf('/');
                if (pathEnd === -1 || pathEnd + 1 === this.namedReference.length) {
                    return this.namedReference;
                }
                else {
                    return this.namedReference.substring(pathEnd + 1);
                }
            }
            if (this.explicitReference) {
                return this.explicitReference.getName();
            }
            return undefined;
        }
        // return p.measure(bodyCode);
    }
    isDerivedFrom(baseDef, resOpt) {
        const def = this.fetchObjectDefinition(resOpt);
        if (def) {
            return def.isDerivedFrom(baseDef, resOpt);
        }
        return false;
    }
    fetchObjectDefinition(resOpt) {
        // let bodyCode = () =>
        {
            if (!resOpt) {
                resOpt = new internal_1.resolveOptions(this, this.ctx.corpus.defaultResolutionDirectives);
            }
            let def = this.fetchResolvedReference(resOpt);
            if (def !== undefined) {
                if (internal_1.isCdmObjectReference(def)) {
                    def = def.fetchResolvedReference();
                }
            }
            if (def !== undefined && !internal_1.isCdmObjectReference(def)) {
                return def;
            }
        }
        // return p.measure(bodyCode);
    }
    fetchObjectDefinitionAsync(resOpt) {
        return __awaiter(this, void 0, void 0, function* () {
            // let bodyCode = () =>
            {
                if (!resOpt) {
                    resOpt = new internal_1.resolveOptions(this, this.ctx.corpus.defaultResolutionDirectives);
                }
                if (!resOpt.wrtDoc) {
                    return undefined;
                }
                const wrtDoc = resOpt.wrtDoc;
                if (!(yield wrtDoc.indexIfNeeded(resOpt, true))) {
                    internal_1.Logger.error(this.ctx, this.TAG, this.fetchObjectDefinitionAsync.name, wrtDoc.atCorpusPath, internal_1.cdmLogCode.ErrIndexFailed);
                    return null;
                }
                let def = this.fetchResolvedReference(resOpt);
                if (def !== undefined && internal_1.isCdmObjectReference(def)) {
                    def = def.fetchResolvedReference();
                }
                if (def !== undefined && !internal_1.isCdmObjectReference(def)) {
                    return def;
                }
            }
            // return p.measure(bodyCode);
        });
    }
    validate() {
        // let bodyCode = () =>
        {
            if (!this.namedReference && !this.explicitReference) {
                let missingFields = ['namedReference', 'explicitReference'];
                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);
    }
    visit(pathFrom, preChildren, postChildren) {
        // let bodyCode = () =>
        {
            let path;
            if (this.namedReference) {
                path = pathFrom + this.namedReference;
            }
            else {
                // when an object is defined inline inside a reference, we need a path to the reference
                // AND a path to the inline object. The 'correct' way to do this is to name the reference (inline) and the
                // defined object objectName so you get a path like extendsEntity/(inline)/MyBaseEntity. that way extendsEntity/(inline)
                // gets you the reference where there might be traits, etc. and extendsEntity/(inline)/MyBaseEntity gets the
                // entity defintion. HOWEVER! there are situations where (inline) would be ambiguous since there can be more than one
                // object at the same level, like anywhere there is a collection of references or the collection of attributes.
                // so we will flip it (also preserves back compat) and make the reference extendsEntity/MyBaseEntity/(inline) so that
                // extendsEntity/MyBaseEntity gives the reference (like before) and then extendsEntity/MyBaseEntity/(inline) would give
                // the inline defined object.
                // ALSO, ALSO!!! since the ability to use a path to request an object (through) a reference is super useful, lets extend
                // the notion and use the word (object) in the path to mean 'drill from reference to def' This would work then on
                // ANY reference, not just inline ones
                if (this.explicitReference !== undefined) {
                    // ref path is name of defined object
                    path = this.explicitReference.fetchDeclaredPath(pathFrom);
                }
                else {
                    path = pathFrom;
                }
            }
            const refPath = `${path}/(ref)`;
            if (preChildren && preChildren(this, refPath)) {
                return false;
            }
            if (this.explicitReference && !this.namedReference && this.explicitReference.visit(pathFrom, preChildren, postChildren)) {
                return true;
            }
            if (this.visitRef(path, preChildren, postChildren)) {
                return true;
            }
            if (this.appliedTraits) {
                if (this.appliedTraits.visitArray(`${refPath}/appliedTraits/`, preChildren, postChildren)) {
                    return true;
                }
            }
            if (postChildren && postChildren(this, refPath)) {
                return true;
            }
            return false;
        }
        // return p.measure(bodyCode);
    }
    /**
     * @internal
     */
    constructResolvedAttributes(resOpt, under) {
        // let bodyCode = () =>
        {
            // find and cache the complete set of attributes
            const rasb = new internal_1.ResolvedAttributeSetBuilder();
            rasb.ras.setAttributeContext(under);
            let def = this.fetchObjectDefinition(resOpt);
            if (def) {
                let acpRef;
                if (under) {
                    // ask for a 'pass through' context, that is, no new context at this level
                    acpRef = {
                        under: under,
                        type: internal_1.cdmAttributeContextType.passThrough
                    };
                }
                let resAtts = def.fetchResolvedAttributes(resOpt, acpRef);
                if (resAtts && resAtts.set.length > 0) {
                    // resAtts = resAtts.copy(); should not need this copy now that we copy from the cache. lets try!
                    rasb.mergeAttributes(resAtts);
                    rasb.removeRequestedAtts();
                }
            }
            else {
                const defName = this.fetchObjectDefinitionName();
                internal_1.Logger.warning(this.ctx, this.TAG, this.constructResolvedAttributes.name, this.atCorpusPath, internal_1.cdmLogCode.WarnResolveObjectFailed, defName);
            }
            return rasb;
        }
        // return p.measure(bodyCode);
    }
    /**
     * @internal
     */
    constructResolvedTraits(rtsb, resOpt) {
        // let bodyCode = () =>
        {
            const objDef = this.fetchObjectDefinition(resOpt);
            if (objDef) {
                let rtsInh = objDef.fetchResolvedTraits(resOpt);
                if (rtsInh) {
                    rtsInh = rtsInh.deepCopy();
                }
                rtsb.takeReference(rtsInh);
            }
            else if (this.optional !== undefined && !this.optional) {
                internal_1.Logger.warning(this.ctx, this.TAG, this.constructResolvedTraits.name, this.atCorpusPath, internal_1.cdmLogCode.WarnResolveObjectFailed, this.fetchObjectDefinitionName());
            }
            if (this.appliedTraits) {
                for (const trait of this.appliedTraits) {
                    rtsb.mergeTraits(trait.fetchResolvedTraits(resOpt));
                }
            }
        }
        // return p.measure(bodyCode);
    }
}
exports.CdmObjectReferenceBase = CdmObjectReferenceBase;
/**
 * @internal
 */
CdmObjectReferenceBase.resAttToken = '/(resolvedAttributes)/';
//# sourceMappingURL=CdmObjectReferenceBase.js.map