import { __assign, __read, __spread } from "tslib";
import { basename, getGlobalObject, logger, timestampWithMs } from '@sentry/utils';
/**
 * Used to extract Tracing integration from the current client,
 * without the need to import `Tracing` itself from the @sentry/apm package.
 * @deprecated as @sentry/tracing should be used over @sentry/apm.
 */
var TRACING_GETTER = {
    id: 'Tracing',
};
/**
 * Used to extract BrowserTracing integration from @sentry/tracing
 */
var BROWSER_TRACING_GETTER = {
    id: 'BrowserTracing',
};
// Mappings from operation to corresponding lifecycle hook.
var HOOKS = {
    activate: ['activated', 'deactivated'],
    create: ['beforeCreate', 'created'],
    destroy: ['beforeDestroy', 'destroyed'],
    mount: ['beforeMount', 'mounted'],
    update: ['beforeUpdate', 'updated'],
};
var COMPONENT_NAME_REGEXP = /(?:^|[-_/])(\w)/g;
var ROOT_COMPONENT_NAME = 'root';
var ANONYMOUS_COMPONENT_NAME = 'anonymous component';
/** JSDoc */
var Vue = /** @class */ (function () {
    /**
     * @inheritDoc
     */
    function Vue(options) {
        var _this = this;
        /**
         * @inheritDoc
         */
        this.name = Vue.id;
        /**
         * Cache holding already processed component names
         */
        this._componentsCache = {};
        /** Keep it as attribute function, to keep correct `this` binding inside the hooks callbacks  */
        // eslint-disable-next-line @typescript-eslint/typedef
        this._applyTracingHooks = function (vm, getCurrentHub) {
            // Don't attach twice, just in case
            if (vm.$options.$_sentryPerfHook) {
                return;
            }
            vm.$options.$_sentryPerfHook = true;
            var name = _this._getComponentName(vm);
            var rootMount = name === ROOT_COMPONENT_NAME;
            var spans = {};
            // Render hook starts after once event is emitted,
            // but it ends before the second event of the same type.
            //
            // Because of this, we start measuring inside the first event,
            // but finish it before it triggers, to skip the event emitter timing itself.
            var rootHandler = function (hook) {
                var now = timestampWithMs();
                // On the first handler call (before), it'll be undefined, as `$once` will add it in the future.
                // However, on the second call (after), it'll be already in place.
                if (_this._rootSpan) {
                    _this._finishRootSpan(now, getCurrentHub);
                }
                else {
                    vm.$once("hook:" + hook, function () {
                        // Create an activity on the first event call. There'll be no second call, as rootSpan will be in place,
                        // thus new event handler won't be attached.
                        // We do this whole dance with `TRACING_GETTER` to prevent `@sentry/apm` from becoming a peerDependency.
                        // We also need to ask for the `.constructor`, as `pushActivity` and `popActivity` are static, not instance methods.
                        /* eslint-disable @typescript-eslint/no-unsafe-member-access */
                        // eslint-disable-next-line deprecation/deprecation
                        var tracingIntegration = getCurrentHub().getIntegration(TRACING_GETTER);
                        if (tracingIntegration) {
                            _this._tracingActivity = tracingIntegration.constructor.pushActivity('Vue Application Render');
                            var transaction = tracingIntegration.constructor.getTransaction();
                            if (transaction) {
                                _this._rootSpan = transaction.startChild({
                                    description: 'Application Render',
                                    op: 'Vue',
                                });
                            }
                            // Use functionality from @sentry/tracing
                        }
                        else {
                            var activeTransaction = getActiveTransaction(getCurrentHub());
                            if (activeTransaction) {
                                _this._rootSpan = activeTransaction.startChild({
                                    description: 'Application Render',
                                    op: 'Vue',
                                });
                            }
                        }
                        /* eslint-enable @typescript-eslint/no-unsafe-member-access */
                    });
                }
            };
            var childHandler = function (hook, operation) {
                // Skip components that we don't want to track to minimize the noise and give a more granular control to the user
                var shouldTrack = Array.isArray(_this._options.tracingOptions.trackComponents)
                    ? _this._options.tracingOptions.trackComponents.indexOf(name) > -1
                    : _this._options.tracingOptions.trackComponents;
                if (!_this._rootSpan || !shouldTrack) {
                    return;
                }
                var now = timestampWithMs();
                var span = spans[operation];
                // On the first handler call (before), it'll be undefined, as `$once` will add it in the future.
                // However, on the second call (after), it'll be already in place.
                if (span) {
                    span.finish();
                    _this._finishRootSpan(now, getCurrentHub);
                }
                else {
                    vm.$once("hook:" + hook, function () {
                        if (_this._rootSpan) {
                            spans[operation] = _this._rootSpan.startChild({
                                description: "Vue <" + name + ">",
                                op: operation,
                            });
                        }
                    });
                }
            };
            // Each component has it's own scope, so all activities are only related to one of them
            _this._options.tracingOptions.hooks.forEach(function (operation) {
                // Retrieve corresponding hooks from Vue lifecycle.
                // eg. mount => ['beforeMount', 'mounted']
                var internalHooks = HOOKS[operation];
                if (!internalHooks) {
                    logger.warn("Unknown hook: " + operation);
                    return;
                }
                internalHooks.forEach(function (internalHook) {
                    var handler = rootMount
                        ? rootHandler.bind(_this, internalHook)
                        : childHandler.bind(_this, internalHook, operation);
                    var currentValue = vm.$options[internalHook];
                    if (Array.isArray(currentValue)) {
                        vm.$options[internalHook] = __spread([handler], currentValue);
                    }
                    else if (typeof currentValue === 'function') {
                        vm.$options[internalHook] = [handler, currentValue];
                    }
                    else {
                        vm.$options[internalHook] = [handler];
                    }
                });
            });
        };
        this._options = __assign(__assign({ 
            // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
            Vue: getGlobalObject().Vue, attachProps: true, logErrors: false, tracing: false }, options), { tracingOptions: __assign({ hooks: ['mount', 'update'], timeout: 2000, trackComponents: false }, options.tracingOptions) });
    }
    /**
     * @inheritDoc
     */
    Vue.prototype.setupOnce = function (_, getCurrentHub) {
        if (!this._options.Vue) {
            logger.error('Vue integration is missing a Vue instance');
            return;
        }
        this._attachErrorHandler(getCurrentHub);
        if (this._options.tracing) {
            this._startTracing(getCurrentHub);
        }
    };
    /**
     * Extract component name from the ViewModel
     */
    Vue.prototype._getComponentName = function (vm) {
        // Such level of granularity is most likely not necessary, but better safe than sorry. — Kamil
        if (!vm) {
            return ANONYMOUS_COMPONENT_NAME;
        }
        if (vm.$root === vm) {
            return ROOT_COMPONENT_NAME;
        }
        if (!vm.$options) {
            return ANONYMOUS_COMPONENT_NAME;
        }
        if (vm.$options.name) {
            return vm.$options.name;
        }
        if (vm.$options._componentTag) {
            return vm.$options._componentTag;
        }
        // injected by vue-loader
        if (vm.$options.__file) {
            var unifiedFile = vm.$options.__file.replace(/^[a-zA-Z]:/, '').replace(/\\/g, '/');
            var filename = basename(unifiedFile, '.vue');
            return (this._componentsCache[filename] ||
                (this._componentsCache[filename] = filename.replace(COMPONENT_NAME_REGEXP, function (_, c) {
                    return c ? c.toUpperCase() : '';
                })));
        }
        return ANONYMOUS_COMPONENT_NAME;
    };
    /** Finish top-level span and activity with a debounce configured using `timeout` option */
    Vue.prototype._finishRootSpan = function (timestamp, getCurrentHub) {
        var _this = this;
        if (this._rootSpanTimer) {
            clearTimeout(this._rootSpanTimer);
        }
        this._rootSpanTimer = setTimeout(function () {
            if (_this._tracingActivity) {
                // We do this whole dance with `TRACING_GETTER` to prevent `@sentry/apm` from becoming a peerDependency.
                // We also need to ask for the `.constructor`, as `pushActivity` and `popActivity` are static, not instance methods.
                // eslint-disable-next-line deprecation/deprecation
                var tracingIntegration = getCurrentHub().getIntegration(TRACING_GETTER);
                if (tracingIntegration) {
                    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
                    tracingIntegration.constructor.popActivity(_this._tracingActivity);
                }
            }
            // We should always finish the span, only should pop activity if using @sentry/apm
            if (_this._rootSpan) {
                _this._rootSpan.finish(timestamp);
            }
        }, this._options.tracingOptions.timeout);
    };
    /** Inject configured tracing hooks into Vue's component lifecycles */
    Vue.prototype._startTracing = function (getCurrentHub) {
        var applyTracingHooks = this._applyTracingHooks;
        this._options.Vue.mixin({
            beforeCreate: function () {
                // eslint-disable-next-line deprecation/deprecation
                if (getCurrentHub().getIntegration(TRACING_GETTER) || getCurrentHub().getIntegration(BROWSER_TRACING_GETTER)) {
                    // `this` points to currently rendered component
                    applyTracingHooks(this, getCurrentHub);
                }
                else {
                    logger.error('Vue integration has tracing enabled, but Tracing integration is not configured');
                }
            },
        });
    };
    /** Inject Sentry's handler into owns Vue's error handler  */
    Vue.prototype._attachErrorHandler = function (getCurrentHub) {
        var _this = this;
        // eslint-disable-next-line @typescript-eslint/unbound-method
        var currentErrorHandler = this._options.Vue.config.errorHandler;
        this._options.Vue.config.errorHandler = function (error, vm, info) {
            var metadata = {};
            if (vm) {
                try {
                    metadata.componentName = _this._getComponentName(vm);
                    if (_this._options.attachProps) {
                        metadata.propsData = vm.$options.propsData;
                    }
                }
                catch (_oO) {
                    logger.warn('Unable to extract metadata from Vue component.');
                }
            }
            if (info) {
                metadata.lifecycleHook = info;
            }
            if (getCurrentHub().getIntegration(Vue)) {
                // Capture exception in the next event loop, to make sure that all breadcrumbs are recorded in time.
                setTimeout(function () {
                    getCurrentHub().withScope(function (scope) {
                        scope.setContext('vue', metadata);
                        getCurrentHub().captureException(error);
                    });
                });
            }
            if (typeof currentErrorHandler === 'function') {
                currentErrorHandler.call(_this._options.Vue, error, vm, info);
            }
            if (_this._options.logErrors) {
                if (_this._options.Vue.util) {
                    _this._options.Vue.util.warn("Error in " + info + ": \"" + error.toString() + "\"", vm);
                }
                // eslint-disable-next-line no-console
                console.error(error);
            }
        };
    };
    /**
     * @inheritDoc
     */
    Vue.id = 'Vue';
    return Vue;
}());
export { Vue };
/** Grabs active transaction off scope */
export function getActiveTransaction(hub) {
    if (hub && hub.getScope) {
        var scope = hub.getScope();
        if (scope) {
            return scope.getTransaction();
        }
    }
    return undefined;
}
//# sourceMappingURL=vue.js.map