const DEBUG_ENABLED = false;

const LogLevel = {
    ALL: 0,
    DEBUG: 20,
    INFO: 40,
    WARN: 50,
    ERROR: 60,
    FATAL: 70
};

const consoleLogger = {
    debug: (...args) => console.debug('[mailmindr]', ...args),
    log: (...args) => console.log('[mailmindr]', ...args),
    info: (...args) => console.info('[mailmindr]', ...args),
    warn: (...args) => console.warn('[mailmindr]', ...args),
    error: (...args) => console.error('[mailmindr]', ...args)
};

const sendMessageToSkidder = message => {
    try {
        browser.runtime.sendMessage(
            'skidder.extension@arndissler.com',
            message
        );
    } catch (sendException) {
        console.error('sending failed', sendException);
        return false;
    }
    return true;
};

class Logger {
    constructor(scope, severity, parentScope) {
        const _scopeChain = [
            ...((parentScope === null || parentScope === void 0
                ? void 0
                : parentScope.scope) || [])
        ];
        _scopeChain.push(scope);

        this._severity = severity;
        this._scopes = _scopeChain;
        this._connected = false;
        this._retry = true;
        this._buffer = [];
        this._enabledScopes = [];

        console.log(`starting logger for scope '${this.scope}'`);

        this.getFilterFromStorage().then(enabledScopes => {
            this._enabledScopes = enabledScopes;
            let enabled = false;
            for (let localScope in enabledScopes) {
                let severityString = enabledScopes[localScope];
                let severity = LogLevel[severityString] || LogLevel.WARN;
                if (localScope.indexOf('*') >= 0) {
                    let theScope = localScope.substring(
                        0,
                        localScope.indexOf('*')
                    );
                    if (enabled === false) {
                        const item = this._scopes.find(s =>
                            s.name.startsWith(theScope)
                        );
                        enabled = item !== undefined;
                    }
                } else {
                    if (enabled === false) {
                        // 
                        enabled = this._scopes.find(s => s.name === localScope);
                    }
                }
                this._severity = severity;
            }

            this.tryConnectAndSendBuffer();
        });
    }

    createContextLogger(scope) {
        return new Logger(scope, this._severity, this);
    }

    get scope() {
        return (this._scopes || []).map(({ name }) => name).join('.');
    }

    trySend(item) {
        if (this._connected) {
            sendMessageToSkidder(item);
        } else {
            this._buffer.push(item);
        }
    }

    async getFilterFromStorage() {
        try {
            const { logFilter } = await messenger.storage.local.get(
                'logFilter'
            );

            return logFilter;
        } catch (exception) {
            return [];
        }
    }

    async tryConnectAndSendBuffer() {
        let count = 0;
        while (this._connected === false && this._retry) {
            try {
                await sleep(500);

                const message = {
                    message: 'connect'
                };
                const result = await browser.runtime.sendMessage(
                    'skidder.extension@arndissler.com',
                    message
                );

                if (DEBUG_ENABLED) {
                    console.log('mailmindr conn', result);
                }

                if (result) {
                    /* first send all the data we already have, before we set connected to 'true' */
                    this._buffer.forEach(item => sendMessageToSkidder(item));

                    this._connected = true;
                }
            } catch (connectionError) {
                count++;
                this._retry = count > 30;
                if (!this._retry) {
                    consoleLogger.info(
                        `logger for '${this.scope}' is still not connected, stopping.`
                    );
                }
            }
        }
    }

    onErrorHandler(message, url, line, column, error) {
        this.pass(LogLevel.ERROR, message, { url, line, column, error });
    }

    pass(severity, message, context, ...args) {
        const logItem = {
            message,
            severity,
            timestamp: performance.now(),
            arguments: args,
            scope: this._scopes,
            context
        };

        if (this._severity > severity) {
            return;
        }

        this.trySend(logItem);

        switch (severity) {
            case LogLevel.FATAL:
                consoleLogger.error(message, logItem);
                break;
            case LogLevel.ERROR:
                consoleLogger.error(message, logItem);
                break;
            case LogLevel.DEBUG:
                consoleLogger.debug(message, logItem);
                break;
            case LogLevel.WARN:
                consoleLogger.warn(message, logItem);
                break;
            case LogLevel.INFO:
                consoleLogger.info(message, logItem);
                break;
            default:
                consoleLogger.info(message, logItem);
                break;
        }
    }

    log(message, context, ...args) {
        this.pass(LogLevel.INFO, message, context, ...args);
    }

    info(message, context, ...args) {
        this.pass(LogLevel.INFO, message, context, ...args);
    }

    warn(message, context, ...args) {
        this.pass(LogLevel.WARN, message, context, ...args);
    }

    debug(message, context, ...args) {
        this.pass(LogLevel.DEBUG, message, context, ...args);
    }

    fatal(message, context, ...args) {
        this.pass(LogLevel.ERROR, message, context, ...args);
    }

    error(message, context, ...args) {
        this.pass(LogLevel.FATAL, message, context, ...args);
    }
}

export const createLogger = (contextName, severity) =>
    new Logger({ name: contextName });

const sleep = async milliseconds =>
    new Promise(resolve => setTimeout(resolve, milliseconds));

export const createCorrelationId = (
    readableContextIdentifier,
    parenCorrelationId
) => {
    const uniqueId = Math.random()
        .toString(16)
        .replace('.', 'x');
    return `${
        parenCorrelationId || '' ? `${parenCorrelationId}::` : ''
    }${readableContextIdentifier || ''}/${uniqueId}`;
};
