"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getReferenceType = exports.BaseNodeImpl = exports.makeAttributeEventName = void 0;
/**
 * @module node-opcua-address-space
 */
const events_1 = require("events");
const chalk = require("chalk");
const lodash_1 = require("lodash");
const node_opcua_assert_1 = require("node-opcua-assert");
const node_opcua_data_model_1 = require("node-opcua-data-model");
const node_opcua_data_value_1 = require("node-opcua-data-value");
const node_opcua_debug_1 = require("node-opcua-debug");
const node_opcua_nodeid_1 = require("node-opcua-nodeid");
const node_opcua_status_code_1 = require("node-opcua-status-code");
const node_opcua_types_1 = require("node-opcua-types");
const node_opcua_utils_1 = require("node-opcua-utils");
const node_opcua_variant_1 = require("node-opcua-variant");
const dump_tools_1 = require("../source/helpers/dump_tools");
const session_context_1 = require("../source/session_context");
const cetools = require("./address_space_change_event_tools");
const base_node_private_1 = require("./base_node_private");
const reference_impl_1 = require("./reference_impl");
const role_permissions_1 = require("./role_permissions");
// tslint:disable:no-var-requires
// tslint:disable:no-bitwise
// tslint:disable:no-console
const doDebug = false;
const warningLog = (0, node_opcua_debug_1.make_warningLog)(__filename);
function defaultBrowseFilterFunc(context) {
    return true;
}
function _get_QualifiedBrowseName(browseName) {
    return (0, node_opcua_data_model_1.coerceQualifiedName)(browseName);
}
function _is_valid_BrowseDirection(browseDirection) {
    return (browseDirection === node_opcua_data_model_1.BrowseDirection.Forward ||
        browseDirection === node_opcua_data_model_1.BrowseDirection.Inverse ||
        browseDirection === node_opcua_data_model_1.BrowseDirection.Both);
}
function makeAttributeEventName(attributeId) {
    const attributeName = node_opcua_data_model_1.attributeNameById[attributeId];
    return attributeName + "_changed";
}
exports.makeAttributeEventName = makeAttributeEventName;
/**
 * Base class for all Node classes
 *
 * BaseNode is the base class for all the OPCUA objects in the address space
 * It provides attributes and a set of references to other nodes.
 * see:
 * {{#crossLink "UAObject"}}{{/crossLink}},
 * {{#crossLink "UAVariable"}}{{/crossLink}},
 * {{#crossLink "Reference"}}{{/crossLink}},
 * {{#crossLink "UAMethod"}}{{/crossLink}},
 * {{#crossLink "UAView"}}{{/crossLink}},
 * {{#crossLink "UAObjectType"}}{{/crossLink}},
 * {{#crossLink "UADataType"}}{{/crossLink}},
 * {{#crossLink "UAVariableType"}}{{/crossLink}},
 *
 *
 */
