"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var sp_core_library_1 = require("@microsoft/sp-core-library");
var sp_diagnostics_1 = require("@microsoft/sp-diagnostics");
var sp_dynamic_data_1 = require("@microsoft/sp-dynamic-data");
var lodash = (0, tslib_1.__importStar)(require("@microsoft/sp-lodash-subset"));
/**
 * The Dynamic Data Provider allows components to consume Dynamic Data.
 * It allows components to request Dynamic Data sources and register/unregister to those.
 *
 * @public
 */
var DynamicDataProvider = /** @class */ (function () {
    function DynamicDataProvider() {
        this._isDisposed = false;
        this._isInitialized = false;
        this._isSourcesChangedEventListenerAdded = false;
    }
    /**
     * Initializes the Dynamic Data Provider for a specific component.
     *
     * @param component - Component that is going to use the Dynamic Data Provider.
     *
     * @internal
     */
    DynamicDataProvider.prototype._initialize = function (component, serviceScope) {
        sp_core_library_1.Validate.isNotNullOrUndefined(component, 'component');
        sp_core_library_1.Validate.isNotNullOrUndefined(serviceScope, 'serviceScope');
        this._component = component;
        // Initialize all the required datastructures
        this._pendingRegistrations = new Map();
        this._dynamicDataManager = serviceScope.consume(sp_dynamic_data_1._DynamicDataUtilities.IDynamicDataManagerServiceKey);
        this._isInitialized = true;
        // Method bindings
        this._onSourcesChanged = this._onSourcesChanged.bind(this);
    };
    /**
     * Disposes the Dynamic Data Provider.
     */
    DynamicDataProvider.prototype.dispose = function () {
        if (!this._isDisposed) {
            delete this._pendingRegistrations;
            this._dynamicDataManager.sourcesChangedEvent.remove(this._component, this._onSourcesChanged);
            delete this._dynamicDataManager;
            delete this._component;
            this._isInitialized = false;
            this._isDisposed = true;
        }
    };
    Object.defineProperty(DynamicDataProvider.prototype, "isDisposed", {
        /**
         * Returns true if the Dynamic Data Provider is disposed.
         */
        get: function () {
            return this._isDisposed;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(DynamicDataProvider.prototype, "_isProviderInitialized", {
        /**
         * Returns true if the Dynamic Data Provider is intialized.
         * @internal
         */
        get: function () {
            return this._isInitialized;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(DynamicDataProvider.prototype, "_dynamicDataSourceId", {
        /**
         * Returns the dynamic data source id for the current instance of the component.
         * @internal
         */
        get: function () {
            return this._component._dynamicDataSourceId;
        },
        enumerable: false,
        configurable: true
    });
    /**
     * Registers a callback for updates when a Dynamic Data source is changed.
     * This happens when any property is updated within the source.
     *
     * @privateRemarks
     * We register the source, only if the source is available else defer the registration
     * to when the availableSources change.
     *
     * @param sourceId - Id of the Dynamic Data Source.
     * @param callback - Function to execute when the source updates its data.
     */
    DynamicDataProvider.prototype.registerSourceChanged = function (sourceId, callback) {
        this._assertNotDisposed();
        this._assertInitialized();
        sp_core_library_1.Validate.isNonemptyString(sourceId, 'sourceId');
        sp_core_library_1.Validate.isNotNullOrUndefined(callback, 'callback');
        var availableSourceIds = this.getAvailableSources().map(function (source) { return source.id; });
        if (availableSourceIds.indexOf(sourceId) !== -1) {
            // requested source is available
            this._dynamicDataManager.registerSourceChanged(sourceId, this._component, callback);
            this._logFeatureEntry('RegisterSourceChanged', { sourceId: sourceId });
        }
        else {
            // requested source is NOT available
            this._addPendingRegistration({ id: sourceId, callback: callback });
        }
    };
    /**
     * Unregisters a callback for updates when a Dynamic Data source is changed.
     *
     * @param sourceId - Id of the Dynamic Data Source.
     * @param callback - Function to remove from the registration. Must be the same it was registered with.
     */
    DynamicDataProvider.prototype.unregisterSourceChanged = function (sourceId, callback) {
        this._assertNotDisposed();
        this._assertInitialized();
        sp_core_library_1.Validate.isNonemptyString(sourceId, 'sourceId');
        sp_core_library_1.Validate.isNotNullOrUndefined(callback, 'callback');
        var registration = { id: sourceId, callback: callback };
        if (!this.isRegistrationPending(registration)) {
            // Source is already registered.
            this._dynamicDataManager.unregisterSourceChanged(sourceId, this._component, callback);
            this._logFeatureEntry('UnregisterSourceChanged', { sourceId: sourceId });
        }
        else {
            // Source is in pending state
            this._removePendingRegistration(registration);
        }
    };
    /**
     * Registers a callback for updates when a property is changed in a Dynamic Data source.
     *
     * @privateRemarks
     * We register the source, only if the source is available else defer the registration
     * to when the availableSources change.
     *
     * @param sourceId - Id of the Dynamic Data Source.
     * @param propertyId - Id of the property of the source.
     * @param callback - Function to execute when the source updates its data.
     */
    DynamicDataProvider.prototype.registerPropertyChanged = function (sourceId, propertyId, callback) {
        this._assertNotDisposed();
        this._assertInitialized();
        sp_core_library_1.Validate.isNonemptyString(sourceId, 'sourceId');
        sp_core_library_1.Validate.isNonemptyString(propertyId, 'propertyId');
        sp_core_library_1.Validate.isNotNullOrUndefined(callback, 'callback');
        var availableSourceIds = this.getAvailableSources().map(function (source) { return source.id; });
        if (availableSourceIds.indexOf(sourceId) !== -1) {
            // requested source is available
            this._dynamicDataManager.registerPropertyChanged(sourceId, propertyId, this._component, callback);
            this._logFeatureEntry('RegisterPropertyChanged', { sourceId: sourceId, propertyId: propertyId });
        }
        else {
            // requested source is NOT available
            this._addPendingRegistration({ id: sourceId, propertyId: propertyId, callback: callback });
        }
    };
    /**
     * Unregisters a callback for updates when a property is changed in a Dynamic Data source.
     *
     * @privateRemarks
     * If the sourceId is present in _registeredSourcesForPropertyChanged then
     *    it removes from sourceID from the set _registeredSourcesForPropertyChanged and
     *    invokes unregisterPropertyChanged event on the DynamicDataManager
     *
     * else if the sourceId is present in _pendingSourcesForPropertyChanged then
     *    it just removes the sourceId from the set _pendingSourcesForPropertyChanged
     *
     * @param sourceId - Id of the Dynamic Data Source.
     * @param propertyId - Id of the property of the source.
     * @param callback - Function to remove from the registration. Must be the same it was registered with.
     */
    DynamicDataProvider.prototype.unregisterPropertyChanged = function (sourceId, propertyId, callback) {
        this._assertNotDisposed();
        this._assertInitialized();
        sp_core_library_1.Validate.isNonemptyString(sourceId, 'sourceId');
        sp_core_library_1.Validate.isNonemptyString(propertyId, 'propertyId');
        sp_core_library_1.Validate.isNotNullOrUndefined(callback, 'callback');
        var registration = {
            id: sourceId,
            callback: callback,
            propertyId: propertyId
        };
        // registration is in pending state.
        if (!this.isRegistrationPending(registration)) {
            this._dynamicDataManager.unregisterPropertyChanged(sourceId, propertyId, this._component, callback);
            this._logFeatureEntry('UnregisterPropertyChanged', { sourceId: sourceId, propertyId: propertyId });
        }
        else {
            // Already registered.
            this._removePendingRegistration(registration);
        }
    };
    /**
     * Registers a callback to an event that raises when the list of available Dynamic Data Sources is updated.
     *
     * @param callback - Function to execute when the sources are updated.
     */
    DynamicDataProvider.prototype.registerAvailableSourcesChanged = function (callback) {
        sp_core_library_1.Validate.isNotNullOrUndefined(callback, 'callback');
        this._assertNotDisposed();
        this._assertInitialized();
        this._dynamicDataManager.sourcesChangedEvent.add(this._component, callback);
    };
    /**
     * Unregisters a callback to an event that raises when the list of available Dynamic Data Sources is updated.
     *
     * @privateRemarks
     * This api is used by the consumer of the dynamic data.
     *
     * @param callback - Function to remove from the registration. Must be the same it was registered with.
     */
    DynamicDataProvider.prototype.unregisterAvailableSourcesChanged = function (callback) {
        sp_core_library_1.Validate.isNotNullOrUndefined(callback, 'callback');
        this._assertNotDisposed();
        this._assertInitialized();
        this._dynamicDataManager.sourcesChangedEvent.remove(this._component, callback);
    };
    /**
     * Returns a list with all available Dynamic Data Sources.
     *
     * @returns Read-only array with all available sources.
     */
    DynamicDataProvider.prototype.getAvailableSources = function () {
        this._assertNotDisposed();
        this._assertInitialized();
        return this._dynamicDataManager.getSources();
    };
    /**
     * Returns a list of all the available Dynamic Data Sources which have same component id.
     *
     * @returns Read-only array of available sources with same component id.
     * @alpha
     */
    DynamicDataProvider.prototype.getAvailableSourcesByComponentId = function (componentId) {
        this._assertNotDisposed();
        this._assertInitialized();
        var sources = [];
        this._dynamicDataManager.getSources().forEach(function (source) {
            if (componentId.equals(sp_core_library_1.Guid.tryParse(source.metadata.componentId) || sp_core_library_1.Guid.empty)) {
                sources.push(source);
            }
        });
        return sources;
    };
    /**
     * Returns a Dynamic Data Source, by its id.
     * If the source is not present, it returns undefined.
     *
     * @param id - Id of the Dynamic Data Source.
     */
    DynamicDataProvider.prototype.tryGetSource = function (id) {
        this._assertNotDisposed();
        this._assertInitialized();
        sp_core_library_1.Validate.isNotNullOrUndefined(id, 'id');
        return this._dynamicDataManager.tryGetSource(id);
    };
    /**
     * Returns specific data from a source based on a Dynamic Data reference.
     *
     * @param dataReference - Dynamic Data Reference.
     * @internal
     */
    DynamicDataProvider.prototype._getData = function (dataReference) {
        this._assertNotDisposed();
        this._assertInitialized();
        sp_core_library_1.Validate.isNotNullOrUndefined(dataReference, 'dataReference');
        var dataSource = this._dynamicDataManager.tryGetSource(dataReference.sourceId);
        if (!dataSource) {
            return undefined;
        }
        var data = dataSource.getPropertyValue(dataReference.property); // tslint:disable-line:no-any
        if (data && dataReference.propertyPath) {
            // multiple levels of sub-properties are separated by '.' in the property path.
            var subpaths = dataReference.propertyPath.split('.');
            subpaths.forEach(function (subpath) {
                // If sub-property yields to an array value, then the path to the sub-property
                // will have [*] appended after the name of the sub-property.
                // Hence separating out to get the subPropertyName.
                var subpathElements = subpath.split(DynamicDataProvider._jsonPathArrayRegex);
                var subProperty = subpathElements[0];
                // If the supplied reference has an array([*]) notation, then we extract the data
                // accordingly. [*] means all the entries and if there is a number instead of the '*'
                // then extract that specific entry.
                if (data) {
                    if (Array.isArray(data)) {
                        var dataItems_1 = []; /* tslint:disable-line:no-any */
                        data.forEach(function (item) {
                            if (item.hasOwnProperty(subProperty)) {
                                dataItems_1.push(item[subProperty]);
                            }
                        });
                        /*
                         * If a specific entry is asked, we return it else we return all the entries,
                         * essentially it is treated as [*].
                         */
                        var index = subpathElements[1];
                        if (subpathElements.length > 1) {
                            /**
                             * This means that the current subpath element is an array type, an array inside an array.
                             * Since the subpath element is an array, we are normalizing the existing dataItems object
                             * i.e., dataItems will have an array of array objects.
                             */
                            var tempDataItems_1 = []; /* tslint:disable-line:no-any */
                            dataItems_1.forEach(function (dataItem) {
                                dataItem.forEach(function (internalItem) { return tempDataItems_1.push(internalItem); });
                            });
                            dataItems_1 = tempDataItems_1;
                        }
                        if (!isNaN(parseInt(index, 10))) {
                            data = dataItems_1[index];
                        }
                        else {
                            data = dataItems_1;
                        }
                    }
                    else if (data.hasOwnProperty(subProperty)) {
                        // data is an object type
                        data = data[subProperty];
                    }
                    else {
                        // If the control is here, it means that the subProperty was not found in data.
                        // Hence the next iteration would get data as undefined.
                        data = undefined;
                    }
                }
            });
        }
        return data;
    };
    DynamicDataProvider.prototype._onSourcesChanged = function () {
        var _this = this;
        var availableSourceIds = this.getAvailableSources().map(function (source) { return source.id; });
        // Registering to sourceChanged event
        this._pendingRegistrations.forEach(function (src) {
            // register to sourceChanged event, if the source is available.
            if (availableSourceIds.indexOf(src.id) !== -1) {
                // Source is available
                if (!src.propertyId) {
                    // No propertyId, means register to sourceChanged event.
                    _this._dynamicDataManager.registerSourceChanged(src.id, _this._component, src.callback);
                    _this._logFeatureEntry('RegisterSourceChanged', { sourceId: src.id });
                }
                else {
                    // With propertyId, means register to propertyChanged event.
                    _this._dynamicDataManager.registerPropertyChanged(src.id, src.propertyId, _this._component, src.callback);
                    _this._logFeatureEntry('RegisterPropertyChanged', { sourceId: src.id, propertyId: src.propertyId });
                }
                _this._removePendingRegistration(src);
            }
        });
    };
    DynamicDataProvider.prototype._assertNotDisposed = function () {
        if (this.isDisposed) {
            throw new Error('Dynamic Data Provider has been disposed');
        }
    };
    DynamicDataProvider.prototype._assertInitialized = function () {
        if (!this._isInitialized) {
            throw new Error('Dynamic Data Provider has not been initialized.');
        }
    };
    /**
     * Adds the registration to the pending registrations if not already added.
     * Also adds a listener to the _DynamicDataManager's sourcesChangedEvent.
     */
    DynamicDataProvider.prototype._addPendingRegistration = function (registration) {
        if (!this.isRegistrationPending(registration)) {
            this._pendingRegistrations.set(registration.id + (registration.propertyId || ''), {
                id: registration.id,
                propertyId: registration.propertyId,
                callback: registration.callback
            });
        }
        if (!this._isSourcesChangedEventListenerAdded) {
            this._dynamicDataManager.sourcesChangedEvent.add(this._component, this._onSourcesChanged);
            this._isSourcesChangedEventListenerAdded = true;
        }
    };
    /**
     * Removes the pending registration and then, if there are no more pending registrations
     * remove the 'sourcesChangedEvent' listener.
     */
    DynamicDataProvider.prototype._removePendingRegistration = function (registration) {
        this._pendingRegistrations.delete(registration.id + (registration.propertyId || ''));
        if (this._pendingRegistrations.size === 0) {
            this._dynamicDataManager.sourcesChangedEvent.remove(this._component, this._onSourcesChanged);
            this._isSourcesChangedEventListenerAdded = false;
        }
    };
    /**
     * Returns true if the incoming source is already added to the _pendingRegistrations Map.
     *
     * @param incomingRegistration - registration to be checked.
     */
    DynamicDataProvider.prototype.isRegistrationPending = function (incomingRegistration) {
        return this._pendingRegistrations.has(incomingRegistration.id + (incomingRegistration.propertyId || ''));
    };
    /**
     * Logs a feature event with any extra data from the producer of the event.
     * @param logFeature - Name of the feature to log.
     * @param contextualProps - Contextual log properties of the feature event.
     */
    DynamicDataProvider.prototype._logFeatureEntry = function (logFeature, contextualProps) {
        var isInternal = this._component.manifest.isInternal || false;
        var logProperties = {
            alias: this._component.manifest.alias,
            isInternal: isInternal.toString()
        };
        var logEntry = new sp_diagnostics_1._LogEntry('DynamicDataProvider', logFeature, sp_diagnostics_1._LogType.Event, lodash.merge(logProperties, contextualProps));
        sp_diagnostics_1._EngagementLogger.logEventWithLogEntry(logEntry);
    };
    /**
     * Regular expression to evaluate arrays in a json path expression.
     * @internal
     */
    DynamicDataProvider._jsonPathArrayRegex = new RegExp(/\[|\]/);
    return DynamicDataProvider;
}());
exports.default = DynamicDataProvider;
//# sourceMappingURL=DynamicDataProvider.js.map