/**
 * Logger utility - Central logging configuration
 * Sealed namespace pattern - all exports through Logger namespace
 * Replaces console.* calls throughout the codebase
 */
import { isNonEmptyString } from '../helper/index.js';
import { appConfig } from './app.js';
import { Env } from './env.js';
const isProduction = () => appConfig.isProduction();
const getEnvString = (key, fallback) => {
    const envGet = Env.get;
    if (typeof envGet !== 'function')
        return fallback;
    try {
        const value = envGet(key, fallback);
        if (typeof value === 'string')
            return value;
        if (value === null || value === undefined)
            return fallback;
        return String(value);
    }
    catch {
        return fallback;
    }
};
const getEnvBool = (key, fallback) => {
    const envGetBool = Env.getBool;
    if (typeof envGetBool !== 'function')
        return fallback;
    try {
        return envGetBool(key, fallback);
    }
    catch {
        return fallback;
    }
};
const getLogFormat = () => getEnvString('LOG_FORMAT', 'text');
const isJsonFormat = (value) => value === 'json';
// Log level priority: lower means more verbose
const levelPriority = {
    debug: 0,
    info: 1,
    warn: 2,
    error: 3,
    fatal: 4,
};
const getConfiguredLogLevel = () => {
    const raw = getEnvString('LOG_LEVEL', Env.LOG_LEVEL ?? 'debug')
        .trim()
        .toLowerCase();
    if (raw === 'debug')
        return 'debug';
    if (raw === 'info')
        return 'info';
    if (raw === 'warn')
        return 'warn';
    if (raw === 'error')
        return 'error';
    return 'info';
};
const shouldEmit = (level) => {
    // If global disable, never emit
    if (getEnvBool('DISABLE_LOGGING', false))
        return false;
    // Respect configured LOG_LEVEL
    const configured = getConfiguredLogLevel();
    const lp = levelPriority[level];
    const configuredLp = levelPriority[configured] ?? levelPriority['info'];
    return lp >= configuredLp;
};
const BASE_SENSITIVE_FIELDS = Object.freeze([
    'password',
    'token',
    'authorization',
    'secret',
    'apikey',
    'api_key',
    'jwt',
    'bearer',
]);
const SENSITIVE_FIELD_KEY_PATTERN = /^[a-z0-9_.-]+$/;
const parseSensitiveFields = (rawValue) => {
    try {
        return rawValue
            .split(',')
            .map((value) => value.trim().toLowerCase())
            .filter((value) => isNonEmptyString(value) && SENSITIVE_FIELD_KEY_PATTERN.test(value));
    }
    catch {
        return [];
    }
};
const getSensitiveFields = () => {
    const configuredFieldsRaw = getEnvString('SENSITIVE_FIELDS', '');
    const configuredFields = isNonEmptyString(configuredFieldsRaw)
        ? parseSensitiveFields(configuredFieldsRaw)
        : [];
    return new Set([...BASE_SENSITIVE_FIELDS, ...configuredFields]);
};
const redactSensitiveData = (data) => {
    const sensitiveFields = getSensitiveFields();
    const seen = new WeakSet();
    const walk = (value) => {
        if (Array.isArray(value)) {
            if (seen.has(value))
                return '[Circular]';
            seen.add(value);
            return value.map((v) => walk(v));
        }
        if (typeof value === 'object' && value !== null) {
            const asObj = value;
            if (seen.has(asObj))
                return '[Circular]';
            seen.add(asObj);
            const out = {};
            for (const [key, inner] of Object.entries(asObj)) {
                if (sensitiveFields.has(key.toLowerCase())) {
                    out[key] = '[REDACTED]';
                }
                else {
                    out[key] = walk(inner);
                }
            }
            return out;
        }
        return value;
    };
    return walk(data);
};
const safeStringify = (obj, indent = false) => {
    const seen = new WeakSet();
    return JSON.stringify(obj, (_key, value) => {
        if (typeof value === 'object' && value !== null) {
            const asObj = value;
            if (seen.has(asObj))
                return '[Circular]';
            seen.add(asObj);
        }
        return value;
    }, indent ? 2 : 0);
};
let fileWriterPromise;
let fileWriter;
const getFileWriter = () => {
    if (fileWriter !== undefined)
        return;
    if (fileWriterPromise !== undefined)
        return;
    fileWriterPromise = import('./FileLogWriter.js')
        .then((mod) => {
        fileWriter = mod.FileLogWriter;
        return mod;
    })
        .catch(() => {
        fileWriterPromise = undefined;
        return { FileLogWriter: { write: (_line) => undefined } };
    });
};
const shouldLogToFile = () => {
    // Respect global disable
    if (getEnvBool('DISABLE_LOGGING', false))
        return false;
    // Prefer dynamic lookup so late-bound env (tests, some runtimes) is respected.
    const channel = getEnvString('LOG_CHANNEL', '').trim().toLowerCase();
    const channelWantsFile = channel === 'file' || channel === 'all';
    if (!getEnvBool('LOG_TO_FILE', false) && !channelWantsFile)
        return false;
    if (typeof process === 'undefined')
        return false;
    return true;
};
const buildFileLine = (params) => {
    if (isJsonFormat(getLogFormat()))
        return params.formatted;
    let line = params.formatted;
    if (typeof params.errorMessage === 'string' && params.errorMessage.length > 0) {
        line = `${line} ${params.errorMessage}`;
    }
    else if (params.data !== undefined && params.data !== '') {
        line = `${line} ${safeStringify(redactSensitiveData(params.data))}`;
    }
    return line;
};
const writeToFile = (line) => {
    if (!shouldLogToFile())
        return;
    if (fileWriter !== undefined) {
        fileWriter.write(line);
        return;
    }
    getFileWriter();
    fileWriterPromise?.then((mod) => mod.FileLogWriter.write(line));
};
const formatLogMessage = (params) => {
    if (isJsonFormat(getLogFormat())) {
        return safeStringify({
            timestamp: new Date().toISOString(),
            level: params.level,
            message: params.message,
            category: params.category,
            data: redactSensitiveData(params.data),
            error: params.errorMessage,
        });
    }
    // text format
    return `[${params.level.toUpperCase()}] ${params.message}`;
};
/**
 * Helper to extract error message from unknown error type
 */
