/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */
import { EnvironmentInjector } from '../../di/r3_injector';
import { throwError } from '../../util/assert';
import { getComponentDef } from '../definition';
import { getNodeInjectorLView, NodeInjector } from '../di';
import { setInjectorProfiler } from './injector_profiler';
/**
 * These are the data structures that our framework injector profiler will fill with data in order
 * to support DI debugging APIs.
 *
 * resolverToTokenToDependencies: Maps an injector to a Map of tokens to an Array of
 * dependencies. Injector -> Token -> Dependencies This is used to support the
 * getDependenciesFromInjectable API, which takes in an injector and a token and returns it's
 * dependencies.
 *
 * resolverToProviders: Maps a DI resolver (an Injector or an LView) to the providers configured
 * within it This is used to support the getInjectorProviders API, which takes in an injector and
 * returns the providers that it was configured with.
 *
 * standaloneInjectorToComponent: Maps the injector of a standalone component to the standalone
 * component that it is associated with. Used in the getInjectorProviders API, specificially in the
 * discovery of import paths for each provider. This is necessary because the imports array of a
 * standalone component is processed and configured in its standalone injector, but exists within
 * the component's definition. Because getInjectorProviders takes in an injector, if that injector
 * is the injector of a standalone component, we need to be able to discover the place where the
 * imports array is located (the component) in order to flatten the imports array within it to
 * discover all of it's providers.
 *
 *
 * All of these data structures are instantiated with WeakMaps. This will ensure that the presence
 * of any object in the keys of these maps does not prevent the garbage collector from collecting
 * those objects. Because of this property of WeakMaps, these data structures will never be the
 * source of a memory leak.
 *
 * An example of this advantage: When components are destroyed, we don't need to do
 * any additional work to remove that component from our mappings.
 *
 */
class DIDebugData {
    constructor() {
        this.resolverToTokenToDependencies = new WeakMap();
        this.resolverToProviders = new WeakMap();
        this.standaloneInjectorToComponent = new WeakMap();
    }
    reset() {
        this.resolverToTokenToDependencies =
            new WeakMap();
        this.resolverToProviders = new WeakMap();
        this.standaloneInjectorToComponent = new WeakMap();
    }
}
let frameworkDIDebugData = new DIDebugData();
export function getFrameworkDIDebugData() {
    return frameworkDIDebugData;
}
/**
 * Initalize default handling of injector events. This handling parses events
 * as they are emitted and constructs the data structures necessary to support
 * some of debug APIs.
 *
 * See handleInjectEvent, handleCreateEvent and handleProviderConfiguredEvent
 * for descriptions of each handler
 *
 * Supported APIs:
 *               - getDependenciesFromInjectable
 *               - getInjectorProviders
 */
export function setupFrameworkInjectorProfiler() {
    frameworkDIDebugData.reset();
    setInjectorProfiler((injectorProfilerEvent) => handleInjectorProfilerEvent(injectorProfilerEvent));
}
function handleInjectorProfilerEvent(injectorProfilerEvent) {
    const { context, type } = injectorProfilerEvent;
    if (type === 0 /* InjectorProfilerEventType.Inject */) {
        handleInjectEvent(context, injectorProfilerEvent.service);
    }
    else if (type === 1 /* InjectorProfilerEventType.InstanceCreatedByInjector */) {
        handleInstanceCreatedByInjectorEvent(context, injectorProfilerEvent.instance);
    }
    else if (type === 2 /* InjectorProfilerEventType.ProviderConfigured */) {
        handleProviderConfiguredEvent(context, injectorProfilerEvent.providerRecord);
    }
}
/**
 *
 * Stores the injected service in frameworkDIDebugData.resolverToTokenToDependencies
 * based on it's injector and token.
 *
 * @param context InjectorProfilerContext the injection context that this event occurred in.
 * @param data InjectedService the service associated with this inject event.
 *
 */