class BaseNodeImpl extends events_1.EventEmitter {
    /**
     * @internal
     * @param options
     */
    constructor(options) {
        super();
        this.nodeClass = node_opcua_data_model_1.NodeClass.Unspecified;
        (0, node_opcua_assert_1.assert)(this.nodeClass === node_opcua_data_model_1.NodeClass.Unspecified, "must not be specify a nodeClass");
        (0, node_opcua_assert_1.assert)(options.addressSpace); // expecting an address space
        (0, node_opcua_assert_1.assert)(options.browseName instanceof node_opcua_data_model_1.QualifiedName, "Expecting a valid QualifiedName");
        (0, node_opcua_assert_1.assert)(options.nodeId instanceof node_opcua_nodeid_1.NodeId, "Expecting a valid NodeId");
        options.references = options.references || [];
        const _private = (0, base_node_private_1.BaseNode_initPrivate)(this);
        _private.__address_space = options.addressSpace;
        this.nodeId = (0, node_opcua_nodeid_1.resolveNodeId)(options.nodeId);
        // QualifiedName
        /**
         * the node browseName
         * @property browseName
         * @type QualifiedName
         * @static
         */
        this.browseName = _get_QualifiedBrowseName(options.browseName);
        // re-use browseName as displayName if displayName is missing
        options.displayName = options.displayName || this.browseName.name.toString();
        if (options.description === undefined) {
            options.description = null;
        }
        this._setDisplayName(options.displayName);
        this._setDescription(options.description);
        // user defined filter function for browsing
        const _browseFilter = options.browseFilter || defaultBrowseFilterFunc;
        (0, node_opcua_assert_1.assert)(typeof _browseFilter === "function");
        _private._browseFilter = _browseFilter;
        // normalize reference type
        // this will convert any referenceType expressed with its inverseName into
        // its normal name and fix the isForward flag accordingly.
        // ( e.g "ComponentOf" isForward:true => "HasComponent", isForward:false)
        for (const reference of options.references) {
            this.__addReference(reference);
        }
        this._accessRestrictions = options.accessRestrictions;
        this._rolePermissions = (0, role_permissions_1.coerceRolePermissions)(options.rolePermissions);
    }
    static makeAttributeEventName(attributeId) {
        return makeAttributeEventName(attributeId);
    }
    get addressSpace() {
        const _private = (0, base_node_private_1.BaseNode_getPrivate)(this);
        // istanbul ignore next
        if (!_private) {
            throw new Error("Internal error , cannot extract private data from " + this.browseName.toString());
        }
        return _private.__address_space;
    }
    get addressSpacePrivate() {
        const _private = (0, base_node_private_1.BaseNode_getPrivate)(this);
        // istanbul ignore next
        if (!_private) {
            throw new Error("Internal error , cannot extract private data from " + this.browseName.toString());
        }
        return _private.__address_space;
    }
    get displayName() {
        const _private = (0, base_node_private_1.BaseNode_getPrivate)(this);
        return _private._displayName;
    }
    setDisplayName(value) {
        this._setDisplayName(value);
        /**
         * fires when the displayName is changed.
         * @event DisplayName_changed
         * @param dataValue {DataValue}
         */
        this._notifyAttributeChange(node_opcua_data_model_1.AttributeIds.DisplayName);
    }
    get description() {
        const _private = (0, base_node_private_1.BaseNode_getPrivate)(this);
        return _private._description;
    }
    setDescription(value) {
        this._setDescription(value);
        /**
         * fires when the description attribute is changed.
         * @event Description_changed
         * @param dataValue {DataValue}
         */
        this._notifyAttributeChange(node_opcua_data_model_1.AttributeIds.Description);
    }
    /**
     * returns the nodeId of this node's Type Definition
     */
    get typeDefinition() {
        const _cache = (0, base_node_private_1.BaseNode_getCache)(this);
        if (!_cache.typeDefinition) {
            const has_type_definition_ref = this.findReference("HasTypeDefinition", true);
            _cache.typeDefinition = has_type_definition_ref ? has_type_definition_ref.nodeId : null;
        }
        return _cache.typeDefinition;
    }
    /**
     * returns the nodeId of this node's Type Definition
     */
    get typeDefinitionObj() {
        const _cache = (0, base_node_private_1.BaseNode_getCache)(this);
        if (undefined === _cache.typeDefinitionObj) {
            const nodeId = this.typeDefinition;
            _cache.typeDefinitionObj = nodeId ? this.addressSpace.findNode(nodeId) : null;
        }
        return _cache.typeDefinitionObj;
    }
    get parentNodeId() {
        const parent = this.parent;
        return parent ? parent.nodeId : undefined;
    }
    /**
     * namespace index
     */
    get namespaceIndex() {
        return this.nodeId.namespace;
    }
    /**
     * namespace uri
     */
    get namespaceUri() {
        return this.addressSpace.getNamespaceUri(this.namespaceIndex);
    }
    /**
     * the parent node
     */
    get parent() {
        const _private = (0, base_node_private_1.BaseNode_getPrivate)(this);
        if (_private._parent === undefined) {
            // never been set before
            _private._parent = _setup_parent_item.call(this, _private._referenceIdx);
        }
        return _private._parent || null;
    }
    /**
     * @property modellingRule
     * @type {String|undefined}
     */
    get modellingRule() {
        const r = this.findReferencesAsObject("HasModellingRule");
        if (!r || r.length === 0) {
            return null; /// "? modellingRule missing ?"; // consider "Mandatory"
        }
        const r0 = r[0];
        return r0.browseName.toString();
    }
    getDisplayName(locale) {
        const _private = (0, base_node_private_1.BaseNode_getPrivate)(this);
        return _private._displayName[0].text;
    }
    get namespace() {
        return this.addressSpacePrivate.getNamespace(this.nodeId.namespace);
    }
    // ---------------------------------------------------------------------------------------------------
    // Finders
    // ---------------------------------------------------------------------------------------------------
    findReferencesEx(referenceType, browseDirection) {
        browseDirection = browseDirection !== undefined ? browseDirection : node_opcua_data_model_1.BrowseDirection.Forward;
        (0, node_opcua_assert_1.assert)(_is_valid_BrowseDirection(browseDirection));
        (0, node_opcua_assert_1.assert)(browseDirection !== node_opcua_data_model_1.BrowseDirection.Both);
        const referenceTypeNode = this._coerceReferenceType(referenceType);
        if (!referenceTypeNode) {
            // note: when loading nodeset2.xml files, reference type may not exit yet
            // throw new Error("expecting valid reference name " + strReference);
            return [];
        }
        const isForward = browseDirection === node_opcua_data_model_1.BrowseDirection.Forward;
        const results = [];
        const process = (referenceIdx) => {
            const references = Object.values(referenceIdx);
            for (const ref of references) {
                if (ref.isForward === isForward && referenceTypeNode && referenceTypeNode.checkHasSubtype(ref.referenceType)) {
                    results.push(ref);
                }
            }
        };
        const _private = (0, base_node_private_1.BaseNode_getPrivate)(this);
        process(_private._referenceIdx);
        process(_private._back_referenceIdx);
        return results;
    }
    findReferences(referenceType, isForward = true) {
        const _cache = (0, base_node_private_1.BaseNode_getCache)(this);
        const _private = (0, base_node_private_1.BaseNode_getPrivate)(this);
        const referenceTypeNode = this._coerceReferenceType(referenceType);
        if (!referenceTypeNode) {
            // note: when loading nodeset2.xml files, reference type may not exit yet
            // throw new Error("expecting valid reference name " + strReference);
            return [];
        }
        const hash = "_ref_" + referenceTypeNode.nodeId.toString() + isForward.toString();
        if (_cache[hash]) {
            return _cache[hash];
        }
        // istanbul ignore next
        if (doDebug && !this.addressSpace.findReferenceType(referenceTypeNode.nodeId)) {
            throw new Error("expecting valid reference name " + referenceType);
        }
        const result = [];
        for (const ref of Object.values(_private._referenceIdx)) {
            if (ref.isForward === isForward) {
                if ((0, node_opcua_nodeid_1.sameNodeId)(ref.referenceType, referenceTypeNode.nodeId)) {
                    result.push(ref);
                }
            }
        }
        for (const ref of Object.values(_private._back_referenceIdx)) {
            if (ref.isForward === isForward) {
                if ((0, node_opcua_nodeid_1.sameNodeId)(ref.referenceType, referenceTypeNode.nodeId)) {
                    result.push(ref);
                }
            }
        }
        _cache[hash] = result;
        return result;
    }
    findReference(strReference, isForward) {
        const refs = this.findReferences(strReference, isForward);
        if (refs.length !== 1 && refs.length !== 0) {
            throw new Error("findReference: expecting only one or zero element here");
        }
        return refs[0] || null;
    }
    findReferencesExAsObject(referenceType, browseDirection) {
        const references = this.findReferencesEx(referenceType, browseDirection);
        return _asObject(references, this.addressSpace);
    }
    findReferencesAsObject(referenceType, isForward) {
        const references = this.findReferences(referenceType, isForward);
        return _asObject(references, this.addressSpace);
    }
    /**
     * return an array with the Aggregates of this object.
     */
    getAggregates() {
        const _cache = (0, base_node_private_1.BaseNode_getCache)(this);
        if (!_cache._aggregates) {
            _cache._aggregates = this.findReferencesExAsObject("Aggregates", node_opcua_data_model_1.BrowseDirection.Forward);
        }
        return _cache._aggregates;
    }
    /**
     * return an array with the components of this object.
     */
    getComponents() {
        const _cache = (0, base_node_private_1.BaseNode_getCache)(this);
        if (!_cache._components) {
            _cache._components = this.findReferencesExAsObject("HasComponent", node_opcua_data_model_1.BrowseDirection.Forward);
        }
        return _cache._components;
    }
    /**
     *  return a array with the properties of this object.
     */
    getProperties() {
        const _cache = (0, base_node_private_1.BaseNode_getCache)(this);
        if (!_cache._properties) {
            _cache._properties = this.findReferencesExAsObject("HasProperty", node_opcua_data_model_1.BrowseDirection.Forward);
        }
        return _cache._properties;
    }
    /**
     * return a array with the notifiers of this object.
     */
    getNotifiers() {
        const _cache = (0, base_node_private_1.BaseNode_getCache)(this);
        if (!_cache._notifiers) {
            _cache._notifiers = this.findReferencesAsObject("HasNotifier", true);
        }
        return _cache._notifiers;
    }
    /**
     * return a array with the event source of this object.
     */
    getEventSources() {
        const _cache = (0, base_node_private_1.BaseNode_getCache)(this);
        if (!_cache._eventSources) {
            _cache._eventSources = this.findReferencesAsObject("HasEventSource", true);
        }
        return _cache._eventSources;
    }
    /**
     * return a array of the objects for which this node is an EventSource
     */
    getEventSourceOfs() {
        const _cache = (0, base_node_private_1.BaseNode_getCache)(this);
        if (!_cache._eventSources) {
            _cache._eventSources = this.findReferencesAsObject("HasEventSource", false);
        }
        return _cache._eventSources;
    }
    getComponentByName(browseName, namespaceIndex) {
        const components = this.getComponents();
        const select = _filter_by_browse_name(components, browseName, namespaceIndex);
        (0, node_opcua_assert_1.assert)(select.length <= 1, "BaseNode#getComponentByName found duplicated reference");
        if (select.length === 1) {
            const component = select[0];
            if (component.nodeClass === node_opcua_data_model_1.NodeClass.Method) {
                warningLog("please use getMethodByName to retrieve a method");
                return null;
            }
            (0, node_opcua_assert_1.assert)(component.nodeClass === node_opcua_data_model_1.NodeClass.Variable || component.nodeClass === node_opcua_data_model_1.NodeClass.Object);
            return component;
        }
        else {
            return null;
        }
    }
    getPropertyByName(browseName, namespaceIndex) {
        const properties = this.getProperties();
        const select = _filter_by_browse_name(properties, browseName, namespaceIndex);
        (0, node_opcua_assert_1.assert)(select.length <= 1, "BaseNode#getPropertyByName found duplicated reference");
        if (select.length === 1 && select[0].nodeClass !== node_opcua_data_model_1.NodeClass.Variable) {
            throw new Error("Expecting a property to be of nodeClass==NodeClass.Variable");
        }
        return select.length === 1 ? select[0] : null;
    }
    getFolderElementByName(browseName, namespaceIndex) {
        const elements = this.getFolderElements();
        const select = _filter_by_browse_name(elements, browseName, namespaceIndex);
        return select.length === 1 ? select[0] : null;
    }
    /**
     * returns the list of nodes that this folder object organizes
     */
    getFolderElements() {
        return this.findReferencesExAsObject("Organizes", node_opcua_data_model_1.BrowseDirection.Forward);
    }
    /**
     * returns the list of methods that this object provides
     * @method getMethods
     * @return an array with Method objects.
     *
     *
     * Note: internally, methods are special types of components
     */
    getMethods() {
        const _cache = (0, base_node_private_1.BaseNode_getCache)(this);
        if (!_cache._methods) {
            const components = this.getComponents();
            _cache._methods = components.filter((obj) => obj.nodeClass === node_opcua_data_model_1.NodeClass.Method);
        }
        return _cache._methods;
    }
    /**
     * returns the method exposed by this object and with the given nodeId
     */
    getMethodById(nodeId) {
        const methods = this.getMethods();
        const found = methods.find((m) => m.nodeId.toString() === nodeId.toString());
        return found || null;
    }
    getMethodByName(methodName, namespaceIndex) {
        const methods = this.getMethods();
        const select = _filter_by_browse_name(methods, methodName, namespaceIndex);
        (0, node_opcua_assert_1.assert)(select.length <= 1, "BaseNode#getMethodByName found duplicated reference");
        return select.length === 1 ? select[0] : null;
    }
    getWriteMask() {
        return 0;
    }
    getUserWriteMask() {
        return 0;
    }
    readAttribute(context, attributeId, indexRange, dataEncoding) {
        indexRange;
        dataEncoding;
        (0, node_opcua_assert_1.assert)(!context || context instanceof session_context_1.SessionContext);
        const options = {};
        options.statusCode = node_opcua_status_code_1.StatusCodes.Good;
        switch (attributeId) {
            case node_opcua_data_model_1.AttributeIds.NodeId: // NodeId
                options.value = { dataType: node_opcua_variant_1.DataType.NodeId, value: this.nodeId };
                break;
            case node_opcua_data_model_1.AttributeIds.NodeClass: // NodeClass
                (0, node_opcua_assert_1.assert)(isFinite(this.nodeClass));
                options.value = { dataType: node_opcua_variant_1.DataType.Int32, value: this.nodeClass };
                break;
            case node_opcua_data_model_1.AttributeIds.BrowseName: // QualifiedName
                (0, node_opcua_assert_1.assert)(this.browseName instanceof node_opcua_data_model_1.QualifiedName);
                options.value = { dataType: node_opcua_variant_1.DataType.QualifiedName, value: this.browseName };
                break;
            case node_opcua_data_model_1.AttributeIds.DisplayName: // LocalizedText
                options.value = { dataType: node_opcua_variant_1.DataType.LocalizedText, value: this.displayName[0] };
                break;
            case node_opcua_data_model_1.AttributeIds.Description: // LocalizedText
                options.value = { dataType: node_opcua_variant_1.DataType.LocalizedText, value: this.description };
                break;
            case node_opcua_data_model_1.AttributeIds.WriteMask:
                options.value = { dataType: node_opcua_variant_1.DataType.UInt32, value: this.getWriteMask() };
                break;
            case node_opcua_data_model_1.AttributeIds.UserWriteMask:
                options.value = { dataType: node_opcua_variant_1.DataType.UInt32, value: this.getUserWriteMask() };
                break;
            case node_opcua_data_model_1.AttributeIds.AccessRestrictions:
                return this._readAccessRestrictions(context);
            case node_opcua_data_model_1.AttributeIds.RolePermissions:
                return this._readRolePermissions(context);
            case node_opcua_data_model_1.AttributeIds.UserRolePermissions:
                return this._readUserRolePermissions(context);
            default:
                options.value = null;
                options.statusCode = node_opcua_status_code_1.StatusCodes.BadAttributeIdInvalid;
                break;
        }
        // xx options.serverTimestamp = new Date();
        return new node_opcua_data_value_1.DataValue(options);
    }
    writeAttribute(context, writeValue, callback) {
        context = context || session_context_1.SessionContext.defaultContext;
        (0, node_opcua_assert_1.assert)(context instanceof session_context_1.SessionContext);
        (0, node_opcua_assert_1.assert)(typeof callback === "function");
        if (writeValue.attributeId === undefined ||
            writeValue.attributeId <= 0 ||
            writeValue.attributeId > node_opcua_data_model_1.AttributeIds.UserExecutable) {
            return callback(null, node_opcua_status_code_1.StatusCodes.BadAttributeIdInvalid);
        }
        // by default Node is read-only,
        // this method needs to be overridden to change the behavior
        callback(null, node_opcua_status_code_1.StatusCodes.BadNotWritable);
    }
    fullName() {
        if (this.parentNodeId) {
            const parent = this.addressSpace.findNode(this.parentNodeId);
            // istanbul ignore else
            if (parent) {
                return parent.fullName() + "." + this.browseName.toString() + "";
            }
            else {
                return "NOT YET REGISTERED" + this.parentNodeId.toString() + "." + this.browseName.toString() + "";
            }
        }
        return this.browseName.toString();
    }
    ownReferences() {
        const _private = (0, base_node_private_1.BaseNode_getPrivate)(this);
        return Object.values(_private._referenceIdx);
    }
    /**
     * @method browseNodeByTargetName
     *
     * @param relativePathElement
     * @param isLast
     * @return {NodeId[]}
     */
    browseNodeByTargetName(relativePathElement, isLast) {
        relativePathElement.targetName = relativePathElement.targetName || new node_opcua_data_model_1.QualifiedName({});
        // part 4.0 v1.03 $7.26 RelativePath
        // The BrowseName of the target node.
        // The final element may have an empty targetName. In this situation all targets of the references identified by
        // the referenceTypeId are the targets of the RelativePath.
        // The targetName shall be specified for all other elements.
        // The current path cannot be followed any further if no targets with the specified BrowseName exist.
        (0, node_opcua_assert_1.assert)(relativePathElement.targetName instanceof node_opcua_data_model_1.QualifiedName);
        (0, node_opcua_assert_1.assert)(relativePathElement.targetName.namespaceIndex >= 0);
        (0, node_opcua_assert_1.assert)(relativePathElement.targetName.name.length > 0);
        // The type of reference to follow from the current node.
        // The current path cannot be followed any further if the referenceTypeId is not available on the Node instance.
        // If not specified then all References are included and the parameter includeSubtypes is ignored.
        (0, node_opcua_assert_1.assert)(Object.prototype.hasOwnProperty.call(relativePathElement, "referenceTypeId"));
        // Indicates whether the inverse Reference should be followed.
        // The inverse reference is followed if this value is TRUE.
        (0, node_opcua_assert_1.assert)(Object.prototype.hasOwnProperty.call(relativePathElement, "isInverse"));
        // Indicates whether subtypes of the ReferenceType should be followed.
        // Subtypes are included if this value is TRUE.
        (0, node_opcua_assert_1.assert)(Object.prototype.hasOwnProperty.call(relativePathElement, "includeSubtypes"));
        const references = this.allReferences();
        const _check_reference = (reference) => {
            if (relativePathElement.referenceTypeId.isEmpty()) {
                return true;
            }
            (0, node_opcua_assert_1.assert)(relativePathElement.referenceTypeId instanceof node_opcua_nodeid_1.NodeId);
            if ((relativePathElement.isInverse && reference.isForward) ||
                (!relativePathElement.isInverse && !reference.isForward)) {
                return false;
            }
            (0, node_opcua_assert_1.assert)(Object.prototype.hasOwnProperty.call(reference, "isForward"));
            const referenceType = resolveReferenceType(this.addressSpace, reference);
            const referenceTypeId = referenceType.nodeId;
            if ((0, node_opcua_nodeid_1.sameNodeId)(relativePathElement.referenceTypeId, referenceTypeId)) {
                return true;
            }
            if (relativePathElement.includeSubtypes) {
                const baseType = this.addressSpace.findReferenceType(relativePathElement.referenceTypeId);
                if (baseType && referenceType.isSupertypeOf(baseType)) {
                    return true;
                }
            }
            return false;
        };
        const nodeIdsMap = {};
        let nodeIds = [];
        for (const reference of references) {
            if (!_check_reference(reference)) {
                continue;
            }
            const obj = resolveReferenceNode(this.addressSpace, reference);
            // istanbul ignore next
            if (!obj) {
                throw new Error(" cannot find node with id " + reference.nodeId.toString());
            }
            if ((0, lodash_1.isEqual)(obj.browseName, relativePathElement.targetName)) {
                // compare QualifiedName
                const key = obj.nodeId.toString();
                if (!Object.prototype.hasOwnProperty.call(nodeIdsMap, key)) {
                    nodeIds.push(obj.nodeId);
                    nodeIdsMap[key] = obj;
                }
            }
        }
        if (nodeIds.length === 0 && (this.nodeClass === node_opcua_data_model_1.NodeClass.ObjectType || this.nodeClass === node_opcua_data_model_1.NodeClass.VariableType)) {
            const nodeType = this;
            if (nodeType.subtypeOf) {
                // browsing also InstanceDeclarations included in base type
                const baseType = this.addressSpace.findNode(nodeType.subtypeOf);
                const n = baseType.browseNodeByTargetName(relativePathElement, isLast);
                nodeIds = [].concat(nodeIds, n);
            }
        }
        return nodeIds;
    }
    /**
     * browse the node to extract information requested in browseDescription
     * and returns an array with reference descriptions
     *
     *
     *
     */
    browseNode(browseDescription, context) {
        (0, node_opcua_assert_1.assert)(isFinite(browseDescription.nodeClassMask));
        const do_debug = false;
        const _private = (0, base_node_private_1.BaseNode_getPrivate)(this);
        const addressSpace = this.addressSpace;
        const referenceTypeId = normalize_referenceTypeId(addressSpace, browseDescription.referenceTypeId);
        (0, node_opcua_assert_1.assert)(referenceTypeId instanceof node_opcua_nodeid_1.NodeId);
        const browseDirection = browseDescription.browseDirection !== undefined ? browseDescription.browseDirection : node_opcua_data_model_1.BrowseDirection.Both;
        // get all possible references
        let references = this.allReferences();
        /* istanbul ignore next */
        if (do_debug) {
            console.log("all references :", this.nodeId.toString(), this.browseName.toString());
            (0, dump_tools_1.dumpReferences)(addressSpace, Object.values(_private._referenceIdx));
        }
        // filter out references not matching referenceType
        references = _filter_by_referenceType.call(this, browseDescription, references, referenceTypeId);
        references = _filter_by_direction(references, browseDirection);
        references = _filter_by_nodeClass.call(this, references, browseDescription.nodeClassMask);
        references = _filter_by_userFilter.call(this, references, context);
        if (context) {
            references = _filter_by_context(this, references, context);
        }
        const referenceDescriptions = (0, base_node_private_1._constructReferenceDescription)(addressSpace, references, browseDescription.resultMask);
        /* istanbul ignore next */
        if (do_debug) {
            (0, dump_tools_1.dumpReferenceDescriptions)(this.addressSpace, referenceDescriptions);
        }
        return referenceDescriptions;
    }
    allReferences() {
        const _private = (0, base_node_private_1.BaseNode_getPrivate)(this);
        return Object.values(_private._referenceIdx).concat(Object.values(_private._back_referenceIdx));
    }
    /**
     * @method addReference
     * @param reference
     * @param reference.referenceType {String}
     * @param [reference.isForward = true] {Boolean}
     * @param reference.nodeId {Node|NodeId|String}
     *
     * @example
     *
     *     view.addReference({ referenceType: "Organizes", nodeId: myDevice });
     *
     * or
     *
     *     myDevice1.addReference({ referenceType: "OrganizedBy", nodeId: view });
     */
    addReference(reference) {
        const referenceNode = this.__addReference(reference);
        const addressSpace = this.addressSpace;
        if (!resolveReferenceType(addressSpace, referenceNode)) {
            throw new Error("BaseNode#addReference : invalid reference  " + reference.toString());
        }
        this._clear_caches();
        _propagate_ref.call(this, addressSpace, referenceNode);
        this.install_extra_properties();
        cetools._handle_add_reference_change_event(this, referenceNode.nodeId);
    }
    removeReference(referenceOpts) {
        const _private = (0, base_node_private_1.BaseNode_getPrivate)(this);
        (0, node_opcua_assert_1.assert)(Object.prototype.hasOwnProperty.call(referenceOpts, "referenceType"));
        // xx isForward is optional : assert(Object.prototype.hasOwnProperty.call(reference,"isForward"));
        (0, node_opcua_assert_1.assert)(Object.prototype.hasOwnProperty.call(referenceOpts, "nodeId"));
        const addressSpace = this.addressSpace;
        // istanbul ignore next
        if (!addressSpace) {
            console.log(" Where is addressSpace ?");
        }
        const reference = addressSpace.normalizeReferenceTypes([referenceOpts])[0];
        const h = reference.hash;
        const relatedNode = addressSpace.findNode(reference.nodeId);
        const invReference = new reference_impl_1.ReferenceImpl({
            isForward: !reference.isForward,
            nodeId: this.nodeId,
            referenceType: reference.referenceType
        });
        if (_private._referenceIdx[h]) {
            delete _private._referenceIdx[h];
            base_node_private_1.BaseNode_remove_backward_reference.call(relatedNode, invReference);
        }
        else if (_private._back_referenceIdx[h]) {
            relatedNode.removeReference(invReference);
        }
        else {
            //            throw new Error("Cannot find reference " + reference);
        }
        (0, base_node_private_1._handle_HierarchicalReference)(this, reference);
        this.uninstall_extra_properties(reference);
        this._clear_caches();
    }
    /**
     *
     */
    resolveNodeId(nodeId) {
        return this.addressSpace.resolveNodeId(nodeId);
    }
    install_extra_properties() {
        const addressSpace = this.addressSpace;
        if (addressSpace.isFrugal) {
            // skipping
            return;
        }
        install_components_as_object_properties(this);
        function install_extra_properties_on_parent(ref) {
            const node = reference_impl_1.ReferenceImpl.resolveReferenceNode(addressSpace, ref);
            install_components_as_object_properties(node);
        }
        // make sure parent have extra properties updated
        const parentComponents = this.findReferencesEx("HasComponent", node_opcua_data_model_1.BrowseDirection.Inverse);
        const parentSubfolders = this.findReferencesEx("Organizes", node_opcua_data_model_1.BrowseDirection.Inverse);
        const parentProperties = this.findReferencesEx("HasProperty", node_opcua_data_model_1.BrowseDirection.Inverse);
        for (const p of parentComponents) {
            install_extra_properties_on_parent(p);
        }
        for (const p of parentSubfolders) {
            install_extra_properties_on_parent(p);
        }
        for (const p of parentProperties) {
            install_extra_properties_on_parent(p);
        }
    }
    uninstall_extra_properties(reference) {
        const addressSpace = this.addressSpace;
        if (addressSpace.isFrugal) {
            // skipping
            return;
        }
        const childNode = resolveReferenceNode(addressSpace, reference);
        const name = (0, node_opcua_utils_1.lowerFirstLetter)(childNode.browseName.name.toString());
        if (Object.prototype.hasOwnProperty.call(reservedNames, name)) {
            // istanbul ignore next
            if (doDebug) {
                // tslint:disable-next-line:no-console
                console.log(chalk.bgWhite.red("Ignoring reserved keyword                                     " + name));
            }
            return;
        }
        /* istanbul ignore next */
        if (!Object.prototype.hasOwnProperty.call(this, name)) {
            return;
        }
        Object.defineProperty(this, name, {
            value: undefined
        });
    }
    toString() {
        const options = new base_node_private_1.ToStringBuilder();
        base_node_private_1.BaseNode_toString.call(this, options);
        return options.toString();
    }
    /**
     * @property isFalseSubStateOf
     * @type {BaseNode|null}
     */
    get isFalseSubStateOf() {
        const r = this.findReferencesAsObject("HasFalseSubState", false);
        if (!r || r.length === 0) {
            return null;
        }
        (0, node_opcua_assert_1.assert)(r.length === 1);
        return r[0];
    }
    /**
     * @property isTrueSubStateOf
     * @type {BaseNode|null}
     */
    get isTrueSubStateOf() {
        const r = this.findReferencesAsObject("HasTrueSubState", false);
        if (!r || r.length === 0) {
            return null;
        }
        (0, node_opcua_assert_1.assert)(r.length === 1);
        return r[0];
    }
    /**
     * @method getFalseSubStates
     * @return {UAStateVariable[]} return an array with the SubStates of this object.
     */
    getFalseSubStates() {
        return this.findReferencesAsObject("HasFalseSubState");
    }
    /**
     * @method getTrueSubStates
     * @return {UAStateVariable[]} return an array with the SubStates of this object.
     */
    getTrueSubStates() {
        return this.findReferencesAsObject("HasTrueSubState");
    }
    findHierarchicalReferences() {
        return this.findReferencesEx("HierarchicalReferences", node_opcua_data_model_1.BrowseDirection.Forward);
    }
    getChildByName(browseName, namespaceIndex) {
        // Attention: getChild doesn't care about namespace on browseName
        //            !!!!
        if (browseName instanceof node_opcua_data_model_1.QualifiedName) {
            browseName = browseName.name.toString();
        }
        (0, node_opcua_assert_1.assert)(typeof browseName === "string");
        const _cache = (0, base_node_private_1.BaseNode_getCache)(this);
        const addressSpace = this.addressSpace;
        if (!_cache._childByNameMap) {
            _cache._childByNameMap = {};
            const childReferenceTypes = this.findReferencesEx("HasChild");
            for (const r of childReferenceTypes) {
                const child = resolveReferenceNode(addressSpace, r);
                _cache._childByNameMap[child.browseName.name.toString()] = child;
            }
        }
        const ret = _cache._childByNameMap[browseName.toString()] || null;
        return ret;
    }
    get toStateNode() {
        const nodes = this.findReferencesAsObject("ToState", true);
        (0, node_opcua_assert_1.assert)(nodes.length <= 1);
        return nodes.length === 1 ? nodes[0] : null;
    }
    get fromStateNode() {
        const nodes = this.findReferencesAsObject("FromState", true);
        (0, node_opcua_assert_1.assert)(nodes.length <= 1);
        return nodes.length === 1 ? nodes[0] : null;
    }
    /**
     * this methods propagates the forward references to the pointed node
     * by inserting backward references to the counter part node
     */
    propagate_back_references() {
        const _private = (0, base_node_private_1.BaseNode_getPrivate)(this);
        if (this.addressSpace.suspendBackReference) {
            // this indicates that the base node is constructed from an xml definition
            // propagate_back_references will be called later once the file has been completely processed.
            return;
        }
        const addressSpace = this.addressSpace;
        for (const reference of Object.values(_private._referenceIdx)) {
            _propagate_ref.call(this, addressSpace, reference);
        }
    }
    /**
     * the dispose method should be called when the node is no longer used, to release
     * back pointer to the address space and clear caches.
     *
     * @method dispose
     *
     */
    dispose() {
        this.emit("dispose");
        this.removeAllListeners();
        this._clear_caches();
        const _private = (0, base_node_private_1.BaseNode_getPrivate)(this);
        Object.values(_private._back_referenceIdx).forEach((ref) => ref.dispose());
        Object.values(_private._referenceIdx).forEach((ref) => ref.dispose());
        (0, base_node_private_1.BaseNode_removePrivate)(this);
    }
    // istanbul ignore next
    dumpXML(xmlWriter) {
        console.error(" This ", node_opcua_data_model_1.NodeClass[this.nodeClass]);
        (0, node_opcua_assert_1.assert)(false, "BaseNode#dumpXML NOT IMPLEMENTED !");
        (0, node_opcua_assert_1.assert)(xmlWriter);
    }
    /**
     * Undo the effect of propagate_back_references
     */
    unpropagate_back_references() {
        const _private = (0, base_node_private_1.BaseNode_getPrivate)(this);
        const addressSpace = this.addressSpace;
        for (const reference of Object.values(_private._referenceIdx)) {
            // filter out non  Hierarchical References
            const referenceType = resolveReferenceType(addressSpace, reference);
            // istanbul ignore next
            if (!referenceType) {
                console.error(chalk.red(" ERROR"), " cannot find reference ", reference.referenceType, reference.toString());
            }
            const related_node = resolveReferenceNode(addressSpace, reference);
            if (related_node) {
                (0, node_opcua_assert_1.assert)(reference.nodeId.toString() !== this.nodeId.toString());
                base_node_private_1.BaseNode_remove_backward_reference.call(related_node, new reference_impl_1.ReferenceImpl({
                    isForward: !reference.isForward,
                    nodeId: this.nodeId,
                    referenceType: reference.referenceType
                }));
            } // else addressSpace may be incomplete
        }
    }
    installPostInstallFunc(f) {
        if (!f) {
            // nothing to do
            return;
        }
        function chain(f1, f2) {
            return function chaiFunc(...args) {
                if (f1) {
                    f1.apply(this, args);
                }
                if (f2) {
                    f2.apply(this, args);
                }
            };
        }
        this._postInstantiateFunc = chain.call(this, this._postInstantiateFunc, f);
    }
    _on_child_added() {
        this._clear_caches();
    }
    _on_child_removed(obj) {
        // obj; // unused;
        this._clear_caches();
    }
    /**
     * @private
     * @param reference
     */
    _add_backward_reference(reference) {
        base_node_private_1.BaseNode_add_backward_reference.call(this, reference);
    }
    _coerceReferenceType(referenceType) {
        let result;
        if (typeof referenceType === "string") {
            result = this.addressSpace.findReferenceType(referenceType);
            /* istanbul ignore next */
            if (!result) {
                throw new Error("Cannot coerce reference with name " + referenceType);
            }
        }
        else if (referenceType instanceof node_opcua_nodeid_1.NodeId) {
            result = this.addressSpace.findNode(referenceType);
            if (!result) {
                return null;
            }
        }
        else {
            result = referenceType;
        }
        (0, node_opcua_assert_1.assert)(result, "reference must exists");
        (0, node_opcua_assert_1.assert)(result.nodeClass === node_opcua_data_model_1.NodeClass.ReferenceType);
        return result;
    }
    __addReference(referenceOpts) {
        const _private = (0, base_node_private_1.BaseNode_getPrivate)(this);
        (0, node_opcua_assert_1.assert)(Object.prototype.hasOwnProperty.call(referenceOpts, "referenceType"));
        // xx isForward is optional : assert(Object.prototype.hasOwnProperty.call(reference,"isForward"));
        (0, node_opcua_assert_1.assert)(Object.prototype.hasOwnProperty.call(referenceOpts, "nodeId"));
        const addressSpace = this.addressSpace;
        const reference = addressSpace.normalizeReferenceTypes([referenceOpts])[0];
        (0, node_opcua_assert_1.assert)(reference instanceof reference_impl_1.ReferenceImpl);
        const h = reference.hash;
        (0, node_opcua_assert_1.assert)(!_private._back_referenceIdx[h], "reference exists already in _back_references");
        (0, node_opcua_assert_1.assert)(!_private._referenceIdx[h], "reference exists already in _references");
        _private._referenceIdx[h] = reference;
        (0, base_node_private_1._handle_HierarchicalReference)(this, reference);
        return reference;
    }
    _setDisplayName(displayName) {
        const displayNames = Array.isArray(displayName) ? displayName : [displayName];
        const _displayNames = displayNames.map(node_opcua_data_model_1.coerceLocalizedText);
        const _private = (0, base_node_private_1.BaseNode_getPrivate)(this);
        _private._displayName = _displayNames;
    }
    _setDescription(description) {
        const __description = (0, node_opcua_data_model_1.coerceLocalizedText)(description);
        const _private = (0, base_node_private_1.BaseNode_getPrivate)(this);
        _private._description = __description;
    }
    _notifyAttributeChange(attributeId) {
        const event_name = BaseNodeImpl.makeAttributeEventName(attributeId);
        this.emit(event_name, this.readAttribute(session_context_1.SessionContext.defaultContext, attributeId));
    }
    _clear_caches() {
        (0, base_node_private_1.BaseNode_clearCache)(this);
    }
    _readAccessRestrictions(context) {
        // https://reference.opcfoundation.org/v104/Core/docs/Part3/8.56/
        if (this.accessRestrictions === undefined) {
            return new node_opcua_data_value_1.DataValue({ statusCode: node_opcua_status_code_1.StatusCodes.BadAttributeIdInvalid });
        }
        return new node_opcua_data_value_1.DataValue({
            statusCode: node_opcua_status_code_1.StatusCodes.Good,
            value: {
                dataType: node_opcua_variant_1.DataType.UInt16,
                value: this.accessRestrictions
            }
        });
    }
    _readRolePermissions(context) {
        // https://reference.opcfoundation.org/v104/Core/docs/Part3/4.8.3/
        // to do check that current user can read permission
        if (context && !(context === null || context === void 0 ? void 0 : context.checkPermission(this, node_opcua_types_1.PermissionType.ReadRolePermissions))) {
            return new node_opcua_data_value_1.DataValue({
                statusCode: node_opcua_status_code_1.StatusCodes.BadSecurityModeInsufficient
            });
        }
        if (this.rolePermissions === undefined) {
            // to do : If not specified, the value of DefaultUserRolePermissions Property from
            // the Namespace Metadata Object associated with the Node is used instead.
            return new node_opcua_data_value_1.DataValue({
                statusCode: node_opcua_status_code_1.StatusCodes.BadAttributeIdInvalid
            });
        }
        const rolePermissions = this.rolePermissions.map(({ roleId, permissions }) => {
            return new node_opcua_types_1.RolePermissionType({
                roleId: toRoleNodeId(roleId),
                permissions
            });
        });
        return new node_opcua_data_value_1.DataValue({
            statusCode: node_opcua_status_code_1.StatusCodes.Good,
            value: {
                dataType: node_opcua_variant_1.DataType.ExtensionObject,
                arrayType: node_opcua_variant_1.VariantArrayType.Array,
                value: rolePermissions
            }
        });
    }
    _readUserRolePermissions(context) {
        if (this.rolePermissions === undefined) {
            // to do : If not specified, the value of DefaultUserRolePermissions Property from
            // the Namespace Metadata Object associated with the Node is used instead.
            return new node_opcua_data_value_1.DataValue({
                statusCode: node_opcua_status_code_1.StatusCodes.BadAttributeIdInvalid
            });
        }
        const context1 = context === null ? session_context_1.SessionContext.defaultContext : context;
        // for the time being ...
        // get user Permission
        const rolePermissions = this.rolePermissions
            .map(({ roleId, permissions }) => {
            return new node_opcua_types_1.RolePermissionType({
                roleId: toRoleNodeId(roleId),
                permissions
            });
        })
            .filter(({ roleId }) => context1.currentUserHasRole(roleId));
        return new node_opcua_data_value_1.DataValue({
            statusCode: node_opcua_status_code_1.StatusCodes.Good,
            value: {
                dataType: node_opcua_variant_1.DataType.ExtensionObject,
                arrayType: node_opcua_variant_1.VariantArrayType.Array,
                value: rolePermissions
            }
        });
    }
    /**
     *
     * @param rolePermissions
     */
    setRolePermissions(rolePermissions) {
        this._rolePermissions = (0, role_permissions_1.coerceRolePermissions)(rolePermissions);
    }
    getRolePermissions(inherited) {
        if (this.rolePermissions === undefined && inherited) {
            return this.namespace.getDefaultRolePermissions();
        }
        return this._rolePermissions || null;
    }
    get rolePermissions() {
        return this._rolePermissions || undefined;
    }
    setAccessRestrictions(accessRestrictions) {
        this._accessRestrictions = accessRestrictions;
    }
    get accessRestrictions() {
        return this._accessRestrictions;
    }
    getAccessRestrictions(inherited) {
        if (this._accessRestrictions === undefined && inherited) {
            return this.namespace.getDefaultAccessRestrictions();
        }
        return this._accessRestrictions || node_opcua_data_model_1.AccessRestrictionsFlag.None;
    }
}
exports.BaseNodeImpl = BaseNodeImpl;
function toRoleNodeId(s) {
    if (typeof s === "string") {
        return (0, node_opcua_nodeid_1.resolveNodeId)(session_context_1.WellKnownRolesNodeId[s]);
    }
    return (0, node_opcua_nodeid_1.coerceNodeId)(s);
}
let displayWarning = true;
function toString_ReferenceDescription(ref, options) {
    const addressSpace = options.addressSpace;
    // xx assert(ref instanceof ReferenceDescription);
    const refNode = addressSpace.findNode(ref.referenceType);
    if (!refNode) {
        return "Unknown Ref : " + ref;
    }
    const r = new reference_impl_1.ReferenceImpl({
        isForward: ref.isForward,
        nodeId: ref.nodeId,
        referenceType: refNode.browseName.toString()
    });
    const str = r.toString(options);
    r.dispose();
    return str;
}
function _setup_parent_item(references) {
    references = Object.values(references);
    const _private = (0, base_node_private_1.BaseNode_getPrivate)(this);
    (0, node_opcua_assert_1.assert)(!_private._parent, "_setup_parent_item has been already called");
    const addressSpace = this.addressSpace;
    if (references.length > 0) {
        references = this.findReferencesEx("Aggregates", node_opcua_data_model_1.BrowseDirection.Inverse);
        if (references.length >= 1) {
            // istanbul ignore next
            if (references.length > 1) {
                if (displayWarning) {
                    const options = { addressSpace };
                    // tslint:disable-next-line:no-console
                    console.warn("  More than one Aggregates reference have been found for parent of object");
                    // tslint:disable-next-line:no-console
                    console.warn("    object node id:", this.nodeId.toString(), chalk.cyan(this.browseName.toString()));
                    // tslint:disable-next-line:no-console
                    console.warn("    browseResults:");
                    // tslint:disable-next-line:no-console
                    console.warn(references.map((f) => toString_ReferenceDescription(f, options)).join("\n"));
                    // tslint:disable-next-line:no-console
                    console.warn("    first one will be used as parent");
                    // xx assert(browseResults.length === 1);
                    displayWarning = false;
                }
            }
            return reference_impl_1.ReferenceImpl.resolveReferenceNode(addressSpace, references[0]);
        }
    }
    return null;
}
function _asObject(references, addressSpace) {
    function toObject(reference) {
        const obj = resolveReferenceNode(addressSpace, reference);
        // istanbul ignore next
        if (false && !obj) {
            // tslint:disable-next-line:no-console
            console.log(chalk.red(" Warning :  object with nodeId ") +
                chalk.cyan(reference.nodeId.toString()) +
                chalk.red(" cannot be found in the address space !"));
        }
        return obj;
    }
    function remove_null(o) {
        return !!o;
    }
    return references.map(toObject).filter(remove_null);
}
function _filter_by_browse_name(components, browseName, namespaceIndex) {
    let select = [];
    if ((namespaceIndex === null || namespaceIndex === undefined) && typeof browseName === "string") {
        select = components.filter((c) => c.browseName.name.toString() === browseName);
        if (select && select.length > 1) {
            warningLog("Multiple children exist with name ", browseName, " please specify a namespace index");
        }
    }
    else {
        const _browseName = (0, node_opcua_data_model_1.coerceQualifiedName)(typeof browseName === "string" ? { name: browseName, namespaceIndex } : browseName);
        select = components.filter((c) => c.browseName.name === _browseName.name && c.browseName.namespaceIndex === _browseName.namespaceIndex);
    }
    return select;
}
let displayWarningReferencePointingToItSelf = true;
function _is_massively_used_reference(referenceType) {
    const name = referenceType.browseName.toString();
    return name === "HasTypeDefinition" || name === "HasModellingRule";
}
function _propagate_ref(addressSpace, reference) {
    // filter out non  Hierarchical References
    const referenceType = reference_impl_1.ReferenceImpl.resolveReferenceType(addressSpace, reference);
    // istanbul ignore next
    if (!referenceType) {
        // tslint:disable-next-line:no-console
        console.error(chalk.red(" ERROR"), " cannot find reference ", reference.referenceType, reference.toString());
    }
    // ------------------------------- Filter out back reference when reference type
    //                                 is HasTypeDefinition, HasModellingRule, etc ...
    //
    // var referenceNode = Reference.resolveReferenceNode(addressSpace,reference);
    // ignore propagation on back reference to UAVariableType or UAObject Type reference
    // because there are too many !
    if (!referenceType || _is_massively_used_reference(referenceType)) {
        return;
    }
    // ------------------------------- EXPERIMENT
    // xx if (!referenceType.isSupertypeOf(hierarchicalReferencesId)) { return; }
    const related_node = resolveReferenceNode(addressSpace, reference);
    if (related_node) {
        // verify that reference doesn't point to object it this (see mantis 3099)
        if ((0, node_opcua_nodeid_1.sameNodeId)(reference.nodeId, this.nodeId)) {
            // istanbul ignore next
            if (displayWarningReferencePointingToItSelf) {
                // this could happen with method
                console.warn("  Warning: a Reference is pointing to this ", this.nodeId.toString(), this.browseName.toString());
                displayWarningReferencePointingToItSelf = false;
            }
        }
        // xx ignore this assert(reference.nodeId.toString() !== this.nodeId.toString());
        // function w(s,l) { return (s+"                                                          ").substr(0,l);}
        // if (reference.isForward) {
        //    console.log("  CHILD => ",w(related_node.browseName   + " " + related_node.nodeId.toString(),30),
        //    "  PARENT   ",w(this.browseName + " " + this.nodeId.toString(),30) , reference.toString());
        // } else {
        //    console.log("  CHILD => ",w(this.browseName   + " " + this.nodeId.toString(),30),
        //   "  PARENT   ",w(related_node.browseName + " " + related_node.nodeId.toString(),30) , reference.toString());
        //
        // }
        related_node._add_backward_reference(new reference_impl_1.ReferenceImpl({
            _referenceType: getReferenceType(reference),
            isForward: !reference.isForward,
            node: this,
            nodeId: this.nodeId,
            referenceType: reference.referenceType
        }));
    } // else addressSpace may be incomplete and under construction (while loading a nodeset.xml file for instance)
}
function nodeid_is_nothing(nodeid) {
    return nodeid.value === 0 && nodeid.namespace === 0;
}
/**
 * @method normalize_referenceTypeId
 * @param addressSpace {IAddressSpace}
 * @param referenceTypeId {String|NodeId|null} : the referenceType either as a string or a nodeId
 * @return {NodeId}
 */
