/**
 * LogStreamer — 병렬 에이전트 로그 스트리밍 및 관리
 *
 * 여러 워커의 로그를 실시간으로 스트리밍, 필터링, 포맷팅합니다.
 */
import { createWriteStream } from "fs";
import { mkdir } from "fs/promises";
import { join } from "path";
export class LogStreamer {
    options;
    logs = [];
    streams = new Map();
    eventHandlers = [];
    consoleStream;
    constructor(options = {}) {
        this.options = {
            logDir: options.logDir ?? ".omk/logs",
            enableConsole: options.enableConsole ?? true,
            enableFile: options.enableFile ?? true,
            bufferSize: options.bufferSize ?? 1000,
            format: options.format ?? "colored",
            filter: options.filter ?? {},
        };
        this.consoleStream = process.stdout;
    }
    /**
     * 초기화 (로그 디렉토리 생성)
     */
    async initialize(runId) {
        if (this.options.enableFile && runId) {
            const logPath = join(this.options.logDir, runId);
            await mkdir(logPath, { recursive: true });
        }
    }
    /**
     * 워커 로그 핸들 생성
     */
    createWorkerHandle(workerId, runId) {
        let fileStream;
        if (this.options.enableFile) {
            const logPath = runId
                ? join(this.options.logDir, runId, `${workerId}.log`)
                : join(this.options.logDir, `${workerId}.log`);
            fileStream = createWriteStream(logPath, { flags: "a" });
            this.streams.set(workerId, fileStream);
        }
        const handle = {
            workerId,
            onStdout: (line) => {
                this.addEntry({
                    timestamp: new Date().toISOString(),
                    workerId,
                    level: "stdout",
                    message: line,
                });
            },
            onStderr: (line) => {
                this.addEntry({
                    timestamp: new Date().toISOString(),
                    workerId,
                    level: "stderr",
                    message: line,
                });
            },
            log: (level, message, metadata) => {
                this.addEntry({
                    timestamp: new Date().toISOString(),
                    workerId,
                    level,
                    message,
                    metadata,
                });
            },
            close: () => {
                if (fileStream) {
                    fileStream.end();
                    this.streams.delete(workerId);
                }
            },
        };
        return handle;
    }
    /**
     * 로그 엔트리 추가
     */
    addEntry(entry) {
        // 필터 적용
        if (!this.shouldLog(entry)) {
            return;
        }
        // 버퍼 관리
        this.logs.push(entry);
        if (this.logs.length > this.options.bufferSize) {
            this.logs.shift();
        }
        // 콘솔 출력
        if (this.options.enableConsole) {
            this.writeToConsole(entry);
        }
        // 파일 출력
        if (this.options.enableFile) {
            this.writeToFile(entry);
        }
        // 이벤트 핸들러 호출
        for (const handler of this.eventHandlers) {
            handler(entry);
        }
    }
    /**
     * 필터 체크
     */
    shouldLog(entry) {
        const { filter } = this.options;
        if (filter.workers && !filter.workers.includes(entry.workerId)) {
            return false;
        }
        if (filter.levels && !filter.levels.includes(entry.level)) {
            return false;
        }
        if (filter.patterns && filter.patterns.length > 0) {
            const matches = filter.patterns.some((pattern) => pattern.test(entry.message));
            if (!matches) {
                return false;
            }
        }
        return true;
    }
    /**
     * 콘솔에 로그 출력
     */
    writeToConsole(entry) {
        const formatted = this.formatEntry(entry);
        this.consoleStream.write(formatted + "\n");
    }
    /**
     * 파일에 로그 출력
     */
    writeToFile(entry) {
        const stream = this.streams.get(entry.workerId);
        if (stream) {
            const formatted = this.formatEntry(entry, "json");
            stream.write(formatted + "\n");
        }
    }
    /**
     * 로그 엔트리 포맷팅
     */
    formatEntry(entry, format) {
        const useFormat = format ?? this.options.format;
        if (useFormat === "json") {
            return JSON.stringify(entry);
        }
        const timestamp = entry.timestamp.split("T")[1].replace("Z", "");
        const prefix = `[${timestamp}] [${entry.workerId}]`;
        if (useFormat === "colored") {
            const colors = {
                stdout: "\x1b[37m", // white
                stderr: "\x1b[31m", // red
                info: "\x1b[34m", // blue
                warn: "\x1b[33m", // yellow
                error: "\x1b[31m", // red
            };
            const reset = "\x1b[0m";
            const color = colors[entry.level];
            return `${color}${prefix} [${entry.level.toUpperCase()}]${reset} ${entry.message}`;
        }
        return `${prefix} [${entry.level.toUpperCase()}] ${entry.message}`;
    }
    /**
     * 이벤트 핸들러 등록
     */
    onLog(handler) {
        this.eventHandlers.push(handler);
        return () => {
            const index = this.eventHandlers.indexOf(handler);
            if (index !== -1) {
                this.eventHandlers.splice(index, 1);
            }
        };
    }
    /**
     * 로그 조회
     */
    getLogs(workerId) {
        if (workerId) {
            return this.logs.filter((entry) => entry.workerId === workerId);
        }
        return [...this.logs];
    }
    /**
     * 최근 로그 조회
     */
    getRecentLogs(count = 50, workerId) {
        const logs = workerId ? this.getLogs(workerId) : this.logs;
        return logs.slice(-count);
    }
    /**
     * 로그 기록
     */
    log(level, message, metadata) {
        this.addEntry({
            timestamp: new Date().toISOString(),
            workerId: "orchestrator",
            level,
            message,
            metadata,
        });
    }
    /**
     * 워커 시작 로그
     */
    logWorkerStart(worker) {
        this.addEntry({
            timestamp: new Date().toISOString(),
            workerId: worker.nodeId,
            level: "info",
            message: `Worker started (retry: ${worker.retryCount}/${worker.maxRetries})`,
            metadata: { assignment: worker.assignment },
        });
    }
    /**
     * 워커 완료 로그
     */
    logWorkerComplete(worker, success) {
        this.addEntry({
            timestamp: new Date().toISOString(),
            workerId: worker.nodeId,
            level: success ? "info" : "error",
            message: success
                ? `Worker completed in ${worker.durationMs}ms`
                : `Worker failed: ${worker.error ?? "Unknown error"}`,
            metadata: {
                success,
                durationMs: worker.durationMs,
                retryCount: worker.retryCount,
            },
        });
    }
    /**
     * 이벤트 로그
     */
    logEvent(event) {
        this.addEntry({
            timestamp: event.timestamp,
            workerId: event.nodeId ?? "orchestrator",
            level: "info",
            message: `Event: ${event.type}`,
            metadata: event.data,
        });
    }
    /**
     * 모든 스트림 종료
     */
    async close() {
        for (const [workerId, stream] of this.streams.entries()) {
            await new Promise((resolve) => {
                stream.end(() => resolve());
            });
            this.streams.delete(workerId);
        }
    }
    /**
     * 로그 요약 출력
     */
    formatSummary() {
        const lines = [];
        lines.push("📜 Log Summary");
        lines.push("─".repeat(50));
        lines.push(`Total Entries: ${this.logs.length}`);
        const byLevel = this.logs.reduce((acc, entry) => {
            acc[entry.level] = (acc[entry.level] || 0) + 1;
            return acc;
        }, {});
        lines.push("By Level:");
        for (const [level, count] of Object.entries(byLevel)) {
            lines.push(`  ${level}: ${count}`);
        }
        const byWorker = this.logs.reduce((acc, entry) => {
            acc[entry.workerId] = (acc[entry.workerId] || 0) + 1;
            return acc;
        }, {});
        lines.push("\nBy Worker:");
        for (const [workerId, count] of Object.entries(byWorker)) {
            lines.push(`  ${workerId}: ${count} entries`);
        }
        return lines.join("\n");
    }
}
