"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var lodash = (0, tslib_1.__importStar)(require("@microsoft/sp-lodash-subset"));
var sp_core_library_1 = require("@microsoft/sp-core-library");
var sp_dynamic_data_1 = require("@microsoft/sp-dynamic-data");
var DynamicDataProvider_1 = (0, tslib_1.__importDefault)(require("./DynamicDataProvider"));
var sp_diagnostics_1 = require("@microsoft/sp-diagnostics");
/**
 * Serializable object that simplifies usage of a DynamicProperty.
 *
 * @remarks
 * This is built for usage in web part properties, and modified through the property pane.
 * Can be initialized with a default callback.
 * Web parts initialize with the render method so if the 3rd party developer does nothing, a change in the
 * DynamicProperty will re-render the web part automatically.
 *
 * @public
 */
var DynamicProperty = /** @class */ (function () {
    /**
     * Create a new DynamicProperty object.
     * @param provider - DynamicDataProvider.
     * @param callback - Optional. Default callback to be registered for updates in the DynamicDataSource.
     */
    function DynamicProperty(provider, callback) {
        this._hasValue = false;
        this._isDisposed = false;
        sp_core_library_1.Validate.isNotNullOrUndefined(provider, 'provider');
        // tslint:disable-next-line:no-string-literal
        this['__type'] = DynamicProperty._TYPE_NAME;
        this._provider = provider;
        this._callbacks = new Set();
        this._pendingCallbacks = new Set();
        if (callback) {
            // The default callback will be added once a source is configured
            this._pendingCallbacks.add(callback);
            this._defaultCallback = callback;
        }
        else {
            this._defaultCallback = undefined;
        }
        this._onSourcesChanged = this._onSourcesChangedCallback.bind(this);
        this._cloneDeep = this._cloneDeep.bind(this);
        this._provider.registerAvailableSourcesChanged(this._onSourcesChanged);
    }
    /**
     * Sets the value of the DynamicProperty to be a reference to a DynamicDynamicSource
     * and property.  The reference is of the form `<source>:<property>:<path>`.
     * @param reference - Id of the DynamicDataSource and property.
     */
    DynamicProperty.prototype.setReference = function (reference) {
        var _this = this;
        sp_core_library_1.Validate.isNonemptyString(reference, 'id');
        this._value = undefined;
        this._hasValue = false;
        // Potentially changing the source. Remove all callbacks, change the source, and add callbacks again.
        var callbacks = new Set();
        this._callbacks.forEach(function (callback) { return callbacks.add(callback); });
        this._callbacks.forEach(function (callback) { return _this.unregister(callback); });
        this._reference = new sp_dynamic_data_1.DynamicDataReference(reference);
        this._sourceId = this._getSourceId();
        callbacks.forEach(function (callback) { return _this.register(callback); });
        // The source has changed, so act if a new source was added
        this._onSourcesChangedCallback();
    };
    /**
     * Sets a static value in the DynamicProperty.
     * @param value - Value for the DynamicProperty.
     */
    DynamicProperty.prototype.setValue = function (value) {
        var _this = this;
        // Removing the source. Remove all callback, set the value, and add the callback again so they are not lost.
        // Callbacks will be stored in the pending callbacks list.
        var callbacks = this._callbacks;
        callbacks.forEach(function (callback) { return _this.unregister(callback); });
        this._value = value;
        this._reference = undefined;
        this._sourceId = undefined;
        this._hasValue = true;
        callbacks.forEach(function (callback) { return _this.register(callback); });
    };
    Object.defineProperty(DynamicProperty.prototype, "isDisposed", {
        /**
         * {@inheritDoc @microsoft/sp-core-library#IDisposable.isDisposed}
         */
        get: function () {
            return this._isDisposed;
        },
        enumerable: false,
        configurable: true
    });
    /**
     * {@inheritDoc @microsoft/sp-core-library#IDisposable.dispose}
     */
    DynamicProperty.prototype.dispose = function () {
        var _this = this;
        if (!this._isDisposed) {
            this._provider.unregisterAvailableSourcesChanged(this._onSourcesChanged);
            if (this._defaultCallback) {
                this.unregister(this._defaultCallback);
                delete this._defaultCallback;
            }
            this._callbacks.forEach(function (callback) { return _this.unregister(callback); });
            delete this._callbacks;
            delete this._onSourcesChanged;
            delete this._pendingCallbacks;
            delete this._provider;
            delete this._reference;
            delete this._sourceId;
            this._isDisposed = true;
        }
    };
    /**
     * Returns a serializable version of the object.
     * To be used when `JSON.stringify()` is called on the object (i.e. during Web Part serialization)
     *
     * @remarks
     * This doesn't follow the naming rule for internal methods because the JavaScript engine needs this exact name.
     *
     * @internal
     */
    DynamicProperty.prototype.toJSON = function () {
        if (this._hasValue) {
            return {
                __type: DynamicProperty._TYPE_NAME,
                value: this._value
            };
        }
        else {
            return {
                __type: DynamicProperty._TYPE_NAME,
                reference: this._reference
            };
        }
    };
    Object.defineProperty(DynamicProperty.prototype, "reference", {
        /**
         * DynamicDataReference string that the DynamicProperty object points to.
         * The reference is of the form `<source>:<property>:<path>`.
         *
         * @remarks
         * If the DynamicProperty is set up with static data, this returns undefined.
         */
        get: function () {
            return this._hasValue ? undefined : this._reference.reference;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(DynamicProperty.prototype, "value", {
        /**
         * Returns the value of the DynamicProperty object, if it has a non-dynamic value.
         * Used for serialization purposes. The proper API to access the value of the object is `tryGetValue()`
         *
         * @internal
         */
        get: function () {
            return this._value;
        },
        enumerable: false,
        configurable: true
    });
    /**
     * Returns the value of the object.
     *
     * @remarks
     * If the value is an array, then the first element of the array is returned;
     * otherwise, the value itself is returned.
     *
     * NOTE: This api will never return an array.  To retrieve an array value,
     * use the {@link DynamicProperty.tryGetValues} API.
     *
     * @returns the value associated with the dynamic property or
     * undefined if the source or the data doesn't exist.
     */
    DynamicProperty.prototype.tryGetValue = function () {
        if (this.isDisposed &&
            !sp_core_library_1._SPKillSwitch.isActivated('413769dc-5e0c-4609-9c86-bdf099fe84e3' /*, '12/02/2021', 'DisposedUndefinedNotException'*/)) {
            return undefined;
        }
        else {
            sp_core_library_1.Validate.isNotDisposed(this, 'DynamicData');
        }
        var value = this._getData();
        if (Array.isArray(value)) {
            value = value[0];
        }
        return value;
    };
    /**
     * Returns the value of the object.
     *
     * @remarks
     * If the value is NOT an array, then an array is returned with the value being the single entry;
     * otherwise, the value itself is returned.  If the property is undefined or cannot be found,
     * an empty array will be returned.
     *
     * NOTE: This api will always return an array and it assumes that the array is homogeneous.
     * To retrieve a non-array value, use the {@link DynamicProperty.tryGetValue} API.
     *
     * @returns the value associated with the dynamic property or
     * an empty array if the source or the data doesn't exist.
     */
    DynamicProperty.prototype.tryGetValues = function () {
        // tslint:disable-next-line:no-any
        var value = this._getData();
        // OK, time for some massaging of the return.
        if (value) {
            // If there is a value returned, but it isn't an array, create an array and
            // make this the return value the sole contents of the array
            if (!Array.isArray(value)) {
                value = [value];
            }
        }
        else {
            // In this case, we are saying that there is no value.  For this scenario
            // we simply return an empty array.
            value = [];
        }
        return value;
    };
    /**
     * Returns the DynamicDataSource that the DynamicProperty object refers to.
     * Returns undefined if the source doesn't exist.
     */
    DynamicProperty.prototype.tryGetSource = function () {
        if (this.isDisposed &&
            !sp_core_library_1._SPKillSwitch.isActivated('413769dc-5e0c-4609-9c86-bdf099fe84e3' /*, '12/02/2021', 'DisposedUndefinedNotException'*/)) {
            return undefined;
        }
        else {
            sp_core_library_1.Validate.isNotDisposed(this, 'DynamicData');
        }
        return this._hasValue ? undefined : this._provider.tryGetSource(this._sourceId);
    };
    /**
     * Registers a callback function for updates on the associated DynamicDataSource.
     * @param callback - Callback function to register.
     */
    DynamicProperty.prototype.register = function (callback) {
        sp_core_library_1.Validate.isNotDisposed(this, 'DynamicData');
        if (!this._hasValue && this._sourceId && !!this._provider.tryGetSource(this._sourceId)) {
            this._provider.registerPropertyChanged(this._sourceId, this._getPropertyToRegister(), callback);
            this._callbacks.add(callback);
        }
        else {
            this._pendingCallbacks.add(callback);
        }
    };
    /**
     * Unregisters a callback function for updates on the associated DynamicDataSource.
     * The callback function must be the same it was registered with.
     * @param callback - Callback function to unregister.
     */
    DynamicProperty.prototype.unregister = function (callback) {
        sp_core_library_1.Validate.isNotDisposed(this, 'DynamicData');
        if (!this._hasValue && this._sourceId && !!this._provider.tryGetSource(this._sourceId)) {
            this._provider.unregisterPropertyChanged(this._sourceId, this._getPropertyToRegister(), callback);
        }
        this._pendingCallbacks.delete(callback);
        this._callbacks.delete(callback);
    };
    /**
     * Returns true if the object has a default callback.
     */
    DynamicProperty.prototype.hasDefaultCallback = function () {
        return !!this._defaultCallback;
    };
    /**
     * Removes the registration for the default callback.
     *
     * @remarks
     * This can be useful for advanced users who only want their custom callbacks.
     */
    DynamicProperty.prototype.removeDefaultCallback = function () {
        if (this._defaultCallback) {
            this.unregister(this._defaultCallback);
            this._defaultCallback = undefined;
        }
    };
    /**
     * Returns the id of the DynamicDataSource if the DynamicProperty has a reference set.
     *
     * @internal
     */
    DynamicProperty.prototype._getSourceId = function () {
        sp_core_library_1.Validate.isNotDisposed(this, 'DynamicData');
        return this._hasValue ? '' : this._reference.sourceId;
    };
    /**
     * Returns the property name of the DynamicDataSource that the DynamicProperty points to.
     *
     * NOTE: This is accessed from the property pane code while building the DynamicDataWidget.
     * @internal
     */
    DynamicProperty.prototype._getProperty = function () {
        sp_core_library_1.Validate.isNotDisposed(this, 'DynamicData');
        return this._hasValue ? '' : this._reference.property;
    };
    /**
     * Returns the path from the property that the object points to, if applicable.
     * If not applicable, returns an empty string.
     *
     * @internal
     */
    DynamicProperty.prototype._getPropertyPath = function () {
        sp_core_library_1.Validate.isNotDisposed(this, 'DynamicData');
        return this._hasValue ? '' : this._reference.propertyPath || '';
    };
    /**
     * Returns a deep cloned version of the current instance.
     *
     * @remarks
     * Since the dynamic property is maintaining an instance of the DynamicDataProvider,
     * which which when serialized will result in 'Maximum call stack size exceeded' based
     * on what kind of sources they hold references to.
     *
     * For example, if the dynamicDataProvider is holding PageContext source, which internally
     * has serviceScope in its state. Deep cloning this source would result in the above mentioned
     * error.
     *
     * Hence we would want to just clone the dynamicDataProvider not deep clone it.
     *
     * @returns - Custom deep Cloned version of the current instance.
     *
     * @internal
     */
    // tslint:disable-next-line:no-any
    DynamicProperty.prototype._cloneDeep = function (instance) {
        return lodash.cloneDeepWith(instance, function (prop) {
            if (prop instanceof DynamicDataProvider_1.default) {
                return prop;
            }
        });
    };
    /**
     * Returns the property name of the DynamicDataSource that the DynamicProperty points to.
     *
     * @remarks
     * Removes the array notation from the property name, if any, before it is being registered.
     * This is to avoid any mismatch between the strings passed by the source when it notifies
     * a property change, as the source doesn't pass any array notation with the property name.
     * Array notation is only for internal representaion, for a property whose value is an array type.
     */
    DynamicProperty.prototype._getPropertyToRegister = function () {
        sp_core_library_1.Validate.isNotDisposed(this, 'DynamicData');
        // Split the property string on the array notation to get
        // the property name. If the array notation is not present,
        // then the original string is returned in the array.
        return this._getProperty().split(DynamicProperty._arrayRegex)[0];
    };
    /**
     * Callback when the DynamicDataSources list changes.
     * It registers the pending callbacks into the DynamicDataProvidere.
     */
    DynamicProperty.prototype._onSourcesChangedCallback = function () {
        var _this = this;
        if (!this._hasValue && this._sourceId && !!this._provider.tryGetSource(this._sourceId)) {
            var callbacksToAdd = this._pendingCallbacks;
            this._pendingCallbacks = new Set();
            callbacksToAdd.forEach(function (callback) {
                _this.register(callback);
                callback();
            });
        }
    };
    DynamicProperty.prototype._getData = function () {
        if (this.isDisposed &&
            !sp_core_library_1._SPKillSwitch.isActivated('413769dc-5e0c-4609-9c86-bdf099fe84e3' /*, '12/02/2021', 'DisposedUndefinedNotException'*/)) {
            return undefined;
        }
        else {
            sp_core_library_1.Validate.isNotDisposed(this, 'DynamicData');
        }
        var value;
        try {
            value = this._hasValue ? this._value : this._provider._getData(this._reference);
        }
        catch (error) {
            sp_diagnostics_1._EngagementLogger.logEventWithLogEntry(error);
            throw error;
        }
        sp_diagnostics_1._EngagementLogger.logEventWithLogEntry(new sp_diagnostics_1._LogEntry('DynamicData', 'getData', sp_diagnostics_1._LogType.Event, { value: JSON.stringify(this.value) }));
        return value;
    };
    /**
     * Constant with the type name of the DynamicProperty.
     * @internal
     */
    DynamicProperty._TYPE_NAME = 'DynamicProperty';
    DynamicProperty._arrayRegex = new RegExp(/\[\*\]$/);
    return DynamicProperty;
}());
exports.default = DynamicProperty;
//# sourceMappingURL=DynamicProperty.js.map