/*!
 * Copyright 2017 - 2020 by ChartIQ, Inc.
 * All rights reserved.
 */
/**
 * The Logger Client supports very efficient and configurable run-time logging to the <a href="/docs/troubleshooting/CentralLogger">Central Logger</a>.
 * Logging has a small performance overhead, so developers can liberally instrument their code with log messages for debugging and diagnostics.
 * By default, only error and warning messages are captured by the Logger, with the other message types (e.g., log, info, debug) disabled.
 * Which message types are enabled or disabled is fully controlled from the <a href="/docs/troubleshooting/CentralLogger">Central Logger</a> - this means developers can fully instrument their code once and dynamically enable and disable logging later, as needed, for debugging or field support.
 *
 * The Finsemble team uses the Central Logger to <a href="/docs/troubleshooting/troubleshooting">capture log message for field support</a>.
 * Finsemble customers, building their own Finsemble applications, have the option to do the same.
 *
 * :::note
 * The Logger Client **wraps** all console logging (e.g., `console.error`, `console.log`) so these message can also be captured and viewed in the Central Logger. Console logging is never disabled locally. For better performance, we recommend most of your code's instrumentation be based on the Logger Client (e.g., `FSBL.Clients.Logger.debug(...)` instead of the `console.debug(...)`).
 * :::
 *
 * Using the Logger is similar to using the browser's console for logging (e.g., `console.error` or `console.log`), although the Logger Client is accessed through the FSBL object as shown in the examples below.
 *
 * ```javascript
 * 			FSBL.Clients.Logger.error("an error message", anErrorObject);
 * 			FSBL.Clients.Logger.warn("a warning message", object1, object2, object3);
 * 			FSBL.Clients.Logger.log("logging message");
 * 			FSBL.Clients.Logger.info("logging message");
 * 			FSBL.Clients.Logger.log("log message");
 * 			FSBL.Clients.Logger.debug("debug message");
 * ```
 * @module Logger
 */
import { System } from "../common/system";
import { Startup } from "./Startup/Startup";
import { Performance } from "../common/Performance";
import RouterClient from "./routerClient";
import ConfigClient from "./configClient";
import { singleton } from "../common/Singleton";
import throttle from "lodash/throttle";
import { cloneDeep } from "lodash";
import { Globals } from "../common/Globals";
import SystemManagerClient from "../common/systemManagerClient";
// capture "everything" at startup; will be filtered later as needed when LoggerService's registration response provides settings; overhead here is not too high
const DEFAULT_LOG_SETTINGS = {
    Error: true,
    Warn: true,
    Info: true,
    Log: true,
    Debug: true,
    Verbose: false,
    LocalOnly: false,
};
/**
 * Logger.console has all-inclusive default settings because we always want to capture these
 * if the central logger hasn't yet provided current settings. This is particularly important
 * for debugging startup failures.
 */
const DEFAULT_LOG_SETTINGS_CONSOLE = {
    Error: true,
    Warn: true,
    Info: true,
    Log: true,
    Debug: true,
    Verbose: true,
    LocalOnly: false,
};
let MAX_LOG_MESSAGE_SIZE = 5000;
const OVER_LOG_SIZE_LIMIT_MESSAGE = `NOTE: LOG ARG STRIPPED since greater than ${MAX_LOG_MESSAGE_SIZE / 1000}KB. Check origin's local Console to see output of the complete object.`;
const MAX_QUEUE_SIZE = 20 * 1000; // maximum logger queue size; plenty of space except in config service; generally shouldn't need much since continuously sending to logger if working correctly;
const initialLogState = {
    console: DEFAULT_LOG_SETTINGS_CONSOLE,
    dev: DEFAULT_LOG_SETTINGS,
    system: DEFAULT_LOG_SETTINGS,
    perf: DEFAULT_LOG_SETTINGS,
};
const traceString = () => {
    function getPosition(string, subString, index) {
        return string.split(subString, index).join(subString).length;
    }
    function getErrorObject() {
        try {
            throw Error("");
        }
        catch (err) {
            return err;
        }
    }
    const { stack } = getErrorObject();
    // set number of initial lines in trace to filter out
    let traceOffset = 6;
    if (stack.includes("console.error")) {
        traceOffset++;
    }
    const position = getPosition(stack, "\n", traceOffset);
    const tString = stack.substring(position); // strip off irrelevant part of stack
    return `Log Stack: \n${tString.substr(1)}`; // insert description
};
/**
 * Roughly estimates size of object
 * @param {object} object object to estimate size for
 * @param {number} roughMaxSizeCounted specifies rough max size to count - once this size is reached can stop

 */
