/**
 * CLI orchestrate command — streams live SSE events as phase spinners.
 * Usage: nexus-prime orchestrate "<goal>"
 */
import http from 'http';
import { withSpinner } from './interactive-setup.js';
const ANSI = {
    cyan: '\x1b[36m',
    green: '\x1b[32m',
    yellow: '\x1b[33m',
    magenta: '\x1b[35m',
    red: '\x1b[31m',
    dim: '\x1b[2m',
    bold: '\x1b[1m',
    reset: '\x1b[0m',
};
const tty = () => process.stdout.isTTY === true && !process.env.NO_COLOR;
const c = (s, k) => tty() ? `${ANSI[k]}${s}${ANSI.reset}` : s;
const log = (s) => process.stdout.write(s + '\n');
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
function spin(label) {
    if (!tty()) {
        log(`  ▸ ${label}`);
        return { stop: (ok = true, note = '') => log(`  ${ok ? '✓' : '✗'} ${label}${note ? ' · ' + note : ''}`) };
    }
    let i = 0;
    const id = setInterval(() => {
        process.stdout.write(`\r  ${c(SPINNER_FRAMES[i++ % SPINNER_FRAMES.length], 'cyan')} ${label}    `);
    }, 80);
    return {
        stop(ok = true, note = '') {
            clearInterval(id);
            const icon = ok ? c('✓', 'green') : c('✗', 'red');
            const noteStr = note ? c(` · ${note}`, 'dim') : '';
            process.stdout.write(`\r  ${icon} ${label}${noteStr}          \n`);
        },
    };
}
function humanMs(ms) {
    if (ms < 1000)
        return `${ms}ms`;
    return `${(ms / 1000).toFixed(1)}s`;
}
/** Map SSE event to a human-readable phase label. */
function eventLabel(type, payload) {
    const tool = String(payload.toolName ?? payload.tool ?? '');
    const phase = String(payload.phase ?? payload.stage ?? '');
    switch (true) {
        case type === 'mcp.call.start':
            return `${tool || 'tool'} · ${phase || 'running'}`;
        case type === 'mcp.call.complete':
            return null; // handled as stop
        case type === 'memory.store':
            return `memory · storing`;
        case type === 'memory.recall':
            return `memory · recall`;
        case type === 'tokens.optimized':
            return `tokens · optimizing`;
        case type === 'planner.stage':
            return `planner · ${phase || 'stage'}`;
        case type === 'phantom.worker.start':
            return `phantom · worker spawning`;
        case type.startsWith('darwin.'):
            return `darwin · ${type.replace('darwin.', '')}`;
        case type === 'guardrail.check':
            return `shield · checking`;
        default:
            return null;
    }
}
/**
 * Subscribe to /stream SSE endpoint and return an async iterable of parsed events.
 * Automatically closes after `timeoutMs` milliseconds of inactivity.
 */
