/*
 * Application Insights JavaScript SDK - Core, 3.2.0
 * Copyright (c) Microsoft and contributors. All rights reserved.
 */


import { arrForEach, arrIndexOf, dumpObj, isArray, objDefine, objDefineProp, objForEachKey, objGetOwnPropertyDescriptor } from "@nevware21/ts-utils";
import { UNDEFINED_VALUE } from "../JavaScriptSDK/InternalConstants";
import { _DYN_APPLY, _DYN_HDLR, _DYN_LOGGER, _DYN_PUSH, _DYN_SPLICE, _DYN_THROW_INTERNAL } from "../__DynamicConstants";
import { CFG_HANDLER_LINK, _canMakeDynamic, blockDynamicConversion, throwInvalidAccess } from "./DynamicSupport";
var arrayMethodsToPatch = [
    "push",
    "pop",
    "shift",
    "unshift",
    "splice"
];
export var _throwDynamicError = function (logger, name, desc, e) {
    logger && logger[_DYN_THROW_INTERNAL /* @min:%2ethrowInternal */](3 /* eLoggingSeverity.DEBUG */, 108 /* _eInternalMessageId.DynamicConfigException */, "".concat(desc, " [").concat(name, "] failed - ") + dumpObj(e));
};
function _patchArray(state, target, name) {
    if (isArray(target)) {
        // Monkey Patch the methods that might change the array
        arrForEach(arrayMethodsToPatch, function (method) {
            var orgMethod = target[method];
            target[method] = function () {
                var args = [];
                for (var _i = 0; _i < arguments.length; _i++) {
                    args[_i] = arguments[_i];
                }
                var result = orgMethod[_DYN_APPLY /* @min:%2eapply */](this, args);
                // items may be added, removed or moved so need to make some new dynamic properties
                _makeDynamicObject(state, target, name, "Patching");
                return result;
            };
        });
    }
}
function _getOwnPropGetter(target, name) {
    var propDesc = objGetOwnPropertyDescriptor(target, name);
    return propDesc && propDesc.get;
}
function _createDynamicProperty(state, theConfig, name, value) {
    // Does not appear to be dynamic so lets make it so
    var detail = {
        n: name,
        h: [],
        trk: function (handler) {
            if (handler && handler.fn) {
                if (arrIndexOf(detail.h, handler) === -1) {
                    // Add this handler to the collection that should be notified when the value changes
                    detail.h[_DYN_PUSH /* @min:%2epush */](handler);
                }
                state.trk(handler, detail);
            }
        },
        clr: function (handler) {
            var idx = arrIndexOf(detail.h, handler);
            if (idx !== -1) {
                detail.h[_DYN_SPLICE /* @min:%2esplice */](idx, 1);
            }
        }
    };
    // Flag to optimize lookup response time by avoiding additional function calls
    var checkDynamic = true;
    var isObjectOrArray = false;
    function _getProperty() {
        if (checkDynamic) {
            isObjectOrArray = isObjectOrArray || _canMakeDynamic(_getProperty, state, value);
            // Make sure that if it's an object that we make it dynamic
            if (value && !value[CFG_HANDLER_LINK] && isObjectOrArray) {
                // It doesn't look like it's already dynamic so lets make sure it's converted the object into a dynamic Config as well
                value = _makeDynamicObject(state, value, name, "Converting");
            }
            // If it needed to be converted it now has been
            checkDynamic = false;
        }
        // If there is an active handler then add it to the tracking set of handlers
        var activeHandler = state.act;
        if (activeHandler) {
            detail.trk(activeHandler);
        }
        return value;
    }
    // Tag this getter as our dynamic property and provide shortcut for notifying a change
    _getProperty[state.prop] = {
        chng: function () {
            state.add(detail);
        }
    };
    function _setProperty(newValue) {
        if (value !== newValue) {
            if (!!_getProperty[state.ro] && !state.upd) {
                // field is marked as readonly so return false
                throwInvalidAccess("[" + name + "] is read-only:" + dumpObj(theConfig));
            }
            if (checkDynamic) {
                isObjectOrArray = isObjectOrArray || _canMakeDynamic(_getProperty, state, value);
                checkDynamic = false;
            }
            // The value must be a plain object or an array to enforce the reference (in-place updates)
            var isReferenced = isObjectOrArray && _getProperty[state.rf];
            if (isObjectOrArray) {
                // We are about to replace a plain object or an array
                if (isReferenced) {
                    // Reassign the properties from the current value to the same properties from the newValue
                    // This will set properties not in the newValue to undefined
                    objForEachKey(value, function (key) {
                        value[key] = newValue ? newValue[key] : UNDEFINED_VALUE;
                    });
                    // Now assign / re-assign value with all of the keys from newValue
                    try {
                        objForEachKey(newValue, function (key, theValue) {
                            _setDynamicProperty(state, value, key, theValue);
                        });
                        // Now drop newValue so when we assign value later it keeps the existing reference
                        newValue = value;
                    }
                    catch (e) {
                        // Unable to convert to dynamic property so just leave as non-dynamic
                        _throwDynamicError((state.hdlr || {})[_DYN_LOGGER /* @min:%2elogger */], name, "Assigning", e);
                        // Mark as not an object or array so we don't try and do this again
                        isObjectOrArray = false;
                    }
                }
                else if (value && value[CFG_HANDLER_LINK]) {
                    // As we are replacing the value, if it's already dynamic then we need to notify the listeners
                    // for every property it has already
                    objForEachKey(value, function (key) {
                        // Check if the value is dynamic
                        var getter = _getOwnPropGetter(value, key);
                        if (getter) {
                            // And if it is tell it's listeners that the value has changed
                            var valueState = getter[state.prop];
                            valueState && valueState.chng();
                        }
                    });
                }
            }
            if (newValue !== value) {
                var newIsObjectOrArray = newValue && _canMakeDynamic(_getProperty, state, newValue);
                if (!isReferenced && newIsObjectOrArray) {
                    // As the newValue is an object/array lets preemptively make it dynamic
                    newValue = _makeDynamicObject(state, newValue, name, "Converting");
                }
                // Now assign the internal "value" to the newValue
                value = newValue;
                isObjectOrArray = newIsObjectOrArray;
            }
            // Cause any listeners to be scheduled for notification
            state.add(detail);
        }
    }
    objDefine(theConfig, detail.n, { g: _getProperty, s: _setProperty });
}
export function _setDynamicProperty(state, target, name, value) {
    if (target) {
        // To be a dynamic property it needs to have a get function
        var getter = _getOwnPropGetter(target, name);
        var isDynamic = getter && !!getter[state.prop];
        if (!isDynamic) {
            _createDynamicProperty(state, target, name, value);
        }
        else {
            // Looks like it's already dynamic just assign the new value
            target[name] = value;
        }
    }
    return target;
}
export function _setDynamicPropertyState(state, target, name, flags) {
    if (target) {
        // To be a dynamic property it needs to have a get function
        var getter = _getOwnPropGetter(target, name);
        var isDynamic = getter && !!getter[state.prop];
        var inPlace = flags && flags[0 /* _eSetDynamicPropertyFlags.inPlace */];
        var rdOnly = flags && flags[1 /* _eSetDynamicPropertyFlags.readOnly */];
        var blkProp = flags && flags[2 /* _eSetDynamicPropertyFlags.blockDynamicProperty */];
        if (!isDynamic) {
            if (blkProp) {
                try {
                    // Attempt to mark the target as blocked from conversion
                    blockDynamicConversion(target);
                }
                catch (e) {
                    _throwDynamicError((state.hdlr || {})[_DYN_LOGGER /* @min:%2elogger */], name, "Blocking", e);
                }
            }
            try {
                // Make sure it's dynamic so that we can tag the property as per the state
                _setDynamicProperty(state, target, name, target[name]);
                getter = _getOwnPropGetter(target, name);
            }
            catch (e) {
                // Unable to convert to dynamic property so just leave as non-dynamic
                _throwDynamicError((state.hdlr || {})[_DYN_LOGGER /* @min:%2elogger */], name, "State", e);
            }
        }
        // Assign the optional flags if true
        if (inPlace) {
            getter[state.rf] = inPlace;
        }
        if (rdOnly) {
            getter[state.ro] = rdOnly;
        }
        if (blkProp) {
            getter[state.blkVal] = true;
        }
    }
    return target;
}
export function _makeDynamicObject(state, target, name, desc) {
    try {
        // Assign target with new value properties (converting into dynamic properties in the process)
        objForEachKey(target, function (key, value) {
            // Assign and/or make the property dynamic
            _setDynamicProperty(state, target, key, value);
        });
        if (!target[CFG_HANDLER_LINK]) {
            // Link the config back to the dynamic config details
            objDefineProp(target, CFG_HANDLER_LINK, {
                get: function () {
                    return state[_DYN_HDLR /* @min:%2ehdlr */];
                }
            });
            _patchArray(state, target, name);
        }
    }
    catch (e) {
        // Unable to convert to dynamic property so just leave as non-dynamic
        _throwDynamicError((state.hdlr || {})[_DYN_LOGGER /* @min:%2elogger */], name, desc, e);
    }
    return target;
}
//# sourceMappingURL=DynamicProperty.js.map