const getErrorMessage = (error) => {
    if (error === undefined) {
        return '';
    }
    if (error instanceof Error) {
        return error.message;
    }
    if (typeof error === 'string')
        return error;
    if (typeof error === 'number' || typeof error === 'bigint')
        return error.toString();
    if (typeof error === 'boolean')
        return error ? 'true' : 'false';
    if (typeof error === 'symbol')
        return error.toString();
    if (typeof error === 'function')
        return '[Function]';
    try {
        return safeStringify(error);
    }
    catch {
        return '[Unserializable error]';
    }
};
const emitCloudLogs = (event) => {
    // Lazy-load to avoid cycles and avoid cost when disabled.
    void (async () => {
        try {
            if (event.level === 'error' || event.level === 'fatal') {
                const mod = await import('./logging/KvLogger.js');
                void mod.KvLogger.enqueue(event);
            }
        }
        catch {
            // best-effort
        }
        try {
            if (event.level === 'warn' || event.level === 'error' || event.level === 'fatal') {
                const mod = await import('./logging/SlackLogger.js');
                void mod.SlackLogger.enqueue(event);
            }
        }
        catch {
            // best-effort
        }
        try {
            const mod = await import('./logging/HttpLogger.js');
            void mod.HttpLogger.enqueue(event);
        }
        catch {
            // best-effort
        }
    })();
};
// Private helper functions
const logDebug = (message, data, category) => {
    if (!shouldEmit('debug'))
        return;
    String(category);
    const timestamp = new Date().toISOString();
    const out = formatLogMessage({ level: 'debug', message, data, category });
    writeToFile(buildFileLine({ formatted: out, data }));
    if (isJsonFormat(getLogFormat())) {
        console.debug(out); // eslint-disable-line no-console
    }
    else {
        console.debug(out, data ?? ''); // eslint-disable-line no-console
    }
    emitCloudLogs({
        timestamp,
        level: 'debug',
        message,
        category,
        data: redactSensitiveData(data),
    });
};
const logInfo = (message, data, category) => {
    if (!shouldEmit('info'))
        return;
    String(category);
    const timestamp = new Date().toISOString();
    const out = formatLogMessage({ level: 'info', message, data, category });
    writeToFile(buildFileLine({ formatted: out, data }));
    if (isJsonFormat(getLogFormat())) {
        console.log(out); // eslint-disable-line no-console
    }
    else {
        console.log(out, data ?? ''); // eslint-disable-line no-console
    }
    emitCloudLogs({
        timestamp,
        level: 'info',
        message,
        category,
        data: redactSensitiveData(data),
    });
};
const logWarn = (message, data, category) => {
    if (!shouldEmit('warn'))
        return;
    String(category);
    const timestamp = new Date().toISOString();
    const out = formatLogMessage({ level: 'warn', message, data, category });
    writeToFile(buildFileLine({ formatted: out, data }));
    if (isJsonFormat(getLogFormat())) {
        console.warn(out); // eslint-disable-line no-console
    }
    else {
        console.warn(out, data ?? ''); // eslint-disable-line no-console
    }
    emitCloudLogs({
        timestamp,
        level: 'warn',
        message,
        category,
        data: redactSensitiveData(data),
    });
};
const logError = (message, error, category) => {
    if (!shouldEmit('error'))
        return;
    const errorMessage = getErrorMessage(error);
    String(category);
    const timestamp = new Date().toISOString();
    const out = formatLogMessage({
        level: 'error',
        message,
        category,
        errorMessage,
    });
    writeToFile(buildFileLine({ formatted: out, errorMessage }));
    if (isJsonFormat(getLogFormat())) {
        console.error(out); // eslint-disable-line no-console
    }
    else {
        console.error(out, errorMessage); // eslint-disable-line no-console
    }
    emitCloudLogs({
        timestamp,
        level: 'error',
        message,
        category,
        error: errorMessage,
    });
};
const logFatal = (message, error, category) => {
    if (!shouldEmit('fatal'))
        return;
    const errorMessage = getErrorMessage(error);
    String(category);
    const timestamp = new Date().toISOString();
    const out = formatLogMessage({
        level: 'fatal',
        message,
        category,
        errorMessage,
    });
    writeToFile(buildFileLine({ formatted: out, errorMessage }));
    if (isJsonFormat(getLogFormat())) {
        console.error(out); // eslint-disable-line no-console
    }
    else {
        console.error(out, errorMessage); // eslint-disable-line no-console
    }
    emitCloudLogs({
        timestamp,
        level: 'fatal',
        message,
        category,
        error: errorMessage,
    });
    if (isProduction() && typeof process !== 'undefined') {
        process.exit(1);
    }
};
const createLoggerScope = (scope) => {
    return {
        debug(message, data) {
            logDebug(`[${scope}] ${message}`, data, scope);
        },
        info(message, data) {
            logInfo(`[${scope}] ${message}`, data, scope);
        },
        warn(message, data) {
            logWarn(`[${scope}] ${message}`, data, scope);
        },
        error(message, error) {
            logError(`[${scope}] ${message}`, error, scope);
        },
        fatal(message, error) {
            logFatal(`[${scope}] ${message}`, error, scope);
        },
    };
};
// Expose log cleanup API and sealed namespace with all logger functionality
export const cleanLogsOnce = async () => {
    if (!shouldLogToFile())
        return [];
    try {
        const mod = await import('./FileLogWriter.js');
        const deleted = mod.cleanOnce();
        logInfo('Log cleanup executed', { deletedCount: deleted.length });
        return deleted;
    }
    catch (err) {
        logError('Log cleanup failed', err);
        return [];
    }
};
export const Logger = Object.freeze({
    debug: logDebug,
    info: logInfo,
    warn: logWarn,
    error: logError,
    fatal: logFatal,
    cleanLogsOnce,
    scope: createLoggerScope,
});
export default Logger;
