"use strict";
var __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.logger = void 0;
exports.logWithSession = logWithSession;
exports.createSessionLogger = createSessionLogger;
exports.setSessionMetadata = setSessionMetadata;
exports.recordConversation = recordConversation;
exports.getSessionSnapshot = getSessionSnapshot;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const winston_1 = require("winston");
const logDir = path_1.default.resolve(process.cwd(), "logs");
if (!fs_1.default.existsSync(logDir))
    fs_1.default.mkdirSync(logDir, { recursive: true });
const sessionStore = new Map();
const MAX_TRACKED_SESSIONS = getPositiveInt(process.env.LOG_MAX_TRACKED_SESSIONS, 250);
const MAX_LOGS_PER_SESSION = getPositiveInt(process.env.LOG_MAX_ENTRIES_PER_SESSION, 400);
const MAX_CONVERSATION_ENTRIES_PER_SESSION = getPositiveInt(process.env.LOG_MAX_CONVERSATION_ENTRIES, 200);
const MAX_TEXT_LENGTH = getPositiveInt(process.env.LOG_MAX_TEXT_LENGTH, 6000);
function getPositiveInt(rawValue, fallback) {
    const parsedValue = Number.parseInt(rawValue !== null && rawValue !== void 0 ? rawValue : "", 10);
    if (Number.isNaN(parsedValue) || parsedValue <= 0) {
        return fallback;
    }
    return parsedValue;
}
function normalizeSessionId(sessionId) {
    return sessionId.trim();
}
function truncateText(value) {
    if (value.length <= MAX_TEXT_LENGTH) {
        return value;
    }
    const removedChars = value.length - MAX_TEXT_LENGTH;
    return `${value.slice(0, MAX_TEXT_LENGTH)}...<truncated:${removedChars}>`;
}
function safeSerialize(value) {
    const seen = new WeakSet();
    const replacer = (_key, currentValue) => {
        if (typeof currentValue === "bigint") {
            return currentValue.toString();
        }
        if (currentValue instanceof Error) {
            return {
                name: currentValue.name,
                message: currentValue.message,
                stack: currentValue.stack,
            };
        }
        if (typeof currentValue === "function") {
            return `[Function:${currentValue.name || "anonymous"}]`;
        }
        if (typeof currentValue === "symbol") {
            return currentValue.toString();
        }
        if (typeof currentValue === "object" && currentValue !== null) {
            if (seen.has(currentValue)) {
                return "[Circular]";
            }
            seen.add(currentValue);
        }
        return currentValue;
    };
    try {
        return JSON.parse(JSON.stringify(value, replacer));
    }
    catch (_a) {
        return String(value);
    }
}
function normalizeMessage(message) {
    var _a;
    if (typeof message === "string") {
        return truncateText(message);
    }
    if (message instanceof Error) {
        return truncateText((_a = message.stack) !== null && _a !== void 0 ? _a : message.message);
    }
    const serialized = safeSerialize(message);
    if (typeof serialized === "string") {
        return truncateText(serialized);
    }
    return truncateText(JSON.stringify(serialized));
}
function sanitizeMeta(meta) {
    const sanitized = {};
    for (const [key, value] of Object.entries(meta)) {
        sanitized[key] = safeSerialize(value);
    }
    return sanitized;
}
function trimToMaxEntries(items, maxEntries) {
    if (items.length <= maxEntries) {
        return;
    }
    items.splice(0, items.length - maxEntries);
}
function pruneSessionStore() {
    while (sessionStore.size > MAX_TRACKED_SESSIONS) {
        let oldestSessionId;
        let oldestTimestamp = Number.POSITIVE_INFINITY;
        for (const [sessionId, state] of sessionStore.entries()) {
            const timestamp = Date.parse(state.lastActivityAt);
            if (timestamp < oldestTimestamp) {
                oldestTimestamp = timestamp;
                oldestSessionId = sessionId;
            }
        }
        if (!oldestSessionId) {
            break;
        }
        sessionStore.delete(oldestSessionId);
    }
}
function getOrCreateSessionState(sessionId) {
    const normalizedId = normalizeSessionId(sessionId);
    let state = sessionStore.get(normalizedId);
    if (state) {
        return state;
    }
    const now = new Date().toISOString();
    state = {
        createdAt: now,
        lastActivityAt: now,
        metadata: {},
        logs: [],
        conversation: [],
        errorCount: 0,
    };
    sessionStore.set(normalizedId, state);
    pruneSessionStore();
    return state;
}
function extractMetaFromInfo(info) {
    const meta = {};
    for (const [key, value] of Object.entries(info)) {
        if (key === "level" ||
            key === "message" ||
            key === "timestamp" ||
            key === "stack" ||
            key === "sessionId" ||
            key === "session_id") {
            continue;
        }
        meta[key] = value;
    }
    return sanitizeMeta(meta);
}
function appendSessionLog(sessionId, level, message, meta) {
    const now = new Date().toISOString();
    const state = getOrCreateSessionState(sessionId);
    state.logs.push({
        timestamp: now,
        level,
        message: truncateText(message),
        meta,
    });
    trimToMaxEntries(state.logs, MAX_LOGS_PER_SESSION);
    state.lastActivityAt = now;
    if (level === "error") {
        state.errorCount += 1;
    }
}
const trackSessionFormat = (0, winston_1.format)((info) => {
    var _a;
    const infoRecord = info;
    const rawSessionId = (_a = infoRecord.sessionId) !== null && _a !== void 0 ? _a : infoRecord.session_id;
    if (typeof rawSessionId === "string" && rawSessionId.trim().length > 0) {
        const sessionId = normalizeSessionId(rawSessionId);
        const level = typeof info.level === "string" ? info.level : "info";
        const message = normalizeMessage(info.message);
        const meta = extractMetaFromInfo(infoRecord);
        appendSessionLog(sessionId, level, message, meta);
    }
    return info;
});
const loggerFormat = winston_1.format.combine(trackSessionFormat(), winston_1.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss.SSS" }), winston_1.format.errors({ stack: true }), winston_1.format.splat());
const consoleFormat = winston_1.format.combine(winston_1.format.colorize({ all: true }), winston_1.format.printf((_a) => {
    var { timestamp, level, message, stack, sessionId } = _a, meta = __rest(_a, ["timestamp", "level", "message", "stack", "sessionId"]);
    const sessionTag = typeof sessionId === "string" && sessionId.length > 0 ? ` [session:${sessionId}]` : "";
    const metaStr = Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : "";
    const body = stack ? `${message}\n${stack}` : message;
    return `${timestamp} [${level}]${sessionTag} ${body}${metaStr}`;
}));
exports.logger = (0, winston_1.createLogger)({
    level: (_a = process.env.LOG_LEVEL) !== null && _a !== void 0 ? _a : "info",
    format: loggerFormat,
    transports: [
        new winston_1.transports.File({
            filename: path_1.default.join(logDir, "app.log"),
            format: winston_1.format.json(),
        }),
        new winston_1.transports.File({
            filename: path_1.default.join(logDir, "error.log"),
            level: "error",
            format: winston_1.format.json(),
        }),
        new winston_1.transports.Console({
            format: consoleFormat,
        }),
    ],
});
function logWithSession(level, sessionId, message, meta = {}) {
    const normalizedId = normalizeSessionId(sessionId);
    const normalizedMessage = normalizeMessage(message);
    const normalizedMeta = sanitizeMeta(meta);
    if (!normalizedId) {
        exports.logger.log(level, normalizedMessage, normalizedMeta);
        return;
    }
    exports.logger.log(level, normalizedMessage, Object.assign(Object.assign({}, normalizedMeta), { sessionId: normalizedId }));
}
function createSessionLogger(sessionId) {
    return {
        log: (level, message, meta = {}) => logWithSession(level, sessionId, message, meta),
        error: (message, meta = {}) => logWithSession("error", sessionId, message, meta),
        warn: (message, meta = {}) => logWithSession("warn", sessionId, message, meta),
        info: (message, meta = {}) => logWithSession("info", sessionId, message, meta),
        debug: (message, meta = {}) => logWithSession("debug", sessionId, message, meta),
        verbose: (message, meta = {}) => logWithSession("verbose", sessionId, message, meta),
        http: (message, meta = {}) => logWithSession("http", sessionId, message, meta),
        silly: (message, meta = {}) => logWithSession("silly", sessionId, message, meta),
    };
}
function setSessionMetadata(sessionId, metadata) {
    const normalizedId = normalizeSessionId(sessionId);
    if (!normalizedId) {
        return;
    }
    const state = getOrCreateSessionState(normalizedId);
    Object.assign(state.metadata, sanitizeMeta(metadata));
    state.lastActivityAt = new Date().toISOString();
}
function recordConversation(sessionId, role, content, meta = {}) {
    const normalizedId = normalizeSessionId(sessionId);
    if (!normalizedId) {
        return;
    }
    const now = new Date().toISOString();
    const state = getOrCreateSessionState(normalizedId);
    const normalizedContent = normalizeMessage(content);
    const sanitizedMeta = sanitizeMeta(meta);
    state.conversation.push({
        timestamp: now,
        role,
        content: normalizedContent,
        meta: sanitizedMeta,
    });
    trimToMaxEntries(state.conversation, MAX_CONVERSATION_ENTRIES_PER_SESSION);
    state.lastActivityAt = now;
    logWithSession("info", normalizedId, `conversation.${role}`, {
        preview: truncateText(normalizedContent.slice(0, 250)),
    });
}
function getSessionSnapshot(sessionId) {
    const normalizedId = normalizeSessionId(sessionId);
    if (!normalizedId) {
        return null;
    }
    const state = sessionStore.get(normalizedId);
    if (!state) {
        return null;
    }
    return {
        sessionId: normalizedId,
        metadata: sanitizeMeta(state.metadata),
        logs: state.logs.map((entry) => (Object.assign(Object.assign({}, entry), { meta: sanitizeMeta(entry.meta) }))),
        conversation: state.conversation.map((entry) => (Object.assign(Object.assign({}, entry), { meta: sanitizeMeta(entry.meta) }))),
        summary: {
            created_at: state.createdAt,
            last_activity_at: state.lastActivityAt,
            log_count: state.logs.length,
            conversation_count: state.conversation.length,
            error_count: state.errorCount,
        },
    };
}
