"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.stopCleanJob = exports.setupCleanJob = exports.setupAll = exports.module = exports.setupLogger = exports.customTransports = exports.customFormat = exports.sanitizedConfig = exports.disableColors = exports.defaultLogFile = void 0;
const winston_daily_rotate_file_1 = __importDefault(require("@zwave-js/winston-daily-rotate-file"));
const fs_extra_1 = require("fs-extra");
const winston_1 = __importDefault(require("winston"));
const app_1 = require("../config/app");
const utils_1 = require("./utils");
const path = __importStar(require("path"));
const promises_1 = require("fs/promises");
const escape_string_regexp_1 = __importDefault(require("escape-string-regexp"));
const { format, transports, addColors } = winston_1.default;
const { combine, timestamp, label, printf, colorize, splat } = format;
exports.defaultLogFile = 'z-ui_%DATE%.log';
exports.disableColors = process.env.NO_LOG_COLORS === 'true';
// ensure store and logs directories exist
(0, fs_extra_1.ensureDirSync)(app_1.storeDir);
(0, fs_extra_1.ensureDirSync)(app_1.logsDir);
// custom colors for timestamp and module
addColors({
    time: 'grey',
    module: 'bold',
});
const colorizer = colorize();
/**
 * Generate logger configuration starting from settings.gateway
 */
function sanitizedConfig(module, config) {
    config = config || {};
    const filePath = (0, utils_1.joinPath)(app_1.logsDir, config.logFileName || exports.defaultLogFile);
    return {
        module: module || '-',
        enabled: config.logEnabled !== undefined ? config.logEnabled : true,
        level: config.logLevel || 'info',
        logToFile: config.logToFile !== undefined ? config.logToFile : false,
        filePath: filePath,
    };
}
exports.sanitizedConfig = sanitizedConfig;
/**
 * Return a custom logger format
 */
function customFormat(config, noColor = false) {
    noColor = noColor || exports.disableColors;
    const formats = [
        splat(), // used for formats like: logger.log('info', Message %s', strinVal)
        timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
        format((info) => {
            info.level = info.level.toUpperCase();
            return info;
        })(),
        label({ label: config.module.toUpperCase() }),
    ];
    if (!noColor) {
        formats.push(colorize({ level: true }));
    }
    // must be added at last
    formats.push(printf((info) => {
        if (!noColor) {
            info.timestamp = colorizer.colorize('time', info.timestamp);
            info.label = colorizer.colorize('module', info.label || '-');
        }
        return `${info.timestamp} ${info.level} ${info.label}: ${info.message}${info.stack ? '\n' + info.stack : ''}`;
    }));
    return combine(...formats);
}
exports.customFormat = customFormat;
/**
 * Create the base transports based on settings provided
 */
function customTransports(config) {
    const transportsList = [];
    if (process.env.ZUI_NO_CONSOLE !== 'true') {
        transportsList.push(new transports.Console({
            format: customFormat(config),
            level: config.level,
            stderrLevels: ['error'],
        }));
    }
    if (config.logToFile) {
        let fileTransport;
        if (process.env.DISABLE_LOG_ROTATION === 'true') {
            fileTransport = new transports.File({
                format: customFormat(config, true),
                filename: config.filePath,
                level: config.level,
            });
        }
        else {
            const options = {
                filename: config.filePath,
                auditFile: (0, utils_1.joinPath)(app_1.logsDir, 'zui-logs.audit.json'),
                datePattern: 'YYYY-MM-DD',
                createSymlink: true,
                symlinkName: path
                    .basename(config.filePath)
                    .replace(`_%DATE%`, '_current'),
                zippedArchive: true,
                maxFiles: process.env.ZUI_LOG_MAXFILES || '7d',
                maxSize: process.env.ZUI_LOG_MAXSIZE || '50m',
                level: config.level,
                format: customFormat(config, true),
            };
            fileTransport = new winston_daily_rotate_file_1.default(options);
            setupCleanJob(options);
        }
        transportsList.push(fileTransport);
    }
    return transportsList;
}
exports.customTransports = customTransports;
/**
 * Setup a logger
 */
function setupLogger(container, module, config) {
    const sanitized = sanitizedConfig(module, config);
    // Winston automatically reuses an existing module logger
    const logger = container.add(module);
    logger.configure({
        format: combine(format.errors({ stack: true }), format.json()), // to correctly parse errors
        silent: !sanitized.enabled,
        level: sanitized.level,
        transports: customTransports(sanitized),
    });
    logger.module = module;
    logger.setup = (cfg) => setupLogger(container, module, cfg);
    return logger;
}
exports.setupLogger = setupLogger;
const logContainer = new winston_1.default.Container();
/**
 * Create a new logger for a specific module
 */
