/**
 * @module        logs
 * @description   实时日志查看器，支持多项目日志聚合和跟踪模式
 *
 * @author        eddy
 * @organization  wykj
 * @ownership     wykj/eddy
 *
 * @created       2026-03-25
 * @updated       2026-04-03
 *
 * @role          command
 * @layer         command
 * @boundedContext system
 *
 * @trigger       sps logs [project] [--lines N] [--err] [--no-follow]
 * @inputs        可选项目名、行数、错误过滤、跟踪模式标志
 * @outputs       彩色日志流输出（按项目着色）
 * @workflow      1. 发现日志文件 → 2. 读取尾部内容 → 3. 实时跟踪新增行
 */
import { closeSync, existsSync, openSync, readdirSync, readFileSync, readSync, statSync } from 'node:fs';
import { resolve } from 'node:path';
const HOME = process.env.HOME || '/home/coral';
// ── ANSI colors (one per project, cycles) ────────────────────────────
const PROJECT_COLORS = [
    '\x1b[36m', // cyan
    '\x1b[33m', // yellow
    '\x1b[35m', // magenta
    '\x1b[32m', // green
    '\x1b[34m', // blue
    '\x1b[91m', // bright red
    '\x1b[93m', // bright yellow
    '\x1b[95m', // bright magenta
    '\x1b[96m', // bright cyan
];
const RESET = '\x1b[0m';
const DIM = '\x1b[2m';
// ── Discover projects ────────────────────────────────────────────────
function discoverProjects() {
    const projectsDir = resolve(HOME, '.coral', 'projects');
    if (!existsSync(projectsDir))
        return [];
    try {
        return readdirSync(projectsDir, { withFileTypes: true })
            .filter(d => d.isDirectory() && existsSync(resolve(projectsDir, d.name, 'conf')))
            .map(d => d.name)
            .sort();
    }
    catch {
        return [];
    }
}
// ── Get today's log file path ────────────────────────────────────────
function getLogFile(projectName, errOnly) {
    const date = new Date().toISOString().slice(0, 10);
    const prefix = errOnly ? 'error' : 'pipeline';
    return resolve(HOME, '.coral', 'projects', projectName, 'logs', `${prefix}-${date}.log`);
}
/** Find the most recent ACP worker log files for a project. */
function getAcpLogFiles(projectName) {
    const logsDir = resolve(HOME, '.coral', 'projects', projectName, 'logs');
    try {
        return readdirSync(logsDir)
            .filter(f => f.includes('-acp-') && f.endsWith('.log'))
            .map(f => resolve(logsDir, f))
            .sort((a, b) => {
            try {
                return statSync(b).mtimeMs - statSync(a).mtimeMs;
            }
            catch {
                return 0;
            }
        })
            .slice(0, 5); // Keep last 5 ACP log files
    }
    catch {
        return [];
    }
}
// ── Read last N lines from a file ────────────────────────────────────
function tailLines(filePath, n) {
    if (!existsSync(filePath))
        return [];
    try {
        const content = readFileSync(filePath, 'utf-8');
        const lines = content.split('\n').filter(l => l.length > 0);
        return lines.slice(-n);
    }
    catch {
        return [];
    }
}
// ── Color a log line based on level ──────────────────────────────────
function colorLevel(line) {
    if (line.includes(' ERROR '))
        return `\x1b[31m${line}${RESET}`;
    if (line.includes(' WARN '))
        return `\x1b[33m${line}${RESET}`;
    if (line.includes(' OK '))
        return `\x1b[32m${line}${RESET}`;
    if (line.includes(' DEBUG '))
        return `${DIM}${line}${RESET}`;
    return line;
}
function getFileSize(filePath) {
    try {
        return statSync(filePath).size;
    }
    catch {
        return 0;
    }
}
function readNewBytes(filePath, fromOffset) {
    try {
        const size = getFileSize(filePath);
        if (size <= fromOffset)
            return '';
        const buf = Buffer.alloc(size - fromOffset);
        const fd = openSync(filePath, 'r');
        try {
            readSync(fd, buf, 0, buf.length, fromOffset);
        }
        finally {
            closeSync(fd);
        }
        return buf.toString('utf-8');
    }
    catch {
        return '';
    }
}
// ── Main entry ───────────────────────────────────────────────────────
export async function executeLogs(projects, flags, initialLines) {
    const errOnly = !!flags.err;
    const follow = !flags['no-follow'];
    if (projects.length === 0) {
        projects = discoverProjects();
    }
    if (projects.length === 0) {
        console.error('No projects found in ~/.coral/projects/');
        process.exit(1);
    }
    // Assign colors to projects
    const colorMap = new Map();
    projects.forEach((p, i) => {
        colorMap.set(p, PROJECT_COLORS[i % PROJECT_COLORS.length]);
    });
    // Print header
    const mode = errOnly ? 'error' : 'pipeline';
    console.error(`${DIM}── sps logs ── ${mode} ── ${projects.join(', ')} ── ${follow ? 'following' : 'dump'} ──${RESET}\n`);
    // Show initial tail for each project
    const watched = [];
    const today = new Date().toISOString().slice(0, 10);
    for (const projectName of projects) {
        const color = colorMap.get(projectName);
        const filePath = getLogFile(projectName, errOnly);
        const tag = `${color}[${projectName}]${RESET}`;
        // Print last N lines
        const lines = tailLines(filePath, initialLines);
        for (const line of lines) {
            process.stdout.write(`${tag} ${colorLevel(line)}\n`);
        }
        watched.push({
            projectName,
            filePath,
            color,
            offset: getFileSize(filePath),
            lastDate: today,
        });
        // Also watch ACP worker log files
        if (!errOnly) {
            for (const acpLog of getAcpLogFiles(projectName)) {
                const acpLines = tailLines(acpLog, initialLines);
                const acpTag = `${color}[${projectName}/worker]${RESET}`;
                for (const line of acpLines) {
                    process.stdout.write(`${acpTag} ${line}\n`);
                }
                watched.push({
                    projectName,
                    filePath: acpLog,
                    color,
                    offset: getFileSize(acpLog),
                    lastDate: today,
                });
            }
        }
    }
    if (!follow)
        return;
    // ── Follow mode: poll for new data ─────────────────────────────────
    // Handle exit
    const cleanup = () => {
        process.stdout.write(RESET);
        process.exit(0);
    };
    process.on('SIGINT', cleanup);
    process.on('SIGTERM', cleanup);
    // Enable raw mode for 'q' to quit
    if (process.stdin.isTTY) {
        process.stdin.setRawMode(true);
        process.stdin.resume();
        process.stdin.setEncoding('utf-8');
        process.stdin.on('data', (key) => {
            if (key === 'q' || key === '\x03')
                cleanup();
        });
    }
    while (true) {
        const now = new Date().toISOString().slice(0, 10);
        for (const w of watched) {
            // Date rollover — switch to new log file
            if (now !== w.lastDate) {
                w.filePath = getLogFile(w.projectName, errOnly);
                w.offset = 0;
                w.lastDate = now;
            }
            // Check for new file if current doesn't exist yet
            if (!existsSync(w.filePath)) {
                w.filePath = getLogFile(w.projectName, errOnly);
                w.offset = 0;
                continue;
            }
            const newData = readNewBytes(w.filePath, w.offset);
            if (newData.length === 0)
                continue;
            w.offset += Buffer.byteLength(newData, 'utf-8');
            const isWorkerLog = w.filePath.includes('-acp-');
            const tag = `${w.color}[${w.projectName}${isWorkerLog ? '/worker' : ''}]${RESET}`;
            const lines = newData.split('\n').filter(l => l.length > 0);
            for (const line of lines) {
                process.stdout.write(`${tag} ${isWorkerLog ? line : colorLevel(line)}\n`);
            }
        }
        // Discover new ACP log files that appeared since last poll
        if (!errOnly) {
            const watchedPaths = new Set(watched.map(w => w.filePath));
            for (const projectName of projects) {
                for (const acpLog of getAcpLogFiles(projectName)) {
                    if (!watchedPaths.has(acpLog)) {
                        watched.push({
                            projectName,
                            filePath: acpLog,
                            color: colorMap.get(projectName),
                            offset: 0,
                            lastDate: today,
                        });
                    }
                }
            }
        }
        await new Promise(r => setTimeout(r, 500));
    }
}
//# sourceMappingURL=logs.js.map