class PlanadayLogger{
    #modes = [
        {
            name: 'standard',
            description: 'Shows error, warning and info logs',
            types: ['info', 'warning', 'error'],
        }, {
            name: 'debug',
            description: 'Shows all logs',
            types: ['all'],
        }, {
            name: 'error',
            description: 'only shows errors and warnings',
            types: ['warning', 'error'],
        }
    ];

    #selectedMode = {
        name: 'standard',
        description: 'Shows error, warning and info logs',
        types: ['info', 'warning', 'error'],
    };

    #logs = [];
    #version = "";
    #loggerVersion = "1.2.0";

    constructor(logs, version){
        this.#logs = logs;
        this.#version = version;
        const lastConfig = localStorage.getItem("PlanadayLogger");
        if (lastConfig) {
            try {
                const lastConfigObject = JSON.parse(lastConfig);
                if (typeof lastConfigObject === "object" && typeof lastConfigObject["mode"] === "object") this.#selectedMode = lastConfigObject["mode"];
            } catch (e){}
        }
    }

    #saveConfig(){
        localStorage.setItem("PlanadayLogger", JSON.stringify({mode: this.#selectedMode}));
    }

    get help() {
        console.group("planadayLog.help");
        console.info("This text containing info on the logs");
        console.groupEnd();
        console.groupCollapsed("Modes");
        console.info("Standard mode is the default mode")
        console.info("In this mode error, warning and info logs are shown");
        console.groupCollapsed("planadayLog.modes");
        console.info("Shows all possible modes");
        console.groupEnd();
        console.groupCollapsed("planadayLog.mode");
        console.info("Shows the current mode");
        console.groupEnd();
        console.groupCollapsed("planadayLog.setMode('desiredMode')");
        console.info("Switches to the desired mode");
        console.info("for example planadayLog.setMode('debug') will switch to debug mode");
        console.groupEnd();
        console.groupEnd();
        console.groupCollapsed("Values");
        console.groupCollapsed("planadayLog.raw");
        console.info("The raw logs as they are saved");
        console.groupEnd();
        console.groupCollapsed("planadayLog.duration");
        console.info("The duration from the first to the last log");
        console.groupEnd();
        console.groupEnd();
        console.groupCollapsed("Commands");
        console.groupCollapsed("planadayLog.showLogs( filters )");
        console.info("Displays a table with the logs");
        console.groupEnd();
        console.groupCollapsed("planadayLog.logTimes( filters )");
        console.info("Displays a table with only the time, class and type");
        console.groupEnd();
        console.groupCollapsed("planadayLog.perClass( filters )");
        console.info("Displays tables of all the logs grouped by class");
        console.groupEnd();
        console.groupCollapsed("planadayLog.perType( filters )");
        console.info("Displays tables of all the logs grouped by type");
        console.groupEnd();
        console.groupEnd();
        console.groupCollapsed("Filters");
        console.info("format: planadayLog.showLogs( type, class, startTime, endTime )");
        console.info("these are all optional. enter null if you dont want to specify or leave arguments empty if it should handle all logs.");
        console.groupCollapsed("Type");
        console.info("The type of log entry. info, debug, error, etc...");
        console.groupEnd();
        console.groupCollapsed("Class");
        console.info("The class that made the log, must be full class string");
        console.groupEnd();
        console.groupCollapsed("StartTime");
        console.info("Only show logs after this relative time");
        console.groupEnd();
        console.groupCollapsed("EndTime");
        console.info("Only show logs before this relative time");
        console.groupEnd();
        console.groupEnd();
    }

    get raw() {
        return this.#logs;
    }

    version(){
        console.log("Plugin version is: " + this.#version);
        console.log("Logger version is: " + this.#loggerVersion);
    }

    get duration(){
        let latest = null;
        this.#logs.forEach(log => {
            if (log.relativeTime > latest || latest === null){
                latest = log.relativeTime;
            }
        });

        return latest;
    }

    get mode(){
        return this.#selectedMode.name + ": " + this.#selectedMode.description;
    }

    setMode(modeName){
        let foundMode = null;
        this.#modes.forEach((mode) => {
            if (mode.name === modeName){
                foundMode = mode;
            }
        });

        if (foundMode === null){
            return "Mode not found";
        }

        this.#selectedMode = foundMode;

        this.#saveConfig()

        return "Mode is now set to " + foundMode.name;
    }

    get modes(){
        this.#modes.forEach(mode => {
            console.info(mode.name + ": " + mode.description);
        });
    }

    showLogs(type = null, className = null, startTime = null, endTime = null){
        console.table(this.#filterData(type, className, startTime, endTime), ['relativeTime', 'type', 'class', 'message', 'data']);
    }

    logTimes(type = null, className = null, startTime = null, endTime = null){
        console.table(this.#filterData(type, className, startTime, endTime), ['relativeTime', 'type', 'class']);
    }

    perScope(type = null, className = null, startTime = null, endTime = null){
        let scopes = [];
        let scopeNames = [];

        this.#filterData(type, className, startTime, endTime).forEach(log => {
            if (scopes[log.scopeId] === undefined) {
                scopes[log.scopeId] = [];
                scopeNames.push(log.scopeId);
            }
            scopes[log.scopeId].push(log);
        });

        scopeNames.forEach(scopeName => {
            console.groupCollapsed(scopes[scopeName][0].scopeId);
            this.perClass(type, className, startTime, endTime, scopes[scopeName]);
            console.groupEnd();
        });
    }

    perClass(type = null, className = null, startTime = null, endTime = null, dataOverride = null){
        let classes = [];
        let classNames = [];

        (dataOverride ?? this.#filterData(type, className, startTime, endTime)).forEach(log => {
            if (classes[log.class] === undefined) {
                classes[log.class] = [];
                classNames.push(log.class);
            }
            classes[log.class].push(log);
        });

        classNames.forEach(className => {
            console.groupCollapsed(classes[className][0].class);
            console.table(classes[className], ['relativeTime', 'type', 'message', 'data']);
            console.groupEnd();
        });
    }

    perType(type = null, className = null, startTime = null, endTime = null){
        let types = [];
        let typeNames = [];

        this.#filterData(type, className, startTime, endTime).forEach(log => {
            if (types[log.type] === undefined) {
                types[log.type] = [];
                typeNames.push(log.type);
            }
            types[log.type].push(log);
        });

        typeNames.forEach(typeName => {
            console.groupCollapsed(types[typeName][0].type);
            console.table(types[typeName], ['relativeTime', 'class', 'message', 'data']);
            console.groupEnd();
        });
    }

    includeDebug(){
        this.setMode("debug");
        return this;
    }

    onlyErrors(){
        this.setMode("error")
        return this;
    }

    #filterData(type, className, startTime, endTime) {
        let data = [];

        this.#logs.forEach(log => {
            if (
                (type === null || log.type === type) &&
                (this.#selectedMode.types.some((value) => { return value === log.type || value === 'all'; })) &&
                (className === null || log.class === className) &&
                (startTime === null || log.relativeTime >= startTime) &&
                (endTime === null || log.relativeTime <= endTime)
            ) {
                data.push(log);
            }
        });

        return data;
    }
}