function normalize_referenceTypeId(addressSpace, referenceTypeId) {
    if (!referenceTypeId) {
        return (0, node_opcua_nodeid_1.makeNodeId)(0);
    }
    if (typeof referenceTypeId === "string") {
        const ref = addressSpace.findReferenceType(referenceTypeId);
        if (ref) {
            return ref.nodeId;
        }
    }
    let nodeId;
    try {
        nodeId = addressSpace.resolveNodeId(referenceTypeId);
    }
    catch (err) {
        console.log("cannot normalize_referenceTypeId", referenceTypeId);
        throw err;
    }
    (0, node_opcua_assert_1.assert)(nodeId);
    return nodeId;
}
const resolveReferenceNode = reference_impl_1.ReferenceImpl.resolveReferenceNode;
const resolveReferenceType = reference_impl_1.ReferenceImpl.resolveReferenceType;
function _filter_by_referenceType(browseDescription, references, referenceTypeId) {
    // make sure we have a valid referenceTypeId if not null
    if (!nodeid_is_nothing(referenceTypeId)) {
        (0, node_opcua_assert_1.assert)(referenceTypeId instanceof node_opcua_nodeid_1.NodeId);
        const referenceType = this.addressSpace.findNode(referenceTypeId);
        (0, node_opcua_debug_1.dumpIf)(!referenceType, referenceTypeId);
        // istanbul ignore next
        if (!referenceType || referenceType.nodeClass !== node_opcua_data_model_1.NodeClass.ReferenceType) {
            throw new Error("Cannot find reference type");
        }
        if (!browseDescription.includeSubtypes && referenceType.isAbstract) {
            warningLog("filter by reference will skip all reference as referenceType is abstract and includeSubtypes is false");
        }
        references = references.filter((reference) => {
            const ref = resolveReferenceType(this.addressSpace, reference);
            // istanbul ignore next
            if (!ref) {
                throw new Error("Cannot find reference type " + reference.toString());
            }
            // unknown type ... this may happen when the address space is not fully build
            (0, node_opcua_assert_1.assert)(ref.nodeClass === node_opcua_data_model_1.NodeClass.ReferenceType);
            const isSameType = (0, node_opcua_nodeid_1.sameNodeId)(ref.nodeId, referenceType.nodeId);
            if (isSameType) {
                return true;
            }
            if (browseDescription.includeSubtypes) {
                return ref.isSupertypeOf(referenceType);
            }
            else {
                return false;
            }
        });
    }
    return references;
}
function forwardOnly(reference) {
    return reference.isForward;
}
function reverseOnly(reference) {
    return !reference.isForward;
}
function _filter_by_direction(references, browseDirection) {
    if (browseDirection === node_opcua_data_model_1.BrowseDirection.Both) {
        return references;
    }
    if (browseDirection === node_opcua_data_model_1.BrowseDirection.Forward) {
        return references.filter(forwardOnly);
    }
    else {
        return references.filter(reverseOnly);
    }
}
/*
function _filter_by_context(node: BaseNode, references: Reference[], context: SessionContext): Reference[] {
    if (!context.isBrowseAccessRestricted(node)) {
        return references;
    }
    // browse access is restricted for forward
    return [];
}
*/
function _filter_by_context(node, references, context) {
    const addressSpace = node.addressSpace;
    return references.filter((reference) => !context.isBrowseAccessRestricted(resolveReferenceNode(addressSpace, reference)));
}
function _filter_by_nodeClass(references, nodeClassMask) {
    (0, node_opcua_assert_1.assert)(isFinite(nodeClassMask));
    if (nodeClassMask === 0) {
        return references;
    }
    const addressSpace = this.addressSpace;
    return references.filter((reference) => {
        const obj = resolveReferenceNode(addressSpace, reference);
        if (!obj) {
            return false;
        }
        const nodeClassName = node_opcua_data_model_1.NodeClass[obj.nodeClass];
        const value = (0, node_opcua_data_model_1.makeNodeClassMask)(nodeClassName);
        return (value & nodeClassMask) === value;
    });
}
function _filter_by_userFilter(references, context) {
    const addressSpace = this.addressSpace;
    return references.filter((reference) => {
        const obj = resolveReferenceNode(addressSpace, reference);
        // istanbul ignore next
        if (!obj) {
            return false;
        }
        const _private = (0, base_node_private_1.BaseNode_getPrivate)(obj);
        // istanbul ignore next
        if (!_private._browseFilter) {
            throw Error("Internal error : cannot find browseFilter");
        }
        const filter1 = _private._browseFilter.call(obj, context);
        return filter1;
    });
}
const reservedNames = {
    __description: 0,
    __displayName: 0,
    browseName: 0,
    description: 0,
    displayName: 0,
    nodeClass: 0,
    nodeId: 0,
    typeDefinition: 0
};
/*
 * install hierarchical references as javascript properties
 * Components/Properties/Organizes
 */