const getRoughSizeOfObject = (object) => {
    const RoughMaxSizeCounted = MAX_LOG_MESSAGE_SIZE + 1;
    const objectList = [];
    const stack = [object];
    let bytes = 0;
    // prevent infinite recursion, but note each array element is an attempt
    let attempts = 0;
    while (stack.length && attempts <= RoughMaxSizeCounted) {
        attempts++;
        const value = stack.pop();
        if (typeof value === "boolean") {
            bytes = bytes + 4;
        }
        else if (typeof value === "string") {
            bytes = bytes + value.length * 2;
        }
        else if (typeof value === "number") {
            bytes = bytes + 8;
        }
        else if (typeof value === "object" && objectList.indexOf(value) === -1) {
            objectList.push(value);
            for (const i in value) {
                stack.push(value[i]);
            }
        }
    }
    return bytes;
};
// Strip an argument down to a smaller size that can be logged, while retaining some data that might be useful.
// To limit complexity, this is best effort only, both for capturing useful data and for stripping to the target size.
// Stripped data is replaced with a tag to avoid ambiguity in interpreting the data.
// Note: If each object is a tree, both breadth and depth are considered to get a broader range of kept data; however, the
// the efficacy of this algorithm is data dependent.
const getStrippedSmallerObject = (objectToStrip, roughMaxSize, totalBytesUsed = 0, depth = 1) => {
    const SoftMaxTotalTargetSize = MAX_LOG_MESSAGE_SIZE / 1.5;
    const BaseMaxTargetTotalSize = roughMaxSize / 2;
    const MaxPropertySize = BaseMaxTargetTotalSize / 2;
    const StrippedMarker = "*STRIPPED*";
    const TryToKeepSize = 100;
    const KeepSize = 10;
    const KeepDepth = 3;
    const KeepBreath = 1;
    const stripArray = (array) => {
        let arraySize = getRoughSizeOfObject(array);
        if (array.length > 0 && arraySize > MaxPropertySize) {
            // assume each array is homogeneous, so only keep the first element of the array as a representative sample of data
            array[0] = getStrippedSmallerObject(array[0], BaseMaxTargetTotalSize, totalBytesUsed, depth + 1);
            // mark the array stripped
            array[1] = `${StrippedMarker} from size ${array.length}`;
            array.length = 2;
        }
    };
    let strippedObject = cloneDeep(objectToStrip);
    if (typeof strippedObject !== "object") {
        // Simply strip if not an object. Example case is AssimilationService logging a string over 100K, plus target size will grow progressively smaller as depth increases.
        strippedObject = StrippedMarker;
        totalBytesUsed = totalBytesUsed + getRoughSizeOfObject(strippedObject);
    }
    else if (Array.isArray(strippedObject)) {
        stripArray(strippedObject);
        totalBytesUsed = totalBytesUsed + getRoughSizeOfObject(strippedObject);
    }
    else {
        let propertyNames = Object.getOwnPropertyNames(strippedObject);
        // loop though each of the object properties stripping them as needed
        for (let i = 0; i < propertyNames.length; i++) {
            let roughSize = getRoughSizeOfObject(strippedObject[propertyNames[i]]);
            // If total size is getting big (BaseMaxTargetTotalSize), still try to keep all data under a specified size (TryToKeepSize), up a specified depth (KeepDepth), and a specified breath (KeepBreath)
            if (totalBytesUsed > BaseMaxTargetTotalSize &&
                roughSize > TryToKeepSize &&
                depth >= KeepDepth &&
                i >= KeepBreath) {
                strippedObject[propertyNames[i]] = StrippedMarker;
                // If total size is  too big (SoftMaxTotalTargetSize) AND the property is large enough to strip, then must radically strip
            }
            else if (totalBytesUsed > SoftMaxTotalTargetSize && roughSize >= KeepSize) {
                strippedObject[propertyNames[i]] = StrippedMarker;
                // Otherwise only strip objects that are proportionally too big (i.e. greater than MaxPropertySize)
            }
            else if (roughSize > MaxPropertySize) {
                if (Array.isArray(strippedObject[propertyNames[i]])) {
                    stripArray(strippedObject[propertyNames[i]]);
                }
                else {
                    // Recursive call where the target size gets progressively smaller as the depth increases
                    strippedObject[propertyNames[i]] = getStrippedSmallerObject(strippedObject[propertyNames[i]], roughMaxSize / 1.2, totalBytesUsed, depth + 1);
                }
            }
            totalBytesUsed = totalBytesUsed + getRoughSizeOfObject(strippedObject[propertyNames[i]]);
        }
    }
    return strippedObject;
};
/**
 * The router uses this to filter out certain channels
 */
