"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var sp_diagnostics_1 = require("@microsoft/sp-diagnostics");
var sp_core_library_1 = require("@microsoft/sp-core-library");
var sp_dynamic_data_1 = require("@microsoft/sp-dynamic-data");
var lodash = (0, tslib_1.__importStar)(require("@microsoft/sp-lodash-subset"));
var sp_lodash_subset_1 = require("@microsoft/sp-lodash-subset");
var DynamicDataProvider_1 = (0, tslib_1.__importDefault)(require("./DynamicDataProvider"));
var strings = (0, tslib_1.__importStar)(require("./loc/Strings.resx"));
/**
 * The Dynamic Data Source Manager is responsible for:
 *  - Constructing the dynamic data source
 *  - Allowing initialization of the dynamic data source by a component
 *  - Allowing data source to update its metadata and notify when data has been updated.
 *
 * @public
 */
var DynamicDataSourceManager = /** @class */ (function () {
    function DynamicDataSourceManager() {
        this._src = undefined;
        this._isDisposed = false;
        this._isInitialized = false;
        this._logSource = sp_diagnostics_1._LogSource.create('DynamicDataSourceManager');
    }
    /**
     * Initializes the Dynamic Data source manager for a specific component.
     *
     * @param component - Component on which the source is being constructed.
     *
     * @internal
     */
    DynamicDataSourceManager.prototype._initialize = function (component, serviceScope) {
        sp_core_library_1.Validate.isNotNullOrUndefined(component, 'component');
        sp_core_library_1.Validate.isNotNullOrUndefined(serviceScope, 'serviceScope');
        this._component = component;
        this._dynamicDataManager = serviceScope.consume(sp_dynamic_data_1._DynamicDataUtilities.IDynamicDataManagerServiceKey);
        this._isInitialized = true;
    };
    /**
     * Disposes the resources held by the Dynamic Data source.
     */
    DynamicDataSourceManager.prototype.dispose = function () {
        if (!this._isDisposed) {
            if (this._src) {
                this._dynamicDataManager.removeSource(this._src.id);
                delete this._src;
            }
            delete this._dynamicDataManager;
            delete this._component;
            this._isInitialized = false;
            this._isDisposed = true;
        }
    };
    Object.defineProperty(DynamicDataSourceManager.prototype, "isDisposed", {
        /**
         * Returns true if the DynamicDataSourceManager is disposed.
         */
        get: function () {
            return this._isDisposed;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(DynamicDataSourceManager.prototype, "_source", {
        /**
         * Returns the source of DynamicDataSourceManager.
         * @internal
         */
        get: function () {
            return this._src;
        },
        enumerable: false,
        configurable: true
    });
    /**
     * Initializes the Dynamic Data Source for the component.
     *
     * @param callableFunctions - Set of methods that are exposed through the Dynamic Data Source
     */
    DynamicDataSourceManager.prototype.initializeSource = function (callableFunctions) {
        this._assertNotDisposed();
        this._assertInitialized();
        if (!!this._src) {
            throw new Error(strings.SourceAlreadyInitialized);
        }
        this._callableFunctions = callableFunctions;
        this._src = this._buildSource(this._component);
        this._dynamicDataManager.addSource(this._src);
        this._logFeatureEntry('DynamicDataSourceInitialized', { sourceId: this._src.id });
    };
    /**
     * Notifies that this source has updated its properties.
     *
     * When invoked, it enables to broadcast the notification to all the consumers who have
     * registered to this notification from this source.
     */
    DynamicDataSourceManager.prototype.notifySourceChanged = function () {
        this._assertNotDisposed();
        this._assertInitialized();
        this._assertSourceExists();
        this._dynamicDataManager.notifySourceChanged(this._src.id);
    };
    /**
     * Notifies that this source has updated a specific property.
     *
     * When invoked, it enables to broadcast the notification to all the consumers who have
     * registered to this notification from this source.
     *
     * @param propertyId - Id of the updated property in the source.
     */
    DynamicDataSourceManager.prototype.notifyPropertyChanged = function (propertyId) {
        sp_core_library_1.Validate.isNonemptyString(propertyId, 'propertyId');
        this._assertNotDisposed();
        this._assertInitialized();
        this._assertSourceExists();
        this._dynamicDataManager.notifyPropertyChanged(this._src.id, propertyId);
    };
    /**
     * Updates the metadata of the Dynamic Data Source.
     * It allows to update fields like title or description.
     * Auto-generated fields like alias, componentId or instanceId cannot be updated.
     *
     * @param metadata - Partial of the metadata.
     */
    DynamicDataSourceManager.prototype.updateMetadata = function (metadata) {
        this._assertNotDisposed();
        this._assertInitialized();
        this._assertSourceExists();
        sp_core_library_1.Validate.isNotNullOrUndefined(metadata, 'metadata');
        // Ensure that auto-generated properties are not updated.
        metadata.alias = undefined;
        metadata.componentId = undefined;
        metadata.instanceId = undefined;
        (0, sp_lodash_subset_1.merge)(this._src.metadata, metadata);
    };
    Object.defineProperty(DynamicDataSourceManager.prototype, "sourceId", {
        /**
         * Returns the Id of the current DataSource.
         * If the component did not initialize as a dynamic data source, then it returns an empty string.
         */
        get: function () {
            return this._src ? this._src.id : '';
        },
        enumerable: false,
        configurable: true
    });
    DynamicDataSourceManager.prototype._buildSource = function (component) {
        // If the source is returning non-empty allowed events then, it has to define
        // 'sendEvent' event handler as well.
        if (this._callableFunctions.allowedEvents && this._callableFunctions.allowedEvents.length > 0) {
            if (!this._callableFunctions.sendEvent) {
                throw new Error(strings.NoSendEventWhenAllowedEventsIsNonEmpty);
            }
        }
        return {
            id: this._component._dynamicDataSourceId,
            metadata: this._buildMetadata(component),
            // Return a clone to ensure that the consumer doesn't modify values in the source
            getPropertyDefinitions: this._getPropertyDefinitions.bind(this),
            getPropertyValue: this._getPropertyValue.bind(this),
            getAnnotatedPropertyValue: this._getAnnotatedPropertyValue.bind(this),
            // async callbacks
            getPropertyDefinitionsAsync: this._getPropertyDefinitionsAsync.bind(this),
            getPropertyValueAsync: this._getPropertyValueAsync.bind(this),
            getAnnotatedPropertyValueAsync: this._getAnnotatedPropertyValueAsync.bind(this),
            allowedEventsAsync: this._allowedEventsAsync.bind(this),
            sendEvent: this._sendEvent.bind(this)
        };
    };
    /**
     * This is the allowedEvents() that the DynamicDataSource actually exposes.
     * It does a copy of the data to ensure the consumer doesn't modify the source.
     */
    DynamicDataSourceManager.prototype._allowedEvents = function () {
        return this._callableFunctions.allowedEvents
            ? (0, sp_lodash_subset_1.cloneDeep)(this._callableFunctions.allowedEvents())
            : undefined;
    };
    /**
     * This is the sendEvent() that the consumer invokes to send an event to the source, with data.
     * @param eventName - Name of the event
     * @param data - Associated data
     */
    // tslint:disable-next-line:no-any
    DynamicDataSourceManager.prototype._sendEvent = function (eventName, data) {
        this._assertValidEvent(eventName);
        this._callableFunctions.sendEvent(eventName, data);
    };
    /**
     * This is the getPropertyDefinitions() that the DynamicDataSource actually exposes.
     * It does a copy of the data to ensure the consumer doesn't modify the source.
     * This assumes that the component has dynamic data and has a controller.
     */
    DynamicDataSourceManager.prototype._getPropertyDefinitions = function () {
        return (0, sp_lodash_subset_1.cloneDeep)(this._callableFunctions.getPropertyDefinitions());
    };
    /**
     * This is the getPropertyValue() that the DynamicDataSource actually exposes.
     * It does a copy of the data to ensure the consumer doesn't modify the source.
     * This assumes that the component has the source initialized.
     * If there is no value returned, it checks whether the propertyId is incorrect or the value is actually undefined.
     *
     * @remarks
     * If the property value associated with the property id, is an array then the
     * propertyId will have [*] notation appended at the end to support valid JSON Path notation.
     * Hence when reading the property id, we account for that and extract the name accordingly.
     */
    // tslint:disable-next-line:no-any
    DynamicDataSourceManager.prototype._getPropertyValue = function (propertyId) {
        sp_core_library_1.Validate.isNotNullOrUndefined(propertyId, 'propertyId');
        var qosMonitor = new sp_diagnostics_1._QosMonitor('DynamicDataSourceManager._getPropertyValue');
        var paths = propertyId.split(DynamicDataProvider_1.default._jsonPathArrayRegex);
        try {
            // tslint:disable-next-line:no-any
            var propertyValue = this._callableFunctions.getPropertyValue(paths.shift());
            if (paths.length > 0) {
                if (!isNaN(parseInt(paths[0], 10)) && Array.isArray(propertyValue)) {
                    propertyValue = propertyValue[paths[0]];
                }
            }
            if (propertyValue === undefined) {
                if (
                // This code can be slow if there is a many property definitions (O(n) for number of properties).
                // It's done this way to prioritize memory consumption instead of CPU cycles as this is an optional feature,
                // so if it's not used there is no memory foorprint
                this._callableFunctions.getPropertyDefinitions().filter(function (def) { return def.id === propertyId; }).length ===
                    0 &&
                    this._src // Check that the source exists to get its title
                ) {
                    throw new Error("Property \"".concat(propertyId, "\" doesn't exist in source \"").concat(this._src.metadata.title, "\""));
                }
            }
            qosMonitor.writeSuccess((0, tslib_1.__assign)((0, tslib_1.__assign)({}, ((this._src && this._src.metadata) || {})), { sourceId: this._src && this._src.id, propertyValue: propertyValue, propertyId: propertyId }));
            return (0, sp_lodash_subset_1.cloneDeep)(propertyValue);
        }
        catch (error) {
            qosMonitor.writeUnexpectedFailure('NoPropertyId', error, { propertyId: propertyId });
            sp_diagnostics_1._TraceLogger.logErrorData({
                source: this._logSource,
                error: error
            });
            return undefined;
        }
    };
    /**
     * Returns the real time value of the property.
     * @param propertyId - property id for which the value is requested.
     */
    DynamicDataSourceManager.prototype._getPropertyDefinitionsAsync = function () {
        return Promise.resolve(this._getPropertyDefinitions());
    };
    /**
     * Returns the real time value of the property.
     * @param propertyId - property id for which the value is requested.
     */
    // tslint:disable-next-line:no-any
    DynamicDataSourceManager.prototype._getPropertyValueAsync = function (propertyId) {
        return Promise.resolve(this._getPropertyValue(propertyId));
    };
    /**
     * This is the allowedEvents() that the DynamicDataSource actually exposes.
     * It does a copy of the data to ensure the consumer doesn't modify the source.
     */
    DynamicDataSourceManager.prototype._allowedEventsAsync = function () {
        return Promise.resolve(this._allowedEvents());
    };
    /**
     * This is the getPropertyValueSchema() that the DynamicDataSource actually exposes.
     * It does a copy of the data to ensure the consumer doesn't modify the source.
     * If the source doesn't provide the annotated value, then it falls back to whatever
     * 'getPropertyValue' returns as the sample value and metadata would be undefined.
     *
     * @remarks
     * If the property value associated with the property id, is an array then the
     * propertyId will have [*] notation appended at the end to support valid JSON Path notation.
     * Hence when reading the property id, we account for that and extract the name accordingly.
     *
     * @param propertyId - property id for which the schema is requested.
     */
    DynamicDataSourceManager.prototype._getAnnotatedPropertyValue = function (propertyId) {
        sp_core_library_1.Validate.isNotNullOrUndefined(propertyId, 'propertyId');
        var qosMonitor = new sp_diagnostics_1._QosMonitor('DynamicDataSourceManager._getAnnotatedPropertyValue');
        var annotatedPropertyValue;
        var getAnnotatedPropertyValue = this._callableFunctions.getAnnotatedPropertyValue;
        if (getAnnotatedPropertyValue) {
            var paths = propertyId.split(DynamicDataProvider_1.default._jsonPathArrayRegex);
            try {
                // tslint:disable-next-line:no-any
                annotatedPropertyValue = this._callableFunctions.getAnnotatedPropertyValue(paths.shift());
                if (paths.length > 0) {
                    if (!isNaN(parseInt(paths[0], 10)) && Array.isArray(annotatedPropertyValue)) {
                        annotatedPropertyValue = annotatedPropertyValue[paths[0]];
                    }
                }
                qosMonitor.writeSuccess((0, tslib_1.__assign)((0, tslib_1.__assign)({}, ((this._src && this._src.metadata) || {})), { sourceId: this._src && this._src.id, annotatedPropertyValue: annotatedPropertyValue, propertyId: propertyId }));
            }
            catch (error) {
                qosMonitor.writeExpectedFailure('NoAnnotatedPropertyValue', error, { annotatedPropertyValue: annotatedPropertyValue });
                sp_diagnostics_1._TraceLogger.logErrorData({
                    source: this._logSource,
                    error: error
                });
                annotatedPropertyValue = undefined;
            }
        }
        if (!annotatedPropertyValue) {
            annotatedPropertyValue = {
                sampleValue: this._getPropertyValue(propertyId),
                metadata: undefined
            };
        }
        return annotatedPropertyValue;
    };
    /**
     * Returns the annotated value of the property.
     * @param propertyId - property id for which the value is requested.
     */
    DynamicDataSourceManager.prototype._getAnnotatedPropertyValueAsync = function (propertyId) {
        return Promise.resolve(this._getAnnotatedPropertyValue(propertyId));
    };
    DynamicDataSourceManager.prototype._buildMetadata = function (component) {
        var metadata = {
            title: component.manifest.alias,
            alias: component.manifest.alias,
            componentId: component.componentId,
            instanceId: component.instanceId
        };
        // Web parts have properties "title" and "description" that should go to the source by default.
        var componentWithMetadata = component; // tslint:disable-line:no-any
        if (componentWithMetadata.title) {
            metadata.title = componentWithMetadata.title;
        }
        if (componentWithMetadata.description) {
            metadata.description = componentWithMetadata.description;
        }
        return metadata;
    };
    /**
     * Asserts if the event is a valid event on the source.
     *
     * Its an invalid event in two cases:
     *  1. When '_allowedEvent()' api returns undefined
     *  2. When the event name is not one of the allowed events on the source.
     *
     * @param eventName - Name of the event.
     */
    DynamicDataSourceManager.prototype._assertValidEvent = function (eventName) {
        if (!this._allowedEvents() || (0, sp_lodash_subset_1.findIndex)(this._allowedEvents(), { name: eventName }) === -1) {
            throw new Error(sp_core_library_1.Text.format(strings.InvalidEvent, eventName, this._src.metadata.title));
        }
    };
    DynamicDataSourceManager.prototype._assertNotDisposed = function () {
        if (this.isDisposed) {
            throw new Error(strings.DynamicDataSourceManagerIsDisposed);
        }
    };
    DynamicDataSourceManager.prototype._assertInitialized = function () {
        if (!this._isInitialized) {
            throw new Error(strings.DynamicDataSourceManagerIsNotInitialized);
        }
    };
    DynamicDataSourceManager.prototype._assertSourceExists = function () {
        if (!this._src) {
            throw new Error(strings.DynamicDataSourceIsNotInitialized);
        }
    };
    /**
     * Logs a feature event.
     * @param logFeature - Name of the feature to log.
     * @param contextualProps - Contextual log properties of the feature event.
     */
    DynamicDataSourceManager.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('DynamicDataSourceManager', logFeature, sp_diagnostics_1._LogType.Event, lodash.merge(logProperties, contextualProps));
        sp_diagnostics_1._EngagementLogger.logEventWithLogEntry(logEntry);
    };
    return DynamicDataSourceManager;
}());
exports.default = DynamicDataSourceManager;
//# sourceMappingURL=DynamicDataSourceManager.js.map