function install_components_as_object_properties(parentObj) {
    if (!parentObj) {
        return;
    }
    const addressSpace = parentObj.addressSpace;
    const hierarchicalRefs = parentObj.findHierarchicalReferences();
    const children = hierarchicalRefs.map((r) => reference_impl_1.ReferenceImpl.resolveReferenceNode(addressSpace, r));
    for (const child of children) {
        if (!child) {
            continue;
        }
        // assumption: we ignore namespace here .
        const name = (0, node_opcua_utils_1.lowerFirstLetter)(child.browseName.name.toString());
        if (Object.prototype.hasOwnProperty.call(reservedNames, name)) {
            // ignore reserved names
            if (doDebug) {
                console.log(chalk.bgWhite.red("Ignoring reserved keyword                                               " + name));
            }
            continue;
        }
        // ignore reserved names
        if (doDebug) {
            console.log("Installing property " + name, " on ", parentObj.browseName.toString());
        }
        /* istanbul ignore next */
        if (Object.prototype.hasOwnProperty.call(parentObj, name)) {
            continue;
        }
        Object.defineProperty(parentObj, name, {
            configurable: true,
            enumerable: true,
            // xx writable: false,
            get() {
                return child;
            }
            // value: child
        });
    }
}
function getReferenceType(reference) {
    return reference._referenceType;
}
exports.getReferenceType = getReferenceType;
//# sourceMappingURL=base_node_impl.js.map