function handleInjectEvent(context, data) {
    const diResolver = getDIResolver(context.injector);
    if (diResolver === null) {
        throwError('An Inject event must be run within an injection context.');
    }
    const diResolverToInstantiatedToken = frameworkDIDebugData.resolverToTokenToDependencies;
    if (!diResolverToInstantiatedToken.has(diResolver)) {
        diResolverToInstantiatedToken.set(diResolver, new WeakMap());
    }
    // if token is a primitive type, ignore this event. We do this because we cannot keep track of
    // non-primitive tokens in WeakMaps since they are not garbage collectable.
    if (!canBeHeldWeakly(context.token)) {
        return;
    }
    const instantiatedTokenToDependencies = diResolverToInstantiatedToken.get(diResolver);
    if (!instantiatedTokenToDependencies.has(context.token)) {
        instantiatedTokenToDependencies.set(context.token, []);
    }
    const { token, value, flags } = data;
    instantiatedTokenToDependencies.get(context.token).push({ token, value, flags });
}
/**
 *
 * If the created instance is an instance of a standalone component, maps the injector to that
 * standalone component in frameworkDIDebugData.standaloneInjectorToComponent
 *
 * @param context InjectorProfilerContext the injection context that this event occurred in.
 * @param data InjectorCreatedInstance an object containing the instance that was just created
 *
 */
function handleInstanceCreatedByInjectorEvent(context, data) {
    const { value } = data;
    if (getDIResolver(context.injector) === null) {
        throwError('An InjectorCreatedInstance event must be run within an injection context.');
    }
    // if our value is an instance of a standalone component, map the injector of that standalone
    // component to the component class. Otherwise, this event is a noop.
    let standaloneComponent = undefined;
    if (typeof value === 'object') {
        standaloneComponent = value?.constructor;
    }
    if (standaloneComponent === undefined || !isStandaloneComponent(standaloneComponent)) {
        return;
    }
    const environmentInjector = context.injector.get(EnvironmentInjector, null, { optional: true });
    // Standalone components should have an environment injector. If one cannot be
    // found we may be in a test case for low level functionality that did not explictly
    // setup this injector. In those cases, we simply ignore this event.
    if (environmentInjector === null) {
        return;
    }
    const { standaloneInjectorToComponent } = frameworkDIDebugData;
    // If our injector has already been mapped, as is the case
    // when a standalone component imports another standalone component,
    // we consider the original component (the component doing the importing)
    // as the component connected to our injector.
    if (standaloneInjectorToComponent.has(environmentInjector)) {
        return;
    }
    // If our injector hasn't been mapped, then we map it to the standalone component
    standaloneInjectorToComponent.set(environmentInjector, standaloneComponent);
}
function isStandaloneComponent(value) {
    const def = getComponentDef(value);
    return !!def?.standalone;
}
/**
 *
 * Stores the emitted ProviderRecords from the InjectorProfilerEventType.ProviderConfigured
 * event in frameworkDIDebugData.resolverToProviders
 *
 * @param context InjectorProfilerContext the injection context that this event occurred in.
 * @param data ProviderRecord an object containing the instance that was just created
 *
 */
function handleProviderConfiguredEvent(context, data) {
    const { resolverToProviders } = frameworkDIDebugData;
    const diResolver = getDIResolver(context?.injector);
    if (diResolver === null) {
        throwError('A ProviderConfigured event must be run within an injection context.');
    }
    if (!resolverToProviders.has(diResolver)) {
        resolverToProviders.set(diResolver, []);
    }
    resolverToProviders.get(diResolver).push(data);
}
function getDIResolver(injector) {
    let diResolver = null;
    if (injector === undefined) {
        return diResolver;
    }
    // We use the LView as the diResolver for NodeInjectors because they
    // do not persist anywhere in the framework. They are simply wrappers around an LView and a TNode
    // that do persist. Because of this, we rely on the LView of the NodeInjector in order to use
    // as a concrete key to represent this injector. If we get the same LView back later, we know
    // we're looking at the same injector.
    if (injector instanceof NodeInjector) {
        diResolver = getNodeInjectorLView(injector);
    }
    // Other injectors can be used a keys for a map because their instances
    // persist
    else {
        diResolver = injector;
    }
    return diResolver;
}
// inspired by
// https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-canbeheldweakly
function canBeHeldWeakly(value) {
    // we check for value !== null here because typeof null === 'object
    return value !== null &&
        (typeof value === 'object' || typeof value === 'function' || typeof value === 'symbol');
}
//# sourceMappingURL=data:application/json;base64,[encoded data, 19484 characters]