import { globalConfig } from "../core/config";
import { Math_floor, performanceNow } from "./builtins";

const circularJson = require("circular-json");

/*
Logging functions
- To be extended
*/

/**
 * Base logger class
 */
class Logger {
    constructor(context) {
        this.context = context;
    }

    debug(...args) {
        globalDebug(this.context, ...args);
    }

    log(...args) {
        globalLog(this.context, ...args);
    }

    warn(...args) {
        globalWarn(this.context, ...args);
    }

    error(...args) {
        globalError(this.context, ...args);
    }
}

export function createLogger(context) {
    return new Logger(context);
}

function prepareObjectForLogging(obj, maxDepth = 1) {
    if (!window.Sentry) {
        // Not required without sentry
        return obj;
    }

    if (typeof obj !== "object" && !Array.isArray(obj)) {
        return obj;
    }
    const result = {};
    for (const key in obj) {
        const val = obj[key];

        if (typeof val === "object") {
            if (maxDepth > 0) {
                result[key] = prepareObjectForLogging(val, maxDepth - 1);
            } else {
                result[key] = "[object]";
            }
        } else {
            result[key] = val;
        }
    }
    return result;
}

/**
 * Serializes an error
 * @param {Error|ErrorEvent} err
 */
export function serializeError(err) {
    if (!err) {
        return null;
    }
    const result = {
        type: err.constructor.name,
    };

    if (err instanceof Error) {
        result.message = err.message;
        result.name = err.name;
        result.stack = err.stack;
        result.type = "{type.Error}";
    } else if (err instanceof ErrorEvent) {
        result.filename = err.filename;
        result.message = err.message;
        result.lineno = err.lineno;
        result.colno = err.colno;
        result.type = "{type.ErrorEvent}";

        if (err.error) {
            result.error = serializeError(err.error);
        } else {
            result.error = "{not-provided}";
        }
    } else {
        result.type = "{unkown-type:" + typeof err + "}";
    }

    return result;
}

/**
 * Serializes an event
 * @param {Event} event
 */
function serializeEvent(event) {
    let result = {
        type: "{type.Event:" + typeof event + "}",
    };
    result.eventType = event.type;
    return result;
}

/**
 * Prepares a json payload
 * @param {string} key
 * @param {any} value
 */
function preparePayload(key, value) {
    if (value instanceof Error || value instanceof ErrorEvent) {
        return serializeError(value);
    }
    if (value instanceof Event) {
        return serializeEvent(value);
    }
    if (typeof value === "undefined") {
        return null;
    }
    return value;
}

/**
 * Stringifies an object containing circular references and errors
 * @param {any} payload
 */
export function stringifyObjectContainingErrors(payload) {
    return circularJson.stringify(payload, preparePayload);
}

export function globalDebug(context, ...args) {
    if (G_IS_DEV) {
        logInternal(context, console.log, prepareArgsForLogging(args));
    }
}

export function globalLog(context, ...args) {
    // eslint-disable-next-line no-console
    logInternal(context, console.log, prepareArgsForLogging(args));
}

export function globalWarn(context, ...args) {
    // eslint-disable-next-line no-console
    logInternal(context, console.warn, prepareArgsForLogging(args));
}

export function globalError(context, ...args) {
    args = prepareArgsForLogging(args);
    // eslint-disable-next-line no-console
    logInternal(context, console.error, args);

    if (window.Sentry) {
        window.Sentry.withScope(scope => {
            scope.setExtra("args", args);
            window.Sentry.captureMessage(internalBuildStringFromArgs(args), "error");
        });
    }
}

function prepareArgsForLogging(args) {
    let result = [];
    for (let i = 0; i < args.length; ++i) {
        result.push(prepareObjectForLogging(args[i]));
    }
    return result;
}

/**
 * @param {Array<any>} args
 */
function internalBuildStringFromArgs(args) {
    let result = [];

    for (let i = 0; i < args.length; ++i) {
        let arg = args[i];
        if (
            typeof arg === "string" ||
            typeof arg === "number" ||
            typeof arg === "boolean" ||
            arg === null ||
            arg === undefined
        ) {
            result.push("" + arg);
        } else if (arg instanceof Error) {
            result.push(arg.message);
        } else {
            result.push("[object]");
        }
    }
    return result.join(" ");
}

export function logSection(name, color) {
    while (name.length <= 14) {
        name = " " + name + " ";
    }
    name = name.padEnd(19, " ");

    const lineCss =
        "letter-spacing: -3px; color: " + color + "; font-size: 6px; background: #eee; color: #eee;";
    const line = "%c----------------------------";
    console.log("\n" + line + " %c" + name + " " + line + "\n", lineCss, "color: " + color, lineCss);
}

function extractHandleContext(handle) {
    let context = handle || "unknown";
    if (handle && handle.constructor && handle.constructor.name) {
        context = handle.constructor.name;
        if (context === "String") {
            context = handle;
        }
    }

    if (handle && handle.name) {
        context = handle.name;
    }
    return context + "";
}

function logInternal(handle, consoleMethod, args) {
    const context = extractHandleContext(handle).padEnd(20, " ");
    const labelColor = handle && handle.LOG_LABEL_COLOR ? handle.LOG_LABEL_COLOR : "#aaa";

    if (G_IS_DEV && globalConfig.debug.logTimestamps) {
        const timestamp = "⏱ %c" + (Math_floor(performanceNow()) + "").padEnd(6, " ") + "";
        consoleMethod.call(
            console,
            timestamp + " %c" + context,
            "color: #7f7;",
            "color: " + labelColor + ";",
            ...args
        );
    } else {
        // if (G_IS_DEV && !globalConfig.debug.disableLoggingLogSources) {
        consoleMethod.call(console, "%c" + context, "color: " + labelColor, ...args);
        // } else {
        // consoleMethod.call(console, ...args);
        // }
    }
}
