/**
 * olam logs <world> — Stream application logs from a world.
 *
 * Phase C / C1 (engine-aware): on invocation, fetches /health to read the
 * X-Olam-Engine header, then streams from `GET /v1/worlds/<id>/logs` on
 * host-cp which delegates to the active ContainerEngine (Docker | K8s).
 * Output is engine-agnostic newline-delimited:
 *   `<ISO timestamp> <pod-or-container>/<container> <line>`
 *
 * Pre-C the command consumed the per-world CP's /api/logs SSE channel via
 * the host-cp proxy. That code path stays alive elsewhere in the codebase
 * (per-world CP still serves the SSE stream) but olam-cli no longer calls
 * it — revert this commit and the old behavior comes back wholesale.
 *
 * Options:
 *   --service <name>   Stream a single container (K8s) / pass-through (Docker)
 *   --follow           Stream indefinitely until Ctrl-C (default: false)
 *   --tail <n>         Lines to show before exiting (default: 200)
 */
import * as http from 'node:http';
import pc from 'picocolors';
import { readToken } from './host-cp.js';
import { loadContext } from '../context.js';
import { printError } from '../output.js';
const HOST_CP_PORT = 19000;
// ---------------------------------------------------------------------------
// Pure helpers — exported for unit testing
// ---------------------------------------------------------------------------
/**
 * Color a log line based on its log-level keyword.
 * ERROR → red, WARN → yellow, INFO → dim, default → unchanged.
 */
export function colorLine(line) {
    if (/\bERROR\b/.test(line))
        return pc.red(line);
    if (/\bWARN\b/.test(line))
        return pc.yellow(line);
    if (/\bINFO\b/.test(line))
        return pc.dim(line);
    return line;
}
/**
 * Format a log line for terminal output.
 * Prepends `[service] ` (cyan) when showService is true.
 */
export function formatLine(line, service, showService) {
    const prefix = showService && service ? `${pc.cyan(`[${service}]`)} ` : '';
    return prefix + colorLine(line);
}
export function parseSseEvent(raw) {
    if (!raw.startsWith('data: '))
        return null;
    try {
        const parsed = JSON.parse(raw.slice(6));
        if (!parsed || typeof parsed !== 'object')
            return null;
        const ev = parsed;
        if (ev.type === 'replay' && Array.isArray(ev.lines))
            return ev;
        if (ev.type === 'line' && typeof ev.line === 'string')
            return ev;
        return null;
    }
    catch {
        return null;
    }
}
export function buildLogsUrl(worldId, opts, port = HOST_CP_PORT) {
    const qs = new URLSearchParams({
        tail: String(opts.tail),
        follow: opts.follow ? '1' : '0',
    });
    if (opts.service)
        qs.set('service', opts.service);
    return `http://127.0.0.1:${port}/v1/worlds/${encodeURIComponent(worldId)}/logs?${qs.toString()}`;
}
// ---------------------------------------------------------------------------
// Command registration
// ---------------------------------------------------------------------------
export function registerLogs(program) {
    program
        .command('logs')
        .description('Stream application logs from a world (engine-agnostic)')
        .argument('<world>', 'World ID')
        .option('--service <name>', 'Stream a specific container within the world')
        .option('--follow', 'Stream indefinitely until Ctrl-C', false)
        .option('--tail <n>', 'Number of lines to display before exiting', '200')
        .action(async (worldId, opts) => {
        const { ctx, error } = await loadContext();
        if (!ctx) {
            printError(error?.message ?? 'Olam is not configured. Run `olam init` first.');
            process.exitCode = 1;
            return;
        }
        const world = ctx.worldManager.getWorld(worldId);
        if (!world) {
            printError(`World "${worldId}" not found. Run \`olam list\` to see worlds.`);
            process.exitCode = 1;
            return;
        }
        const token = readToken();
        if (!token) {
            printError('Host CP is not running. Start it with `olam host-cp start`.');
            process.exitCode = 1;
            return;
        }
        const tailLimit = Math.max(1, parseInt(opts.tail, 10) || 200);
        const url = buildLogsUrl(worldId, {
            tail: tailLimit,
            follow: opts.follow,
            ...(opts.service ? { service: opts.service } : {}),
        });
        let done = false;
        let resolveStream;
        const streamDone = new Promise((r) => { resolveStream = r; });
        const finish = () => {
            if (!done) {
                done = true;
                resolveStream();
            }
        };
        const req = http.get(url, { headers: { Authorization: `Bearer ${token}` } }, (res) => {
            if (res.statusCode !== 200) {
                printError(`Log stream returned HTTP ${res.statusCode ?? 'unknown'}`);
                process.exitCode = 1;
                finish();
                res.destroy();
                return;
            }
            // Server emits one canonical line per record:
            //   `<ts> <pod>/<container> <line>\n`
            // We pass the line through `colorLine` for level-based coloring
            // and write to stdout verbatim. No SSE framing on the new path.
            let buf = '';
            res.on('data', (chunk) => {
                if (done)
                    return;
                buf += chunk.toString('utf8');
                const rawLines = buf.split('\n');
                buf = rawLines.pop() ?? '';
                for (const raw of rawLines) {
                    if (done)
                        break;
                    if (!raw)
                        continue;
                    process.stdout.write(colorLine(raw) + '\n');
                }
            });
            res.on('end', () => {
                if (buf)
                    process.stdout.write(colorLine(buf) + '\n');
                finish();
            });
            res.on('error', (err) => {
                if (!done)
                    printError(`Stream error: ${err.message}`);
                finish();
            });
        });
        req.on('error', (err) => {
            if (!done) {
                const msg = err.code === 'ECONNREFUSED'
                    ? 'Host CP is not reachable. Is it running? (`olam host-cp start`)'
                    : `Connection error: ${err.message}`;
                printError(msg);
                process.exitCode = 1;
            }
            finish();
        });
        // once: prevents listener accumulation in test harnesses that invoke
        // the action handler multiple times in the same process.
        process.once('SIGINT', () => {
            done = true;
            req.destroy();
            process.exit(0);
        });
        await streamDone;
    });
}
//# sourceMappingURL=logs.js.map