import { Logger, ThinEngine, Observable } from './loader/babylonjs-import';
import { buildInfo } from './BuildInfo';
import { StopWatch } from './loader/stopwatch';
/**
 * Represents a telemetry event that can be use to track events.
 */
export class TelemetryEvent {
    name;
    _client;
    _sw = new StopWatch();
    /**
     * Creates a new instance of TelemetryEvent.
     * @param name The name of the telemetry event.
     * @param _client The telemetry client used to track the event.
     */
    constructor(name, _client) {
        this.name = name;
        this._client = _client;
        this._sw.resetAndStart();
    }
    /**
     * Stops the telemetry event and tracks it with the specified custom properties.
     * Also adds a duration in ms to the logged event properties.
     * @param customProperties Optional custom properties to be associated with the event.
     */
    stop(customProperties) {
        this._sw.stop();
        const durationMs = this._sw.totalElapsed;
        this._client.trackEvent({ name: this.name }, { durationMs, ...customProperties });
    }
}
/**
 * Logs telemetry to the console.
 */
class ConsoleTelemetryClient {
    _logLevel = new Map([
        [0, console.log], // Verbose = 0
        [1, console.info], // Information = 1
        [2, console.warn], // Warn = 2
        [3, console.error], // Error = 3
        [4, console.error] // Critical = 4
    ]);
    setAuthenticatedUserContext(authenticatedUserId, accountId, storeInCookie) {
        this.consoleWrite(1)('setAuthenticatedUserContext:', { userId: authenticatedUserId, customer: accountId });
    }
    trackEvent(event, customProperties) {
        const { name, ...rest } = event;
        this.consoleWrite(1)('event:', name, ...this.filterLoggable(rest, customProperties));
    }
    trackException(exceptionTelemetry, customProperties) {
        const { severityLevel, exception, error, id, ...rest } = exceptionTelemetry;
        this.consoleWrite(severityLevel ?? 3)('exception:', ...this.filterLoggable(id, exception ?? error, rest, customProperties));
    }
    trackTrace(trace, customProperties) {
        const { severityLevel, message, ...rest } = trace;
        this.consoleWrite(severityLevel ?? 0)(message, ...this.filterLoggable(rest, customProperties));
    }
    trackMetric(metric, customProperties) {
        const { name, ...rest } = metric;
        this.consoleWrite(1)('metric:', name, ...this.filterLoggable(rest, customProperties));
    }
    consoleWrite(severityLevel) {
        return this._logLevel.get(severityLevel) ?? console.log;
    }
    filterLoggable(...data) {
        return data.filter((c) => this.isLoggable(c));
    }
    isLoggable(value) {
        const notLoggable = value === undefined || value === null || (Object.keys(value).length === 0 && value.constructor === Object);
        return !notLoggable;
    }
}
const consoleTelemetryClientName = 'console';
const applicationInsightsTelemetryClientName = 'appInsights';
/**
 * Used to combine a set of {@link TwinfinityTelemetryClient} instances and
 * have calls to the trackXYZ methods, on this instance, forwarded to them.
 */