function module(module) {
    return setupLogger(logContainer, module);
}
exports.module = module;
/**
 * Setup all loggers starting from config
 */
function setupAll(config) {
    stopCleanJob();
    logContainer.loggers.forEach((logger) => {
        logger.setup(config);
    });
}
exports.setupAll = setupAll;
let cleanJob;
function setupCleanJob(settings) {
    if (cleanJob) {
        return;
    }
    let maxFilesMs;
    let maxFiles;
    let maxSizeBytes;
    const logger = module('LOGGER');
    // convert maxFiles to milliseconds
    if (settings.maxFiles !== undefined) {
        const matches = settings.maxFiles.toString().match(/(\d+)([dhm])/);
        if (settings.maxFiles) {
            const value = parseInt(matches[1]);
            const unit = matches[2];
            switch (unit) {
                case 'd':
                    maxFilesMs = value * 24 * 60 * 60 * 1000;
                    break;
                case 'h':
                    maxFilesMs = value * 60 * 60 * 1000;
                    break;
                case 'm':
                    maxFilesMs = value * 60 * 1000;
                    break;
            }
        }
        else {
            maxFiles = Number(settings.maxFiles);
        }
    }
    if (settings.maxSize !== undefined) {
        // convert maxSize to bytes
        const matches2 = settings.maxSize.toString().match(/(\d+)([kmg])/);
        if (matches2) {
            const value = parseInt(matches2[1]);
            const unit = matches2[2];
            switch (unit) {
                case 'k':
                    maxSizeBytes = value * 1024;
                    break;
                case 'm':
                    maxSizeBytes = value * 1024 * 1024;
                    break;
                case 'g':
                    maxSizeBytes = value * 1024 * 1024 * 1024;
                    break;
            }
        }
        else {
            maxSizeBytes = Number(settings.maxSize);
        }
    }
    // clean up old log files based on maxFiles and maxSize
    const filePathRegExp = new RegExp((0, escape_string_regexp_1.default)(path.basename(settings.filename)).replace(/%DATE%/g, '(.*)'));
    const logsDir = path.dirname(settings.filename);
    const deleteFile = async (filePath) => {
        logger.info(`Deleting log file: ${filePath}`);
        return (0, promises_1.unlink)(filePath).catch((e) => {
            if (e.code !== 'ENOENT') {
                logger.error(`Error deleting log file: ${filePath}`, e);
            }
        });
    };
    const clean = async () => {
        try {
            logger.info('Cleaning up log files...');
            const files = await (0, promises_1.readdir)(logsDir);
            const logFiles = files.filter((file) => file !== settings.symlinkName && filePathRegExp.test(file));
            const fileStats = await Promise.allSettled(logFiles.map(async (file) => ({
                file,
                stats: await (0, promises_1.stat)(path.join(logsDir, file)),
            })));
            const logFilesStats = [];
            for (const res of fileStats) {
                if (res.status === 'fulfilled') {
                    logFilesStats.push(res.value);
                }
                else {
                    logger.error('Error getting file stats:', res.reason);
                }
            }
            logFilesStats.sort((a, b) => a.stats.mtimeMs - b.stats.mtimeMs);
            // sort by mtime
            let totalSize = 0;
            let deletedFiles = 0;
            for (const { file, stats } of logFilesStats) {
                const filePath = path.join(logsDir, file);
                totalSize += stats.size;
                // last modified time in milliseconds
                const fileMs = stats.mtimeMs;
                const shouldDelete = (maxSizeBytes && totalSize > maxSizeBytes) ||
                    (maxFiles && logFiles.length - deletedFiles > maxFiles) ||
                    (maxFilesMs && fileMs && Date.now() - fileMs > maxFilesMs);
                if (shouldDelete) {
                    await deleteFile(filePath);
                    deletedFiles++;
                }
            }
        }
        catch (e) {
            logger.error('Error cleaning up log files:', e);
        }
    };
    cleanJob = setInterval(clean, 60 * 60 * 1000);
    clean().catch(() => { });
}
exports.setupCleanJob = setupCleanJob;
function stopCleanJob() {
    if (cleanJob) {
        clearInterval(cleanJob);
        cleanJob = undefined;
    }
}
exports.stopCleanJob = stopCleanJob;
exports.default = logContainer.loggers;