const channelFilter = (channel) => 
// remove Finsemble.heartbeat transmits/receives for the time being, also prevent logging of log messages
channel === "logger.service.logMessages" || channel === "Finsemble.heartbeat";
/**
 * @ignore
 */
export class LoggerConnection {
    constructor() {
        this.routerClient = null;
        this.viewId = null;
        this.isRegistering = false; // if registering start
        this.isRegistered = false; // if registering complete
        this.calibratedTimeStampOffset = 0;
        this.newCalibratedTimeStampOffset = 0;
        this.filteredMessagesCounter = 0;
        this.startupTime = 0;
        this.loggerQueue = [];
        this.loggerClientName = System.Window.getCurrent().name;
        this.onLogStateChangeCallback = [];
        // Will be updated on registration with Central Logger, but capture everything until then.
        this.currentLogState = initialLogState;
        this.skipLargeObjects = true;
        this.setRouterClient = (routerClient) => {
            this.routerClient = routerClient;
        };
        this.system = {
            error: (...args) => {
                this.logIt("system", "Error", args);
            },
            warn: (...args) => {
                this.logIt("system", "Warn", args);
            },
            info: (...args) => {
                this.logIt("system", "Info", args);
            },
            log: (...args) => {
                this.logIt("system", "Log", args);
            },
            debug: (...args) => {
                this.logIt("system", "Debug", args);
            },
            verbose: (...args) => {
                this.logIt("system", "Verbose", args);
            },
            deprecated: (...args) => {
                this.logIt("system", "Error", args);
            },
        };
        this.perf = {
            error: (...args) => {
                this.logIt("perf", "Error", args);
            },
            warn: (...args) => {
                this.logIt("perf", "Warn", args);
            },
            info: (...args) => {
                this.logIt("perf", "Info", args);
            },
            log: (...args) => {
                this.logIt("perf", "Log", args);
            },
            debug: (...args) => {
                this.logIt("perf", "Debug", args);
            },
            verbose: (...args) => {
                this.logIt("perf", "Verbose", args);
            },
            deprecated: (...args) => {
                this.logIt("perf", "Error", args);
            },
        };
        this.console = {
            error: (...args) => {
                this.logIt("console", "Error", args);
            },
            warn: (...args) => {
                this.logIt("console", "Warn", args);
            },
            info: (...args) => {
                this.logIt("console", "Info", args);
            },
            log: (...args) => {
                this.logIt("console", "Log", args);
            },
            debug: (...args) => {
                this.logIt("console", "Debug", args);
            },
            verbose: (...args) => {
                this.logIt("console", "Verbose", args);
            },
            deprecated: (...args) => {
                this.logIt("console", "Error", args);
            },
        };
        const transmitAndClearQueue = () => {
            var _a;
            if (this.loggerQueue.length > 0) {
                (_a = this.routerClient) === null || _a === void 0 ? void 0 : _a.transmit("logger.service.logMessages", this.loggerQueue);
                this.loggerQueue = [];
            }
        };
        // HERE is the interval for transmitting queued messages to the logger service
        // An earlier interval for non-routerService windows was 250.  This resulted in very large (e.g. 12MB) messages that contributes to overall delays and higher peak memory usage.
        // However, making the interval small enough (e.g. 20) to ensure small messages increased the traffic too much. So a value of 125 appears to be a reasonable balance.
        const ms = this.loggerClientName === "routerService" ? 100 : 125;
        this.transmitAndClearQueue = throttle(transmitAndClearQueue, ms, {
            leading: false,
        });
    }
    // if log state changes then update queue based on that data (e.g. if no longer logging debug messages, then remove them from the queue)
    updateQueueBasedOnState(calibrateTimeFlag) {
        const { loggerQueue, currentLogState, calibratedTimeStampOffset } = this;
        this.system.debug("Logger updateQueueBasedOnState", calibrateTimeFlag, calibratedTimeStampOffset, "QUEUE LENGTH", loggerQueue.length, currentLogState);
        this.loggerQueue = loggerQueue.flatMap((message) => {
            if (currentLogState[message.category][message.logType]) {
                if (!currentLogState[message.category].LocalOnly) {
                    if (calibrateTimeFlag) {
                        message.logTimestamp = message.logTimestamp + calibratedTimeStampOffset; // if flag set then timestamp hasn't been adjusted yet by calibrated offset time
                    }
                    return [message];
                }
                else {
                    // only now know LocalOnly for messages, so print those queued out otherwise they will be lost
                    globalThis.console.log(message.category, message.logType, message.logTimestamp - Performance.timing.navigationStart, message.logData, "(Previously queued!)");
                }
            }
            return [];
        });
    }
    onStateChanged(cb) {
        if (this.onLogStateChangeCallback.indexOf(cb) === -1) {
            this.onLogStateChangeCallback.push(cb);
        }
    }
    setLogState(state, calibrateTimeFlag) {
        if (state && state.dev) {
            this.currentLogState = state;
            this.onLogStateChangeCallback.forEach((callback) => {
                callback(state);
            });
        }
        this.updateQueueBasedOnState(calibrateTimeFlag);
    }
    addToQueue(message) {
        const { loggerQueue } = this;
        if (loggerQueue.length < MAX_QUEUE_SIZE) {
            loggerQueue.push(message);
        }
        else {
            globalThis.console.error("Logging Queue Overflowed! Dumping queue here then continuing", loggerQueue.length);
            let counter = 0;
            let logMessage;
            while ((logMessage = loggerQueue.shift())) {
                globalThis.console.log(`Message ${counter++}`, logMessage);
            }
            this.system.error("Logging Queue Overflowed! Dumping queue in client then continuing.");
        }
    }
    callStack() {
        return traceString();
    }
    setting() {
        return this.currentLogState;
    }
    outputToConsole(level, args) {
        var _a;
        const where = {
            Debug: globalThis.console.debug,
            Info: globalThis.console.info,
            Warn: globalThis.console.warn,
            Error: (_a = globalThis.console.unwrappedError) !== null && _a !== void 0 ? _a : globalThis.console.error,
            Log: globalThis.console.log,
            Verbose: globalThis.console.log,
        };
        // Helper to flatten the array of arguments passed in, so we can log the full message locally.
        //const flatten = (arr : any[]) => arr.reduce((flat, toFlatten) => flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten), []);
        try {
            where[level](...args);
        }
        catch (err) {
            where.Warn("Error outputting log to console", err, traceString());
        }
    }
    formatAndQueueMessage(category, level, args) {
        var _a;
        const force = args[0] === "forceObjectsToLogger";
        // Kicks out overly-large objects to prevent the loggerService from jamming up.
        // Ideally instead of not sending the message at all, we could send the first X bytes of the message.
        const filteredArgs = args.flatMap((arg) => {
            const messageSize = getRoughSizeOfObject(arg);
            if (!force && messageSize > MAX_LOG_MESSAGE_SIZE) {
                this.outputToConsole("Warn", [
                    `Message too large to send to the logger. Max message size:${MAX_LOG_MESSAGE_SIZE / 1000}KB`,
                    `Approx message size: ${messageSize / 1000}KB`,
                    ...args,
                ]);
                if (this.skipLargeObjects) {
                    return [
                        `Log argument greater than ${MAX_LOG_MESSAGE_SIZE / 1000}KB. Check local Console to see output of the object.`,
                    ];
                }
                else {
                    const strippedArg = getStrippedSmallerObject(arg, MAX_LOG_MESSAGE_SIZE);
                    const strippedSize = getRoughSizeOfObject(strippedArg);
                    // Send the stripped version of the arg as the log data. Note that the strip algorithm is best effort.
                    // In some rare cases the stripped arg might still be too big. If this happens, the algorithm gives up and doesn't send any partial arg.
                    if (strippedSize > MAX_LOG_MESSAGE_SIZE) {
                        return [
                            `LOG ARG STRIPPED but not logged here still greater than ${MAX_LOG_MESSAGE_SIZE / 1000}KB. Check local Console to see output of the object. (Tag:Logging-Drop)`,
                        ];
                    }
                    else {
                        return [
                            OVER_LOG_SIZE_LIMIT_MESSAGE,
                            `OldSize=${messageSize} (Tag:Logging-Strip) NewSize=${strippedSize}`,
                            strippedArg,
                        ];
                    }
                }
            }
            else if (arg instanceof Error) {
                // Convert arg into a string if it doesn't stringify properly. JavaScript Error class (such as thrown by an unhandled exception) doesn't stringify properly so you must coerce it to a string.
                return [arg.toString()];
            }
            else if (arg === "forceObjectsToLogger") {
                // Don't send the first argument to the router, maybe in the future we can use Logger.force.error() instead
                return [];
            }
            else {
                return [arg];
            }
        });
        let logData = null;
        try {
            logData = JSON.stringify(filteredArgs);
        }
        catch (err) {
            logData = `*** Logger error can't JSON.stringify: ${traceString()}`;
        }
        const outputToConsoleType = level === "Error" || level === "Warn" || category === "console";
        // if console type, or error or warning, then always log locally
        if (outputToConsoleType && !Globals.runningInFSBLUnitTest)
            this.outputToConsole(level, args);
        const myViewId = (_a = this.viewId) !== null && _a !== void 0 ? _a : (System.isBrowserView() && System.getWebContentsId());
        const message = {
            // Replace "console" with "system" for transmission to the central logger
            category: category === "console" ? "system" : category,
            logType: level,
            logClientName: myViewId ? `${this.loggerClientName}: view-${myViewId}` : this.loggerClientName,
            logData,
            logTimestamp: Performance.timing.navigationStart + Performance.now() + this.calibratedTimeStampOffset,
        };
        if (myViewId)
            message.viewId = myViewId;
        this.addToQueue(message);
        if (this.isRegistered)
            this.transmitAndClearQueue();
    }
    logIt(category, level, args) {
        var _a;
        if (globalThis.glue42gd) {
            // If we're using an imported Logger while running in the polyfill, then redirect
            // log messages to the polyfill logger API
            if (globalThis.FSBL) {
                (_a = globalThis.FSBL.Clients.Logger) === null || _a === void 0 ? void 0 : _a[level.toLowerCase()](...args);
            }
            return;
        }
        if (this.currentLogState[category][level]) {
            if (level === "Warn" || level === "Error") {
                args.push(traceString());
            }
            if (!this.currentLogState[category].LocalOnly) {
                this.formatAndQueueMessage(category, level, args);
            }
            else {
                const lowerCaseLevel = level.toLowerCase();
                args.unshift(`${category} ${lowerCaseLevel} (${Performance.now()}):`);
                this.outputToConsole(level, args);
            }
        }
    }
    error(...args) {
        this.logIt("dev", "Error", args);
    }
    warn(...args) {
        this.logIt("dev", "Warn", args);
    }
    info(...args) {
        this.logIt("dev", "Info", args);
    }
    log(...args) {
        this.logIt("dev", "Log", args);
    }
    debug(...args) {
        this.logIt("dev", "Debug", args);
    }
    verbose(...args) {
        this.logIt("dev", "Verbose", args);
    }
    deprecated(...args) {
        this.logIt("dev", "Error", args);
    }
    completeRegistration(loggerState) {
        ConfigClient.get(["finsemble", "servicesConfig", "logger"], (err, data) => {
            var _a, _b;
            if (!err) {
                this.skipLargeObjects = (_a = data === null || data === void 0 ? void 0 : data.skipLargeObjects) !== null && _a !== void 0 ? _a : this.skipLargeObjects;
                /**
                 * Config created to allow for easier testing
                 */
                MAX_LOG_MESSAGE_SIZE = (_b = data === null || data === void 0 ? void 0 : data.maxLogMessageSize) !== null && _b !== void 0 ? _b : MAX_LOG_MESSAGE_SIZE;
            }
        });
        // Since we don't have a viewId when early logger messages were received, we now loop through the queue and add the viewId
        this.loggerQueue.forEach((message) => {
            if (this.viewId)
                message.viewId = this.viewId;
        });
        this.isRegistered = true;
        if (loggerState) {
            if (this.loggerClientName !== "routerService") {
                this.calibratedTimeStampOffset = this.newCalibratedTimeStampOffset; // from now the real offset time will be used for all timestamps
                this.setLogState(loggerState, true); // true indicates must adjust already queued timestamps by the new offset time
            }
            else {
                // The router service doesn't need to calibrate time since it is the reference time
                this.setLogState(loggerState, false);
            }
        }
        else {
            globalThis.console.error("Logger.startLogger() Initial state not received from logger service");
        }
        this.formatAndQueueMessage("system", "Debug", ["Logger Registered"]);
        this.transmitAndClearQueue();
    }
    registerWithLoggerService() {
        var _a;
        const { windowName, webContentsName } = Startup.getInstance().getStartupData();
        // If in an iframe then append the name
        this.loggerClientName = typeof window !== "undefined" && window.top !== window ? `${windowName}.Frame` : windowName;
        const clientChannel = `finsemble.logger.client.${this.loggerClientName}`;
        //viewId may not be set when the logger is constructed. Clients aren't registered until FSBL is ready so it will definitely be set by now.
        if (!this.viewId && System.isBrowserView())
            this.viewId = System.getWebContentsId();
        this.system.debug("logger.service.registering", this.loggerClientName);
        System.Window.getCurrent().getOptions(async (options) => {
            var _a, _b;
            const centralLoggerNamePrefix = (_b = (_a = options === null || options === void 0 ? void 0 : options.customData) === null || _a === void 0 ? void 0 : _a.component) === null || _b === void 0 ? void 0 : _b.centralLoggerNamePrefix;
            if (!this.routerClient)
                return;
            const { err, response } = await this.routerClient.query("logger.service.register", {
                clientName: this.loggerClientName,
                clientChannel,
                displayName: webContentsName !== null && webContentsName !== void 0 ? webContentsName : windowName,
                windowName,
                viewId: this.viewId,
                centralLoggerNamePrefix: centralLoggerNamePrefix,
            });
            if (err) {
                globalThis.console.warn("Logger service not available. Trying to connect again in 750ms.", err);
                // for some very early clients the logger may not be ready yet, so retry after a small wait
                setTimeout(() => {
                    this.registerWithLoggerService();
                }, 750);
            }
            else {
                this.completeRegistration(response.data);
            }
        });
        (_a = this.routerClient) === null || _a === void 0 ? void 0 : _a.addListener(clientChannel, (error, message) => {
            this.system.debug("logger.client.setLogState", message.data);
            this.setLogState(message.data, false);
        });
    }
    unregisterClient(params, cb) {
        var _a;
        this.system.debug("logger.service.unregister", this.loggerClientName, params, this.viewId);
        this.transmitAndClearQueue(); // send any message currently in the log queue
        const unregisterParams = params || {
            clientName: this.loggerClientName,
            viewId: this.viewId,
        };
        (_a = this.routerClient) === null || _a === void 0 ? void 0 : _a.query("logger.service.unregister", unregisterParams, cb);
    }
    async waitForServiceAndCalibrate() {
        var _a;
        await SystemManagerClient.waitForStartup("loggerService");
        this.system.debug("Logger onReady", this.loggerClientName);
        // You must calibrate the timer so that the messages are correctly sorted in the central logger;
        // this is necessary because there is timer drift between windows --- this appears to be a Chromium
        // bug we have to work around it.  The timeOffset value adjusts the time using the routerService's
        // time as a central reference point.
        (_a = this.routerClient) === null || _a === void 0 ? void 0 : _a.calibrateTimeWithRouterService((timeOffset) => {
            this.newCalibratedTimeStampOffset = timeOffset;
            if (!this.isRegistering) {
                this.isRegistering = true;
                this.startupTime = performance.now() - this.startupTime;
                this.registerWithLoggerService();
            }
        });
    }
    async start(routerClient) {
        this.routerClient = routerClient !== null && routerClient !== void 0 ? routerClient : RouterClient;
        this.startupTime = performance.now();
        await this.waitForServiceAndCalibrate();
        // Unregister browser views as they are removed
        if (System.isBrowserView() && Globals.addEventListener) {
            Globals.addEventListener("beforeunload", () => {
                this.unregisterClient();
            });
        }
    }
}
/**
 * @language javascript, java, dotnet
 * Log a dev error message.
 *
 * ```javascript
 * FSBL.Clients.Logger.error("some message", parm1, parm2);
 * ```
 * @param {any} message parameter of any type that can be stringified (e.g. string, object)
 */