export class CompoundTelemetryClient {
    _clients = new Map();
    static _babylonJSRawLogs = CompoundTelemetryClient.createRawBabylonJSLogsObservable();
    static _anonymousUserId = 'anonymous-63137';
    static _noAccountId = 'not-available';
    _userId = CompoundTelemetryClient._anonymousUserId;
    _accountId = CompoundTelemetryClient._noAccountId;
    /** Optional Role name that application insights will report. A good idea is to use application name for this. */
    role;
    /** AccountId that application insights will report. In twinfinity its a good idea to set it to the
     * customer hostname.
     */
    get accountId() {
        return this._accountId;
    }
    /** User id that application insights will report. In twinfinity its a good idea to set this to the
     * logged in user.
     */
    get userId() {
        return this._userId;
    }
    /**
     * Constructor. While possible to use one should probably use {@link createDefault} instead.
     */
    constructor() {
        // Forward all (raw) babylonjs logs to all TelemetryClients that are not explicitly
        // configured to not get them (typically ConsoleTelemetryClient does not want them
        // since babylonjs logger already writes to console. No reason to output it to console
        // twice)
        CompoundTelemetryClient._babylonJSRawLogs.add((ed) => {
            for (const { client, disableBabylonJSLoggerForwarding } of this._clients.values()) {
                if (!disableBabylonJSLoggerForwarding) {
                    // TODO Add babylonjs property so we can see that log came from babylonjs
                    client.trackTrace(ed);
                }
            }
        });
    }
    /**
     * Set the authenticated user id and the account id. Used for identifying a specific signed-in user. Parameters must not contain whitespace or ,;=|
     *
     * The method will only set the `authenticatedUserId` and `accountId` in the current page view. To set them for the whole session, you should set `storeInCookie = true`
     * @param {string} authenticatedUserId
     * @param {string} [accountId]
     * @param {boolean} [storeInCookie=false]
     */
    setAuthenticatedUserContext(authenticatedUserId, accountId, storeInCookie) {
        const clean = (str) => {
            return str.replace(/[\s,;=|\\]+/gm, '_').toLowerCase();
        };
        this._userId = authenticatedUserId ?? CompoundTelemetryClient._anonymousUserId;
        this._accountId = accountId ?? CompoundTelemetryClient._noAccountId;
        for (const { client } of this._clients.values()) {
            client.setAuthenticatedUserContext(clean(this._userId), clean(this.accountId), storeInCookie);
        }
    }
    /**
     * Creates the default {@link CompoundTelemetryClient} instance.
     * It will log to console by default and if it detects a application insights
     * instance in the page it will use that as well.
     */
    static createDefault() {
        const telemetry = new CompoundTelemetryClient();
        // Always log to console by default.
        telemetry.isConsoleLoggingEnabled = true;
        if (applicationInsightsTelemetryClientName in window) {
            // If application insights is detected in the page then use it for logging by default too,.
            const appInsightsClient = window.appInsights;
            telemetry.set({ name: applicationInsightsTelemetryClientName, client: appInsightsClient });
        }
        return telemetry;
    }
    /**
     * `true` if console logging is enabled, otherwise `false`.
     */
    get isConsoleLoggingEnabled() {
        return this._clients.has(consoleTelemetryClientName);
    }
    /**
     * Set to `true` to enable console logging. `false` to disable.
     */
    set isConsoleLoggingEnabled(v) {
        if (v) {
            this.set({
                name: consoleTelemetryClientName,
                client: new ConsoleTelemetryClient(),
                disableBabylonJSLoggerForwarding: true
            });
        }
        else {
            this._clients.delete(consoleTelemetryClientName);
        }
    }
    /**
     * Iterator for all registered {@link TwinfinityTelemetryClientConfig}.
     * See {@link set} and {@link delete} as well.
     * @returns Iterator for all registered {@link TwinfinityTelemetryClientConfig}.
     */
    entries() {
        return this._clients.values();
    }
    /**
     * Register a {@link TwinfinityTelemetryClientConfig} instance. Future calls to trackXYZ methods
     * will be forwarded to the client property of this instance.
     * @param o a {@link TwinfinityTelemetryClientConfig}.
     */
    set(o) {
        this._clients.set(o.name, o);
        if (this.isApplicationInsightsClient(o.client)) {
            // If we add a application insights client then add extra
            // data to each telemetry item that is sent.
            o.client.addTelemetryInitializer((i) => {
                if (!i.data)
                    i.data = {};
                if (!i.data.twinfinity)
                    i.data.twinfinity = {};
                i.data.twinfinity.buildInfo = buildInfo;
                if (!i.data.babylonjs)
                    i.data.babylonjs = {};
                i.data.babylonjs.version = ThinEngine.Version;
                if (this.role)
                    i.tags['ai.cloud.role'] = this.role;
            });
        }
    }
    /**
     * Unregister a {@link TwinfinityTelemetryClientConfig}. Calls to the trackXYZ methods will no longer be forwarded to that
     * instance.
     * @param name Name of {@link TwinfinityTelemetryClientConfig} to delete.
     * @returns `true` if {@link TwinfinityTelemetryClientConfig} existed (and hence was deleted), otherwise `false`
     */
    delete(name) {
        return this._clients.delete(name);
    }
    /** Track an event. See Application insights documentation for more information */
    trackEvent(event, customProperties) {
        for (const { client } of this._clients.values())
            client.trackEvent(event, customProperties);
    }
    /**
     * Starts tracking an event with the given name.
     * @param name - The name of the event to track.
     */
    startTrackEvent(name) {
        return new TelemetryEvent(name, this);
    }
    /** Track an exception. See Application insights documentation for more information */
    trackException(exception, customProperties) {
        for (const { client } of this._clients.values())
            client.trackException(exception, customProperties);
    }
    /** Track a trace. See Application insights documentation for more information */
    trackTrace(trace, customProperties) {
        for (const { client } of this._clients.values())
            client.trackTrace(trace, customProperties);
    }
    /** Track a metric. See Application insights documentation for more information */
    trackMetric(metric, customProperties) {
        for (const { client } of this._clients.values())
            client.trackMetric(metric, customProperties);
    }
    static createRawBabylonJSLogsObservable() {
        const bjsLogger = Logger;
        const observable = new Observable();
        const LOG_LEVELS = 'LogLevels';
        const propertyDescriptor = Object.getOwnPropertyDescriptor(bjsLogger, LOG_LEVELS);
        const bjsLogLevelsSetter = propertyDescriptor.set.bind(bjsLogger);
        const LOG = 'Log';
        const WARN = 'Warn';
        const ERROR = 'Error';
        const ogLoggers = new Map();
        ogLoggers.set(LOG, {
            severityLevel: 0,
            logFunction: Logger.Log
        });
        ogLoggers.set(WARN, {
            severityLevel: 2,
            logFunction: Logger.Warn
        });
        ogLoggers.set(ERROR, {
            severityLevel: 3,
            logFunction: Logger.Error
        });
        const reAssignLogFunctions = () => {
            const logFunctionNames = [LOG, WARN, ERROR];
            logFunctionNames.forEach((logFunctionName) => {
                bjsLogger[logFunctionName] = (message, limit) => {
                    const ogLogger = ogLoggers.get(logFunctionName);
                    if (ogLogger) {
                        ogLogger.logFunction(message, limit);
                        observable.notifyObservers({ message, severityLevel: ogLogger.severityLevel });
                    }
                };
            });
        };
        reAssignLogFunctions();
        if (bjsLogLevelsSetter) {
            Object.defineProperty(Logger, LOG_LEVELS, {
                set: function (level) {
                    bjsLogLevelsSetter(level);
                    reAssignLogFunctions();
                }
            });
        }
        else {
            throw new Error('No setter for the log levels?');
        }
        // Ensures that the above hooks are applied. (Otherwise Logger.Log, etc will refer to old
        // Logger._LogEnabled etc functions, Ie not the ones we just created above.).
        Logger.LogLevels = Logger.AllLogLevel;
        return observable;
    }
    isApplicationInsightsClient(client) {
        if ('addTelemetryInitializer' in client) {
            return true;
        }
        return false;
    }
}
/**
 * Use for telemetry logging. Use this instead of console.log and similiar. It is used internally by
 * the Twinfinity Client API.
 * By default logs are output to console. If application insights is detected in the page,
 * when `telemetry` is first accessed, then it will also automatically log to application insights.
 *
 * Simplest way to forward logs to application insights is to use the "Snippet based setup"
 * from https://docs.microsoft.com/en-us/azure/azure-monitor/app/javascript.
 * Ensure that the snippet comes before all other scripts in the page.
 *
 * @example
 * ```typescript
 * // Log a warning.
 * telemetry.traceTrace({message: "log message", severityLevel: 2});
 * ```
 * @example
 * ```typescript
 * // Explicitly register existing application insights instance. See "npm based setup" at
 * // https://docs.microsoft.com/en-us/azure/azure-monitor/app/javascript for how to get the instance.
 * telemetry.set('myApplicationInsights', applicationInsightsInstance);
 * telemetry.traceTrace({message: "log message" }); // Log a debug log. Same output as console.log
 * ```
 * @example
 * ```typescript
 * // Explicitly register custom telemetryclient
 * class CustomClient implements TwinfinityTelemetryClient { ... }
 * telemetry.set('customclient', new CustomClient());
 * telemetry.isConsoleLoggingEnabled = false; // Disable console logging
 * // Log a debug log. Will be forwarded to the custom client. If
 * // application insights was registered in the page then the log will be sent
 * // there too.
 * telemetry.traceTrace({message: "log message" });
 * ```
 */
export const telemetry = CompoundTelemetryClient.createDefault();
//# sourceMappingURL=Telemetry.js.map