"use strict";
// TUI client. Connects to a running Harness daemon over HTTP + WebSocket
// and provides a readline-driven chat interface that shares state with
// the Web UI in real time. No React, no Ink — just Node built-ins +
// the existing `ws` dependency.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.createTuiClient = createTuiClient;
const readline = __importStar(require("readline"));
const ws_1 = require("ws");
const render_1 = require("./render");
const DEFAULT_BASE_URL = 'http://127.0.0.1:4300';
const ACTIVE_REFRESH_MS = 5_000;
function createTuiClient(options = {}) {
    const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, '');
    const useColor = options.useColor ?? Boolean(process.stdout.isTTY);
    const stdin = options.stdin ?? process.stdin;
    const stdout = options.stdout ?? process.stdout;
    const fetchImpl = options.fetchImpl ?? globalThis.fetch;
    const wsImpl = options.webSocketImpl ?? ws_1.WebSocket;
    if (!fetchImpl) {
        throw new Error('global fetch is required (Node 18+)');
    }
    const transcript = [];
    let active = [];
    let connected = false;
    let model = options.model ?? '';
    let hint = '';
    let stopped = false;
    let ws = null;
    let refreshTimer = null;
    const rl = readline.createInterface({
        input: stdin,
        output: stdout,
        terminal: Boolean(stdin.isTTY),
        prompt: '> ',
    });
    function appendEntry(entry) {
        transcript.push({ ...entry, timestamp: entry.timestamp ?? new Date().toISOString() });
        render();
    }
    function render() {
        const cols = stdout.columns ?? 80;
        const bar = (0, render_1.formatActiveSubagentsBar)(active, useColor);
        const status = (0, render_1.formatStatusLine)({ connected, model, hint }, useColor);
        const recent = transcript.slice(-12); // keep terminal output bounded
        for (const entry of recent.slice(-1)) {
            const lines = (0, render_1.formatChatEntry)(entry, cols, useColor);
            for (const line of lines)
                stdout.write(line + '\n');
        }
        if (bar)
            stdout.write(bar + '\n');
        stdout.write(status + '\n');
        if (typeof rl.prompt === 'function' && !stopped) {
            rl.prompt(true);
        }
    }
    function refreshActiveSubagents() {
        fetchImpl(`${baseUrl}/api/subagents`)
            .then((response) => response.json())
            .then((raw) => {
            const data = raw;
            if (!data || !Array.isArray(data.subagents))
                return;
            active = data.subagents.map((record) => ({
                id: record.id,
                name: record.name,
                durationMs: record.durationMs ?? 0,
            }));
            render();
        })
            .catch(() => { });
    }
    function connectWebSocket() {
        if (stopped)
            return;
        const wsUrl = baseUrl.replace(/^http/, 'ws') + '/ws';
        try {
            ws = new wsImpl(wsUrl);
        }
        catch {
            // Daemon unreachable — try again shortly.
            hint = 'daemon unreachable; retrying';
            connected = false;
            render();
            setTimeout(connectWebSocket, 3_000).unref?.();
            return;
        }
        ws.on('open', () => {
            connected = true;
            hint = '';
            render();
        });
        ws.on('close', () => {
            connected = false;
            hint = 'WS closed; retrying';
            render();
            if (!stopped)
                setTimeout(connectWebSocket, 3_000).unref?.();
        });
        ws.on('error', () => {
            connected = false;
            hint = 'WS error';
        });
        ws.on('message', (raw) => {
            let parsed;
            try {
                parsed = JSON.parse(raw.toString());
            }
            catch {
                return;
            }
            const handle = (harnessEvent) => {
                const category = harnessEvent.category;
                const type = harnessEvent.type;
                if (category === 'system' && (type === 'subagent.start' || type === 'subagent.end' || type === 'subagent.cancel')) {
                    refreshActiveSubagents();
                }
            };
            if (parsed.type === 'event_batch' && Array.isArray(parsed.events)) {
                for (const inner of parsed.events) {
                    if (inner?.event)
                        handle(inner.event);
                }
                return;
            }
            if (parsed.type !== 'event' || !parsed.event)
                return;
            handle(parsed.event);
        });
    }
    async function send(message) {
        const text = message.trim();
        if (!text)
            return;
        appendEntry({ role: 'user', text });
        let assistantBuffer = '';
        let response;
        try {
            response = await fetchImpl(`${baseUrl}/api/chat`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json', Accept: 'text/event-stream' },
                body: JSON.stringify({ message: text, model }),
            });
        }
        catch (error) {
            const messageText = error instanceof Error ? error.message : String(error);
            appendEntry({ role: 'error', text: `Failed to reach daemon: ${messageText}` });
            return;
        }
        if (!response.ok || !response.body) {
            appendEntry({ role: 'error', text: `Daemon responded ${response.status}` });
            return;
        }
        const reader = response.body.getReader();
        const decoder = new TextDecoder();
        let buffer = '';
        while (true) {
            const { value, done } = await reader.read();
            if (done)
                break;
            buffer += decoder.decode(value, { stream: true });
            const parsed = (0, render_1.parseSseChunk)(buffer);
            buffer = parsed.remainder;
            for (const event of parsed.events) {
                const payload = event.payload;
                if (!payload || typeof payload.type !== 'string')
                    continue;
                if (payload.type === 'text' && typeof payload.content === 'string') {
                    assistantBuffer += payload.content;
                }
                else if (payload.type === 'tool_call' && payload.call?.name) {
                    appendEntry({ role: 'tool', text: `\u2192 ${payload.call.name}` });
                }
                else if (payload.type === 'tool_result' && payload.result) {
                    const status = payload.result.success === false ? '\u2717' : '\u2713';
                    const snippet = String(payload.result.output ?? '').split('\n')[0].slice(0, 120);
                    appendEntry({ role: 'tool', text: `${status} ${snippet}` });
                }
                else if (payload.type === 'error' && payload.message) {
                    appendEntry({ role: 'error', text: payload.message });
                }
                else if (payload.type === 'done') {
                    // Drain any buffered assistant text into the transcript.
                    if (assistantBuffer.trim()) {
                        appendEntry({ role: 'assistant', text: assistantBuffer });
                        assistantBuffer = '';
                    }
                }
            }
        }
        if (assistantBuffer.trim()) {
            appendEntry({ role: 'assistant', text: assistantBuffer });
        }
    }
    rl.on('line', (line) => {
        const trimmed = line.trim();
        if (!trimmed) {
            rl.prompt();
            return;
        }
        if (trimmed === '/quit' || trimmed === '/exit') {
            stop().catch(() => { });
            return;
        }
        if (trimmed === '/help') {
            appendEntry({ role: 'system', text: 'Commands: /quit, /exit, /help, /agents, /clear' });
            rl.prompt();
            return;
        }
        if (trimmed === '/agents') {
            const summary = active.length === 0 ? 'No active sub-agents.' : active.map((record) => `${record.name} ${Math.round(record.durationMs / 1000)}s [${record.id}]`).join('\n');
            appendEntry({ role: 'system', text: summary });
            rl.prompt();
            return;
        }
        if (trimmed === '/clear') {
            transcript.length = 0;
            stdout.write('\x1b[2J\x1b[0f');
            render();
            return;
        }
        send(trimmed).catch((error) => {
            const messageText = error instanceof Error ? error.message : String(error);
            appendEntry({ role: 'error', text: messageText });
        });
    });
    rl.on('close', () => {
        stop().catch(() => { });
    });
    async function stop() {
        if (stopped)
            return;
        stopped = true;
        if (refreshTimer)
            clearInterval(refreshTimer);
        refreshTimer = null;
        if (ws) {
            try {
                ws.close();
            }
            catch { /* best-effort */ }
            ws = null;
        }
        rl.close();
    }
    // Wire startup actions.
    refreshActiveSubagents();
    connectWebSocket();
    refreshTimer = setInterval(refreshActiveSubagents, ACTIVE_REFRESH_MS);
    if (typeof refreshTimer.unref === 'function')
        refreshTimer.unref();
    appendEntry({ role: 'system', text: `Connected to ${baseUrl}. Type /help for commands.` });
    return {
        getTranscript: () => transcript.slice(),
        getActiveSubagents: () => active.slice(),
        send,
        render,
        stop,
    };
}
//# sourceMappingURL=client.js.map