export const error = (...args) => {
    const loggerConnection = singleton("LoggerClient", LoggerConnection);
    return loggerConnection.error(...args);
};
/**
 * @language javascript, java, dotnet
 * Log a dev warning message.
 *
 *
 * ```javascript
 * FSBL.Clients.Logger.warn("some message", parm1, parm2);
 * ```
 * @param {any} message parameter of any type that can be stringified (e.g. string, object)
 */
export const warn = (...args) => {
    const loggerConnection = singleton("LoggerClient", LoggerConnection);
    return loggerConnection.warn(...args);
};
/**
 * @language javascript, java, dotnet
 * Log a dev info message.
 *
 * ```javascript
 * FSBL.Clients.Logger.info("some message", parm1, parm2);
 * ```
 * @param {any} message parameter of any type that can be stringified (e.g. string, object)
 */
export const info = (...args) => {
    const loggerConnection = singleton("LoggerClient", LoggerConnection);
    return loggerConnection.info(...args);
};
/**
 * @language javascript, java, dotnet
 * Log a dev log message.
 *
 * ```javascript
 * FSBL.Clients.Logger.log("some message", parm1, parm2);
 * ```
 * @param {any} message parameter of any type that can be stringified (e.g. string, object)
 */
export const log = (...args) => {
    const loggerConnection = singleton("LoggerClient", LoggerConnection);
    return loggerConnection.log(...args);
};
/**
 * @language javascript, java, dotnet
 * Log a dev debug message.
 *
 * ```javascript
 * FSBL.Clients.Logger.debug("some message", parm1, parm2);
 * ```
 * @param {any} message parameter of any type that can be stringified (e.g. string, object)
 */