function subscribeSSE(port, runId, timeoutMs = 120_000) {
    let resolve = null;
    const queue = [];
    let done = false;
    let timer = null;
    let req = null;
    function resetTimer() {
        if (timer)
            clearTimeout(timer);
        timer = setTimeout(() => {
            done = true;
            req?.destroy();
            if (resolve) {
                resolve({ value: undefined, done: true });
                resolve = null;
            }
        }, timeoutMs);
    }
    function push(event) {
        if (done)
            return;
        resetTimer();
        if (resolve) {
            const r = resolve;
            resolve = null;
            r({ value: event, done: false });
        }
        else {
            queue.push(event);
        }
    }
    const options = {
        hostname: '127.0.0.1',
        port,
        path: `/stream?runtimeId=`,
        method: 'GET',
        headers: { Accept: 'text/event-stream', 'Cache-Control': 'no-cache' },
    };
    req = http.request(options, (res) => {
        resetTimer();
        let buf = '';
        res.setEncoding('utf8');
        res.on('data', (chunk) => {
            buf += chunk;
            const parts = buf.split('\n\n');
            buf = parts.pop() ?? '';
            for (const part of parts) {
                const dataLine = part.split('\n').find(l => l.startsWith('data:'));
                if (!dataLine)
                    continue;
                try {
                    const raw = JSON.parse(dataLine.slice(5).trim());
                    const event = {
                        type: String(raw.type ?? raw.event ?? ''),
                        payload: (raw.payload ?? raw.data ?? raw),
                        runId: String(raw.runId ?? raw.payload?.runId ?? ''),
                        tokensSaved: Number(raw.tokensSaved ?? raw.payload?.tokensSaved ?? 0),
                    };
                    // Filter to events relevant to this run (or global events)
                    if (!event.runId || event.runId === runId || event.type === 'dashboard.action') {
                        push(event);
                    }
                }
                catch { /* skip malformed */ }
            }
        });
        res.on('end', () => {
            done = true;
            if (timer)
                clearTimeout(timer);
            if (resolve) {
                resolve({ value: undefined, done: true });
                resolve = null;
            }
        });
    });
    req.on('error', () => {
        done = true;
        if (timer)
            clearTimeout(timer);
        if (resolve) {
            resolve({ value: undefined, done: true });
            resolve = null;
        }
    });
    req.end();
    resetTimer();
    return {
        [Symbol.asyncIterator]() { return this; },
        async next() {
            if (queue.length > 0)
                return { value: queue.shift(), done: false };
            if (done)
                return { value: undefined, done: true };
            return new Promise(r => { resolve = r; });
        },
        async return() {
            done = true;
            req?.destroy();
            if (timer)
                clearTimeout(timer);
            return { value: undefined, done: true };
        },
    };
}
export async function runOrchestrateStream(goal, opts = {}) {
    const port = opts.port ?? Number(process.env.NEXUS_DASHBOARD_PORT ?? 3377);
    const dashUrl = `http://localhost:${port}`;
    log('');
    log(c('◆ Nexus Prime · Orchestrate', 'cyan'));
    log(`  ${c('Goal', 'yellow')}: ${goal}`);
    log('');
    // POST to /api/orchestrate
    let runId;
    try {
        const enqueue = await withSpinner('Connecting to control plane', (async () => {
            const res = await fetch(`${dashUrl}/api/orchestrate`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ goal }),
                signal: AbortSignal.timeout(10_000),
            });
            if (!res.ok)
                throw new Error(`HTTP ${res.status}: ${await res.text()}`);
            return res.json();
        })());
        runId = enqueue.runId ?? `run_${Date.now()}`;
    }
    catch (err) {
        log(`  ${c('✗', 'red')} Failed to reach daemon: ${err instanceof Error ? err.message : String(err)}`);
        log(`  ${c('→', 'dim')} Start daemon: nexus-prime daemon`);
        process.exit(1);
    }
    log(`  ${c('Run ID', 'dim')}: ${c(runId, 'dim')}`);
    log(`  ${c('Watch ', 'dim')}: ${c(`${dashUrl}/`, 'dim')}`);
    log('');
    // Stream events
    const started = Date.now();
    let totalTokensSaved = 0;
    let toolCount = 0;
    const activeSpinners = new Map();
    let completed = false;
    const eventStream = subscribeSSE(port, runId, opts.timeoutMs ?? 120_000);
    for await (const event of eventStream) {
        const { type, payload = {} } = event;
        const tool = String(payload.toolName ?? payload.tool ?? '');
        const tokSaved = Number(payload.tokensSaved ?? event.tokensSaved ?? 0);
        if (tokSaved > 0)
            totalTokensSaved += tokSaved;
        if (type === 'mcp.call.start') {
            toolCount++;
            const label = eventLabel(type, payload) ?? tool;
            const key = `${tool}_${toolCount}`;
            activeSpinners.set(key, spin(label));
        }
        else if (type === 'mcp.call.complete') {
            const key = [...activeSpinners.keys()].find(k => k.startsWith(tool + '_')) ?? [...activeSpinners.keys()].pop();
            if (key) {
                const sp = activeSpinners.get(key);
                const durationMs = Number(payload.durationMs ?? 0);
                const saved = Number(payload.tokensSaved ?? 0);
                const notes = [
                    durationMs > 0 ? humanMs(durationMs) : '',
                    saved > 0 ? `${saved.toLocaleString()} tok saved` : '',
                ].filter(Boolean).join(' · ');
                sp?.stop(!payload.error, notes || undefined);
                activeSpinners.delete(key);
            }
        }
        else if (type === 'tokens.optimized') {
            const saved = Number(payload.tokensSaved ?? payload.saved ?? 0);
            if (saved > 0) {
                log(`  ${c('◈', 'yellow')} tokens optimized · ${c(`${saved.toLocaleString()} saved`, 'yellow')}`);
            }
        }
        else if (type === 'memory.store') {
            log(`  ${c('◎', 'magenta')} memory stored · ${c(String(payload.key ?? payload.content ?? '').slice(0, 60), 'dim')}`);
        }
        else if (type === 'planner.stage') {
            const label = eventLabel(type, payload);
            if (label)
                log(`  ${c('▸', 'cyan')} ${label}`);
        }
        else if (type === 'phantom.worker.start') {
            const sp = spin('phantom · spawning worker');
            activeSpinners.set('phantom', sp);
        }
        else if (type === 'phantom.worker.complete') {
            activeSpinners.get('phantom')?.stop(true);
            activeSpinners.delete('phantom');
        }
        else if (type === 'dashboard.action') {
            const action = String(payload.action ?? '');
            const status = String(payload.status ?? '');
            if (action.includes('orchestrate') || (payload.target && payload.target === runId)) {
                if (status === 'completed' || status === 'done') {
                    completed = true;
                    // Stop remaining spinners
                    for (const [key, sp] of activeSpinners) {
                        sp.stop(true);
                        activeSpinners.delete(key);
                    }
                    break;
                }
                else if (status === 'failed') {
                    for (const [key, sp] of activeSpinners) {
                        sp.stop(false);
                        activeSpinners.delete(key);
                    }
                    break;
                }
            }
        }
    }
    // Stop any lingering spinners
    for (const [, sp] of activeSpinners)
        sp.stop(true);
    const elapsed = humanMs(Date.now() - started);
    log('');
    log(c('╔══════════════════════════════════════════════════════╗', 'dim'));
    log(c('║', 'dim') + c(`  ✓ Orchestration complete`, 'green').padEnd(65) + c('║', 'dim'));
    log(c('╠', 'dim') + c('─'.repeat(54), 'dim') + c('╣', 'dim'));
    log(c('║', 'dim') + `  Time       : ${c(elapsed, 'cyan')}`.padEnd(65) + c('║', 'dim'));
    log(c('║', 'dim') + `  Tools used : ${c(String(toolCount), 'cyan')}`.padEnd(65) + c('║', 'dim'));
    if (totalTokensSaved > 0) {
        log(c('║', 'dim') + `  Tokens saved: ${c(totalTokensSaved.toLocaleString(), 'yellow')}`.padEnd(65) + c('║', 'dim'));
    }
    log(c('║', 'dim') + `  Dashboard  : ${c(dashUrl + '/', 'dim')}`.padEnd(65) + c('║', 'dim'));
    log(c('╚══════════════════════════════════════════════════════╝', 'dim'));
    log('');
}