export const debug = (...args) => {
    const loggerConnection = singleton("LoggerClient", LoggerConnection);
    return loggerConnection.debug(...args);
};
/**
 * @language javascript, java, dotnet
 * Log a dev verbose message (an extra level of verbose-debug output).
 *
 * ```javascript
 * FSBL.Clients.Logger.verbose("some message", parm1, parm2);
 * ```
 * @param {any} message parameter of any type that can be stringified (e.g. string, object)
 */
export const verbose = (...args) => {
    const loggerConnection = singleton("LoggerClient", LoggerConnection);
    return loggerConnection.verbose(...args);
};
/**
 * Log the use of a deprecated API, config, component, or otherwise
 *
 * ```javascript
 * FSBL.Clients.Logger.deprecated("some message", parm1, parm2);
 * ```
 * @param {any} message parameter of any type that can be stringified (e.g. string, object)
 * @ignore
 */
export const deprecated = (...args) => {
    const loggerConnection = singleton("LoggerClient", LoggerConnection);
    return loggerConnection.deprecated(...args);
};
const CentralLoggerClient = {
    error,
    warn,
    info,
    log,
    debug,
    verbose,
    /**
     * @private
     */
    deprecated,
    /**
     * Starts the logger client. There is no need to wait for this method to finish
     * because the logger client queues messages until it connects. logger.start() must be
     * called explicitly by each FSBL entry point.
     * @private
     */
    start: (routerClient) => {
        const loggerConnection = singleton("LoggerClient", LoggerConnection);
        loggerConnection.start(routerClient);
    },
    /**
     * @private
     */
    channelFilter: (channel) => channelFilter(channel),
    /**
     * @private
     */
    setting: () => {
        const loggerConnection = singleton("LoggerClient", LoggerConnection);
        return loggerConnection.setting();
    },
    /**
     * @private
     */
    callStack: () => {
        const loggerConnection = singleton("LoggerClient", LoggerConnection);
        return loggerConnection.callStack();
    },
    /**
     * @private
     */
    unregisterClient: (params, cb) => {
        const loggerConnection = singleton("LoggerClient", LoggerConnection);
        loggerConnection.unregisterClient(params, cb);
    },
    /**
     * Sets the router client for this instance of the Logger client.
     * @private
     */
    setRouterClient: (routerClient) => {
        const loggerConnection = singleton("LoggerClient", LoggerConnection);
        loggerConnection.setRouterClient(routerClient);
    },
    /**
     * Used by assimilation service
     * @private
     */
    onStateChanged(cb) {
        const loggerConnection = singleton("LoggerClient", LoggerConnection);
        loggerConnection.onStateChanged(cb);
    },
    /**
     * @private
     */
    system: {
        error: (...args) => {
            const loggerConnection = singleton("LoggerClient", LoggerConnection);
            return loggerConnection.system.error(...args);
        },
        warn: (...args) => {
            const loggerConnection = singleton("LoggerClient", LoggerConnection);
            return loggerConnection.system.warn(...args);
        },
        info: (...args) => {
            const loggerConnection = singleton("LoggerClient", LoggerConnection);
            return loggerConnection.system.info(...args);
        },
        log: (...args) => {
            const loggerConnection = singleton("LoggerClient", LoggerConnection);
            return loggerConnection.system.log(...args);
        },
        debug: (...args) => {
            const loggerConnection = singleton("LoggerClient", LoggerConnection);
            return loggerConnection.system.debug(...args);
        },
        verbose: (...args) => {
            const loggerConnection = singleton("LoggerClient", LoggerConnection);
            return loggerConnection.system.verbose(...args);
        },
        deprecated: (...args) => {
            const loggerConnection = singleton("LoggerClient", LoggerConnection);
            return loggerConnection.system.deprecated(...args);
        },
    },
    /**
     * @private
     */
    perf: {
        error: (...args) => {
            const loggerConnection = singleton("LoggerClient", LoggerConnection);
            return loggerConnection.perf.error(...args);
        },
        warn: (...args) => {
            const loggerConnection = singleton("LoggerClient", LoggerConnection);
            return loggerConnection.perf.warn(...args);
        },
        info: (...args) => {
            const loggerConnection = singleton("LoggerClient", LoggerConnection);
            return loggerConnection.perf.info(...args);
        },
        log: (...args) => {
            const loggerConnection = singleton("LoggerClient", LoggerConnection);
            return loggerConnection.perf.log(...args);
        },
        debug: (...args) => {
            const loggerConnection = singleton("LoggerClient", LoggerConnection);
            return loggerConnection.perf.debug(...args);
        },
        verbose: (...args) => {
            const loggerConnection = singleton("LoggerClient", LoggerConnection);
            return loggerConnection.perf.verbose(...args);
        },
        deprecated: (...args) => {
            const loggerConnection = singleton("LoggerClient", LoggerConnection);
            return loggerConnection.perf.deprecated(...args);
        },
    },
    /**
     * @private
     *
     * logger.console.<level> is equivalent to logger.system.<level> except that the log
     * message is also displayed in the developer tools debugger. This set of functions
     * obeys the log level that is set within Central Logger (whereas `console.log` does not).
     *
     * When a window is starting up, logger.console.<level> will *always* log to the console
     * if it is called before the window has received its initial log level settings from
     * the logger service. This can be useful for debugging startup issues (without creating
     * stray console.log() statements).
     */
    console: {
        error: (...args) => {
            const loggerConnection = singleton("LoggerClient", LoggerConnection);
            return loggerConnection.console.error(...args);
        },
        warn: (...args) => {
            const loggerConnection = singleton("LoggerClient", LoggerConnection);
            return loggerConnection.console.warn(...args);
        },
        info: (...args) => {
            const loggerConnection = singleton("LoggerClient", LoggerConnection);
            return loggerConnection.console.info(...args);
        },
        log: (...args) => {
            const loggerConnection = singleton("LoggerClient", LoggerConnection);
            return loggerConnection.console.log(...args);
        },
        debug: (...args) => {
            const loggerConnection = singleton("LoggerClient", LoggerConnection);
            return loggerConnection.console.debug(...args);
        },
        verbose: (...args) => {
            const loggerConnection = singleton("LoggerClient", LoggerConnection);
            return loggerConnection.console.verbose(...args);
        },
        deprecated: (...args) => {
            const loggerConnection = singleton("LoggerClient", LoggerConnection);
            return loggerConnection.console.deprecated(...args);
        },
    },
};
/** An implementation of the ICentralLogger interface that
 * merely logs straight to the console rather than going over to
 * Central Logging service. Used in situations where use of the
 * Central Logging service is not possible (such as in test
 * environments, or in the Central Logging service itself).
 * @ignore
 */
export const LocalLogger = {
    start: () => { },
    channelFilter,
    setting: () => initialLogState,
    callStack: () => traceString(),
    unregisterClient: () => { },
    setRouterClient: () => { },
    onStateChanged: () => { },
    warn: globalThis.console.warn,
    info: globalThis.console.info,
    log: globalThis.console.log,
    debug: globalThis.console.debug,
    error: globalThis.console.error,
    verbose: globalThis.console.debug,
    deprecated: globalThis.console.error,
    system: {
        warn: globalThis.console.warn,
        info: globalThis.console.info,
        log: globalThis.console.log,
        debug: globalThis.console.debug,
        error: globalThis.console.error,
        verbose: globalThis.console.debug,
        deprecated: globalThis.console.error,
    },
    perf: {
        warn: globalThis.console.warn,
        info: globalThis.console.info,
        log: globalThis.console.log,
        debug: globalThis.console.debug,
        error: globalThis.console.error,
        verbose: globalThis.console.debug,
        deprecated: globalThis.console.error,
    },
    console: {
        warn: globalThis.console.warn,
        info: globalThis.console.info,
        log: globalThis.console.log,
        debug: globalThis.console.debug,
        error: globalThis.console.error,
        verbose: globalThis.console.debug,
        deprecated: globalThis.console.error,
    },
};
/** When running unit tests, we don't want to use a CentralLogger. If there is no "window"
 * then we assume we're running under node.
 */
const useLocalLogger = typeof window === "undefined";
/**
 * @ignore
 */
export const LoggerClient = CentralLoggerClient;
/**
 * Backward compatibility in case anyone out there is still importing Logger
 * @ignore
 */
export const Logger = LoggerClient;
/**
 * Ignore
 */
export default LoggerClient;
//# sourceMappingURL=logger.js.map